diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp index 8641d340..d34c89b2 100644 --- a/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp +++ b/AppleIntelTGLCompat/AppleIntelTGLCompat.cpp @@ -19,19 +19,21 @@ template static F method(void *object, size_t offset) { return reinterpret_cast((*static_cast(object))[offset / 8]); } static void *manager(void *controller) { return field(controller, 0xc40); } -static void *hardwareAccess; +static void *legacyTGLAccess; static XELP::ProbeBinding probeBinding; static uint32_t probeBindingState; // 0 unbound, 1 publishing, 2 immutable/pinned. -// Generic register/DSB operations still belong only to the qualified TGL -// backend. The separately bound Display13 port probe never opens this gate. +// Generic register/DSB operations belong to the qualified Display12 families. +// The separately bound Display13 port probe never opens this gate. static bool hardwareAllowed(void *controller) { if (!controller) return false; auto service = static_cast(controller); auto pci = OSDynamicCast(IOPCIDevice, service->getProvider()); const auto profile=XELP::deviceProfile(pci); - if(!profile.admitted() || profile.family!=XELP::Family::TigerLake)return false; - __atomic_store_n(&hardwareAccess, manager(controller), __ATOMIC_RELEASE); + const auto adapter=XELP::displayAdapter(profile.family); + if(!profile.admitted() || (adapter!=XELP::DisplayAdapter::TGL && adapter!=XELP::DisplayAdapter::ADLS))return false; + if(adapter==XELP::DisplayAdapter::TGL) + __atomic_store_n(&legacyTGLAccess,manager(controller),__ATOMIC_RELEASE); return true; } @@ -49,6 +51,9 @@ static uint32_t read32(void *access, unsigned long offset) { // and board-policy checks. The provider independently verifies physical identity; // it does not read plugin boot arguments or depend on framebuffer globals. // The provider pins the immutable binding until reboot, like the donor owner. +extern "C" uint32_t xelpProbeCapabilities(uint32_t schema,uint32_t configABI) { + return schema==XELP::ProbeCapabilityABI && configABI==XELP::ProbeABI ? XELP::ProbeDisplay13ComboDP : 0; +} extern "C" bool xelpConfigureDisplay(void *controller,const XELP::ProbeConfig *config) { if(!controller || !config || !config->valid())return false; auto service=static_cast(controller); @@ -213,9 +218,9 @@ extern "C" uint32_t tglProbePort(void *port) { } // ABI0 TGL callers have no binding entry. Only a manager established by // hardwareAllowed with a validated TGL profile may use the legacy path. - // Other backend owners never establish hardwareAccess. + // ADL-S establishes generic access but never this unbound TGL fallback. auto access = field(port, 0x578); - if (!access || access != __atomic_load_n(&hardwareAccess, __ATOMIC_ACQUIRE)) return 0; + if (!access || access != __atomic_load_n(&legacyTGLAccess, __ATOMIC_ACQUIRE)) return 0; auto config = field(port, 0x548); if(!config)return 0; ProbeIO io {access}; diff --git a/AppleIntelTGLCompat/AppleIntelTGLCompatPortProbe.hpp b/AppleIntelTGLCompat/AppleIntelTGLCompatPortProbe.hpp index a01798eb..11f1e51e 100644 --- a/AppleIntelTGLCompat/AppleIntelTGLCompatPortProbe.hpp +++ b/AppleIntelTGLCompat/AppleIntelTGLCompatPortProbe.hpp @@ -1,6 +1,6 @@ // SPDX-License-Identifier: BSD-2-Clause -// Shared c004fc1a donor port-mode provider. ADL-N currently admits Combo -// HDMI only; TC HDMI is deferred and fixed DP needs a separate policy. +// Shared c004fc1a donor port-mode provider. Fixed Combo HDMI/DP classification +// does not grant PHY, AUX, boot-handoff or Type-C ownership. #ifndef TGL_COMPAT_PORT_PROBE_HPP #define TGL_COMPAT_PORT_PROBE_HPP #include diff --git a/CHANGELOG.md b/CHANGELOG.md index 0965b184..e01b57c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,23 @@ # Changelog +## 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. + +### Added + +- Add Alder Lake-S graphics acceleration and HDMI/DisplayPort output, including compatible USB-C wiring and up to three displays. +- Accept Tiger Lake-H45, Alder Lake-HX and Raptor Lake-S/HX using shared support. These configurations still need hardware testing. +- Use firmware information to configure more display layouts automatically. + +### Fixed + +- Avoid some startup panics caused by display hardware not being ready. +- Restore Safari graphics-driver access regardless of where macOS installs Safari updates. +- [WhateverXe Graphics 0.2.0](https://github.com/b00t0x/WhateverXe-Graphics/releases) fixes observed Chrome drawing failures and rendering errors on smaller integrated GPUs. + +DisplayPort/USB-C reconnection can still freeze the computer. See the [README](https://github.com/b00t0x/WhateverXe#tested-features-and-known-issues) for tested configurations and other known issues. + ## 0.4.0 Add experimental Tiger Lake laptop display support and automatic Display 13 tiling selection. diff --git a/README.md b/README.md index dc0fc4b5..f720223b 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,11 @@ This project is experimental and requires the signed graphics package from [WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics). It installs the graphics stack under `/Library` and updates the Auxiliary Kernel Collection (AuxKC), without patching System files or snapshots. WEX and the graphics package -have independent release versions. +have independent release versions. Graphics **0.2.0** is needed for the GPUs +added in WEX **0.5.0** and supplies the latest rendering fixes. Previously +supported Tiger Lake/Alder Lake-N configurations can retain Graphics 0.1.0, +but will not receive the new package fixes or features requiring its updated +components. Use WEX 0.5.0 with Graphics 0.2.0 for the complete set of current features and fixes. ## Compatibility @@ -32,14 +36,41 @@ WEX selects the OS ABI and GPU backend independently. OS-specific graphics payloads and the common AppleIntelTGLCompat provider are installed by WhateverXe Graphics; GPU generation and connector routing are handled by WEX. -| GPU family | Device IDs with an implemented backend | +### Tested on hardware + +| GPU family | Tested configurations | | --- | --- | -| Tiger Lake | `9A40`, `9A49`, `9A78` | -| Alder Lake-N / Twin Lake | `46D0`–`46D4` | +| Tiger Lake-U | 48- and 80-EU graphics; desktop-style and laptop display layouts | +| Alder Lake-N | 16- and 24-EU graphics; two HDMI displays | +| Alder Lake-S | UHD Graphics 770 (`4690`); HDMI, DisplayPort and a USB-C port wired as fixed DisplayPort | + +These are tested configurations within each family, not a guarantee for every +model or connector. See [tested features and known issues](#tested-features-and-known-issues) +for OS coverage and remaining problems. + +### Expected to work, not yet tested on hardware + +- Other supported Tiger Lake-U/H35 configurations, including 96-EU graphics. +- Tiger Lake-H45 (GT1): `9A60`, `9A68`, `9A70`. +- Other supported Alder Lake-N configurations and Twin Lake. +- Other supported Alder Lake-S configurations, Alder Lake-HX, Raptor Lake-S and Raptor Lake-HX. + +These devices use implemented support shared with the tested families. Their +IDs are accepted automatically; no CPU-model or machine-specific setting is +needed. Hardware validation is still outstanding. All accepted IDs and resource +limits are listed in [device selection](docs/device-selection.md). + +### Further implementation required -Support varies by system and connector layout. The table includes implemented -backends, not a guarantee for every machine. See [device selection](docs/device-selection.md) -for detailed selection conditions and overrides. +- Alder Lake-H/P/U and Raptor Lake-H/P/U: additional graphics and display support + is needed. These are distinct from the HX products listed above. +- Rocket Lake-S: its display power and clock handling differ from Tiger Lake. + It is not supported by this release, and no hardware validation is scheduled. + +### No support planned + +- Xe-LPG, including Meteor Lake integrated graphics. +- Xe2 and later graphics architectures. ## Required components @@ -62,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** before installing the graphics +Boot the target OS with WEX **0.3.0 or newer** (0.5.0 for newly added GPUs) before installing the graphics pkg. If an older Xe System patch is installed, restore it using its original recovery procedure first. The pkg does not change EFI, System files, snapshots or SIP settings. See [installation and removal](https://github.com/b00t0x/WhateverXe-Graphics#install) @@ -132,6 +163,9 @@ WEX automatically handles systems with or without an internal display. No family-selection or per-feature enable arguments are required for Alder Lake-N / Twin Lake. +GPU backend selection is independent of hardware test coverage: untested PCI +revisions use the same family implementation. Revision-specific behavior is +added when a documented difference or observed failure requires it. The supported Display 13 backend selects Y-tiled scanout automatically; `AAPL,GfxYTile` is not required in OpenCore. Existing values are preserved. @@ -143,35 +177,42 @@ its X-tiled scanout path is unsupported. Display 12 tiling selection is unchange | Hardware | Verified features | | --- | --- | | Tiger Lake / Alder Lake-N | Big Sur–Tahoe: dual 1920×1080 at 60 Hz output, Metal/MPS, OpenGL, OpenCL, hardware H.264 encode/decode, HEVC decode, display sleep/wake, Safari video decoding and Widevine playback | -| Tiger Lake laptops | Big Sur–Tahoe: internal 1366×768 eDP display, brightness control, HDMI and USB-C DisplayPort Alt Mode, up to three simultaneous displays; HDMI and both USB-C ports reconnected successfully on Tahoe | +| Tiger Lake laptops | Big Sur–Tahoe: internal 1366×768 eDP display, brightness control, HDMI and USB-C DisplayPort Alt Mode, up to three simultaneous displays; USB-C reconnection remains unreliable | | Alder Lake-N | Big Sur/Tahoe: automatic Y tiling without an EFI `AAPL,GfxYTile` setting | +| Alder Lake-S | Big Sur–Tahoe: Metal, OpenGL, OpenCL, hardware H.264 encode/decode, HEVC decode and three displays (two 1080p60 and one 720p60); Safari video playback; Widevine checked on Big Sur and Tahoe | +| Tiger Lake / Alder Lake-S | Tahoe: Chrome hardware-accelerated Canvas, WebGL and WebGPU | These results cover tested systems, not every GPU, display, adapter or dock. +The listed resolutions describe test coverage; they are not a fixed resolution +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. - Reconnecting a monitor to a dedicated DisplayPort connector can leave the - screen black or freeze the computer. USB-C DisplayPort Alt Mode passed the - reconnection checks above. + screen black or freeze the computer. USB-C DisplayPort Alt Mode reconnection + can also freeze the computer; this has been reproduced on both Big Sur and + Tahoe. - Safari video playback may occasionally drop frames. - Up to three displays can be used simultaneously; a fourth display will not receive an image. +- DisplayPort output or reconnection can be slow on Alder Lake-S. +- Some connector layouts, Thunderbolt-managed outputs and panel backlight + methods are not supported. See [display limits](docs/device-selection.md#display-limits). +- FairPlay-protected playback is not supported. - Full system sleep/wake and resolutions above those listed above are unverified. USB-C display output currently prevents part of the display hardware from entering a low-power state. ## Roadmap -### Planned - -- Alder Lake and Raptor Lake S/H/P/U support -- Display 13 USB-C and eDP support - -### Not Planned - -- Xe-LPG support -- Xe2 and later architectures +- Alder Lake-H/P/U and Raptor Lake-H/P/U support. +- USB-C and internal-display support on Display 13 GPUs. +- Improve display reconnection reliability and investigate full system sleep/wake. ## Build and test diff --git a/README_ja.md b/README_ja.md index 55b3b771..338368e4 100644 --- a/README_ja.md +++ b/README_ja.md @@ -6,6 +6,8 @@ WhateverXeは、一部のIntel Xe-LP内蔵GPU向けにAppleのグラフィック 本プロジェクトは実験段階であり、[WhateverXe Graphics](https://github.com/b00t0x/WhateverXe-Graphics)の署名済みグラフィックスpkgが必要です。資材を`/Library`へ配置してAuxiliary Kernel Collection(AuxKC)を更新し、Systemファイルやsnapshotにはパッチを適用しません。WEXとグラフィックスpkgは、それぞれ独立したバージョンでリリースします。 +WEX **0.5.0**で追加した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バイナリと1つのグラフィックスpkgで、以下のmacOSに対応します。 @@ -23,12 +25,39 @@ Big Sur〜Sonomaは検証済みカーネルの識別を必要とします。Sequ WEXはOSのABIとGPUバックエンドを独立して選択します。OS別のグラフィックス資材と共通のAppleIntelTGLCompatはWhateverXe Graphicsが導入し、GPU世代・コネクタ配線への対応はWEXが担います。 -| GPUファミリー | バックエンド実装があるdevice ID | +### 実機で動作確認済み + +| GPUファミリー | 確認した構成 | | --- | --- | -| Tiger Lake | `9A40`、`9A49`、`9A78` | -| Alder Lake-N / Twin Lake | `46D0`–`46D4` | +| Tiger Lake-U | 48 EU・80 EU。外部画面を使う構成とノートPCの画面構成 | +| Alder Lake-N | 16 EU・24 EU。HDMI 2画面 | +| Alder Lake-S | UHD Graphics 770(`4690`)。HDMI、DisplayPort、固定DisplayPortとして配線されたUSB-C端子 | + +各ファミリーの一部構成で確認した結果であり、全モデル・全端子の動作保証ではありません。 +OS別の検証範囲と残る問題は[動作確認済みの機能と既知の問題](#動作確認済みの機能と既知の問題)を参照してください。 + +### 動作を期待できるが実機未検証 + +- 96 EUを含む、対応IDの他のTiger Lake-U/H35構成。 +- Tiger Lake-H45(GT1):`9A60`、`9A68`、`9A70`。 +- 対応IDの他のAlder Lake-N構成とTwin Lake。 +- 対応IDの他のAlder Lake-S構成、Alder Lake-HX、Raptor Lake-S、Raptor Lake-HX。 + +確認済みのファミリーと処理を共有する実装があり、IDは自動で受け付けます。 +CPUモデル名や機種固有の設定は不要ですが、実機での動作確認は残っています。 +受付IDの一覧と対応する資源構成は[デバイス選択](docs/device-selection.md)を参照してください。 + +### 対応に追加改修が必要 -対応状況は機種やコネクタ構成によって異なります。この表はバックエンドの実装範囲を示すもので、すべての機種での動作を保証するものではありません。詳細な選択条件と上書き設定は[デバイス選択](docs/device-selection.md)を参照してください。 +- Alder Lake-H/P/U、Raptor Lake-H/P/U:描画・表示処理の追加対応が必要です。 + 上記のHX製品とは別の系統です。 +- Rocket Lake-S:Tiger Lakeとは表示の電源・クロック制御が異なります。 + このリリースでは未対応で、実機検証の予定もありません。 + +### 対応予定なし + +- Meteor Lakeの内蔵GPUなど、Xe-LPG。 +- Xe2以降のグラフィックス世代。 ## 必須構成 @@ -45,7 +74,7 @@ SIPは`csr-active-config=03080000`(値`0x803`)に設定してください。 Tahoeでは`CoreMedia / AllowOnePassMetalScaling`の無効化が必要です。WhateverXe Graphicsのインストーラーが設定します。 -グラフィックスpkgはWEX **0.3.0以上**を読み込んだ対象OSから実行してください。旧Xe System patchがある場合は、先に元の復元手順で戻してください。pkgはEFI・Systemファイル・snapshot・SIP設定を変更しません。pkgの使用方法と単独のアンインストールスクリプトは[導入と削除](https://github.com/b00t0x/WhateverXe-Graphics/blob/main/README_ja.md#インストール)を参照してください。 +グラフィックスpkgはWEX **0.3.0以上**(今回追加したGPUでは0.5.0)を読み込んだ対象OSから実行してください。旧Xe System patchがある場合は、先に元の復元手順で戻してください。pkgはEFI・Systemファイル・snapshot・SIP設定を変更しません。pkgの使用方法と単独のアンインストールスクリプトは[導入と削除](https://github.com/b00t0x/WhateverXe-Graphics/blob/main/README_ja.md#インストール)を参照してください。 リンク先の **Mod版** アーカイブから`AMFIPass.kext`を展開し、`EFI/OC/Kexts`へ配置して、`Kernel > Add`で有効にしてください。既存のAMFIPassがある場合はこのMod版へ置き換え、両方を同時に読み込まないでください。upstreamのAMFIPassはMod版の署名チェーンに対応していません。 @@ -92,6 +121,8 @@ OpenCoreの`boot-args`に以下を追加してください。 `-wex-nopanel`ブート引数は廃止され、不要になりました。内蔵ディスプレイの有無は自動で判定します。 Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追加する必要はありません。 +GPU backendの選択と実機検証範囲は独立しています。未検証のPCI revisionも同じfamily実装を使い、 +文書化された差分や実際の不具合で必要になった場合にrevision別の処理を追加します。 対応済みのDisplay 13バックエンドではYタイル出力を自動選択するため、OpenCoreの`AAPL,GfxYTile`設定は不要です。既存の値は保持します。 このバックエンドはXタイル出力に未対応なので、Appleドライバの`-yTileDisable`引数は使用しないでください。Display 12のタイル選択は変更しません。 @@ -101,30 +132,36 @@ Alder Lake-N / Twin Lakeで世代選択や個別機能の有効化引数を追 | ハードウェア | 確認済みの機能 | | --- | --- | | Tiger Lake / Alder Lake-N | Big Sur〜Tahoe:外部2画面の1920×1080・60Hz出力、Metal/MPS、OpenGL、OpenCL、ハードウェアH.264エンコード/デコード、HEVCデコード、画面の消灯/復帰、Safariの動画デコード、Widevine再生 | -| Tiger Lakeラップトップ | Big Sur〜Tahoe:内蔵1366×768 eDP、輝度調整、HDMI、USB-C DisplayPort Alt Mode、最大3画面同時出力。TahoeではHDMIとUSB-Cの2端子で抜き差しも確認 | +| Tiger Lakeラップトップ | Big Sur〜Tahoe:内蔵1366×768 eDP、輝度調整、HDMI、USB-C DisplayPort Alt Mode、最大3画面同時出力。USB-Cの再接続には未解決の問題あり | | Alder Lake-N | Big Sur/Tahoe:EFIの`AAPL,GfxYTile`設定なしでのYタイル自動選択 | +| Alder Lake-S | Big Sur〜Tahoe:Metal、OpenGL、OpenCL、ハードウェアH.264エンコード/デコード、HEVCデコード、3画面(1080p60×2+720p60×1)、Safari動画再生。WidevineはBig SurとTahoeで確認 | +| Tiger Lake / Alder Lake-S | Tahoe:ChromeのCanvas、WebGL、WebGPUによるハードウェア描画 | 検証した機器での結果であり、すべてのGPU・ディスプレイ・アダプター・ドックでの動作を保証するものではありません。 +記載した解像度は検証範囲であり、WEXが設けた固定の解像度制限ではありません。 + +Big Sur〜Tahoeでは、有効なApple platform署名とサービス識別子でWebKit GPUを判定し、 +Safariのインストール先や更新による配置変更に依存せずData側GPUドライバへのアクセスを扱います。 ### 既知の問題と制限 -- 一部の画像変換処理で、アプリケーションがクラッシュすることがあります。 -- DisplayPort専用端子でモニターを接続し直すと、黒画面になったりコンピューターが応答しなくなったりすることがあります。USB-CのDisplayPort Alt Modeでは、上記の抜き差し試験を通過しています。 -- Safariの動画再生で、ときどきコマ落ちが発生します。 -- 同時出力は最大3画面です。4画面目には映像が出ません。 -- システム全体のスリープ/復帰と、上記を超える解像度は未検証です。USB-C映像出力では現在、表示回路の一部を省電力状態へ移行させずに動作させています。 +- 一部の画像変換処理でアプリがクラッシュすることがあります。 +- 専用DisplayPort端子の抜き差しで黒画面やシステム停止が起きることがあります。 + USB-C DisplayPort Alt Modeでも再接続時のシステム停止がBig SurとTahoeの両方で再現しています。 +- Safariの動画再生で時折コマ落ちすることがあります。 +- 同時表示は最大3画面です。4画面目には映像が出ません。 +- Alder Lake-SではDisplayPortの表示開始や再接続後の復帰に時間がかかる場合があります。 +- 一部の端子配線、Thunderbolt管理の出力、内蔵画面の輝度制御方式は未対応です。 + 詳細は[表示の制限](docs/device-selection.md#display-limits)を参照してください。 +- FairPlayで保護されたコンテンツの再生には未対応です。 +- システム全体のスリープ/復帰と、上記を超える解像度は未検証です。 + USB-C映像出力中は表示ハードウェアの一部が低消費電力状態へ移行しません。 ## ロードマップ -### Planned - -- Alder Lake/Raptor LakeのS/H/P/U対応 -- Display 13のUSB-C / eDP対応 - -### Not Planned - -- Xe-LPG対応 -- Xe2以降のアーキテクチャへの対応 +- Alder Lake-H/P/UとRaptor Lake-H/P/Uへの対応。 +- Display 13 GPUでのUSB-C・内蔵画面対応。 +- 画面再接続の安定性改善とシステム全体のスリープ/復帰の調査。 ## ビルドとテスト diff --git a/WhateverXe/kern_alderlake_n.hpp b/WhateverXe/kern_alderlake_n.hpp index 765665ff..2b0e5505 100644 --- a/WhateverXe/kern_alderlake_n.hpp +++ b/WhateverXe/kern_alderlake_n.hpp @@ -7,6 +7,7 @@ #define kern_alderlake_n_hpp #include #include "kern_xelp_platform.hpp" +#include "kern_xelp_gt_resources.hpp" #include "kern_xelp_memory_layout.hpp" #include "kern_xelp_geometry.hpp" #include @@ -25,29 +26,15 @@ inline bool family(uint32_t identity) { return XELP::identify(identity,0).family==XELP::Family::AlderLakeN; } inline bool matches(uint32_t identity, uint8_t revision) { - // adlp_n_revids has only revision0. Unknown steppings need their own WA review. + // Revision identifies the hardware; untested revisions use the family backend. const auto p=XELP::identify(identity,revision); return p.family==XELP::Family::AlderLakeN && p.admitted(); } -struct Resources { - uint32_t slices {}, dss {}, nativeSubslices {}, eus {}, l3Banks {}, urbKiB {}; -}; - -// Gen12 SSEU: one slice, up to two DSS in these small GT SKUs, one disable -// bit per EU pair. Native subslices are twice the DSS population. Preserve -// the physical LP backend; capacity is independent of the PCI product label. -// Four L3 banks/256KiB is the supported small-GT capacity, not eus*constant. -inline bool resources(uint32_t slice, uint32_t dss, uint32_t euDisable, - uint32_t l3Disable, Resources &out) { - out = {}; - if (slice != 1 || !dss || (dss&~0x3FU) || (euDisable&~0xFFU) || l3Disable != 0xF0) - return false; - const auto count=static_cast(__builtin_popcount(dss)); - const auto eus=2*count*(8-static_cast(__builtin_popcount(euDisable))); - if (count>2 || (eus!=16 && eus!=24 && eus!=32)) return false; - out = {1, count, 2*count, eus, 4, 256}; - return true; +using Resources=XELP::SmallGTResources; +using XELP::smallGTResources; +inline bool resources(uint32_t slice,uint32_t dss,uint32_t euDisable,uint32_t l3Disable,Resources &out) { + return smallGTResources(slice,dss,euDisable,l3Disable,out); } struct Register { uint32_t offset {}, value {}; }; @@ -133,10 +120,10 @@ template GTReadout readGT(IO &io, bool admitted, uint32_t *edram=nullp } enum class ReadoutStatus : uint32_t { NotAdmitted, Complete, Unavailable, PowerChanged }; struct DisplayReadout { - uint32_t version {1}; + uint32_t version {2}; ReadoutStatus status {ReadoutStatus::NotAdmitted}; uint32_t count {}, poweredPipes {}, activePipes {}, hdmiBPipes {}; - Register registers[128] {}; + Register registers[160] {}; }; // Read only. Check Display 13 PW_A/B/C before pipe-local registers; do not @@ -159,7 +146,7 @@ template DisplayReadout readDisplay(IO &io, bool admitted) { if (r.status != ReadoutStatus::Complete) return r; // Always-on / PW1 display state, including BIOS and DDI I/O ownership. const uint32_t common[] {0x45400, 0x45408, 0x4540C, 0x45450, 0x45454, 0x4545C, - 0x45420, 0x45424, 0x42000, 0x42084, + 0x45420, 0x45424, 0x45444, 0x42000, 0x42084, 0x44300, 0x4438C, 0x45008, 0x4500C, 0x45010, 0x45014, 0x46000, 0x46004, 0x164280, 0x164284, 0x164288, 0x16428C, 0x64000, 0x64100, 0x64200}; @@ -180,6 +167,14 @@ template DisplayReadout readDisplay(IO &io, bool admitted) { 0x70180, 0x70188, 0x7018C, 0x70190, 0x7019C, 0x701A4, 0x701AC, 0x7027C, 0x7037C, 0x70280, 0x70380, 0x70480, 0x70580, 0x70680, 0x70780}; for (auto offset : local) add(offset + 0x1000 * pipe); + // TGL+ DP transport is transcoder-indexed, not DDI-indexed. M2/N2 + // are absent on Display13 (Linux intel_cpu_transcoder_has_m2_n2). + const uint32_t transport[] {0x60404,0x60540,0x60030,0x60034,0x60040,0x60044}; + for (auto offset : transport) + add(offset + 0x1000 * pipe); + add(0x46140 + 4 * pipe); + // Display13 DSC/joiner belongs to the corresponding pipe well. + add(0x78000 + 0x200 * pipe);add(0x78004 + 0x200 * pipe); } const auto after = add(0x45404); if (r.status == ReadoutStatus::Complete && after != power) @@ -194,9 +189,87 @@ inline bool value(const DisplayReadout &r, uint32_t offset, uint32_t &out) { return false; } +// Source-side certificate for an already-running, uncompressed external SST +// GOP. This does not prove sink DPCD state, PLL rate or native AUX ownership. +inline bool dpSource(const DisplayReadout &boot,unsigned pipe,unsigned ddi) { + if(pipe>=3 || ddi>=2 || boot.status!=ReadoutStatus::Complete || + !(boot.activePipes&boot.poweredPipes&(1U<>1)&7)+1; + // Linux v6.12 i915_reg.h: SST, normal training, scrambling, no FEC/MST, + // port sync, HDCP, DSC or joined pipes. Enhanced framing may be on or off. + return (trans&0xFF700000U)==(0x82000000U|((ddi+1)<<27)) && + !(trans&((1U<<15)|(1U<<9)|(1U<<8))) && !ctl2 && + (lanes==1 || lanes==2 || lanes==4) && ((buf>>1)&7)==lanes-1 && + (buf&0x800000C0U)==0x80000000U && (clock>>28)==ddi+1 && + (tp&~(1U<<18))==0x80000300U && + !(dss1&0xF0300000U) && !(dss2&0x80008000U) && + !(dm&0x81000000U) && (dm&0xFFFFFF) && + dn && !(dn&0xFF000000U) && lm && !(lm&0xFF000000U) && ln && !(ln&0xFF000000U) && + (dm&0xFFFFFF)<=dn; +} +enum class GOPProtocol : uint32_t {TMDS,DP}; +// Source and power must remain unchanged while only the plane is reinitialized. +// This is an early, serialized bootstrap transaction, not a modeset or link +// adoption certificate. A changed link stops further writes, including rollback. +template bool dpSourceUnchanged(IO &io,const DisplayReadout &r,unsigned pipe,unsigned ddi, + uint32_t addedAUXRequest=0) { + if(pipe>=3 || ddi>=2 || (addedAUXRequest && addedAUXRequest!=(2U<<(2*ddi))))return false; + const uint32_t bank=pipe*0x1000; + const uint32_t regs[] {0x60400+bank,0x60404+bank,0x60540+bank,0x60030+bank,0x60034+bank, + 0x60040+bank,0x60044+bank,0x70008+bank,0x46140+4*pipe,0x64000+0x100*ddi, + 0x78000+0x200*pipe,0x78004+0x200*pipe,0x45404,0x45454,0x45444,0x42000}; + for(auto reg:regs) { + uint32_t expected=0; + if(!value(r,reg,expected) || !io.readable(reg))return false; + if(reg==Display13Power::AUX)expected|=addedAUXRequest; + if(io.read(reg)!=expected)return false; + } + return true; +} +// The GOP is already driving DP. Keep its physical AUX domain on without +// inventing a native transaction reference or claiming the firmware request. +// Caller holds the display owner lock throughout both proof and acquisition. +template Display13Power::Result holdDPBootAUX(IO &io,const DisplayReadout &boot, + Display13Power::NativeBridge &bridge,Display13Power::MainLinkAUX &link,unsigned ddi) { + using namespace Display13Power;Result r; + if(ddi>=2 || boot.activePipes!=1 || !dpSource(boot,0,ddi) || !dpSourceUnchanged(io,boot,0,ddi) || + bridge.bootPort!=ddi || bridge.bootAdopted || !bridge.bootPGProtected || !bridge.bootDDIProtected || + !bridge.outputHeld(io,ddi,0) || link.owner || link.bootPending || link.lease.owner)return r; + uint32_t aux=0; + if(!value(boot,AUX,aux) || !(aux&(1U<<(2*ddi))))return r; // No active-link cold power-up. + r=acquire(io,bridge.state,link.lease,auxDomain(ddi)); + if(r.status!=Status::Complete)return r; + link.port=ddi;link.bootPending=true; + if(!dpSourceUnchanged(io,boot,0,ddi,2U<<(2*ddi)) || !held(io,bridge.state,link.lease.domains)) { + // Do not release a newly acquired request under a still-active GOP. + bridge.state.faulted=true;r.status=Status::RequestLost; + } + return r; +} +// Bind the protected lease to the actual, independently validated native port. +// The caller retains that object on success and then adopts the boot path. +// No hardware request or native AUX transaction counter changes at handoff. +template Display13Power::Result adoptDPBootAUX(IO &io,const DisplayReadout &boot, + Display13Power::NativeBridge &bridge,Display13Power::MainLinkAUX &link,uintptr_t target,unsigned ddi) { + using namespace Display13Power;Result r; + if(!target || ddi>=2 || boot.activePipes!=1 || !dpSource(boot,0,ddi) || !dpSourceUnchanged(io,boot,0,ddi) || + bridge.bootPort!=ddi || bridge.bootAdopted || !bridge.bootPGProtected || !bridge.bootDDIProtected || + !bridge.outputHeld(io,ddi,0) || !link.bootPending || link.owner || link.port!=ddi || + link.lease.owner!=&bridge.state || link.lease.domains!=dependencies(auxDomain(ddi)) || + !held(io,bridge.state,link.lease.domains))return r; + link.owner=target;link.bootPending=false;r.status=Status::Complete;return r; +} enum class LinearStatus : uint32_t { NotAdmitted, Ready, Success, NotSingleHDMI, NotLinearFHD, OtherPlaneActive, - StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed + StateChanged, NoVblank, DisableTimeout, EnableTimeout, RollbackFailed, InvalidDPSource }; struct LinearPlan { uint32_t pipe {}, chicken {}, arb {}, control {}, stride {}, position {}, size {}, offset {}, surface {}; @@ -212,11 +285,12 @@ struct LinearResult { // plane on the explicitly selected Combo A/B/C. The default remains B for // the original standalone diagnostics. No new PLL/PHY/power-well cold // initialization, a new GGTT mapping, full modeset or accelerated scanout. -inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1) { +inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned ddi=1,GOPProtocol protocol=GOPProtocol::TMDS) { p={}; if (ddi>=3 || r.status!=ReadoutStatus::Complete || !r.activePipes || (r.activePipes&~7U) || (r.activePipes & (r.activePipes-1)) || (r.activePipes&~r.poweredPipes) || - (ddi==1 && r.activePipes!=r.hdmiBPipes)) + (protocol!=GOPProtocol::TMDS && protocol!=GOPProtocol::DP) || + (protocol==GOPProtocol::TMDS && ddi==1 && r.activePipes!=r.hdmiBPipes)) return LinearStatus::NotSingleHDMI; while (!(r.activePipes & (1U<=3) return LinearStatus::NotSingleHDMI; @@ -229,8 +303,10 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d !value(r,0x701AC+delta,surfLive) || !value(r,0x60400+delta,trans) || !value(r,0x70008+delta,pipeConf) || !value(r,0x60000+delta,htotal) || !value(r,0x6000C+delta,vtotal)) return LinearStatus::NotLinearFHD; - if(!(trans&(1U<<31)) || ((trans>>27)&15)!=ddi+1 || ((trans>>24)&7)>1) + if(!(trans&(1U<<31)) || ((trans>>27)&15)!=ddi+1 || + (protocol==GOPProtocol::TMDS && ((trans>>24)&7)>1)) return LinearStatus::NotSingleHDMI; + if(protocol==GOPProtocol::DP && !dpSource(r,p.pipe,ddi))return LinearStatus::InvalidDPSource; p.geometry=XELPGeometry::linear(p.control,p.stride,p.size,p.position,p.offset); if(!p.geometry.valid || (p.surface&4095) || p.surface!=surfLive || ((trans>>20)&7)!=0 || (pipeConf&0xC0000000)!=0xC0000000 || @@ -251,24 +327,32 @@ inline LinearStatus linearPlan(const DisplayReadout &r, LinearPlan &p,unsigned d return LinearStatus::Ready; } -template LinearResult reinitializeLinear(IO &io, const DisplayReadout &r, bool admitted,unsigned ddi=1) { +template LinearResult reinitializeLinear(IO &io, const DisplayReadout &r, bool admitted,unsigned ddi=1, + GOPProtocol protocol=GOPProtocol::TMDS) { LinearResult out; if (!admitted) return out; LinearPlan p; - out.status=linearPlan(r,p,ddi); + out.status=linearPlan(r,p,ddi,protocol); if(out.status!=LinearStatus::Ready)return out; out.pipe=p.pipe; const auto d=0x1000*p.pipe; const uint32_t ctl=0x70180+d, surf=0x7019C+d, frame=0x70040+d; const auto write=[&](uint32_t reg,uint32_t v){io.write(reg,v);++out.writes;io.read(reg);}; + bool linkChanged=false; + const auto linkStable=[&] { + if(protocol==GOPProtocol::DP && !dpSourceUnchanged(io,r,p.pipe,ddi))linkChanged=true; + return !linkChanged; + }; // Check all captured words used by the transaction before the first write. uint32_t trans=0;value(r,0x60400+d,trans); const Register expected[] {{0x60400+d,trans},{0x42084,p.chicken},{0x70028+d,p.arb},{ctl,p.control}, {0x70188+d,p.stride},{0x7018C+d,p.position},{0x70190+d,p.size}, {0x701A4+d,p.offset},{surf,p.surface}}; for(auto pair:expected)if(io.read(pair.offset)!=pair.value){out.status=LinearStatus::StateChanged;return out;} + if(!linkStable()){out.status=LinearStatus::StateChanged;return out;} const auto waitFrames=[&](uint32_t start) { for(unsigned i=0;i<1000;++i) { + if(!linkStable())return false; const auto now=io.read(frame); if(now==UINT32_MAX)return false; if(uint32_t(now-start)>=2 && uint32_t(now-start)<0x10000000)return true; @@ -278,14 +362,17 @@ template LinearResult reinitializeLinear(IO &io, const DisplayReadout }; out.framesBefore=io.read(frame); if(out.framesBefore==UINT32_MAX || !waitFrames(out.framesBefore)) { - out.status=LinearStatus::NoVblank;return out; + out.status=linkChanged ? LinearStatus::StateChanged : LinearStatus::NoVblank;return out; } const auto rollback=[&] { + if(!linkStable()){out.status=LinearStatus::StateChanged;return;} write(ctl,p.control & ~0x80000000U);write(surf,p.surface); const bool stopped=waitFrames(io.read(frame)); + if(linkChanged){out.status=LinearStatus::StateChanged;return;} write(0x42084,p.chicken);write(0x70028+d,p.arb); write(ctl,p.control);write(surf,p.surface); const bool running=waitFrames(io.read(frame)); + if(linkChanged){out.status=LinearStatus::StateChanged;return;} out.restored=stopped && running && io.read(ctl)==p.control && io.read(surf)==p.surface && io.read(0x42084)==p.chicken && io.read(0x70028+d)==p.arb; if(!out.restored)out.status=LinearStatus::RollbackFailed; diff --git a/WhateverXe/kern_alderlake_s.hpp b/WhateverXe/kern_alderlake_s.hpp new file mode 100644 index 00000000..58c48009 --- /dev/null +++ b/WhateverXe/kern_alderlake_s.hpp @@ -0,0 +1,216 @@ +// ADL-S coordinates from Linux v6.12 intel_bios.c, intel_ddi.c and i915_reg.h. +// Physical DDI, Combo PHY and GMBUS pin are distinct namespaces. This module +// describes hardware; it does not grant donor or runtime admission. +#ifndef KERN_ALDERLAKE_S_HPP +#define KERN_ALDERLAKE_S_HPP +#include +#include "kern_vbt.hpp" +#include "kern_xelp_panel_power.hpp" +#include "kern_xelp_display_resources.hpp" +#include "kern_xelp_gt_resources.hpp" + +namespace AlderLakeS { +constexpr uint32_t PortMask=XELP::ADLSComboResources::physicalMask(); // A, TC1, TC2, TC3, TC4 +// ADL-S/RPL-S share the GT backend across revisions; physical identity is never spoofed. +inline bool identity(uint32_t pci,uint32_t revision) {return revision<=255 && XELP::desktopGTDevice(pci);} +struct GT : XELP::SmallGTResources { + static bool decode(uint32_t pci,uint32_t revision,const uint32_t *fuses,GT &out) { + out={}; + return identity(pci,revision) && XELP::decodeSmallGT(fuses,out); + } +}; +static_assert(sizeof(GT)==6*sizeof(uint32_t),"Preserve GT resource layout"); +// The donor has three Combo clock slots. D/E need a separate clock allocator. +constexpr uint32_t ComboBackendMask=XELP::ADLSComboBackend::mask(XELP::Donor::C004FC1A); // physical A/TC1/TC2, PHY A/B/C +struct Port { + uint32_t ddi {},phy {},buffer {},clock {},clockShift {},clockOff {},hpd {}; + static bool resolve(uint32_t ddi,Port &out) { + out={}; + if(ddi>6 || !(PortMask&(1U<=2 && vbtPin<=5?vbtPin+7:0; +} +// Immutable firmware-derived routing shared by startup, clocks, training and +// hotplug. Unsupported or ambiguous routes stay excluded; never guess a port. +struct Wiring { + uint32_t hdmi {},dp {},excluded {},pin[XELP::PortCount] {},panel {}; + bool valid {}; + uint32_t selected() const {return hdmi|dp|panel;} + bool hasDP(uint32_t d) const {return valid && d=XELP::PortCount || + !((wantDP?dp:hdmi)&(1U<( + static_cast(platform)+0x20+24*c[0]); + } + bool boundDP(const void *platform,const uint32_t *c) const {return matches(c,true) && bound(platform,c);} + bool boundPanel(const void *platform,const uint32_t *c) const {return matchesPanel(c) && bound(platform,c);} + bool boundSST(const void *platform,const uint32_t *c) const {return boundDP(platform,c) || boundPanel(platform,c);} + bool usesDDC(uint32_t bus) const { + if(!valid)return false; + for(unsigned d=0;d(platform); + if(p[0x12]!=9)return false; + // The exact donor's platform tail selects no TCON / Camellia / Banksia. + // VBT-qualified PWM uses native PPS, not an Apple TCON protocol. + const auto tcon=*reinterpret_cast(p+0x190); + if(panel && tcon!=0 && tcon!=2 && tcon!=3)return false; + // Check every selected predecessor before mutating any record. + for(unsigned d=0;d=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; + const uint32_t panel[]={0,0,0,0,2,0x18}; + if(memcmp(p+0x20+24*d,expected,sizeof(expected)) && + (d || memcmp(p+0x20,panel,sizeof(panel))))return false; + } + for(unsigned d=0;d(p+0x20+24*d); + if(panel&(1U<(p+0x190)=0; + return true; + } + static Wiring fromFirmware(const XELP::VBT::Ports &ports) { + Wiring out; + if(!ports.valid())return out; + out.excluded=ports.present; + if(XELP::PanelPower::nativePPS0(ports,XELP::Family::AlderLakeS)) { + const uint32_t zero[5]={}; + if(XELP::PanelPower::select(ports.panel,zero,0,0,0).valid){out.panel=1;out.excluded&=~1U;} + } + for(unsigned d=0;d=3?d+6:d+1); + if(!bus || (isDP && p.aux!=port.aux()))continue; + bool shared=false; + for(unsigned other=0;other3)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; + } +}; +// Keep physical DDI/AUX registers; populate only the separate Combo bank. +// PHY A has a native table: validate it and correct its duplicated lane2 slot. +inline bool comboRegisters(uint32_t ddi,uint32_t *registers,size_t count) { + Port port; + if(!Port::resolve(ddi,port) || !(ComboBackendMask&(1U<2 || !params[0] || params[0]>0x3FF || + params[1]>0x7FFF || !params[3] || params[3]>255 || !params[4] || params[4]>3)return false; + uint32_t p=0; + switch(params[2]) {case 2:p=1;break;case 3:p=2;break;case 5:p=4;break;case 7:p=8;break;default:return false;} + const auto fraction=refCode==2?(params[1]+1)/2:params[1]; + const uint32_t k[]={0,1,2,4}; + out.cfg0=params[0]|(fraction<<10); + out.cfg1=(p<<2)|(k[params[4]]<<6); + if(params[3]!=1)out.cfg1|=0x200|(params[3]<<10); + return true; + } +}; +} +#endif diff --git a/WhateverXe/kern_display13_boot.hpp b/WhateverXe/kern_display13_boot.hpp index 0ee04ea0..07e28cd6 100644 --- a/WhateverXe/kern_display13_boot.hpp +++ b/WhateverXe/kern_display13_boot.hpp @@ -7,6 +7,18 @@ #include "kern_display13_fb_topology.hpp" #include "kern_xelp_memory_layout.hpp" namespace Display13Boot { +// Certificate for the existing linear TMDS boot path. Render qualification is +// separate; callers must also qualify the physical owner, BAR and memory layout. +// DP/other-pipe adoption requires a separate boot contract, not a wider default. +template bool reservedPrefix(IO &io,const AlderLakeN::DisplayReadout &display, + bool bootstrapReady,unsigned ddi,const XELPMemory::Layout &memory) { + AlderLakeN::LinearPlan plan; + if(!bootstrapReady || AlderLakeN::linearPlan(display,plan,ddi)!=AlderLakeN::LinearStatus::Ready || + plan.pipe!=0 || !memory.range(plan.surface,AlderLakeN::ScanoutBytes,true))return false; + for(unsigned i=0;i Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo const auto active=boot.activePipes; if(!memory.valid || !Display13FBTopology::multiOutputWiring(w) || boot.status!=AlderLakeN::ReadoutStatus::Complete || !(active&1) || active==1 || (active&~7U) || (active&~boot.poweredPipes))return r; - uint32_t ports[3] {},clocks[3] {},used=0; - // Every active firmware pipe must be a distinct selected linear TMDS GOP. + uint32_t ports[3] {},clocks[3] {},transport[3] {},used=0,dpPipes=0; + // Every active firmware pipe must be a distinct selected linear GOP. // Pipe identity and physical DDI are independent. No foreign cursor/plane/DSB. for(unsigned p=0;p<3;++p)if(boot.poweredPipes&(1U< Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo } uint32_t ctl=0,trans=0,surface=0,live=0; if(!AlderLakeN::value(boot,0x70180+bank,ctl) || !AlderLakeN::value(boot,0x60400+bank,trans))return r; - const auto port=Display13FBTopology::bootDDI(w,trans); + const auto port=Display13FBTopology::bootDDI(w,trans,true); if(port>=3 || (used&(1U<>24)&7)==2) { + if(!AlderLakeN::dpSource(boot,p,port))return r; + dpPipes|=1U<>20)&7) || !AlderLakeN::value(boot,0x70188+bank,stride) || @@ -74,7 +90,8 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo for(unsigned p=1;p<3;++p)if((active&(1U<=0x60000+bank && reg<0x61000+bank) || - (reg>=0x70000+bank && reg<0x71000+bank) || reg==0x64000+0x100*ports[p])mutableReg=true; + (reg>=0x70000+bank && reg<0x71000+bank) || reg==0x64000+0x100*ports[p] || + reg==0x46140+4*p)mutableReg=true; } if(reg==0x70040 || reg==0x71040 || reg==0x72040)continue; if(!mutableReg && io.read(reg)!=pair.value) { @@ -115,6 +132,12 @@ template Result normalize(IO &io,const AlderLakeN::DisplayReadout &boo r.stage=3;write(0x60404+bank,0);write(0x60400+bank,trans&~(0x80000000U|(15U<<27)|(7U<<24))); if(!wait(0x60400+bank,0x80000000,0))return r; r.stage=4;write(bufReg,buf&~0x80000000U); + // Display12/13 disable_ddi_buf: buffer off, transport off, then idle. + // Stop only this transcoder; preserve the adopted GOP's M/N and link. + if(dpPipes&(1U<3 || (used&(1U<>24)&7)>1)return UINT32_MAX; +inline uint32_t bootDDI(const Display13OutputWiring &w,uint32_t trans,bool dpSource=false) { + if(!selectedWiring(w) || trans==UINT32_MAX || !(trans&(1U<<31)))return UINT32_MAX; const auto selector=(trans>>27)&15; if(selector<1 || selector>3)return UINT32_MAX; const auto port=selector-1; + const auto mode=(trans>>24)&7; + // Explicit source inspection only; runtime adoption keeps the TMDS default. + if(dpSource && mode==2 && port<2 && (w.dp&(1U<1)return UINT32_MAX; return (w.hdmi&(1U<(source); if(src[0x12]!=Ports)return false; @@ -63,46 +59,47 @@ inline bool apply(void *destination,uint32_t bytes,const void *source,const TGLH for(unsigned d=0;d(destination);memcpy(dst,src,Bytes); - if(!w.apply(dst,bytes))return false; - for(unsigned d=0;d(config)==static_cast(platform)+0x20+24*ddi; } -inline bool selected(const void *platform,const void *config,const TGLHDMIWiring &w) { +inline bool selected(const void *platform,const void *config,const Display13OutputWiring &w) { if(!selectedWiring(w))return false; - for(unsigned d=0;d<3;++d)if((w.hdmi&(1U<(config)))return true; return false; } // Transport adapter validates records under the native FB mutex. The binding // core below consumes only validated port identities, independent of transport. -inline XELP::NativePorts boundPorts(const void *platform,const TGLHDMIWiring &w) { +inline XELP::NativePorts boundPorts(const void *platform,const Display13OutputWiring &w) { if(!platform || !selectedWiring(w))return {}; - for(unsigned d=0;d<3;++d)if((w.hdmi&(1U<(platform)+0x20+24*d,w))return {}; - return {platform,w.hdmi}; + return {platform,w.selected()}; } // Same exact donor CPU defect corrected for MI646: the mode validator // asks a TMDS connector for DP lanes. Keep its remaining HDMI constraints. -inline bool skipDPLanes(const void *platform,const void *config,const TGLHDMIWiring &w, +inline bool skipDPLanes(const void *platform,const void *config,const Display13OutputWiring &w, bool exactCaller,bool native,uint32_t available,uint32_t lanes) { - return XELPDonor::skipHDMIDPLanes(selected(platform,config,w),exactCaller,native,available,lanes); + return XELPDonor::skipHDMIDPLanes(selected(platform,config,w) && w.matchesHDMI(static_cast(config)),exactCaller,native,available,lanes); } // ICP/TGP SDE bits24..29 are TC1..6 hotplug, not legacy GMBUS bit24. // Authorize masking only from the stable native connector policy: no TC -// record can be enabled or have panel/connection flags on the Combo HDMI path. -inline uint32_t excludedTypeC(const void *platform,uint32_t bytes,const TGLHDMIWiring &w) { +// record can be enabled or have panel/connection flags on a Combo route. +inline uint32_t excludedTypeC(const void *platform,uint32_t bytes,const Display13OutputWiring &w) { if(!platform || bytes(platform); if(p[0x12]!=Ports)return 0; - for(unsigned d=0;d<3;++d)if((w.hdmi&(1U<(p+0x20+24*d); if(config[0]!=d || (d>=3 && (config[4]!=1 || config[5])))return 0; diff --git a/WhateverXe/kern_display13_hdmi_wiring.hpp b/WhateverXe/kern_display13_hdmi_wiring.hpp index 9f105c1c..158cc8ce 100644 --- a/WhateverXe/kern_display13_hdmi_wiring.hpp +++ b/WhateverXe/kern_display13_hdmi_wiring.hpp @@ -3,6 +3,7 @@ // TC HDMI has been deferred; fixed DP requires its own transport policy. #ifndef KERN_DISPLAY13_HDMI_WIRING_HPP #define KERN_DISPLAY13_HDMI_WIRING_HPP -#include "kern_tgl_hdmi_wiring.hpp" -using Display13HDMIWiring = TGLHDMIWiring; +// Compatibility name for the version1 HDMI property and older callers. +#include "kern_display13_output_wiring.hpp" +using Display13HDMIWiring = Display13OutputWiring; #endif diff --git a/WhateverXe/kern_display13_output_wiring.hpp b/WhateverXe/kern_display13_output_wiring.hpp new file mode 100644 index 00000000..031bdee4 --- /dev/null +++ b/WhateverXe/kern_display13_output_wiring.hpp @@ -0,0 +1,48 @@ +// Protocol selection is separate from native path/power/clock resources. +// Describing a route does not install its hardware backend or authorize boot +// handoff. Keep those admission decisions in the caller's operation contract. +#ifndef KERN_DISPLAY13_OUTPUT_WIRING_HPP +#define KERN_DISPLAY13_OUTPUT_WIRING_HPP +#include "kern_tgl_hdmi_wiring.hpp" +#include "kern_tgl_combo_dp.hpp" +struct Display13OutputWiring : private TGLHDMIWiring { + using TGLHDMIWiring::hdmi; + using TGLHDMIWiring::ddc; + using TGLHDMIWiring::valid; + uint32_t dp {}; + Display13OutputWiring()=default; + Display13OutputWiring(const TGLHDMIWiring &w):TGLHDMIWiring(w) {} + static Display13OutputWiring fromProperty(const void *data,uint32_t bytes) { + return TGLHDMIWiring::fromProperty(data,bytes); + } + bool matchesHDMI(const uint32_t *c) const {return described() && TGLHDMIWiring::matches(c);} + bool usesDDC(uint32_t bus) const {return described() && TGLHDMIWiring::usesDDC(bus);} + uint32_t selected() const {return hdmi|dp;} + bool described() const { + if(!valid || !selected() || (selected()&~7U) || (dp&~3U) || (hdmi&dp))return false; + uint32_t used=0; + for(unsigned d=0;d<3;++d) { + const auto pin=ddc[d]; + if(hdmi&(1U<3 || (used&(1U<=3)return false; + if(hdmi&(1U<=12 && d=11 && d bool valid(IO &io, uint32_t reg, uint32_t &value) { return value != UINT32_MAX; } +// Read-only proof for a caller already holding leases. The same owner lock +// must cover this snapshot and the subsequent register operation. Hardware +// left on by firmware is not a logical reference; no acquisition or polling +// is performed here. TC ownership/health is a separate transport contract. +template bool held(IO &io,const State &s,uint32_t domains) { + if(!s.ready || s.faulted || s.suspended || !domains || (domains&~All))return false; + domains=dependencies(domains); + if(domains&~s.allowed)return false; + uint32_t driverMask=0,ddiMask=0,auxMask=0,fuseMask=1U<<27; + for(unsigned d=0;d bool initialize(IO &io, State &s,uint32_t allowed=LegacyDomains) { @@ -152,7 +179,7 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai if (!(domains & mask)) continue; r.domain = d; if (d == 0) { - r.status = wait(io, Fuses, 1U << 27, 5); // PG0 before PW1. + r.status = wait(io, Fuses, 1U << 27, FuseTimeoutUs); // PG0 before PW1. if (r.status != Status::Complete) break; } if (!valid(io, control(d), value)) { r.status = Status::Unavailable; break; } @@ -173,7 +200,7 @@ template Result acquire(IO &io, State &s, Lease &lease, uint32_t domai r.status=waitTCAuxHealth(io,d);if(r.status!=Status::Complete)break; } if (d < 5) { - r.status = wait(io, Fuses, fuse(d), 1); + r.status = wait(io, Fuses, fuse(d), FuseTimeoutUs); if (r.status != Status::Complete) break; } } @@ -260,7 +287,7 @@ template Result restore(IO &io,State &s) { r.status=Status::Complete; for(unsigned d=0;d Result restore(IO &io,State &s) { r.status=wait(io,control(d),enabled(d),tcAux(d)?500000:1000); if(r.status==Status::Complete && tcAux(d))r.status=waitTCAuxHealth(io,d); if(r.status!=Status::Complete)break; - if(d<5)r.status=wait(io,Fuses,fuse(d),1); + if(d<5)r.status=wait(io,Fuses,fuse(d),FuseTimeoutUs); if(r.status!=Status::Complete)break; } if(r.status==Status::Complete) {s.suspended=false;r.domain=Count;} diff --git a/WhateverXe/kern_display13_power_bridge.hpp b/WhateverXe/kern_display13_power_bridge.hpp index c13d1c46..39ec311a 100644 --- a/WhateverXe/kern_display13_power_bridge.hpp +++ b/WhateverXe/kern_display13_power_bridge.hpp @@ -36,7 +36,7 @@ inline uint32_t nativeDomains(uint32_t ports) { struct NativeBridge { State state; Lease pgLease[4], ddiLease[7], auxLease[7]; - uint32_t pgRefs[4] {}, ddiRefs[7] {}, auxRefs[7] {}; + uint32_t pgRefs[4] {}, ddiRefs[7] {}, auxRefs[7] {}, auxCommands[7] {}; uint32_t selectedPorts {2}; unsigned bootPort {1}; // Boot pipe remains A until path admission is generalized. bool begun {}; @@ -60,6 +60,48 @@ struct NativeBridge { uint32_t nativeDDIBRefs() const { return nativeDDIRefs(1); } uint32_t nativeAUXBRefs() const { return nativeAUXRefs(1); } + bool auxBusy() const { + for(auto count:auxCommands)if(count)return true; + return false; + } + template bool auxHeld(IO &io,unsigned port) const { + return begun && acceptsPort(port) && auxRefs[port] && auxLease[port].owner==&state && + auxLease[port].domains==dependencies(auxDomain(port)) && held(io,state,auxDomain(port)); + } + // The native read/write/I2C callers own getAUX until their HAL call returns. + // Pin that scope against last-put/suspend while the donor waits unlocked. + // Main-link AUX or firmware-on alone is not a transaction reference. + template bool beginAUXCommand(IO &io,unsigned port) { + if(!auxHeld(io,port) || auxCommands[port]==UINT32_MAX)return false; + ++auxCommands[port];return true; + } + template bool endAUXCommand(IO &io,unsigned port) { + if(port>=7 || !auxCommands[port])return false; + const bool ready=auxHeld(io,port);--auxCommands[port];return ready; + } + template Result suspend(IO &io) { + if(auxBusy())return {}; + return Display13Power::suspend(io,state); + } + + // Require the native resource leases, not merely another consumer's + // physical PW1 hold. Caller serializes this proof through its MMIO use. + bool retainsDDI(unsigned port) const { + return begun && acceptsPort(port) && ddiRefs[port] && ddiLease[port].owner==&state && + ddiLease[port].domains==dependencies(ddiDomain(port)); + } + template bool ddiHeld(IO &io,unsigned port) const { + return retainsDDI(port) && held(io,state,ddiDomain(port)); + } + template bool outputHeld(IO &io,unsigned port,unsigned pipe) const { + if(pipe>=3 || !retainsDDI(port))return false; + const auto domain=bit(static_cast(static_cast(Domain::PipeA)+pipe)); + bool retained=false; + for(unsigned pg=0;pg<4;++pg)if(pgRefs[pg] && pgLease[pg].owner==&state && + pgLease[pg].domains==dependencies(nativeScope(pg+1)) && (pgLease[pg].domains&domain))retained=true; + return retained && held(io,state,domain|ddiDomain(port)); + } + template Result begin(IO &io, unsigned port=1, uint32_t ports=2) { Result r; if (begun || state.ready) return r; @@ -157,7 +199,7 @@ struct NativeBridge { Result r; if (!begun || state.faulted || state.suspended) return r; if (!acceptsPort(port)) { r.status = Status::InvalidDomains; return r; } - if (!auxRefs[port]) { r.status = Status::Unbalanced; return r; } + if (!auxRefs[port] || (auxRefs[port]==1 && auxCommands[port])) { r.status = Status::Unbalanced; return r; } if (auxRefs[port] == 1) { r = release(io,state,auxLease[port]); if (r.status != Status::Complete) return r; @@ -188,6 +230,51 @@ struct NativeBridge { }; +// Linux main_link_aux_power_domain_get/put: an active Combo DP link retains +// AUX independently of the donor's per-transaction AUX counters. Caller holds +// the bridge owner lock and retains the port object while owner is nonzero. +// TC has additional ownership/health contracts and is not admitted here. +struct MainLinkAUX { + Lease lease; + uintptr_t owner {}; + unsigned port {UINT32_MAX}; + bool bootPending {}; // Protected GOP lease, before an actual port object exists. + template Result begin(IO &io,NativeBridge &b,uintptr_t target,unsigned ddi) { + Result r; + if(bootPending || !target || ddi>=2 || !b.ddiHeld(io,ddi))return r; + if(owner) { + if(owner==target && port==ddi && lease.owner==&b.state && + lease.domains==dependencies(auxDomain(ddi)) && held(io,b.state,lease.domains))r.status=Status::Complete; + return r; + } + uint32_t buffer; + if(lease.owner || !valid(io,0x64000+0x100*ddi,buffer) || (buffer&0x80000000U))return r; + r=acquire(io,b.state,lease,auxDomain(ddi)); + if(r.status==Status::Complete){owner=target;port=ddi;} + return r; + } + template Result end(IO &io,NativeBridge &b,uintptr_t target,unsigned ddi) { + Result r; + if(bootPending || !target || ddi>=2)return r; + if(!owner && !lease.owner){r.status=Status::Complete;return r;} + if(owner!=target || port!=ddi || lease.owner!=&b.state || lease.domains!=dependencies(auxDomain(ddi)) || + !b.ddiHeld(io,ddi) || !held(io,b.state,lease.domains))return r; + // A real DDI lease also retains PW1, so this release cannot remove + // display/IRQ power; only AUX may be the final physical consumer. + // A partial enable/disable failure must keep both owner and lease. + for(unsigned us=0;;++us) { + uint32_t buffer; + if(!valid(io,0x64000+0x100*ddi,buffer) || (buffer&0x80000000U))return r; + if(buffer&0x80)break; + if(us==8){r.status=Status::StateTimeout;return r;} + io.delayUs(1); + } + r=release(io,b.state,lease); + if(r.status==Status::Complete){owner=0;port=UINT32_MAX;} + return r; + } +}; + // Native object layout for this donor only. The kernel route additionally // checks exact image/prologues and the controller's physical PCI identity. // Validate CPU associations before any bridge begin/MMIO operation. diff --git a/WhateverXe/kern_host.cpp b/WhateverXe/kern_host.cpp index 27717f49..f4eb7fff 100644 --- a/WhateverXe/kern_host.cpp +++ b/WhateverXe/kern_host.cpp @@ -32,7 +32,8 @@ void XeHost::processKernel(KernelPatcher &patcher) { const bool published=gpu && WIOKit::awaitPublishing(gpu); const auto profile=published ? XELP::deviceProfile(gpu) : XELP::Profile {}; active=profile.admitted(); - backlightEnabled=active && profile.family==XELP::Family::TigerLake && + const auto display=XELP::displayAdapter(profile.family); + backlightEnabled=active && (display==XELP::DisplayAdapter::TGL || display==XELP::DisplayAdapter::ADLS) && info->firmwareVendor!=DeviceInfo::FirmwareVendor::Apple; backlightGPU=backlightEnabled ? gpu : nullptr; if(published) { @@ -42,6 +43,8 @@ void XeHost::processKernel(KernelPatcher &patcher) { gpu->setProperty("WhateverXeDeviceSelection",selection,sizeof(selection)); if(!gpu->getProperty("model"))if(const auto name=XELP::model(profile))gpu->setProperty("model",const_cast(name),strlen(name)+1); } + // Capture the unmodified firmware/GT state before admitting a new backend. + if(!active && profile.family==XELP::Family::AlderLakeS)xe.processKernel(patcher,info); if(active) { XeDataAccess::processKernel(patcher); // Xe admission reads physical PCI identity. Do not install WEG's ID spoof hooks. diff --git a/WhateverXe/kern_tgl_combo_dp.hpp b/WhateverXe/kern_tgl_combo_dp.hpp new file mode 100644 index 00000000..0b8e2d67 --- /dev/null +++ b/WhateverXe/kern_tgl_combo_dp.hpp @@ -0,0 +1,49 @@ +// Fixed DP on TGL Combo A/B. Wiring comes from VBT; this adapter only +// connects existing Combo PHY/AUX/PLL and shared SST operations. +#ifndef KERN_TGL_COMBO_DP_HPP +#define KERN_TGL_COMBO_DP_HPP +#include "kern_vbt.hpp" +struct TGLComboDP { + static constexpr uint32_t Mask=3; + static uint32_t fromFirmware(const XELP::VBT::Ports &ports,uint32_t hdmi) { + if(!ports.valid() || ports.unmapped)return 0; + uint32_t selected=0; + for(unsigned d=0;d<2;++d) { + const auto bit=1U<=2 || !(selected&(1U<(static_cast(platform)+0x20+24*d) && + !memcmp(config,expected,sizeof(expected)); + } + static bool accepts(uint32_t selected,const void *platform,size_t length) { + if((selected&~Mask) || !platform || length<0x1C0)return false; + auto p=static_cast(platform);if(p[0x12]!=9)return false; + for(unsigned d=0;d<2;++d)if(selected&(1U<(platform); + for(unsigned d=0;d<2;++d)if(selected&(1U< +#include "kern_alderlake_s.hpp" +#include "kern_xelp_combo_policy.hpp" struct TGLComboSignal { - uint32_t base {},lanes {},level {},swing {},scalar {},cursor {},post1 {}; + uint32_t base {},lanes {},linkRate {},level {},swing {},scalar {},cursor {},post1 {}; bool low {}; bool select(uint32_t ddi,uint32_t count,uint32_t rate,bool lowSwing,bool hobl, - uint32_t voltage,uint32_t emphasis) { - // Normal HBR2 has a separate U/Y-family table. Leave that unreviewed - // path, HOBL and HBR3 with the donor until their contracts are covered. - if(ddi>1 || (count!=1 && count!=2 && count!=4) || hobl || - (rate!=6 && rate!=10 && !(lowSwing && rate==20)) || - voltage>3 || emphasis>3 || voltage+emphasis>3)return false; - static const uint8_t first[]={0,4,7,9}; - static const uint8_t normal[10][4]={ - {0xA,0x32,0x3F,0},{0xA,0x4F,0x37,8},{0xC,0x71,0x2F,0x10},{6,0x7D,0x2B,0x14}, - {0xA,0x4C,0x3F,0},{0xC,0x73,0x34,0xB},{6,0x7F,0x2F,0x10}, - {0xC,0x6C,0x3C,3},{6,0x7F,0x35,0xA},{6,0x7F,0x3F,0} - }; - static const uint8_t reduced[10][4]={ - {0,0x7F,0x3F,0},{8,0x7F,0x38,7},{1,0x7F,0x33,0xC},{9,0x7F,0x31,0xE}, - {8,0x7F,0x3F,0},{1,0x7F,0x38,7},{9,0x7F,0x35,0xA}, - {1,0x7F,0x3F,0},{9,0x7F,0x38,7},{9,0x7F,0x3F,0} - }; - base=ddi ? 0x6C000:0x162000;lanes=count;level=first[voltage]+emphasis;low=lowSwing; - const auto row=low ? reduced[level]:normal[level]; - swing=row[0];scalar=row[1];cursor=row[2];post1=row[3];return true; + uint32_t voltage,uint32_t emphasis, + XELP::ComboPolicy::Platform platform=XELP::ComboPolicy::Platform::TGL,bool panel=true) { + // HOBL is an optional optimization. Until a retry backend owns its + // hobl_active/hobl_failed lifecycle, select Linux's standard fallback + // directly. Never fall through to unqualified donor coefficients just + // because firmware requests HOBL. At HBR3 neither panel flag applies. + (void)hobl; + if(ddi>1 || (count!=1 && count!=2 && count!=4) || + (platform!=XELP::ComboPolicy::Platform::TGL && platform!=XELP::ComboPolicy::Platform::TGLUY))return false; + XELP::ComboPolicy policy; + if(!policy.select(platform,rate,panel,lowSwing,false,voltage,emphasis))return false; + base=ddi?0x6C000:0x162000;lanes=count;linkRate=rate;low=panel && lowSwing;assign(policy);return true; } + // The platform adapter chooses coordinates; electrical policy contains no + // physical DDI, donor field or board-specific connector condition. + bool selectADLP(uint32_t ddi,uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis,bool panel=false,bool lowSwing=false) { + if(ddi>1 || (count!=1 && count!=2 && count!=4))return false; + XELP::ComboPolicy policy; + if(!policy.select(XELP::ComboPolicy::Platform::ADLP,rate,panel,lowSwing,false,voltage,emphasis))return false; + base=ddi?0x6C000:0x162000;lanes=count;linkRate=rate;low=panel && lowSwing;assign(policy);return true; + } + bool selectADLS(uint32_t ddi,uint32_t count,uint32_t rate,uint32_t voltage,uint32_t emphasis,bool panel=false,bool lowSwing=false) { + if(count!=1 && count!=2 && count!=4)return false; + AlderLakeS::Port route; + if(!AlderLakeS::Port::resolve(ddi,route) || !(AlderLakeS::ComboBackendMask&(1U< bool program(Read read,Write write) const { @@ -60,7 +75,8 @@ struct TGLComboSignal { } write(base+0x604,pcs|(1U<<26)); for(unsigned n=0;n<4;++n) { - const bool load=n==1 || n==2 || (lanes==4 && n==3); + // Linux icl_combo_phy_loadgen_select: no loadgen above 600 MHz. + const bool load=linkRate!=30 && (n==1 || n==2 || (lanes==4 && n==3)); dw4[n]=(dw4[n]&~0x80000000U)|(load?0x80000000U:0); write(base+0x890+0x100*n,dw4[n]); } @@ -84,7 +100,7 @@ struct TGLComboSignal { (read(base+0x28)&0xFCU)==laneMask; for(unsigned n=0;n<4;++n) { ok&=(read(base+0x888+0x100*n)&0xB8FFU)==(((swing&8)<<12)|((swing&7)<<11)|0x98U); - ok&=(read(base+0x890+0x100*n)&0x3FFFFU)==((post1<<12)|cursor); + ok&=(read(base+0x890+0x100*n)&0x8003FFFFU)==((dw4[n]&0x80000000U)|(post1<<12)|cursor); ok&=(read(base+0x89C+0x100*n)&0x7F000000U)==(scalar<<24); } return ok; diff --git a/WhateverXe/kern_tgl_fb_binding.hpp b/WhateverXe/kern_tgl_fb_binding.hpp index 62c82004..63432ef9 100644 --- a/WhateverXe/kern_tgl_fb_binding.hpp +++ b/WhateverXe/kern_tgl_fb_binding.hpp @@ -88,8 +88,8 @@ inline Readout inspect(const Objects &o,const XELP::NativePorts &ports,bool atta } r.status=Status::Complete;return r; } -// Compatibility adapter for existing Combo callers and their CPU fixtures. -inline Readout inspect(const Objects &o,const void *platform,const Display13HDMIWiring &w,bool attached=false) { +// Transport qualification precedes the shared native object binding. +inline Readout inspect(const Objects &o,const void *platform,const Display13OutputWiring &w,bool attached=false) { return inspect(o,Display13FBTopology::boundPorts(platform,w),attached); } } diff --git a/WhateverXe/kern_tgl_port_map.hpp b/WhateverXe/kern_tgl_port_map.hpp index 3ab89c14..e05624df 100644 --- a/WhateverXe/kern_tgl_port_map.hpp +++ b/WhateverXe/kern_tgl_port_map.hpp @@ -169,25 +169,5 @@ struct TGLTypeCPort { // A native SST path may use a combo eDP port or a qualified legacy TC port. // This selects register coordinates, not firmware wiring or a fixed pipe. -struct TGLSSTTrainingPort { - uint32_t ddi {}, ddiBuffer {}; - bool combo {}; - static bool resolve(uint32_t physicalDDI, uint32_t kind, uint32_t backend, - bool qualifiedTC, TGLSSTTrainingPort &out) { - out = {}; - const bool edp = physicalDDI < 2 && kind == 0 && backend == 1; - const bool tc = physicalDDI >= 3 && physicalDDI <= 8 && - kind == 1 && (backend == 3 || backend == 4) && qualifiedTC; - if (!edp && !tc) return false; - out.ddi = physicalDDI; out.ddiBuffer = 0x64000 + 0x100 * physicalDDI; - out.combo = edp; - return true; - } - uint32_t mainPowerMask(uint32_t pipe) const { - // TGL PW1 owns combo lanes and pipe A; PW3 adds pipe B / TC lanes, - // and PW4 adds pipe C (Linux v6.12 intel_display_power_map.c). - return pipe == 2 ? 0xFFU : (pipe == 0 && combo ? 3U : 0x3FU); - } -}; #endif diff --git a/WhateverXe/kern_tgl_tc_boot.hpp b/WhateverXe/kern_tgl_tc_boot.hpp index f1855cf6..c0e9a505 100644 --- a/WhateverXe/kern_tgl_tc_boot.hpp +++ b/WhateverXe/kern_tgl_tc_boot.hpp @@ -5,6 +5,7 @@ #ifndef kern_tgl_tc_boot_hpp #define kern_tgl_tc_boot_hpp #include "kern_tgl_tc_mode.hpp" +#include "kern_xelp_sst_route.hpp" #include "kern_tgl_boot_readout.hpp" struct TGLTCWiring { static constexpr uint32_t Mask=0x1F8; // physical DDI3..8, not framebuffer IDs @@ -23,6 +24,9 @@ struct TGLTCWiring { for(unsigned i=0;i<4;++i)for(unsigned j=0;j<4;++j)q[i]|=uint32_t(p[4*i+j])<<(8*j); return q[0]==1?fromMasks(q[1],q[2],q[3]):TGLTCWiring{}; } + XELP::Display12SSTRoutes sstRoutes(uint32_t backend,uint32_t registered,uint32_t alt) const { + return {0,((valid?legacy:0)|registered)&backend&Mask,alt&Mask}; + } bool legacyEligible(uint32_t d,uint32_t backend) const { return valid && d>=3 && d<=8 && (legacy&backend&(1u<>(4*p.type))&15)==0; p.hobl=ports.version>=232 && (u16(power+54)&(1U<=129 && backlight[0]==6) { + const auto entry=backlight+1+6*p.type; + p.pwmPresent=(entry[0]&3)==2;p.activeLowPWM=entry[0]&4; + p.pwmHz=u16(entry+1); + p.backlightType=backlight[113+p.type]&15; + p.controller=backlight[113+p.type]>>4; + p.backlightValid=true; + } } // Return the donor's physical-DDI slot, never an FB index. Display13 VBT // retains C/D/E labels even on ADL-N, whose physical Combo set is only A/B. inline uint32_t ddi(Family family,uint32_t dvo) { + const auto adapter=displayAdapter(family); const uint8_t dp[]={10,7,8,9,11,13,15,17,19}; const uint8_t hd[]={0,1,2,3,12,14,16,18,20}; for(unsigned p=0;p<9;++p)if(dvo==dp[p] || dvo==hd[p]) { - if(family==Family::TigerLake)return p==2 ? PortCount : p; - if(family==Family::AlderLakeN)return p<2 ? p : p>=5 ? p-2 : PortCount; + if(adapter==DisplayAdapter::TGL)return p==2 ? PortCount : p; + if(adapter==DisplayAdapter::ADLN)return p<2 ? p : p>=5 ? p-2 : PortCount; + if(adapter==DisplayAdapter::ADLS)return p==0 ? 0 : p<5 ? p+2 : PortCount; } return PortCount; } inline Ports parse(const void *data,size_t length,Family family) { Ports r; if(!data || !length)return r; - if(family!=Family::TigerLake && family!=Family::AlderLakeN){r.status=Status::Platform;return r;} + if(displayAdapter(family)==DisplayAdapter::Unknown){r.status=Status::Platform;return r;} const auto p=static_cast(data);r.status=Status::Header; if(length<48 || memcmp(p,"$VBT",4))return r; const size_t header=u16(p+22),size=u16(p+24),bdb=u32(p+28); @@ -161,7 +178,9 @@ inline Ports parse(const void *data,size_t length,Family family) { if(stride<38 || stride>64 || (childBytes-5)%stride)return r; for(size_t at=5;atsetProperty("N97BringupVersion", 1ULL, 32); pci->setProperty("N97NativeStartAllowed", false); const auto revision = pci->configRead8(8); - const bool admitted = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci); + const bool renderRequested = adlnRequested && adlnKernelReady && xelpAlderLakeNDevice(pci); + const bool displayRequested = display13FramebufferRequested && xelpDisplay13Device(pci); + const bool admitted = renderRequested || displayRequested; pci->setProperty("N97Admission", admitted); pci->setProperty("N97PhysicalIdentity", pci->configRead32(0), 32); pci->setProperty("N97PhysicalRevision", revision, 32); @@ -3783,19 +3795,19 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { auto wiring=OSDynamicCast(OSData,property); const auto board=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, wiring?wiring->getLength():0,firmwarePorts,3); - if(adlnNativeRequested && !Display13FBTopology::supportedWiring(board)) { + if(displayRequested && !Display13FBTopology::supportedWiring(board)) { pci->setProperty("N97NativeRoutingUnavailable",true);return; } - auto bootDDI=adlnNativeRequested ? UINT32_MAX : 1U; - uint64_t connector[] {1,adlnNativeRequested,wiring!=nullptr,board.valid,board.hdmi,bootDDI}; + auto bootDDI=displayRequested ? UINT32_MAX : 1U; + uint64_t connector[] {1,displayRequested,wiring!=nullptr,board.valid,board.hdmi,bootDDI}; pci->setProperty("N97NativeBootConnector",connector,sizeof(connector)); pci->setProperty("N97DisplayVersion", AlderLakeN::DisplayVersion, 32); - pci->setProperty("N97GTStep", "A0"); - pci->setProperty("N97DisplayStep", "D0"); + pci->setProperty("N97GTStep", revision==0 ? "A0" : "unknown"); + pci->setProperty("N97DisplayStep", revision==0 ? "D0" : "unknown"); pci->setProperty("N97GGC", pci->configRead16(0x50), 32); if (!(pci->configRead16(4) & 2)) return; const bool linearTest = display13FramebufferRequested; - const bool gtTest = checkKernelArgument("-wex-adln-gt") || adlnNativeRequested; + const bool gtTest = renderRequested && (checkKernelArgument("-wex-adln-gt") || adlnNativeRequested); auto map = pci->mapDeviceMemoryWithRegister(0x10, (linearTest || gtTest ? 0 : kIOMapReadOnly) | kIOMapInhibitCache); if (!map) return; @@ -3829,14 +3841,14 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { auto result = AlderLakeN::readDisplay(io, barReady); pci->setProperty("N97Display13Boot", &result, sizeof(result)); bool bootstrapReady=true; - if(adlnNativeRequested && linearTest && (result.activePipes&(result.activePipes-1))) { + if(displayRequested && linearTest && (result.activePipes&(result.activePipes-1))) { auto parked=Display13Boot::normalize(io,result,barReady && Display13FBTopology::multiOutputWiring(board) && xelpMemoryMatches(pci),board,xelpMemory); pci->setProperty("N97DualGOPNormalize",&parked,sizeof(parked)); result=AlderLakeN::readDisplay(io,barReady); pci->setProperty("N97DualGOPAfter",&result,sizeof(result)); bootstrapReady=parked.status==Display13Boot::Status::Complete; } - if(adlnNativeRequested && bootstrapReady) { + if(displayRequested && bootstrapReady) { uint32_t trans=UINT32_MAX; if(AlderLakeN::value(result,0x60400,trans))bootDDI=Display13FBTopology::bootstrapDDI(board, result.status==AlderLakeN::ReadoutStatus::Complete,result.poweredPipes,result.activePipes,trans); @@ -3945,35 +3957,15 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { scan.planeRestored,scan.ptesRestored,scan.retained); } } + bool renderReady=false; if (gtTest) { uint32_t edram=UINT32_MAX; auto gt=AlderLakeN::readGT(io,barReady && xelpMemoryMatches(pci), adlnNativeRequested ? &edram : nullptr); if (adlnNativeRequested) { adlnNativeSubslices=gt.resources.nativeSubslices; adlnL3Banks=gt.resources.l3Banks; adlnNativeEUs=gt.resources.eus; - AlderLakeN::LinearPlan plan; - auto display=AlderLakeN::readDisplay(io,barReady); - auto optionProperty=pci->getProperty("IOMemoryDescriptorOptions"); - auto opts=OSDynamicCast(OSNumber,optionProperty); - const uint64_t mapperOptions=opts ? opts->unsigned64BitValue() : 0; - const auto systemMapper=reinterpret_cast(IOMapper::gSystem); - const bool physicalOptions=(opts && (mapperOptions&kIOMemoryMapperNone)) || - ((!optionProperty || opts) && !mapperOptions && !systemMapper); - uint64_t mapperAdmission[] {1,mapperOptions,systemMapper,physicalOptions}; - IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission)); - bool prefix=bootstrapReady && AlderLakeN::linearPlan(display,plan,bootDDI)==AlderLakeN::LinearStatus::Ready && plan.pipe==0 && - xelpMemory.range(plan.surface,AlderLakeN::ScanoutBytes,true); - for (unsigned i=0;prefix && igetDeviceMemoryWithRegister(0x18); - adlnNativeReady=gt.status==AlderLakeN::GTStatus::Complete && !(edram&1) && prefix && - aperture && aperture->getLength()==0x10000000 && page_size==4096 && - physicalOptions && (pci->configRead16(4)&6)==6 && - checkKernelArgument("-allow3d") && checkKernelArgument("-disablegfxfirmware") && - !checkKernelArgument("-wex-adln-scanout") && !checkKernelArgument("-wex-adln-gpu"); - pci->setProperty("N97NativePreflight",adlnNativeReady); - pci->setProperty("N97NativeReservedGGTT",prefix); - pci->setProperty("N97NativeEDRAM",edram,32); + renderReady=gt.status==AlderLakeN::GTStatus::Complete && !(edram&1); + pci->setProperty("N97NativeEDRAM",edram,32); } pci->setProperty("N97GTReadout",>,sizeof(gt)); pci->setProperty("N97GTTopologyReady",gt.status==AlderLakeN::GTStatus::Complete); @@ -3981,6 +3973,29 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { static_cast(gt.status),gt.requestedMask,gt.releasedMask,gt.releaseFailedMask, gt.resources.eus,gt.resources.l3Banks); } + if(displayRequested) { + auto display=AlderLakeN::readDisplay(io,barReady); + auto optionProperty=pci->getProperty("IOMemoryDescriptorOptions"); + auto opts=OSDynamicCast(OSNumber,optionProperty); + const uint64_t mapperOptions=opts ? opts->unsigned64BitValue() : 0; + const auto systemMapper=reinterpret_cast(IOMapper::gSystem); + const bool physicalOptions=(opts && (mapperOptions&kIOMemoryMapperNone)) || + ((!optionProperty || opts) && !mapperOptions && !systemMapper); + uint64_t mapperAdmission[] {1,mapperOptions,systemMapper,physicalOptions}; + IORegistryEntry::getRegistryRoot()->setProperty("N97NativeMapperAdmission",mapperAdmission,sizeof(mapperAdmission)); + const bool prefix=Display13Boot::reservedPrefix(io,display,bootstrapReady,bootDDI,xelpMemory); + auto aperture=pci->getDeviceMemoryWithRegister(0x18); + const bool memoryReady= + aperture && aperture->getLength()==0x10000000 && page_size==4096 && + physicalOptions && (pci->configRead16(4)&6)==6 && + checkKernelArgument("-allow3d") && checkKernelArgument("-disablegfxfirmware") && + !checkKernelArgument("-wex-adln-scanout") && !checkKernelArgument("-wex-adln-gpu"); + // This shared admission remains the conjunction of all three + // certificates; selecting a Display adapter never admits a GT. + adlnNativeReady=renderReady && prefix && memoryReady; + pci->setProperty("N97NativePreflight",adlnNativeReady); + pci->setProperty("N97NativeReservedGGTT",prefix); + } if (gtTest && checkKernelArgument("-wex-adln-gpu")) { AlderLakeN::GPUResult gpu; static IOBufferMemoryDescriptor *pinnedGPU=nullptr; @@ -4034,15 +4049,20 @@ void XE::adlnBootstrap(IOPCIDevice *pci) { bool XE::xelpBindProbe(void *controller) { if(!xelpProviderProbe || !xelpProviderConfigureProbe || !xeProfile.admitted())return false; XELP::ProbeConfig config;config.profile=xeProfile; - if(xeProfile.display==XELP::Display::XeD) { + if(adlsDisplaySelected) { + if(!tglHDMIWiringApplied || !adlsWiring.valid || !adlsWiring.selected())return false; + config.combo=adlsWiring.selected();config.comboBackend=AlderLakeS::ComboBackendMask; + config.excluded=adlsWiring.excluded; + } else if(xeProfile.display==XELP::Display::XeD) { if(!tglHDMIWiringApplied || !tglTCWiringLoaded || !tglTCWiring.valid)return false; config.combo=tglHDMIWiring.hdmi;config.comboBackend=7; config.legacy=tglTCProbeMask();config.typeC=tglTCWiring.typeC;config.excluded=tglTCWiring.excluded; config.legacyBackend=tglAUXParentMask; } else { if(!display13FBTopologyHooksReady || controller!=adlnTopologyController || - !Display13FBTopology::supportedWiring(adlnHDMIWiring))return false; - config.combo=adlnHDMIWiring.hdmi;config.comboBackend=3; + !Display13FBTopology::adlnWiring(adlnOutputWiring) || + (adlnOutputWiring.dp && (!display13DPProbeReady || !display13FBSSTHooksReady || !display13FBAUXHooksReady)))return false; + config.combo=adlnOutputWiring.selected();config.comboBackend=3; } const bool bound=config.valid() && reinterpret_cast(xelpProviderConfigureProbe)(controller,&config); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeBound",bound); @@ -4075,9 +4095,53 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { // This must precede tglLoadHDMIWiring, tglPrepareMMIOUC and TGL FIA reads. auto physicalGPU = OSDynamicCast(IOPCIDevice, info->videoBuiltin); selectProfile(XELP::deviceProfile(physicalGPU)); + if(display12SmallGTDetected && XELP::display12SmallGTDevice(xeProfile.identity) && + !xeProfile.overridden() && physicalGPU && + XELP::hostOS(XELP::kernelOS(version)) && (physicalGPU->configRead16(4)&2)) { + // Gen12 forcewake/fuse coordinates are shared with the existing reader. + // Its ADL-N profile verdict is not used to admit Display12 GT1. No engine, + // GGTT, display, PLL or reset programming is performed here. + auto map=physicalGPU->mapDeviceMemoryWithRegister(0x10,kIOMapInhibitCache); + if(map && map->getVirtualAddress() && map->getLength()==0x1000000) { + 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); + display12SmallGTReady=XELP::decodeSmallGT(gt.fuses,display12SmallGT) && + (gt.status==AlderLakeN::GTStatus::Complete || gt.status==AlderLakeN::GTStatus::ProfileMismatch) && + gt.requestedMask==3 && gt.releasedMask==3 && !gt.releaseFailedMask && edram==0; + physicalGPU->setProperty(adlsDisplaySelected?"ADLSGTQualified":"XELPSmallGTQualified",display12SmallGTReady); + physicalGPU->setProperty(adlsDisplaySelected?"ADLSGTBoot":"XELPSmallGTBoot",>,sizeof(gt)); + physicalGPU->setProperty(adlsDisplaySelected?"ADLSEDRAM":"XELPSmallGTEDRAM",edram,32); + if(adlsDisplaySelected) { + uint32_t registers[][2] { + {0x42000,0},{0x45404,0},{0x45450,0},{0x45454,0}, + {0x46010,0},{0x46014,0},{0x46018,0},{0x46030,0}, + {0x164280,0},{0x164284,0},{0x164288,0},{0x16428C,0},{0x164290,0},{0x1642BC,0}, + {0xC4000,0},{0xC4030,0},{0xC4034,0},{0xC5014,0}, + {0x64000,0},{0x64100,0},{0x64300,0},{0x64400,0}, + {0x70008,0},{0x71008,0},{0x72008,0}, + {0x60000,0},{0x6000C,0},{0x60400,0},{0x60404,0}, + {0x70180,0},{0x70188,0},{0x7018C,0},{0x70190,0},{0x7019C,0},{0x701A4,0}, + {0x1082C0,0},{0x1082C4,0} + }; + for(auto ®:registers)reg[1]=io.read(reg[0]); + physicalGPU->setProperty("ADLSDisplayBoot",registers,sizeof(registers)); + } + SYSLOG("igfx","Display12 small-GT boot readout status=%u fuses=%x/%x/%x/%x/%x release=%x/%x", + static_cast(gt.status),gt.fuses[0],gt.fuses[1],gt.fuses[2],gt.fuses[3],gt.fuses[4], + gt.releasedMask,gt.releaseFailedMask); + } + if(map)map->release(); + } if(!xeProfile.admitted())return; xelpSelectedGPU=physicalGPU;xelpSelectedProfile=xeProfile; - if (adlnDetected) { + if (adlnDetected || display13FramebufferRequested) { xelpReadFirmware(physicalGPU); tglDeviceId=physicalGPU->configRead16(2); modDVMTCalcFix.processKernel(patcher,info); @@ -4122,7 +4186,7 @@ void XE::processKernel(KernelPatcher &patcher, DeviceInfo *info) { uint32_t registers[][2] {{0xC4000, 0}, {0x44470, 0}, {0x1638A0, 0}, {0x163890, 0}, {0x163894, 0}}; auto base = reinterpret_cast(mmio->getVirtualAddress()); - if (base && mmio->getLength() >= 0x1638A4) { + if (!adlsDisplaySelected && base && mmio->getLength() >= 0x1638A4) { for (auto ® : registers) reg[1] = *reinterpret_cast(base + reg[0]); pci->setProperty("TGLBootPortRegisters", registers, sizeof(registers)); @@ -4148,16 +4212,21 @@ bool XE::processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t add // or fall back to a second generation implementation in WEG. if(probe>=address && probe-address=address && configure-address(index,"_xelpProbeCapabilities",address,size); + patcher.clearError(); + display13DPProbeReady=query>=address && query-address(query)); } + IORegistryEntry::getRegistryRoot()->setProperty("XELPDisplay13DPProbeAvailable",display13DPProbeReady); IORegistryEntry::getRegistryRoot()->setProperty("XELPSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); if(adlnRequested)IORegistryEntry::getRegistryRoot()->setProperty("N97FBSharedProbeAvailable",xelpProviderProbe && xelpProviderConfigureProbe); return true; } - // Keep AlderLakeN accelerator patching outside the large framebuffer patch frame. - // An early return inside processKextFull still allocates that frame. + // Display dispatch is independent of the selected Render implementation. + // Keep both adapters outside the large Display12 framebuffer patch frame. + if(display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index) + return processDisplay13Framebuffer(patcher,index,address,size); if (adlnRequested || adlnDetected) { - if (adlnDetected && adlnKernelReady && display13FramebufferRequested && currentFramebuffer && currentFramebuffer->loadIndex==index) - return processDisplay13Framebuffer(patcher,index,address,size); if (!adlnDetected || !adlnNativeRequested || !currentGraphics || currentGraphics->loadIndex != index) return false; // Matching is persistent. A rejected bootstrap must not leave the // original 46D1-incompatible donor probe reachable and panic before SSH. @@ -4211,6 +4280,7 @@ struct Display13FBPipeContext { struct Display13FBPowerContext { Display13FBPipeContext pipe[3]; Display13Power::NativeBridge bridge; + Display13Power::MainLinkAUX mainLink[2]; Display13Power::Lease engine; Display13DBUF::State dbuf; Display13WM::Latency latency; @@ -4335,7 +4405,7 @@ static bool display13FBAssociations(const void *controller,size_t controllerByte // framebuffer mutex and display-owner lock keep its graph stable; retained FB // identities and published backing are checked independently by the caller. static TGLFBBinding::Readout adlnInspectPathBinding(IOService *fb,OSObject *path, - const void *platform,const Display13HDMIWiring &wiring) { + const void *platform,const Display13OutputWiring &wiring,bool attached=true) { auto controller=static_cast(display13FBPower.controller); if(!controller || !fb || !path || controller->getMetaClass()->getClassSize()<0x1C20 || fb->getMetaClass()->getClassSize()<0x4B90 || path->getMetaClass()->getClassSize()<0x3730)return {}; @@ -4346,7 +4416,7 @@ static TGLFBBinding::Readout adlnInspectPathBinding(IOService *fb,OSObject *path {path,path->getMetaClass()->getClassSize()}, {plane,plane ? plane->getMetaClass()->getClassSize() : 0}, {port,port ? port->getMetaClass()->getClassSize() : 0}, display13FBExpectedOwner(controller) }; - return TGLFBBinding::inspect(objects,platform,wiring,true); + return TGLFBBinding::inspect(objects,platform,wiring,attached); } @@ -4483,6 +4553,21 @@ struct Display13FBPowerIO { } void delayUs(unsigned us) { IODelay(us); } }; +// The static boot reader selects powered banks and fixed Display13 offsets. +// Broader read access does not expand the power-only write allowlist above. +struct Display13FBBootIO : Display13FBPowerIO { + explicit Display13FBBootIO(IOMemoryMap *m):Display13FBPowerIO{m} {} + bool readable(uint32_t r) const { + return map && map->getVirtualAddress() && map->getLength()==0x1000000 && r<=0x1000000-4; + } +}; +// Only used after native object/BAR binding and the power lease proof. The +// common SST/signal sequencers choose all addresses; no caller register input. +struct Display13FBSSTIO { + IOMemoryMap *map; + uint32_t read(uint32_t reg) const {return *reinterpret_cast(map->getVirtualAddress()+reg);} + void write(uint32_t reg,uint32_t value) {*reinterpret_cast(map->getVirtualAddress()+reg)=value;} +}; struct Display13FBDBUFIO { IOMemoryMap *map; bool readable(uint32_t reg) const {return map && map->getLength()==0x1000000 && map->getVirtualAddress() && !(reg&3) && reg<=0xFFFFFC;} @@ -4608,7 +4693,7 @@ static Display13FBMemory::Readout display13FBInspectMemory(IOService *fb,bool bo auto pci=controller ? OSDynamicCast(IOPCIDevice,controller->getProvider()) : nullptr; if(pipe>=3 || !fb || !pci || !c.bridge.begun || c.bridge.state.faulted || c.memoryFaulted || !c.map || c.map->getLength()!=0x1000000 || !c.map->getVirtualAddress() || - !xelpAlderLakeNDevice(pci) || + !xelpDisplay13Device(pci) || !xelpMemoryMatches(pci) || !(pci->configRead16(4)&2))return r; auto aperture=pci->getDeviceMemoryWithRegister(0x18); if(!aperture || aperture->getLength()!=0x10000000 || @@ -4716,7 +4801,7 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x28,0x48,0xff,0x05,0xb6,0x6b,0x08,0x00}, {0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec,0x38,0x49,0x89,0xf6,0x49,0x89,0xfd,0x48} }; - bool known=adlnKernelReady && adlnDetected && size>0x5C4D2; + bool known=display13FramebufferRequested && size>0x5C4D2; for(unsigned i=0;i(index,symbols[i],address,size); known=known && fn==address+offsets[i] && !memcmp(reinterpret_cast(fn),prefixes[i],sizeof(prefixes[i])); @@ -4904,12 +4989,11 @@ bool XE::processDisplay13Framebuffer(KernelPatcher &patcher,size_t index,mach_vm IORegistryEntry::getRegistryRoot()->setProperty("N97FBGTTHooksReady",display13FBGTTHooksReady); display13FBTopologyHooksReady=processDisplay13FramebufferTopology(patcher,index,address,size); IORegistryEntry::getRegistryRoot()->setProperty("N97FBTopologyHooksReady",display13FBTopologyHooksReady); - display13FBPowerBackendReady=display13FBPowerReferencesReady && display13FBEngineEnableHookReady && - display13FBLifecycleHooksReady && display13FBDBUFHooksReady && display13FBWatermarkHooksReady; - display13FBClockReady=display13FBClockReadoutHooksReady && display13FBClockSetHookReady && display13FBLifecycleHooksReady; - display13FBMemoryReady=display13FBMemoryHooksReady && display13FBGTTHooksReady && display13FBABIReady && display13FBBootHandoffHookReady; - const bool backend=display13FBPowerBackendReady && display13FBClockReady && display13FBMemoryReady && - display13FBIRQReady && display13FBWatermarkLayoutReady && display13FBOutputHooksReady && display13FBTopologyHooksReady; + using C=XELP::DisplayContract;const auto installed=display13Available(); + display13FBPowerBackendReady=C{C::Power13,installed}.ready(); + display13FBClockReady=C{C::Clock13,installed}.ready(); + display13FBMemoryReady=C{C::MemoryGroup13,installed}.ready(); + const bool backend=C::display13(installed).ready(); IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferBackendReady",backend); return true; // Installed routes; actual startup/scanout still require runtime checks. } @@ -5126,7 +5210,7 @@ bool XE::processDisplay13FramebufferWatermarks(KernelPatcher &patcher,size_t ind {0x3645,6,{0x81,0xE1,0xFF,0x07,0x00,0x00},{0x81,0xE1,0xFF,0x0F,0x00,0x00}}, {0x38F1,6,{0x81,0xE6,0xFF,0x07,0x00,0x00},{0x81,0xE6,0xFF,0x0F,0x00,0x00}}, }; - bool known=adlnDetected && adlnKernelReady && display13FBDBUFHooksReady && size>0x3D85; + bool known=display13FramebufferRequested && display13FBDBUFHooksReady && size>0x3D85; for(auto &e:entries) { const auto fn=patcher.solveSymbol(index,e.symbol,address,size); known=known && fn==address+e.offset && !memcmp(reinterpret_cast(fn),e.bytes,sizeof(e.bytes)); @@ -5235,7 +5319,7 @@ bool XE::processDisplay13FramebufferIRQ(KernelPatcher &patcher,size_t index,mach {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x38,0x48,0xFF,0x05,0xCE,0x33,0x07,0x00}, {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xEC,0x28,0x48,0xFF,0x05,0xAE,0x37,0x07,0x00}, }; - bool known=adlnKernelReady && adlnDetected && size>0x75D44; + bool known=display13FramebufferRequested && size>0x75D44; for(unsigned i=0;i(index,symbols[i],address,size); known=known && fn==address+offsets[i] && !memcmp(reinterpret_cast(fn),entries[i],sizeof(entries[i])); @@ -5294,7 +5378,7 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) { if(!callbackXE->display13FBIRQReady || !adlnIRQOwner(controller))return kIOReturnNotReady; auto &c=display13FBIRQ;uint64_t record[32] {},check[42] {3};Display13FBIRQIO io {c.base}; const auto excluded=controller==callbackXE->adlnTopologyController ? - Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnHDMIWiring) : 0; + Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; const auto saved=IOSimpleLockLockDisableInterrupt(c.lock); // Preserve enable evidence even when start rejects its unpublished // controller. Inspect only pipe banks already qualified as powered. @@ -5309,13 +5393,13 @@ IOReturn XE::wrapDisplay13FBEnableIRQ(IOService *controller) { check[17]=io.read(0xC400C);check[18]=io.read(0xC4004); constexpr uint32_t otherBanks[] {0x44448,0x44468,0x44478,0x44488}; for(unsigned i=0;i<4;++i)check[19+i]=io.read(otherBanks[i]+4)&~io.read(otherBanks[i]-4); - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const auto admitted=__atomic_load_n(&c.admittedPipes,__ATOMIC_ACQUIRE); const auto actual=Display13FBIRQ::readActivePipes(io,admitted); c.status=c.quiescing ? Display13FBIRQ::Status::Power : actual.status; if(c.status==Display13FBIRQ::Status::Complete)c.status=c.state.begun && c.state.enabled ? - Display13FBIRQ::extendActivePipes(io,c.state,admitted) : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),actual.active,callbackXE->adlnHDMIWiring.hdmi); - } else c.status=c.quiescing ? Display13FBIRQ::Status::Power : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnHDMIWiring.hdmi); + Display13FBIRQ::extendActivePipes(io,c.state,admitted) : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),actual.active,callbackXE->adlnOutputWiring.selected()); + } else c.status=c.quiescing ? Display13FBIRQ::Status::Power : Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected()); adlnIRQCache(controller,io);adlnIRQRecord(record,1); check[23]=static_cast(c.status);check[24]=c.state.begun; check[25]=c.state.enabled;check[26]=c.state.faulted;check[27]=c.state.active;check[28]=c.state.configured; @@ -5351,23 +5435,23 @@ void XE::wrapDisplay13FBConfigureVBL(IOService *controller,IOService *fb,OSObjec const bool valid=display13FBAssociations(controller,0x1C20,fb,fb->getMetaClass()->getClassSize(),display13FBExpectedOwner(controller)) && path->getMetaClass()->getClassSize()>=0x3730; const auto pipe=valid ? Display13FBMemory::field(path,0x3640) : 3; - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) && (c.quiescing || (pipe<3 && (c.blockedPipes&(1U<adlnOutputWiring) && (c.quiescing || (pipe<3 && (c.blockedPipes&(1U<adlnHDMIWiring) && enabled && !c.quiescing) { + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && enabled && !c.quiescing) { const auto admitted=__atomic_load_n(&c.admittedPipes,__ATOMIC_ACQUIRE); c.status=(admitted&(1U<adlnHDMIWiring) && enabled && c.state.begun && c.state.enabled && !c.state.active && !c.state.faulted && + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && enabled && c.state.begun && c.state.enabled && !c.state.active && !c.state.faulted && Display13FBIRQ::powered(io.read(Display13FBIRQ::Driver),pipe) && (io.read(0x70008+0x1000*pipe)&Display13FBIRQ::Enable)) { const auto excluded=controller==callbackXE->adlnTopologyController ? - Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnHDMIWiring) : 0; - c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnHDMIWiring.hdmi); + Display13FBTopology::excludedTypeC(callbackXE->adlnPlatform,sizeof(callbackXE->adlnPlatform),callbackXE->adlnOutputWiring) : 0; + c.status=Display13FBIRQ::enable(io,c.state,excluded,adlnSharedGT(controller),1,callbackXE->adlnOutputWiring.selected()); } if(!c.state.faulted)c.status=Display13FBIRQ::vblank(io,c.state,pipe,enabled); } @@ -5514,13 +5598,13 @@ bool XE::processAlderLakeNSysMem(KernelPatcher &patcher,size_t index,mach_vm_add IORegistryEntry::getRegistryRoot()->setProperty("N97FixedSysMemEnabled",adlnFixedSysMemEnabled);return ready; } void XE::wrapAlderLakeNSysMemAllocate(IOService *controller,uint32_t slot) { - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) || !slot) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) || !slot) { FunctionCast(wrapAlderLakeNSysMemAllocate,callbackXE->orgAlderLakeNSysMemAllocate)(controller,slot);return; } adlnSysMemOperation(controller,slot,false); } void XE::wrapAlderLakeNSysMemFree(IOService *controller,uint32_t slot) { - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) || !slot) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) || !slot) { FunctionCast(wrapAlderLakeNSysMemFree,callbackXE->orgAlderLakeNSysMemFree)(controller,slot);return; } adlnSysMemOperation(controller,slot,true); @@ -5590,7 +5674,7 @@ void XE::adlnSysMemOperation(IOService *controller,uint32_t slot,bool release) { !layout.slots[slot].descriptor && adlnSysMemClaim(controller,slot)==fb; if(ready) { stage=7;auto path=Display13FBMemory::singlePath(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); pipe=binding.pipe;ready=binding.status==TGLFBBinding::Status::Complete && pipe<3; } if(ready) { @@ -5689,11 +5773,11 @@ void XE::adlnSysMemOperation(IOService *controller,uint32_t slot,bool release) { uint32_t XE::adlnRequestedSharedReservation(IOService *controller) { if(!controller || controller->getMetaClass()->getClassSize()<0x1C20)return 0; - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const auto prefix=(uint64_t(Display13FBMemory::field(controller,0xDF8))+0xFFF)&~0xFFFULL; return prefix<0x10000000 ? static_cast(prefix) : 0; } - if(!Display13FBTopology::adlnWiring(callbackXE->adlnHDMIWiring))return 0; + if(!Display13FBTopology::adlnWiring(callbackXE->adlnOutputWiring))return 0; // Allocation/free may change descriptor/state fields later. The validated // fixed layout's registration promise is stable across those transitions. const auto prefix=__atomic_load_n(&display13FBGTT.sharedPoolPrefix,__ATOMIC_ACQUIRE); @@ -5703,13 +5787,13 @@ uint32_t XE::adlnRequestedSharedReservation(IOService *controller) { IOReturn XE::wrapDisplay13FramebufferPlatformCall(IOService *controller,const OSSymbol *name,bool wait, void *arg1,void *arg2,void *arg3,void *arg4) { auto self=callbackXE; - const bool fixed=Display13FBTopology::multiOutputWiring(self->adlnHDMIWiring) && controller && controller==display13FBGTT.controller && + const bool fixed=Display13FBTopology::multiOutputWiring(self->adlnOutputWiring) && controller && controller==display13FBGTT.controller && controller->getMetaClass()->getClassSize()>=0x1C20 && name && name==Display13FBMemory::field(controller,0x1560); if(!fixed)return FunctionCast(wrapDisplay13FramebufferPlatformCall,self->orgDisplay13FramebufferPlatformCall)(controller,name,wait,arg1,arg2,arg3,arg4); const auto layout=Display13FBMemory::poolLayout(controller,controller->getMetaClass()->getClassSize(),0x10000000); const auto plan=Display13FBMemory::sharedReservation(layout,0x10000000,adlnFixedSysMemEnabled); - const bool ready=Display13FBTopology::adlnWiring(self->adlnHDMIWiring) && plan.valid && + const bool ready=Display13FBTopology::adlnWiring(self->adlnOutputWiring) && plan.valid && display13FBGTT.initialized && !display13FBGTT.faulted && !display13FBPower.memoryFaulted && arg1 && arg1==__atomic_load_n(&adlnSharedOwner,__ATOMIC_ACQUIRE) && arg2 && arg3; if(!ready)return kIOReturnNotReady; @@ -5741,7 +5825,7 @@ void XE::wrapDisplay13FBMemoryInit(IOService *controller) { const auto systemMapper=reinterpret_cast(IOMapper::gSystem); const bool physicalOptions=mapperOptions==kIOMemoryMapperNone || (!mapperOptions && !systemMapper); bool valid=s->display13FBGTTHooksReady && !g.controller && !g.faulted && pci && native && access && - xelpAlderLakeNDevice(pci) && xelpMemoryMatches(pci) && + xelpDisplay13Device(pci) && xelpMemoryMatches(pci) && (pci->configRead16(4)&2) && native->getLength()==0x1000000 && native->getVirtualAddress() && access->getMetaClass()->getClassSize()>=0x68 && Display13FBMemory::field(access,0x50)==native->getVirtualAddress() && @@ -5872,10 +5956,10 @@ bool XE::processDisplay13FramebufferTopology(KernelPatcher &patcher,size_t index bool XE::wrapAlderLakeNGetOSInformation(IOService *controller) { auto s=callbackXE; if(!s->display13FBTopologyHooksReady || !controller || controller->getMetaClass()->getClassSize()<0x1C20 || - !Display13FBTopology::supportedWiring(s->adlnHDMIWiring) || !checkKernelArgument("-disablegfxfirmware"))return false; + !Display13FBTopology::supportedWiring(s->adlnOutputWiring) || !checkKernelArgument("-disablegfxfirmware"))return false; if(!FunctionCast(wrapAlderLakeNGetOSInformation,s->orgAlderLakeNGetOSInformation)(controller))return false; auto platform=Display13FBMemory::field(controller,0xD40); - if(!Display13FBTopology::apply(s->adlnPlatform,sizeof(s->adlnPlatform),platform,s->adlnHDMIWiring))return false; + if(!Display13FBTopology::apply(s->adlnPlatform,sizeof(s->adlnPlatform),platform,s->adlnOutputWiring))return false; const auto nativePool=Display13FBMemory::field(controller,0xDE8); const auto profilePool=Display13FBMemory::field(s->adlnPlatform,0x14); const auto requested=s->framebufferPatchFlags.bits.FPFStolenMemorySize ? s->framebufferPatch.fStolenMemorySize : 0; @@ -5902,7 +5986,7 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { auto s=callbackXE; if(!s->display13FBTopologyHooksReady || !s->adlnTopologyController || !port || port->getMetaClass()->getClassSize()<0x580)return 0; auto config=Display13FBMemory::field(port,0x548); - if(Display13FBTopology::selected(s->adlnPlatform,config,s->adlnHDMIWiring)) { + if(Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring)) { // CPU binding only. TC bits are added when mode/AUX/PLL/GOP become // qualified together; a board description alone is insufficient. if(!s->xelpBindProbe(s->adlnTopologyController)) { @@ -5922,7 +6006,7 @@ uint32_t XE::wrapAlderLakeNPortState(OSObject *port) { } // Deliberately excluded connector policy. Do not enter TC/AUX hardware // probing, and do not substitute a connected result for the selected port. - for(unsigned d=0;dadlnHDMIWiring.hdmi&(1U<adlnPlatform,config,d)) { + for(unsigned d=0;dadlnOutputWiring.selected()&(1U<adlnPlatform,config,d)) { *reinterpret_cast(reinterpret_cast(port)+0x554)=0;return 0; } return 0; @@ -5944,6 +6028,10 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m 0x49,0x8B,0x7D,0x00,0x48,0x8B,0x07,0xFF,0x90,0x30,0x01,0x00,0x00, 0x90,0x90,0x90,0x90,0x90,0x90,0x90}; const uint8_t busEntry[] {0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56}; + // Same nominal CFGCR0 write as the Display12 adapter. ADL-P/N also + // requires WA 22010492432 when the physical reference is 38.4 MHz. + const uint8_t fractionWrite[] {0x89,0xfa,0x49,0x8b,0xbd,0x40,0x0c,0x00,0x00,0x48,0x8b,0x07, + 0x48,0x8b,0x75,0xb8,0xff,0x90,0x30,0x01,0x00,0x00}; const uint8_t readEntry[] {0x55,0x48,0x89,0xE5,0x53,0x50}; const uint8_t resetBefore[] {0xBE,0x04,0x51,0x0C,0x00,0xBA,0x01,0x00,0x00,0x00,0xFF,0x90,0x30,0x01,0x00,0x00}; const uint8_t resetAfter[] {0xBE,0x04,0x51,0x0C,0x00,0xBA,0x00,0x00,0x00,0x80,0xFF,0x90,0x30,0x01,0x00,0x00}; @@ -5973,8 +6061,9 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m "__ZN31AppleIntelRegisterAccessManager14ReadRegister32Em",address,size);patcher.clearError(); auto write=patcher.solveSymbol(index, "__ZN31AppleIntelRegisterAccessManager15WriteRegister32Emj",address,size);patcher.clearError(); - if(!adlnDetected || !adlnKernelReady || !display13FBABIReady || size<=0x9A118 || + if(!display13FramebufferRequested || !display13FBABIReady || !display13FBClockReadoutHooksReady || size<=0x9A118 || clocks!=address+0x6CE10 || bus!=address+0x53A7C || + memcmp(reinterpret_cast(address+0x6D95D),fractionWrite,sizeof(fractionWrite)) || memcmp(reinterpret_cast(bus),busEntry,sizeof(busEntry)) || read!=address+0x5E344 || write!=address+0x5EB5C || memcmp(reinterpret_cast(read),readEntry,sizeof(readEntry)) || @@ -6010,7 +6099,261 @@ bool XE::processDisplay13FramebufferOutput(KernelPatcher &patcher,size_t index,m }; const bool ready=patcher.routeMultipleShort(index,routes,arrsize(routes),address,size);patcher.clearError(); IORegistryEntry::getRegistryRoot()->setProperty("N97FBGMBusObservationReady",ready); - return ready && processDisplay13FramebufferColor(patcher,index,address,size); + const bool complete=ready && processDisplay13FramebufferColor(patcher,index,address,size) && + processDisplay13FramebufferSST(patcher,index,address,size) && processDisplay13FramebufferAUX(patcher,index,address,size); + adlnComboPLLFractionCaller=complete ? address+0x6D973 : 0; + return complete; +} + +// The native get/put AUX methods return void. Gate the actual HAL transfer +// after get, then recheck the public operation after put so neither failure +// becomes successful DPCD/EDID data. No ACK/status or retry policy is emulated. +bool XE::processDisplay13FramebufferAUX(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + const char *names[]={"__ZN14AppleIntelPort7readAUXEjPvj","__ZN14AppleIntelPort8writeAUXEjPvj", + "__ZN14AppleIntelPort13runI2COverAUXENS_11AUX_CommandEPjjh", + "__ZN17AppleIntelPortHAL13runAUXCommandEN14AppleIntelPort11AUX_CommandEPjjb"}; + const uint32_t offsets[]={0x1CC72,0x1CEB0,0x1D2B6,0x61748}; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + if(!display13FramebufferRequested || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x138BB0)return false; + for(unsigned i=0;i<4;++i) { + auto fn=patcher.solveSymbol(index,names[i],address,size);patcher.clearError(); + if(fn!=address+offsets[i] || memcmp(reinterpret_cast(fn),entry,sizeof(entry)))return false; + } + auto global=patcher.solveSymbol(index,"_gController",address,size);patcher.clearError(); + if(global!=address+0x138BA8)return false; + KernelPatcher::RouteRequest routes[]={ + {names[0],wrapDisplay13ReadAUX,orgDisplay13ReadAUX},{names[1],wrapDisplay13WriteAUX,orgDisplay13WriteAUX}, + {names[2],wrapDisplay13I2CAUX,orgDisplay13I2CAUX},{names[3],wrapDisplay13RunAUX,orgDisplay13RunAUX} + }; + display13FBAUXHooksReady=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError(); + display13AUXControllerSlot=display13FBAUXHooksReady ? global : 0; + return display13FBAUXHooksReady; +} + +// Caller holds the display owner lock. Probe may run before setPortMode, +// hence mode0 is allowed; a DDI/main-link lease is deliberately not required. +bool XE::display13AUXReady(void *port,bool command) { + auto s=callbackXE;auto &c=display13FBPower;auto object=static_cast(port); + if(!s->display13FramebufferRequested || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBAUXHooksReady || + !object || object->getMetaClass()->getClassSize()<0x5AC || !c.controller || + c.controller!=s->adlnTopologyController || !s->display13AUXControllerSlot || + *reinterpret_cast(s->display13AUXControllerSlot)!=c.controller || !adlnWriterMappingBound() || + !c.bridge.begun || !c.bridge.state.ready || c.bridge.state.faulted || c.bridge.state.suspended || + c.clockFaulted || c.memoryFaulted || !Display13Power::objectAssociations(c.power,0x80,c.controller,0xC60) || + Display13FBMemory::field(c.controller,0xD0) || + Display13FBMemory::field(c.controller,0xB48) || + Display13FBMemory::field(c.controller,0x1BD8) || + !Display13FBMemory::field(c.controller,0xC30))return false; + auto p=static_cast(port);auto config=Display13FBMemory::field(p,0x548); + if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || config[0]>=2 || + Display13FBMemory::field(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)>1 || + Display13FBMemory::field(p,0x578)!=c.access || + Display13FBMemory::field(c.power,0x10)!=c.controller || + Display13FBMemory::field(c.power,0x78)!=c.access)return false; + // Exact c004 HAL uses CTL + five DATA offsets from the port object. + const auto ctl=0x64010+0x100*config[0]; + for(unsigned n=0;n<6;++n)if(Display13FBMemory::field(p,0x594+4*n)!=ctl+4*n)return false; + if(!command)return true; + Display13FBPowerIO io {c.map};return c.bridge.beginAUXCommand(io,config[0]); +} + +uint32_t XE::wrapDisplay13ReadAUX(void *port,uint32_t address,void *data,uint32_t length) { + auto &c=display13FBPower;if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const bool ready=display13AUXReady(port,false);IOLockUnlock(c.lock); + if(!ready)return kIOReturnNotReady; + const auto r=FunctionCast(wrapDisplay13ReadAUX,callbackXE->orgDisplay13ReadAUX)(port,address,data,length); + IOLockLock(c.lock);const bool complete=display13AUXReady(port,false);IOLockUnlock(c.lock); + return r ? r : complete ? 0 : kIOReturnNotReady; +} +uint32_t XE::wrapDisplay13WriteAUX(void *port,uint32_t address,void *data,uint32_t length) { + auto &c=display13FBPower;if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const bool ready=display13AUXReady(port,false);IOLockUnlock(c.lock); + if(!ready)return kIOReturnNotReady; + const auto r=FunctionCast(wrapDisplay13WriteAUX,callbackXE->orgDisplay13WriteAUX)(port,address,data,length); + IOLockLock(c.lock);const bool complete=display13AUXReady(port,false);IOLockUnlock(c.lock); + return r ? r : complete ? 0 : kIOReturnNotReady; +} +uint32_t XE::wrapDisplay13I2CAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,uint8_t flag) { + auto &c=display13FBPower;if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const bool ready=display13AUXReady(port,false);IOLockUnlock(c.lock); + if(!ready)return kIOReturnNotReady; + const auto r=FunctionCast(wrapDisplay13I2CAUX,callbackXE->orgDisplay13I2CAUX)(port,command,packet,length,flag); + IOLockLock(c.lock);const bool complete=display13AUXReady(port,false);IOLockUnlock(c.lock); + return r ? r : complete ? 0 : kIOReturnNotReady; +} +uint32_t XE::wrapDisplay13RunAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,bool flag) { + auto &c=display13FBPower;if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);const bool ready=display13AUXReady(port,true); + const auto ddi=ready ? Display13FBMemory::field(port,0x120) : 7; + IOLockUnlock(c.lock);if(!ready)return kIOReturnNotReady; + const auto r=FunctionCast(wrapDisplay13RunAUX,callbackXE->orgDisplay13RunAUX)(port,command,packet,length,flag); + IOLockLock(c.lock);Display13FBPowerIO io {c.map};const bool held=c.bridge.endAUXCommand(io,ddi); + const bool complete=display13AUXReady(port,false) && held; + if(!complete)adlnFault(c.bridge.state.faulted,__LINE__); + IOLockUnlock(c.lock);return r ? r : complete ? 0 : kIOReturnNotReady; +} + +bool XE::processDisplay13FramebufferSST(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { + // Install the bounded Combo backend independently of selecting DP at startup. + // Startup still requires AUX/probe and DP boot-handoff operation closure. + const char *pattern="__ZN21AppleIntelDisplayPath22setLinkTrainingPatternE19LinkTrainingPattern"; + const char *signal="__ZN17AppleIntelPortHAL29setVoltageSwingAndPreEmphasisEjjP15PortDriveConfig"; + const uint8_t patternEntry[]={0x55,0x48,0x89,0xe5,0x48,0xff,0x05,0x05,0x73,0x05,0x00,0x48,0xff,0x05,0x3e,0x6b}; + const uint8_t signalEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x48,0x83,0xec}; + const uint8_t pcsABI[]={0x8b,0xb3,0x20,0x06,0x00,0x00},lanesABI[]={0x8b,0xb3,0xc8,0x05,0x00,0x00}; + const uint8_t successABI[]={0x31,0xc0,0x48,0x83,0xc4,0x28}; + const char *enable="__ZN17AppleIntelPortHAL10enablePortEv",*disable="__ZN17AppleIntelPortHAL11disablePortEv"; + const uint8_t enableEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x49,0x89,0xff}; + const uint8_t disableEntry[]={0x55,0x48,0x89,0xe5,0x53,0x50,0x48,0x89,0xfb}; + if(!display13FramebufferRequested || !display13FBABIReady || !display13FBPowerReferencesReady || size<0x955C8)return false; + const auto p=patcher.solveSymbol(index,pattern,address,size);patcher.clearError(); + const auto s=patcher.solveSymbol(index,signal,address,size);patcher.clearError(); + const auto e=patcher.solveSymbol(index,enable,address,size);patcher.clearError(); + const auto d=patcher.solveSymbol(index,disable,address,size);patcher.clearError(); + if(p!=address+0x955B8 || s!=address+0x60AE0 || e!=address+0x5F9F4 || d!=address+0x5FC56 || + memcmp(reinterpret_cast(e),enableEntry,sizeof(enableEntry)) || + memcmp(reinterpret_cast(d),disableEntry,sizeof(disableEntry)) || + memcmp(reinterpret_cast(p),patternEntry,sizeof(patternEntry)) || + memcmp(reinterpret_cast(s),signalEntry,sizeof(signalEntry)) || + memcmp(reinterpret_cast(address+0x60EB9),pcsABI,sizeof(pcsABI)) || + memcmp(reinterpret_cast(address+0x61468),lanesABI,sizeof(lanesABI)) || + memcmp(reinterpret_cast(address+0x61478),successABI,sizeof(successABI)))return false; + KernelPatcher::RouteRequest routes[] { + {pattern,wrapDisplay13TrainingPattern,orgDisplay13TrainingPattern}, + {signal,wrapDisplay13SignalLevels,orgDisplay13SignalLevels}, + {enable,wrapDisplay13EnablePort,orgDisplay13EnablePort}, + {disable,wrapDisplay13DisablePort,orgDisplay13DisablePort} + }; + display13FBSSTHooksReady=patcher.routeMultiple(index,routes,arrsize(routes),address,size);patcher.clearError(); + return display13FBSSTHooksReady; +} + +// Called with the display owner lock held. The native caller owns the port; +// require its retained platform record and BAR association before sampling it. +uint8_t *XE::display13SSTPort(void *port) { + auto s=callbackXE;auto &c=display13FBPower;auto object=static_cast(port); + if(!s->display13FramebufferRequested || s->xeProfile.display!=XELP::Display::XeLPD || !s->display13FBSSTHooksReady || + !object || object->getMetaClass()->getClassSize()<0x624 || !c.controller || + c.controller!=s->adlnTopologyController || !adlnWriterMappingBound() || + c.clockFaulted || c.memoryFaulted || c.clockStopped || !s->adlnOutputWiring.described())return nullptr; + auto p=static_cast(port);auto config=Display13FBMemory::field(p,0x548); + if(!Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) || + !(s->adlnOutputWiring.dp&(1U<(p,0x120)!=config[0] || Display13FBMemory::field(p,0x550)!=1 || + Display13FBMemory::field(p,0x578)!=c.access)return nullptr; + return p; +} + +bool XE::display13HDMIPort(void *port) { + auto s=callbackXE;auto object=static_cast(port); + if(!object || object->getMetaClass()->getClassSize()<0x580)return false; + auto config=Display13FBMemory::field(port,0x548); + return Display13FBTopology::selected(s->adlnPlatform,config,s->adlnOutputWiring) && s->adlnOutputWiring.matchesHDMI(config); +} + +bool XE::display13MainLinkAUX(void *port,bool acquire) { + auto &c=display13FBPower;if(!c.lock)return false; + OSObject *retired=nullptr;bool ok=false;IOLockLock(c.lock); + auto p=display13SSTPort(port); + if(p && Display13Power::objectAssociations(c.power,0x80,c.controller,0xC60) && + Display13FBMemory::field(c.power,0x10)==c.controller && + Display13FBMemory::field(c.power,0x78)==c.access) { + const auto ddi=Display13FBMemory::field(p,0x120); + if(ddi<2 && Display13FBMemory::field(p,0x590)==0x64000+0x100*ddi) { + auto &link=c.mainLink[ddi];const auto previous=link.owner;Display13FBPowerIO io{c.map}; + const auto result=acquire ? link.begin(io,c.bridge,reinterpret_cast(port),ddi) : + link.end(io,c.bridge,reinterpret_cast(port),ddi); + ok=result.status==Display13Power::Status::Complete; + if(!previous && link.owner)static_cast(port)->retain(); + if(previous && !link.owner)retired=reinterpret_cast(previous); + } + } + IOLockUnlock(c.lock); + if(retired)retired->release(); // A destructor must not run under the owner lock. + return ok; +} + +uint32_t XE::wrapDisplay13EnablePort(void *port) { + auto s=callbackXE; + if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + return FunctionCast(wrapDisplay13EnablePort,s->orgDisplay13EnablePort)(port); + if(!display13MainLinkAUX(port,true))return kIOReturnNotReady; + const auto result=FunctionCast(wrapDisplay13EnablePort,s->orgDisplay13EnablePort)(port); + if(result)display13MainLinkAUX(port,false); // Retain AUX if partial enable left the buffer active. + return result ? result : display13MainLinkAUX(port,true) ? 0 : kIOReturnNotReady; +} + +uint32_t XE::wrapDisplay13DisablePort(void *port) { + auto s=callbackXE; + if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + return FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); + if(!display13MainLinkAUX(port,true))return kIOReturnNotReady; + const auto result=FunctionCast(wrapDisplay13DisablePort,s->orgDisplay13DisablePort)(port); + const bool released=display13MainLinkAUX(port,false); + return result ? result : released ? 0 : kIOReturnNotReady; +} + +uint32_t XE::wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphasis,void *overrideConfig) { + auto s=callbackXE;auto &c=display13FBPower; + // No protocol replacement for HDMI; preserve the donor's existing behavior. + if(!s->adlnOutputWiring.dp || display13HDMIPort(port)) + return FunctionCast(wrapDisplay13SignalLevels,s->orgDisplay13SignalLevels)(port,swing,emphasis,overrideConfig); + if(!c.lock)return kIOReturnNotReady; + IOLockLock(c.lock);auto p=display13SSTPort(port);uint32_t result=kIOReturnNotReady; + if(p && !overrideConfig) { + const auto ddi=Display13FBMemory::field(p,0x120);TGLComboSignal selected; + Display13FBPowerIO io{c.map}; + if(selected.selectADLP(ddi,Display13FBMemory::field(p,0x124),Display13FBMemory::field(p,0x128),swing,emphasis) && + Display13FBMemory::field(p,0x620)==selected.base+0x804 && + Display13FBMemory::field(p,0x5C8)==selected.base+0x28 && + Display13FBMemory::field(c.access,0x60)>=selected.base+0xBA0 && c.bridge.ddiHeld(io,ddi)) { + Display13FBSSTIO registers{c.map}; + if(selected.program([&](uint32_t r){return registers.read(r);}, + [&](uint32_t r,uint32_t v){registers.write(r,v);} ))result=0; + } + } + IOLockUnlock(c.lock);return result; +} + +uint32_t XE::wrapDisplay13TrainingPattern(void *path,uint32_t pattern) { + auto s=callbackXE;auto &c=display13FBPower; + if(!s->adlnOutputWiring.dp)return FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + auto object=static_cast(path); + if(!c.lock || !object || object->getMetaClass()->getClassSize()<0x3730)return kIOReturnNotReady; + if(display13HDMIPort(Display13FBMemory::field(path,0x3648))) + return FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + // Never hold the nonrecursive power lock over donor training or AUX calls. + // Both register stages revalidate the graph, logical leases and live power. + const auto program=[&](bool before) { + IOLockLock(c.lock);bool ok=false; + auto p=display13SSTPort(Display13FBMemory::field(path,0x3648)); + auto fb=Display13FBMemory::field(path,0x32B0); + const auto binding=p ? adlnInspectPathBinding(fb,object,s->adlnPlatform,s->adlnOutputWiring,false) : TGLFBBinding::Readout{}; + Display13FBPowerIO io{c.map}; + if(binding.status==TGLFBBinding::Status::Complete && c.bridge.outputHeld(io,binding.ddi,binding.pipe)) { + Display13FBSSTIO registers{c.map}; + const auto read=[&](uint32_t r){return registers.read(r);}; + const auto write=[&](uint32_t r,uint32_t v){registers.write(r,v);}; + if(before) { + ok=true; + if(pattern==1) { + const auto r=XELP::SSTProgram::prepare(read,write,binding.ddi,binding.pipe,Display13FBMemory::field(p,0x124),true); + // An already-active, identical route needs no rewrite. Never repair an + // active neighbor or a failed posting read by falling through to native. + ok=r.bufferBefore!=UINT32_MAX && r.clockBefore!=UINT32_MAX && r.funcBefore!=UINT32_MAX && + (!r.reason || (r.reason==2 && r.clockBefore==r.clockWanted && r.funcBefore==r.funcWanted)); + } + } else { + const auto r=XELP::SSTProgram::framing(read,write,binding.pipe,p[0x11A],false,false,0,true); + ok=r.ready && r.after!=UINT32_MAX && !(r.after&0x08000000U) && (!r.active || r.after==r.wanted); + } + } + IOLockUnlock(c.lock);return ok; + }; + if(!program(true))return kIOReturnNotReady; + const auto result=FunctionCast(wrapDisplay13TrainingPattern,s->orgDisplay13TrainingPattern)(path,pattern); + if(result || pattern>5)return result; + return program(false)?0:kIOReturnNotReady; } uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t speed,void *request) { static uint32_t count {}; const auto call=__sync_add_and_fetch(&count,1); @@ -6023,7 +6366,7 @@ uint32_t XE::wrapDisplay13GMBus(IOService *controller,uint32_t bus,uint32_t spee // Both S1 HDMI ports use this donor wait boundary. Qualify the actual // board-selected DDC pin; an A-only boot must not inherit the B-only gate. o.edid=controller==callbackXE->adlnTopologyController && - callbackXE->adlnHDMIWiring.usesDDC(bus) && speed==0 && req && + callbackXE->adlnOutputWiring.usesDDC(bus) && speed==0 && req && *reinterpret_cast(req)==0 && *reinterpret_cast(req+4)==1 && *reinterpret_cast(req+0x0C)==0xA0 && *reinterpret_cast(req+0x38)==128; scope=&o;break; @@ -6270,6 +6613,15 @@ void XE::wrapAlderLakeNGamma10(void *path,void *transaction,uint32_t control) { } void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { + // DISPLAY13_COMBO_FRACTION: qualified nominal writer, physical reference, + // retained controller/manager. No MMIO or other port's PLL inference. + if(callbackXE->display13FramebufferRequested && callbackXE->xeProfile.display==XELP::Display::XeLPD && + callbackXE->adlnComboPLLFractionCaller && reinterpret_cast(__builtin_return_address(0))==callbackXE->adlnComboPLLFractionCaller) { + auto controller=reinterpret_cast(callbackXE->adlnTopologyController); + if(controller && access && *reinterpret_cast(controller+0xC40)==access) + TGLComboPLLFraction::adjust(*reinterpret_cast(controller+0xEA8),reg,value); + } + // DISPLAY13_COMBO_FRACTION_END // This route also carries direct native cursor writes, outside the WM // trigger queue. Apply Display 13's slot WA only to our proven owner. if(reg==0x70080 || reg==0x71080 || reg==0x72080) { @@ -6318,7 +6670,7 @@ void XE::wrapDisplay13GMBusWrite(void *access,uint64_t reg,uint32_t value) { const bool arbitrationRegister=reg==0x70008 || reg==0x71008 || reg==0x72008 || reg==0x70180 || reg==0x71180 || reg==0x72180; const auto writerBase=__atomic_load_n(&display.arbitrationWriterBase,__ATOMIC_ACQUIRE); - if(arbitrationRegister && callbackXE->adlnDetected && callbackXE->adlnNativeReady && access==display.access && + if(arbitrationRegister && callbackXE->display13FramebufferRequested && callbackXE->adlnNativeReady && access==display.access && display.engineEnabled && writerBase && Display13FBMemory::field(access,0x50)==writerBase) { for(unsigned pipe=0;pipe<3;++pipe) { const auto bank=0x1000*pipe; @@ -6419,7 +6771,7 @@ uint32_t XE::wrapDisplay13FBDDC(IOService *fb,int connection,uint32_t block,uint } uint32_t XE::wrapAlderLakeNPortDDC(OSObject *port,uint32_t block,uint8_t *data,bool force) { auto config=Display13FBMemory::field(port,0x548); - const bool selected=Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnHDMIWiring); + const bool selected=Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring); uint64_t r[8] {1,block,force,0,0,config ? config[0] : 9}; auto root=IORegistryEntry::getRegistryRoot();if(selected)root->setProperty("N97FBPortDDCEntry",r,sizeof(r)); const auto result=FunctionCast(wrapAlderLakeNPortDDC,callbackXE->orgAlderLakeNPortDDC)(port,block,data,force);r[3]=result; @@ -6436,7 +6788,7 @@ bool XE::wrapAlderLakeNComputeLanes(OSObject *port,const void *timing,uint32_t b const auto caller=reinterpret_cast(__builtin_return_address(0)); const auto native=FunctionCast(wrapAlderLakeNComputeLanes,callbackXE->orgAlderLakeNComputeLanes)(port,timing,bpp,available,lanes); auto config=Display13FBMemory::field(port,0x548); - const bool corrected=lanes && Display13FBTopology::skipDPLanes(callbackXE->adlnPlatform,config,callbackXE->adlnHDMIWiring, + const bool corrected=lanes && Display13FBTopology::skipDPLanes(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring, caller==callbackXE->adlnHDMILaneCaller,native,available,*lanes); static uint32_t count {};uint64_t r[] {1,__sync_add_and_fetch(&count,1),config ? config[0] : 9,config ? config[2] : 0, bpp,available,native,lanes ? *lanes : UINT32_MAX,caller==callbackXE->adlnHDMILaneCaller,corrected}; @@ -6447,7 +6799,7 @@ bool XE::wrapAlderLakeNComputeLanes(OSObject *port,const void *timing,uint32_t b bool XE::processDisplay13FramebufferABI(KernelPatcher &patcher,size_t index,mach_vm_address_t address,size_t size) { // OS/donor ABI corrections are shared with Display12. Keep hardware // routing and topology in the selected Display13 backend. - if(!adlnDetected || !adlnKernelReady || size<=0xCCCD8)return false; + if(!display13FramebufferRequested || size<=0xCCCD8)return false; const bool surface=tglInstallSonomaSurfaceABI(patcher,index,address,size,true); const bool nvram=xelpInstallNVRAMABI(patcher,index,address,size); auto table=patcher.solveSymbol(index,"__ZTV17AppleIntelPortHAL",address,size); @@ -6481,7 +6833,7 @@ bool XE::processDisplay13FramebufferABI(KernelPatcher &patcher,size_t index,mach bool XE::wrapDisplay13FramebufferInit(IOService *fb,IOService *controller,uint32_t index) { auto pci=controller ? OSDynamicCast(IOPCIDevice,controller->getProvider()) : nullptr; if(!callbackXE->display13FBABIReady || !fb || !pci || index>=3 || - !xelpAlderLakeNDevice(pci) || + !xelpDisplay13Device(pci) || controller->getMetaClass()->getClassSize()<0x1C20 || fb->getMetaClass()->getClassSize()<0x4B90)return false; if(!FunctionCast(wrapDisplay13FramebufferInit,callbackXE->orgDisplay13FramebufferInit)(fb,controller,index))return false; auto manager=Display13FBMemory::field(controller,0xC40); @@ -6513,26 +6865,27 @@ bool XE::wrapDisplay13FramebufferInit(IOService *fb,IOService *controller,uint32 bool XE::wrapDisplay13FramebufferStart(IOService *that,IOService *provider) { auto s=callbackXE;auto pci=OSDynamicCast(IOPCIDevice,provider); - const bool ready=s->adlnDetected && s->adlnKernelReady && s->display13FBPowerReferencesReady && - s->display13FBPowerBackendReady && s->display13FBClockReadoutHooksReady && s->display13FBClockSetHookReady && - s->display13FBClockReady && s->display13FBIRQReady && s->display13FBMemoryHooksReady && - s->display13FBEngineEnableHookReady && s->display13FBLifecycleHooksReady && s->display13FBDBUFHooksReady && s->display13FBWatermarkLayoutReady && s->display13FBWatermarkHooksReady && s->display13FBOutputHooksReady && - s->display13FBBootHandoffHookReady && s->display13FBGTTHooksReady && s->display13FBMemoryReady && s->display13FBABIReady && s->display13FBTopologyHooksReady && - pci && xelpAlderLakeNDevice(pci); - that->setProperty("N97FramebufferBackendReady",ready); - if(!ready)return false; + 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->adlnHDMIWiring=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, + s->adlnOutputWiring=XELP::VBT::selectHDMI(property!=nullptr,wiring?wiring->getBytesNoCopy():nullptr, wiring?wiring->getLength():0,s->firmwarePorts,3); - if(!Display13FBTopology::supportedWiring(s->adlnHDMIWiring))return false; + if(!Display13FBTopology::supportedWiring(s->adlnOutputWiring))return false; + const auto contract=XELP::DisplayContract::display13(s->display13Available(),s->adlnOutputWiring.dp!=0); + const bool ready=s->display13FramebufferRequested && contract.ready() && pci && xelpDisplay13Device(pci); + uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; + that->setProperty("WEXDisplayStartupContract",contractRecord,sizeof(contractRecord)); + if(!contract.ready())for(uint64_t bit=1;bit;bit<<=1)if(contract.missing()&bit) + 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 && (!adlnFixedSysMemEnabled || display13FBSysMem.lock) && display13FBSysMem.retireLock && s->adlnWorkLoopInGate && s->adlnWorkLoopTryClose && s->adlnWorkLoopOpen; - uint64_t dualPolicy[] {1,s->adlnHDMIWiring.hdmi,dualReady,s->adlnNativeReady,s->adlnScanoutDisableFB!=0, + uint64_t dualPolicy[] {1,s->adlnOutputWiring.selected(),dualReady,s->adlnNativeReady,s->adlnScanoutDisableFB!=0, s->orgAlderLakeNSysMemAllocate!=0,s->orgAlderLakeNSysMemFree!=0,display13FBSysMem.lock!=nullptr,display13FBSysMem.retireLock!=nullptr,adlnFixedSysMemEnabled}; IORegistryEntry::getRegistryRoot()->setProperty("N97FramebufferDualPolicy",dualPolicy,sizeof(dualPolicy)); - if(Display13FBTopology::multiOutputWiring(s->adlnHDMIWiring) && !dualReady)return false; + if(Display13FBTopology::multiOutputWiring(s->adlnOutputWiring) && !dualReady)return false; // Native provider-workloop mode creates/binds the PCI interrupt source in // controller start, rather than waiting for an accelerator or FB open. // This backend supports linear boot surfaces and Y-tiled native scanout, @@ -6665,7 +7018,7 @@ void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) if(valid) { auto config=Display13FBMemory::field(port,0x548); valid=Display13FBTopology::bound(callbackXE->adlnPlatform,config,c.bridge.bootPort) && - Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnHDMIWiring) && + Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring) && Display13Power::objectAssociations(c.power,0x80,controller,0x1C20); } check[35]=valid; @@ -6680,7 +7033,18 @@ void XE::wrapAlderLakeNSetupWorkLoop(IOService *controller,IOWorkLoop *workLoop) } check[37]=valid; if(valid) { - if(!c.bridge.bootAdopted)result=c.bridge.adoptBootPath(reinterpret_cast(path),0,c.bridge.bootPort); + if(!c.bridge.bootAdopted) { + const auto ddi=c.bridge.bootPort; + const bool dp=ddi<2 && (callbackXE->adlnOutputWiring.dp&(1U<(port),ddi); + linked=result.status==Display13Power::Status::Complete; + if(linked)port->retain(); // Released only after real buffer stop/idle. + } + if(linked)result=c.bridge.adoptBootPath(reinterpret_cast(path),0,ddi); + } else if(c.bridge.bootPath==reinterpret_cast(path))result.status=Display13Power::Status::Complete; if(result.status==Display13Power::Status::Complete && !Display13Power::publishObject(c.bridge,c.power,0x80,controller,0x1C20))result.status=Display13Power::Status::NotReady; @@ -6700,12 +7064,12 @@ bool XE::wrapAlderLakeNScanoutAllocate(IOService *fb) { auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return false; IORecursiveLockLock(mutex); bool admitted=false,allocated=false,bootScanout=false;Display13FBMemory::Readout r; - unsigned pipe=0;const bool dual=Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring); + unsigned pipe=0;const bool dual=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring); uint64_t record[15] {2,1};IOLockLock(c.lock); auto controller=static_cast(c.controller); TGLFBBinding::Readout binding; if(dual) { - binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); pipe=binding.pipe; } record[12]=pipe;record[13]=static_cast(binding.status);record[14]=dual; @@ -6732,7 +7096,7 @@ bool XE::wrapAlderLakeNScanoutAllocate(IOService *fb) { // before publishing an IRQ-visible identity or surface. if(dual) { const auto path=Display13FBMemory::singlePath(fb); - const auto current=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto current=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); allocated=allocated && current.status==TGLFBBinding::Status::Complete && current.pipe==pipe && current.ddi==binding.ddi && Display13FBMemory::identityAvailable(c.pipe,fb,pipe); if(allocated) { @@ -6775,10 +7139,10 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { IOWorkLoop *acquiredSwapGate=nullptr;OSObject *stopPath=nullptr;bool stopSwap=false; IOLockLock(c.lock); pipe=Display13FBMemory::boundPipe(c.pipe,fb);record[18]=pipe; - const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); record[19]=static_cast(binding.status); if(callbackXE->display13FBMemoryHooksReady && pipe<3 && !c.pipe[pipe].destroyingPath && !c.memoryFaulted && - (!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) || (binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe))) { + (!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) || (binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe))) { r=display13FBInspectMemory(fb,false,pipe); admitted=r.status==Display13FBMemory::Status::Complete; record[2]=reinterpret_cast(fb);record[3]=reinterpret_cast(c.controller); @@ -6786,7 +7150,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { record[6]=Display13FBMemory::field(c.controller,0xDF0);record[7]=Display13FBMemory::field(fb,0x4348); record[8]=Display13FBMemory::field(fb,0x4330);record[9]=static_cast(r.status); } - if(admitted && Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(admitted && Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { auto controller=static_cast(c.controller); const auto slot=Display13FBMemory::field(fb,0x4288); const auto surface=Display13FBMemory::field(fb,0x4348); @@ -6832,7 +7196,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { swap[14]=1; reinterpret_cast(callbackXE->adlnScanoutDisableFB)(fb,false); IOLockLock(c.lock); - const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); swap[15]=static_cast(current.status); Display13FBIRQIO io {c.map->getVirtualAddress()};swap[16]=Display13FBIRQ::consumersStopped(io,1U<adlnHDMIWiring)) { - const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { + const auto current=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); admitted=current.status==TGLFBBinding::Status::Complete && current.pipe==pipe && current.ddi==binding.ddi; if(!admitted) { r.status=Display13FBMemory::Status::Association; @@ -6876,7 +7240,7 @@ void XE::wrapAlderLakeNScanoutSetup(IOService *fb) { adlnPublishMemory("N97FBScanoutSetup",pipe,record,sizeof(record));adlnPublishMemory("N97FBBacking",pipe,&r,sizeof(r)); } void XE::wrapAlderLakeNScanoutFree(IOService *fb) { - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) {adlnFreeStoppedPipe(fb);return;} + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) {adlnFreeStoppedPipe(fb);return;} auto &c=display13FBPower;if(!c.lock || !fb || fb->getMetaClass()->getClassSize()<0x4B90)return; auto mutex=Display13FBMemory::field(fb,0x4220);if(!mutex)return; IORecursiveLockLock(mutex); @@ -6966,7 +7330,7 @@ void XE::adlnFreeStoppedPipe(IOService *fb) { const auto mask=controller ? Display13FBMemory::field(controller,0xDF0) : 0; const auto surface=Display13FBMemory::field(fb,0x4348); const auto pool=slot<3 ? Display13FBMemory::pool(controller,slot) : Display13FBMemory::Pool {}; - const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,Display13FBMemory::singlePath(fb),callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); bool pinned=false;for(const auto &stored:display13FBSysMem.slot)if(stored.framebuffer==fb && stored.buffer)pinned=true; const bool noResource=controller && slot==0xFF && !surface && !pinned && Display13FBMemory::bitmapValid(count,used,mask) && @@ -7056,7 +7420,7 @@ void XE::adlnFreeStoppedPipe(IOService *fb) { } void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { - if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(!Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { FunctionCast(wrapAlderLakeNFreePath,callbackXE->orgAlderLakeNFreePath)(fb,path);return; } auto &c=display13FBPower; @@ -7068,7 +7432,7 @@ void XE::wrapAlderLakeNFreePath(IOService *fb,OSObject *path) { fb->retain();path->retain();IORecursiveLockLock(mutex); uint64_t record[16] {1};bool released=false,detached=false;IOService *withdrawn=nullptr; IOLockLock(c.lock);const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool selected=pipe<3 && binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe; record[1]=pipe;record[2]=selected;record[3]=binding.ddi; const auto countOffset=Display13FBMemory::pathCountOffset(fb); @@ -7159,7 +7523,7 @@ Display13Clock::Readout XE::adlnClockReadout(IOService *controller) { Display13Clock::Readout r; auto pci=controller ? OSDynamicCast(IOPCIDevice,controller->getProvider()) : nullptr; if(callbackXE->display13FBClockReadoutHooksReady && pci && - xelpAlderLakeNDevice(pci) && (pci->configRead16(4)&2) && + xelpDisplay13Device(pci) && (pci->configRead16(4)&2) && controller->getMetaClass()->getClassSize()>=0xEAC && *reinterpret_cast(reinterpret_cast(controller)+0xC60)==pci && *reinterpret_cast(reinterpret_cast(controller)+0xC40)) { @@ -7201,7 +7565,7 @@ void XE::wrapAlderLakeNClockInit(IOService *controller) { } uint64_t XE::wrapAlderLakeNClockProbe(IOService *controller) {return adlnClockReadout(controller).nativeHz();} void XE::wrapAlderLakeNClockSet(IOService *controller,uint64_t nativeHz) { - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) {adlnSetDualClock(controller,nativeHz);return;} + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) {adlnSetDualClock(controller,nativeHz);return;} auto &c=display13FBPower;if(!c.lock)return; Display13Clock::Change result;result.requestedHz=nativeHz; auto fb=c.pipe[0].memoryFramebuffer; @@ -7326,12 +7690,12 @@ void XE::adlnSetDualClock(IOService *controller,uint64_t nativeHz) { if(ready) { TGLFBBinding::PublishedPath paths[3];uintptr_t owners[3]; for(unsigned p=0;p<3;++p) {paths[p]=c.pipe[p].powerPath;owners[p]=reinterpret_cast(c.pipe[p].memoryFramebuffer);} - const auto map=TGLFBBinding::powerPipes(paths,owners,callbackXE->adlnHDMIWiring.hdmi); + const auto map=TGLFBBinding::powerPipes(paths,owners,callbackXE->adlnOutputWiring.selected()); uint32_t published=0;for(unsigned d=0;d<7;++d)if(map.known&(1U<(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); ready=fb==c.pipe[p].memoryFramebuffer && !c.pipe[p].destroyingPath && TGLFBBinding::publish(paths[p],reinterpret_cast(fb),reinterpret_cast(path),binding,p); } @@ -7483,7 +7847,7 @@ void XE::adlnWMSetup(OSObject *plane,OSObject *path,bool cursor) { {plane,plane ? plane->getMetaClass()->getClassSize() : 0}, {port,port ? port->getMetaClass()->getClassSize() : 0},display13FBExpectedOwner(controller) }; - const auto binding=TGLFBBinding::inspect(objects,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=TGLFBBinding::inspect(objects,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); uint64_t bindingRecord[] {1,static_cast(binding.status),binding.framebuffer,binding.pipe,binding.ddi,binding.plane, binding.status==TGLFBBinding::Status::Complete ? Display13FBMemory::allocatedSlot(controller,fb) : UINT32_MAX}; const auto pipe=binding.status==TGLFBBinding::Status::Complete ? binding.pipe : UINT32_MAX; @@ -7587,7 +7951,7 @@ void XE::wrapAlderLakeNPipeWMSetup(IOService *controller,IOService *fb,OSObject // memory ownership; programming still checks the published pipe owner. auto port=associated ? Display13FBMemory::field(path,0x3648) : nullptr; auto config=port && port->getMetaClass()->getClassSize()>=0x580 ? Display13FBMemory::field(port,0x548) : nullptr; - const bool valid=associated && Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnHDMIWiring); + const bool valid=associated && Display13FBTopology::selected(callbackXE->adlnPlatform,config,callbackXE->adlnOutputWiring); auto scaler=valid ? Display13FBMemory::field(path,0x32C0) : nullptr; if(valid && scaler && scaler->getMetaClass()->getClassSize()>=0x58) { // This CPU method is also called before memory/plane programming. Use @@ -7627,7 +7991,7 @@ void XE::wrapAlderLakeNDisableDisplay(IOService *controller,IOService *fb,OSObje if(c.lock && controller==c.controller && fb && fb->getMetaClass()->getClassSize()>=0x4B90 && path && path->getMetaClass()->getClassSize()>=0x3730) { IOLockLock(c.lock); pipe=Display13FBMemory::boundPipe(c.pipe,fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); selected=pipe<3 && binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && Display13FBMemory::field(path,0x3644)==2 && !c.bridge.state.faulted && !c.clockFaulted && !(c.dbuf.updating&(1U<(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool pathValid=binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && display13FBAssociations(c.controller,0x1C20,fb,fb->getMetaClass()->getClassSize(),display13FBExpectedOwner(c.controller)); auto scaler=pathValid ? Display13FBMemory::field(path,0x32C0) : nullptr; @@ -7881,7 +8245,7 @@ void XE::wrapDisplay13WMArmUpdate(IOService *fb) { if(mutex && c.engineEnabled && c.map && c.access && !c.memoryFaulted && !c.clockFaulted && !c.bridge.state.faulted && (owner || !c.pipe[pipe].wmCommitting)) { path=Display13FBMemory::singlePath(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && display13FBAssociations(c.controller,0x1C20,fb,fb->getMetaClass()->getClassSize(),display13FBExpectedOwner(c.controller)) && !Display13FBMemory::field(fb,0x4A48) && adlnWriterMappingBound(); @@ -8049,7 +8413,7 @@ void XE::wrapAlderLakeNEngineEnable(void *power) { !c.bridge.state.suspended && acquired.status==Display13Power::Status::Complete && !c.clockFaulted && clock.status==Display13Clock::Status::Complete && Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { Display13FBDBUFIO io {c.map}; - status=Display13DBUF::begin(io,c.dbuf,__builtin_popcount(callbackXE->adlnHDMIWiring.hdmi)>=3 ? 7U : 0U); + status=Display13DBUF::begin(io,c.dbuf,__builtin_popcount(callbackXE->adlnOutputWiring.selected())>=3 ? 7U : 0U); if(status==Display13DBUF::Status::Complete) { if(acquired.status==Display13Power::Status::Complete)status=Display13DBUF::resumeEngine(io,c.dbuf); else status=Display13DBUF::Status::Power; @@ -8082,7 +8446,7 @@ void XE::wrapAlderLakeNEngineDisable(void *power) { uint64_t record[12] {},hotplug[16] {},irqRelease[12] {};Display13Power::Result released;Display13Clock::Change clock; IOWorkLoop *acquiredGate=nullptr;bool gateAttempted=false,gateReady=false;uint64_t gateEvidence[8] {}; Display13DBUF::Status status=Display13DBUF::Status::Unavailable;IOLockLock(c.lock); - const bool dual=Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring); + const bool dual=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring); auto ensureGate=[&]() { if(!dual)return true; if(gateAttempted)return gateReady; @@ -8123,7 +8487,7 @@ void XE::wrapAlderLakeNEngineDisable(void *power) { if(callbackXE->display13FBLifecycleHooksReady && c.bridge.begun && !c.bridge.state.faulted && c.bridge.state.suspended && !c.engineEnabled && c.clockStopped && !c.engine.domains)status=Display13DBUF::Status::Complete; else if(callbackXE->display13FBLifecycleHooksReady && c.bridge.begun && !c.bridge.state.faulted && - !c.bridge.state.suspended && !c.clockFaulted && !Display13WM::busyPipes(c.pipe) && !c.dbuf.updating && + !c.bridge.state.suspended && !c.bridge.auxBusy() && !c.clockFaulted && !Display13WM::busyPipes(c.pipe) && !c.dbuf.updating && Display13Power::objectAssociations(power,0x80,c.controller,0x1C20)) { if(!c.engineEnabled && c.clockStopped && !c.engine.domains)status=Display13DBUF::Status::Complete; else { @@ -8207,7 +8571,7 @@ void XE::wrapAlderLakeNPowerOverride(void *power,bool enable) { if(quiet) {irq.quiescing=true;irq.blockedPipes=7;} IOSimpleLockUnlockEnableInterrupt(irq.lock,saved); if(quiet) { - Display13FBPowerIO io {c.map};result=Display13Power::suspend(io,c.bridge.state); + Display13FBPowerIO io {c.map};result=c.bridge.suspend(io); const auto done=IOSimpleLockLockDisableInterrupt(irq.lock); irq.quiescing=false;irq.blockedPipes=0;IOSimpleLockUnlockEnableInterrupt(irq.lock,done); } @@ -8243,7 +8607,7 @@ void XE::wrapAlderLakeNPrimaryDBUF(OSObject *plane,uint32_t start,uint32_t end,b if(pipe>=3 || c.pipe[pipe].destroyingPath) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} Display13DBUF::Status status=Display13DBUF::Status::Unavailable; auto path=fb ? Display13FBMemory::field(plane,0x70) : nullptr; - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && Display13FBMemory::field(path,0x32C8)==plane; if(callbackXE->display13FBDBUFHooksReady && associated && c.engineEnabled && @@ -8262,7 +8626,7 @@ void XE::wrapAlderLakeNPrimaryDBUF(OSObject *plane,uint32_t start,uint32_t end,b IOLockUnlock(c.lock); FunctionCast(wrapAlderLakeNPrimaryDBUF,callbackXE->orgAlderLakeNPrimaryDBUF)(plane,start,end,wait); IOLockLock(c.lock); - const auto current=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto current=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); status=current.status==TGLFBBinding::Status::Complete && current.pipe==pipe && current.ddi==binding.ddi ? Display13DBUF::finishPrimary(io,c.dbuf,pipe) : Display13DBUF::fail(c.dbuf,Display13DBUF::Status::Topology); if(status==Display13DBUF::Status::Complete && Display13FBMemory::field(plane,0x10C)!=Display13DBUF::pendingHardwareRaw(c.dbuf,pipe)) @@ -8293,7 +8657,7 @@ void XE::wrapAlderLakeNPlanarDBUFWrite(void *access,uint64_t reg,uint32_t value) uint64_t record[14] {2,reg,value};auto status=Display13DBUF::Status::Unavailable; auto path=Display13FBMemory::singlePath(fb); auto plane=path && path->getMetaClass()->getClassSize()>=0x3730 ? Display13FBMemory::field(path,0x32C8) : nullptr; - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=plane && binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && Display13FBMemory::boundPipe(c.pipe,fb)==pipe && Display13FBMemory::field(plane,0x90)==access && access==c.access && Display13FBMemory::field(plane,0xA0)==reg && Display13FBMemory::field(plane,0x10C)==value && @@ -8329,7 +8693,7 @@ void XE::wrapDisplay13CursorDBUF(OSObject *plane) { const auto pipe=Display13FBMemory::boundPipe(c.pipe,fb); if(pipe>=3 || c.pipe[pipe].destroyingPath) {IOLockUnlock(c.lock);IORecursiveLockUnlock(mutex);return;} auto path=Display13FBMemory::field(plane,0x70); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe && Display13FBMemory::field(path,0x32C8)==plane; if(callbackXE->display13FBDBUFHooksReady && callbackXE->display13FBWatermarkHooksReady && associated && @@ -8374,7 +8738,7 @@ void XE::adlnSetupDBUF(IOService *controller,bool optimized) { // transaction, update only the mutexes this thread already owns. Do not // wait for another FB while holding its peer's mutex. A global caller // owning none can process retained identities one at a time. - const auto selected=Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) ? (held ? (held&published) : published) : (published&1U); + const auto selected=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) ? (held ? (held&published) : published) : (published&1U); uint64_t dispatch[] {1,optimized,published,held,selected}; IORegistryEntry::getRegistryRoot()->setProperty("N97FBDBUFDispatch",dispatch,sizeof(dispatch)); for(unsigned pipe=0;pipe<3;++pipe) { @@ -8390,7 +8754,7 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo IORecursiveLockLock(mutex);IOLockLock(c.lock); Display13DBUF::Layout layout;Display13DBUF::Range relativePrimary;OSObject *plane=nullptr;uint64_t record[18] {},resume[6] {}; auto path=Display13FBMemory::singlePath(fb); - const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnHDMIWiring); + const auto binding=adlnInspectPathBinding(fb,path,callbackXE->adlnPlatform,callbackXE->adlnOutputWiring); const bool associated=Display13FBMemory::boundPipe(c.pipe,fb)==pipe && binding.status==TGLFBBinding::Status::Complete && binding.pipe==pipe; // A cold DDB demand may precede engine resume. Restore its clock/power @@ -8410,7 +8774,7 @@ void XE::adlnSetupDBUFPipe(IOService *controller,IOService *fb,unsigned pipe,boo if(plane && plane->getMetaClass()->getClassSize()>=0x1C8 && Display13FBMemory::field(plane,0x68)==fb && Display13FBMemory::field(plane,0x70)==path) { Display13FBDBUFIO io {c.map};const auto bound=Display13DBUF::bindOffPipe(io,c.dbuf,pipe); - if(bound==Display13DBUF::Status::Complete)layout=Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) ? + if(bound==Display13DBUF::Status::Complete)layout=Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) ? Display13DBUF::pipeLayout(io,c.dbuf,pipe) : Display13DBUF::singlePipeLayout(io,c.dbuf); else layout.status=bound; } @@ -8467,7 +8831,7 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { auto pci=service ? OSDynamicCast(IOPCIDevice,service->getProvider()) : nullptr; if(callbackXE->display13FBPowerReferencesReady && !c.controller && power && pci && !c.clockFaulted && c.clock.status==Display13Clock::Status::Complete && - xelpAlderLakeNDevice(pci) && (pci->configRead16(4)&2) && + xelpDisplay13Device(pci) && (pci->configRead16(4)&2) && Display13Power::objectAssociations(power,static_cast(power)->getMetaClass()->getClassSize(), controller,service->getMetaClass()->getClassSize())) { auto bar=pci->getDeviceMemoryWithRegister(0x10); @@ -8481,7 +8845,8 @@ void XE::wrapAlderLakeNPowerInit(void *power,void *controller) { service->retain();static_cast(power)->retain();static_cast(c.access)->retain(); Display13FBPowerIO io {map}; const auto wells=io.read(Display13Power::Driver),a=io.read(0x60400),pipeA=io.read(0x70008); - const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnHDMIWiring,a); + const auto bootPort=Display13FBTopology::bootDDI(callbackXE->adlnOutputWiring,a,true); + const bool dpBoot=bootPort<2 && (callbackXE->adlnOutputWiring.dp&(1U<adlnHDMIWiring.hdmi); + 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))); + } + 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) { + Display13FBBootIO bootIO{map};const auto source=AlderLakeN::readDisplay(bootIO,true); + result=AlderLakeN::holdDPBootAUX(bootIO,source,c.bridge,c.mainLink[bootPort],bootPort); + if(result.status!=Display13Power::Status::Complete)adlnFault(c.bridge.state.faulted,__LINE__); + } if(result.status==Display13Power::Status::Complete && !Display13Power::publishObject(c.bridge,power,0x80,controller,0xC60)) { result.status=Display13Power::Status::NotReady;adlnFault(c.bridge.state.faulted,__LINE__); } @@ -8577,19 +8952,19 @@ void XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { check[14]=released; if(display13TraceEnabled)c.powerContext[22]=released; uint32_t ddiA=7,ddiB=1,ddiC=7,tcPipes[4] {7,7,7,7};bool mapValid=true;uint32_t known=0; - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring) && released) { + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring) && released) { TGLFBBinding::PublishedPath paths[3];uintptr_t owners[3]; for(unsigned pipe=0;pipe<3;++pipe) { paths[pipe]=c.pipe[pipe].powerPath;owners[pipe]=reinterpret_cast(c.pipe[pipe].memoryFramebuffer); } - const auto map=TGLFBBinding::powerPipes(paths,owners,callbackXE->adlnHDMIWiring.hdmi); + const auto map=TGLFBBinding::powerPipes(paths,owners,callbackXE->adlnOutputWiring.selected()); mapValid=map.valid;known=map.known;ddiA=map.mask[0];ddiB=map.mask[1];ddiC=map.mask[2]; for(unsigned tc=0;tc<4;++tc)tcPipes[tc]=map.mask[tc+3]; } const auto affected=Display13FBIRQ::affectedPipes(released,ddiA,ddiB,ddiC,tcPipes[0],tcPipes[1],tcPipes[2],tcPipes[3]); check[16]=affected;check[17]=mapValid;check[18]=ddiA;check[19]=ddiB;check[20]=known; bool gateReady=true; - if(released && Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(released && Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const bool bound=c.workLoop && Display13FBMemory::field(c.controller,0xE00)==c.workLoop; const bool api=callbackXE->adlnWorkLoopInGate && callbackXE->adlnWorkLoopTryClose && callbackXE->adlnWorkLoopOpen; gateReady=false;gateEvidence[0]=1;gateEvidence[1]=released;gateEvidence[2]=bound;gateEvidence[3]=api; @@ -8606,7 +8981,7 @@ void XE::adlnPowerReference(void *power,uint32_t operation,uint32_t domain) { !(Display13WM::busyPipes(c.pipe,true)&affected) && adlnIRQOwner(static_cast(c.controller))) { auto &irq=display13FBIRQ;Display13FBIRQIO irqIO {irq.base}; const auto saved=IOSimpleLockLockDisableInterrupt(irq.lock); - if(Display13FBTopology::multiOutputWiring(callbackXE->adlnHDMIWiring)) { + if(Display13FBTopology::multiOutputWiring(callbackXE->adlnOutputWiring)) { const auto queued=__atomic_load_n(reinterpret_cast(reinterpret_cast(c.controller)+0xE08),__ATOMIC_ACQUIRE); const auto aux=__atomic_load_n(reinterpret_cast(reinterpret_cast(c.controller)+0xE0C),__ATOMIC_ACQUIRE); const auto hpd=__atomic_load_n(reinterpret_cast(reinterpret_cast(c.controller)+0xE10),__ATOMIC_ACQUIRE); @@ -8983,9 +9358,9 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add patcher.applyLookupPatch(&patch, site, sizeof(find) + 1); skuReady = !memcmp(site, replace, sizeof(replace)); } - // ADL-N and explicit overrides must reach the real selector2 + // Small GTs and explicit overrides must reach the real selector2 // comparison regardless of the physical ID's signed ordering. - if (skuReady && (adlnDetected || xeProfile.overridden())) { + if (skuReady && (adlnDetected || display12SmallGTDetected || xeProfile.overridden())) { static const uint8_t findSplit[] {0x7F,0x15}, replaceSplit[] {0x90,0x90}; auto split=reinterpret_cast(address+0x23C25); skuReady=!memcmp(split,findSplit,sizeof(findSplit)); @@ -9071,11 +9446,12 @@ bool XE::processTGLAccelerator(KernelPatcher &patcher, size_t index, mach_vm_add // G4 (9A78) is GT2 with 3 DSS / 6 native subslices and 6 L3 banks // (TGL PRM Vol4 p3/p9). Keep the 80/96-EU patch byte-for-byte. static const uint8_t urbReplaceG4[] {0x83,0xFE,0x01,0x75,0x59,0x83,0xFA,0x06,0x75,0x54,0x41,0xC7,0x87,0x64,0x11,0x00,0x00,0x06,0x00,0x00,0x00}; - // ADL-N/Twin Lake keep LP silicon selector2. Validate the actual + // Small GT families keep LP silicon selector2. Validate the actual // native subslice count and publish the separately admitted L3 capacity. uint8_t urbReplaceAlderLakeN[] {0x83,0xFE,0x01,0x75,0x59,0x83,0xFA,0x04,0x75,0x54,0x41,0xC7,0x87,0x64,0x11,0x00,0x00,0x04,0x00,0x00,0x00}; if (adlnDetected) { urbReplaceAlderLakeN[7]=adlnNativeSubslices;urbReplaceAlderLakeN[17]=adlnL3Banks; } - const auto urbReplacement = xeProfile.gt==XELP::GT::ADLNSmall ? urbReplaceAlderLakeN : + if(display12SmallGTDetected) {urbReplaceAlderLakeN[7]=display12SmallGT.nativeSubslices;urbReplaceAlderLakeN[17]=display12SmallGT.l3Banks;} + const auto urbReplacement = (xeProfile.gt==XELP::GT::ADLNSmall || XELP::display12SmallGT(xeProfile.gt)) ? urbReplaceAlderLakeN : (xeProfile.gt==XELP::GT::TGLG4 ? urbReplaceG4 : urbReplace); auto gpuInfo=patcher.solveSymbol(index, "__ZN16IntelAccelerator10getGPUInfoEv",address,size); @@ -9905,9 +10281,10 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t // r319: the donor's combo PLL tables still contain ICL MMIO // addresses. TGL moved DPLL0/1 CFGCR0/1 (Linux v6.12 i915_reg.h). // Correct both reads and writes through the native tables; retain - // native divisors, power/lock sequencing and the unused third slot. + // native divisors and power/lock sequencing. ADL-S also uses slot2. const uint64_t before[] {0x164000,0x164080,0x164200,0x164004,0x164084,0x164204}; - const uint64_t after[] {0x164284,0x16428C,0x164200,0x164288,0x164290,0x164204}; + const uint64_t after[] {0x164284,0x16428C,adlsDisplaySelected?0x164294ULL:0x164200ULL, + 0x164288,0x164290,adlsDisplaySelected?0x164298ULL:0x164204ULL}; auto fn = patcher.solveSymbol(index, "__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams", address, size); auto site = reinterpret_cast(address + 0x9A0E8); @@ -9920,6 +10297,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t ready = !memcmp(site, after, sizeof(after)); } IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIComboPLLReady", ready); + adlsPLLTableReady=adlsDisplaySelected && ready; patcher.clearError(); } { @@ -10056,6 +10434,53 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } + if(tglActive) { + // PANEL_TIMING_INSTALL: exact property writer and its native consumer. + const char *symbol="__ZN31AppleIntelFramebufferController26hwGetPanelTimingPropertiesEv"; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x53,0x50,0x48,0x89,0xfb,0x48,0xff,0x05,0x08,0x60,0x09,0x00}; + const uint8_t call[]={0xe8,0xb5,0x4c,0x00,0x00,0x8b,0x93,0x48,0x15,0x00,0x00}; + const auto target=patcher.solveSymbol(index,symbol,address,size); + const auto config=patcher.solveSymbol(index,"__ZN31AppleIntelFramebufferController21hwSetPanelPowerConfigEj",address,size); + const bool known=size>=0x48651 && target==address+0x4848c && config==address+0x437a6 && + !memcmp(reinterpret_cast(target),entry,sizeof(entry)) && + !memcmp(reinterpret_cast(address+0x437d2),call,sizeof(call)); + KernelPatcher::RouteRequest routes[] { + {symbol,wrapTGLPanelTiming,orgTGLPanelTiming}, + {"__ZN31AppleIntelFramebufferController21hwSetPanelPowerConfigEj",wrapTGLPanelConfig,orgTGLPanelConfig} + }; + tglPanelTimingReady=known && patcher.routeMultiple(index,routes,2,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("WEXPanelTimingReady",tglPanelTimingReady); + patcher.clearError(); + // PANEL_TIMING_INSTALL_END + } + + if(adlsDisplaySelected) { + // ADLS_PANEL_PWM_INSTALL: preflight the native lifecycle and PWM enable site. + const char *light="__ZN31AppleIntelFramebufferController10LightUpEDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"; + const char *brightness="__ZN31AppleIntelFramebufferController14hwSetBacklightEj"; + const uint8_t lightEntry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53,0x50,0x49,0x89,0xcf}; + const uint8_t brightnessEntry[]={0x55,0x48,0x89,0xe5,0x41,0x56,0x53,0x48,0xff,0x05,0x90,0x44,0x09,0x00}; + const uint8_t enable[]={0xbe,0x50,0x82,0x0c,0x00,0xba,0x00,0x00,0x00,0x80,0xff,0x90,0x30,0x01,0x00,0x00}; + const auto lightFn=patcher.solveSymbol(index,light,address,size); + const auto brightnessFn=patcher.solveSymbol(index,brightness,address,size); + const bool known=size>=0x508c4 && lightFn==address+0x5063e && brightnessFn==address+0x4a56a && + !memcmp(reinterpret_cast(lightFn),lightEntry,sizeof(lightEntry)) && + !memcmp(reinterpret_cast(brightnessFn),brightnessEntry,sizeof(brightnessEntry)) && + !memcmp(reinterpret_cast(address+0x507e9),enable,sizeof(enable)); + KernelPatcher::RouteRequest routes[] {{light,wrapADLSLightUpEDP,orgADLSLightUpEDP}, + {brightness,wrapADLSPanelBacklight,orgADLSPanelBacklight}}; + if(known && patcher.routeMultiple(index,routes,2,address,size)) { + uint8_t bridge[]={0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xd0,0x90,0x90,0x90,0x90}; + const auto handler=reinterpret_cast(wrapADLSPanelPWMEnable);memcpy(bridge+2,&handler,sizeof(handler)); + patcher.routeBlock(address+0x507e9,bridge,sizeof(bridge)); + adlsPanelPWMReady=patcher.getError()==KernelPatcher::Error::NoError && + !memcmp(reinterpret_cast(address+0x507e9),bridge,sizeof(bridge)); + } + IORegistryEntry::getRegistryRoot()->setProperty("ADLSPanelPWMReady",adlsPanelPWMReady); + patcher.clearError(); + // ADLS_PANEL_PWM_INSTALL_END + } + if (tglActive) { // TGL/Xe-LP has no dedicated eDP transcoder. Keep the allocated // pipe (or getTranscoderOffset's pipe argument), including when @@ -10394,7 +10819,24 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t } if (ready) tglPG1FuseReadCaller = fn + 0x2C7; IORegistryEntry::getRegistryRoot()->setProperty("TGLPG1PollReady", ready); + const bool fuseReady=ready && size>=0x5B640+0x6E1 && + XELPDonor::powerWellFuseSites(reinterpret_cast(fn),size-0x5B640); + if(fuseReady)tglPGFuseWaitFunction=fn; + IORegistryEntry::getRegistryRoot()->setProperty("TGLPG234PollReady",fuseReady); + if(!fuseReady)SYSLOG("igfx","PG2/3/4 fuse wait: donor sites unavailable"); } + if(adlsDisplaySelected) { + const auto symbol="__ZN17AppleIntelPortHAL4initEP10PortConfig"; + const uint8_t entry[]={0x55,0x48,0x89,0xE5,0x41,0x56,0x53,0x49,0x89,0xFE}; + const auto fn=patcher.solveSymbol(index,symbol,address,size); + const bool known=fn==address+0x5F058 && size>0x5F9F4 && + !memcmp(reinterpret_cast(fn),entry,sizeof(entry)); + patcher.clearError(); + KernelPatcher::RouteRequest route(symbol,wrapADLSPortInit,orgADLSPortInit); + adlsPortInitReady=known && patcher.routeMultiple(index,&route,1,address,size); + IORegistryEntry::getRegistryRoot()->setProperty("ADLSPortInitReady",adlsPortInitReady); + patcher.clearError(); + } { KernelPatcher::RouteRequest routes[] { {"__ZN17AppleIntelPortHAL14enableComboPhyEv", wrapTGLEnableCombo, orgTGLEnableCombo}, @@ -10857,6 +11299,7 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t KernelPatcher::RouteRequest hpdRequest("__ZN17AppleIntelPortHAL11getHPDStateEv", wrapTGLHPDState, orgTGLHPDState); const bool hpdOK = patcher.routeMultiple(index, &hpdRequest, 1, address, size); + tglHPDHookReady=hpdOK; IORegistryEntry::getRegistryRoot()->setProperty("TGLWEGHPDObservationRouted", hpdOK); patcher.clearError(); KernelPatcher::RouteRequest laneRequest("__ZN17AppleIntelPortHAL26getDklPhyLaneConfigBitMaskEv", @@ -10879,6 +11322,68 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t patcher.clearError(); } IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPAltFiaReady",tglDPAltFiaReady); + if(adlsDisplaySelected) { + // Keep the physical DDI in every port/power/display object. Only the + // native Combo PLL indices are translated, under the firmware-route entry + // guards below (physical 0/3/4 >> 1 gives PHY 0/1/2). The removed instructions are profiling counters. + const uint8_t adlsClockOnFind[]={0x48,0xff,0x05,0xbe,0x86,0x07,0x00,0x48,0x63,0x00,0x48,0x83,0xf8,0x03}; + const uint8_t adlsClockOnReplace[]={0x48,0x63,0x00,0x48,0xd1,0xe8,0x90,0x48,0x83,0xf8,0x03,0x0f,0x1f,0x00}; + const uint8_t adlsClockSelectCPLLFind[]={0x83,0xc8,0x10}; + const uint8_t adlsClockSelectCPLLReplace[]={0x83,0xc8,0x20}; + const uint8_t adlsClockSelectCFind[]={0x83,0xf9,0x02}; + const uint8_t adlsClockSelectCReplace[]={0x83,0xf9,0x04}; + const uint8_t adlsClockSelectFind[]={0x83,0xf9,0x01}; + const uint8_t adlsClockSelectReplace[]={0x83,0xf9,0x03}; + const uint8_t adlsClockOffFind[]={0x48,0xff,0x05,0xe9,0x79,0x07,0x00,0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0x83,0xfc,0x03}; + const uint8_t adlsClockOffReplace[]={0x48,0x8b,0x8b,0x48,0x05,0x00,0x00,0x4c,0x63,0x21,0x49,0xd1,0xec,0x90,0x49,0x83,0xfc,0x03,0x0f,0x1f,0x00}; + // ADL-S GMBUS uses pins 1,9..12. The wrapper admits VBT-wired + // pins only; extend the donor's legacy range check before MMIO. + const uint8_t adlsClockBusFind[]={0x83,0x7d,0xcc,0x08,0x72,0x2a}; + const uint8_t adlsClockBusReplace[]={0x83,0x7d,0xcc,0x0d,0x72,0x2a}; + const uint8_t entry[]={0x55,0x48,0x89,0xe5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + const auto symbol="__ZN31AppleIntelFramebufferController12enableClocksEP14AppleIntelPortS1_P11ClockParams"; + const auto fn=patcher.solveSymbol(index,symbol,address,size); + bool ready=adlsPLLTableReady && orgTGLDisableClocks && tglClockOffCaller && tglComboPLLFractionCaller && + fn==address+0x6CE10 && size>0x6DCBD && !memcmp(reinterpret_cast(fn),entry,sizeof(entry)); + struct Site {uint32_t offset;const uint8_t *find,*replace;size_t bytes;}; + const Site sites[]={{0x6CFD3,adlsClockOnFind,adlsClockOnReplace,sizeof(adlsClockOnFind)}, + {0x6D4A2,adlsClockSelectCPLLFind,adlsClockSelectCPLLReplace,sizeof(adlsClockSelectCPLLFind)}, + {0x6D219,adlsClockSelectCFind,adlsClockSelectCReplace,sizeof(adlsClockSelectCFind)}, + {0x6D222,adlsClockSelectFind,adlsClockSelectReplace,sizeof(adlsClockSelectFind)}, + {0x6DCA8,adlsClockOffFind,adlsClockOffReplace,sizeof(adlsClockOffFind)}, + {0x53B66,adlsClockBusFind,adlsClockBusReplace,sizeof(adlsClockBusFind)}}; + for(const auto &site:sites)ready=ready && !memcmp(reinterpret_cast(address+site.offset),site.find,site.bytes); + patcher.clearError(); + if(ready) { + KernelPatcher::RouteRequest route(symbol,wrapADLSEnableClocks,orgADLSEnableClocks); + ready=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); + if(ready)for(const auto &site:sites) { + KernelPatcher::LookupPatch patch {currentFramebuffer,site.find,site.replace,site.bytes,1}; + patcher.applyLookupPatch(&patch,reinterpret_cast(address+site.offset),site.bytes+1); + ready &= !memcmp(reinterpret_cast(address+site.offset),site.replace,site.bytes); + patcher.clearError(); + } + } + adlsClockReady=ready; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockReady",ready); + const char *pg="__ZN19AppleIntelPowerWell21hwSetPowerWellStatePGEbj"; + const auto pgfn=patcher.solveSymbol(index,pg,address,size); + const uint8_t pgentry[]={0x55,0x48,0x89,0xE5,0x41,0x57,0x41,0x56,0x41,0x55,0x41,0x54,0x53}; + adlsPowerReady=orgTGLPowerDDIState && pgfn==address+0x5B640 && size>0x5BD40 && + !memcmp(reinterpret_cast(pgfn),pgentry,sizeof(pgentry)); + patcher.clearError(); + if(adlsPowerReady) { + KernelPatcher::RouteRequest route(pg,wrapADLSPowerPG,orgADLSPowerPG); + adlsPowerReady=patcher.routeMultiple(index,&route,1,address,size);patcher.clearError(); + } + IORegistryEntry::getRegistryRoot()->setProperty("ADLSLanePowerReady",adlsPowerReady); + // Native startup remains closed if any required ADL-S adapter is absent. + const auto contract=XELP::DisplayContract::adls(display12Available(),adlsWiring.dp!=0,adlsWiring.panel!=0); + adlsDisplayHooksReady=contract.ready(); + uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayStartupContract",contractRecord,sizeof(contractRecord)); + IORegistryEntry::getRegistryRoot()->setProperty("ADLSDisplayHooksReady",adlsDisplayHooksReady); + } tglRecordStartOrder(1,nullptr); return true; } @@ -10889,6 +11394,127 @@ bool XE::processKextFull(KernelPatcher &patcher, size_t index, mach_vm_address_t return false; } +// Retain hwSetPanelPowerConfig's caller-serialization contract. The wrapper +// completes its timing-property input; it never writes PPS control/status or +// calls the panel on/off sequence recursively. Capture before either config +// branch can overwrite BIOS delays; reset on each native controller start. +void XE::tglApplyPanelTiming(void *controller,bool apply) { + auto s=callbackXE; + auto c=static_cast(controller); + const auto &v=s->firmwarePorts; + if(!c || c!=s->tglObservationController || !s->tglActive || !s->tglPanelTimingReady || + !XELP::PanelPower::nativePPS0(v,s->xeProfile.family) || !s->tglTCNativeFunctions.complete() || + *s->tglTCNativeFunctions.controllerSlot!=controller || !s->orgTGLRegisterRead || + c[0x918] || *reinterpret_cast(c+0xD0) || + *reinterpret_cast(c+0xD40)!=s->tglPlatformCopy)return; + const auto ddi=v.panel.ddi; + auto port=static_cast(s->tglTCNativeFunctions.portByDDI(ddi)); + const auto cfg=reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x20+24*ddi); + if(!port || port[0x117] || *reinterpret_cast(port+0x548)!=cfg || + cfg[0]!=ddi || cfg[2]!=0 || cfg[3]!=0 || *reinterpret_cast(port+0x550)!=1 || + (s->adlsDisplaySelected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg)))return; + auto access=*reinterpret_cast(c+0xC40); + auto power=*reinterpret_cast(c+0xC8); + if(!access || !power || *reinterpret_cast(power+0x10)!=controller || power[0x18] || + *reinterpret_cast(power+0x78)!=access || *reinterpret_cast(port+0x578)!=access || + !*reinterpret_cast(access+0x50) || *reinterpret_cast(access+0x60)<0xC7210)return; + if(!s->tglPanelTimingBIOSValid) { + auto &f=s->tglTCNativeFunctions; + f.getPG(power,1); + const auto read=[&](uint32_t reg){return FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead)(access,reg);}; + const auto wells=read(0x45404),fuse=read(0x42000); + if(wells!=~0U && fuse!=~0U && (wells&1) && (fuse&0x01000000)) { + const auto on=read(0xC7208),off=read(0xC720C),control=read(0xC7204); + if(on!=~0U && off!=~0U && control!=~0U) { + s->tglPanelTimingBIOS[0]=on;s->tglPanelTimingBIOS[1]=off;s->tglPanelTimingBIOS[2]=control; + s->tglPanelTimingBIOSValid=true; + } + } + f.putPG(power,1); + } + if(!apply || !s->tglPanelTimingBIOSValid)return; + const auto &bios=s->tglPanelTimingBIOS; + const auto plan=XELP::PanelPower::select(v.panel,reinterpret_cast(c+0x1548),bios[0],bios[1],bios[2]); + if(plan.valid)memcpy(c+0x1548,plan.fields,sizeof(plan.fields)); + uint32_t detail[]={1,ddi,plan.valid,plan.fields[0],plan.fields[1],plan.fields[2],plan.fields[3],plan.fields[4]}; + IORegistryEntry::getRegistryRoot()->setProperty("WEXPanelTimingLast",detail,sizeof(detail)); +} +void XE::wrapTGLPanelConfig(void *controller,uint32_t config) { + tglApplyPanelTiming(controller,false); + FunctionCast(wrapTGLPanelConfig,callbackXE->orgTGLPanelConfig)(controller,config); +} +uint32_t XE::wrapTGLPanelTiming(void *controller) { + const auto result=FunctionCast(wrapTGLPanelTiming,callbackXE->orgTGLPanelTiming)(controller); + if(!result)tglApplyPanelTiming(controller,true); + return result; +} + +// ADL-S native PWM panel adapter. The panel lifecycle remains synchronous +// and donor-owned; only its period, polarity and duty arithmetic are replaced. +bool XE::adlsPanelPWMTarget(void *controller,void *&access,void *&power) { + auto s=callbackXE;auto c=static_cast(controller);access=power=nullptr; + if(!s->adlsDisplaySelected || XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::ADLS || !s->adlsPanelPWMReady || !s->tglActive || !c || c!=s->tglObservationController || + !s->tglTCNativeFunctions.complete() || *s->tglTCNativeFunctions.controllerSlot!=controller || + !XELP::PanelPower::nativePPS0(s->firmwarePorts,s->xeProfile.family) || c[0x918] || + *reinterpret_cast(c+0xD0) || *reinterpret_cast(c+0xD40)!=s->tglPlatformCopy || + !s->orgTGLRegisterRead || !s->orgTGLRegisterWrite)return false; + auto port=static_cast(s->tglTCNativeFunctions.portByDDI(0)); + if(!port || port[0x117] || *reinterpret_cast(port+0x550)!=1 || !s->adlsWiring.boundPanel(s->tglPlatformCopy,*reinterpret_cast(port+0x548)))return false; + auto a=*reinterpret_cast(c+0xC40);auto p=*reinterpret_cast(c+0xC8); + if(!a || !p || *reinterpret_cast(p+0x10)!=controller || p[0x18] || + *reinterpret_cast(p+0x78)!=a || *reinterpret_cast(port+0x578)!=a || + !*reinterpret_cast(a+0x50) || *reinterpret_cast(a+0x60)<0xC825C)return false; + access=a;power=p;return true; +} +bool XE::adlsCapturePanelPWM(void *controller) { + auto s=callbackXE;void *access,*power; + if(!adlsPanelPWMTarget(controller,access,power))return false; + if(s->adlsPanelPWM.valid())return true; + auto &f=s->tglTCNativeFunctions;f.getPG(power,1); + const auto read=[&](uint32_t reg){return FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead)(access,reg);}; + const auto wells=read(0x45404),fuse=read(0x42000); + if(wells!=~0U && fuse!=~0U && (wells&1) && (fuse&0x01000000)) { + const auto period=read(0xC8254),control=read(0xC8250); + const auto &p=s->firmwarePorts.panel; + s->adlsPanelPWM=XELP::PanelPWM::select(period,control,period?0:read(0xC2014),p.pwmHz,p.activeLowPWM); + } + f.putPG(power,1);return s->adlsPanelPWM.valid(); +} +uint32_t XE::wrapADLSLightUpEDP(void *controller,void *fb,void *path,const void *timing) { + auto s=callbackXE; + if(s->adlsDisplaySelected && s->adlsWiring.panel) { + if(!adlsCapturePanelPWM(controller))return kIOReturnNotReady; + *reinterpret_cast(static_cast(controller)+0xE60)=s->adlsPanelPWM.period; + } + return FunctionCast(wrapADLSLightUpEDP,s->orgADLSLightUpEDP)(controller,fb,path,timing); +} +uint32_t XE::wrapADLSPanelBacklight(void *controller,uint32_t brightness) { + auto s=callbackXE; + if(!s->adlsDisplaySelected || !s->adlsWiring.panel) + return FunctionCast(wrapADLSPanelBacklight,s->orgADLSPanelBacklight)(controller,brightness); + void *access,*power;uint32_t duty; + if(!adlsCapturePanelPWM(controller) || !adlsPanelPWMTarget(controller,access,power) || + !s->adlsPanelPWM.duty(brightness,duty))return kIOReturnNotReady; + auto &f=s->tglTCNativeFunctions;f.getPG(power,1); + const auto wells=FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead)(access,0x45404); + const auto fuse=FunctionCast(wrapTGLRegisterRead,s->orgTGLRegisterRead)(access,0x42000); + const bool ready=wells!=~0U && fuse!=~0U && (wells&1) && (fuse&0x01000000); + if(ready) { + wrapTGLRegisterWrite(access,0xC8258,duty); + *reinterpret_cast(static_cast(controller)+0xE58)=brightness; + } + f.putPG(power,1);return ready?0:kIOReturnNotReady; +} +void XE::wrapADLSPanelPWMEnable(void *access) { + auto s=callbackXE;uint32_t value=0x80000000; + if(s->adlsDisplaySelected && s->adlsWiring.panel) { + void *expected,*power; + if(!s->adlsPanelPWM.valid() || !adlsPanelPWMTarget(s->tglObservationController,expected,power) || access!=expected)return; + value|=s->adlsPanelPWM.polarity; + } + wrapTGLRegisterWrite(access,0xC8250,value); +} + bool XE::wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, OSObject *value) { if(framebuffer)return framebuffer->setProperty("AAPL,LCD-PowerState-ON",value); static uint32_t skipped {}; @@ -10901,7 +11527,7 @@ bool XE::wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, O // explicit profile or a mismatched donor connector must not be partly applied. bool XE::wrapTGLGetOSInformation(void *controller) { const auto r=FunctionCast(wrapTGLGetOSInformation,callbackXE->orgTGLGetOSInformation)(controller); - if(r)tglBeginTCColdProtection(controller); + if(r && !callbackXE->adlsDisplaySelected)tglBeginTCColdProtection(controller); auto self=callbackXE; if(!r)return r; auto c=static_cast(controller); @@ -10924,13 +11550,18 @@ bool XE::wrapTGLGetOSInformation(void *controller) { } auto copy=reinterpret_cast(self->tglPlatformCopy); if(copy!=platform)memcpy(copy,platform,sizeof(self->tglPlatformCopy)); - if(!self->tglHDMIWiringLoaded || !self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) || - !self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy))) { + const bool wiringApplied=self->adlsDisplaySelected ? self->adlsWiring.apply(copy,sizeof(self->tglPlatformCopy)) : + self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB) && + TGLComboDP::accepts(self->tglComboDPMask,copy,sizeof(self->tglPlatformCopy)) && + self->tglHDMIWiring.apply(copy,sizeof(self->tglPlatformCopy)) && + TGLComboDP::apply(self->tglComboDPMask,copy,sizeof(self->tglPlatformCopy)); + if(!self->tglHDMIWiringLoaded || !wiringApplied) { IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringGuardFailed",true);return false; } auto config=reinterpret_cast(copy+0x20); platform=copy;self->tglHDMIWiringApplied=true; - IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->tglHDMIWiring.hdmi,32); + if(self->adlsDisplaySelected)IORegistryEntry::getRegistryRoot()->setProperty("ADLSDPWiringApplied",self->adlsWiring.dp,32); + IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiringApplied",self->adlsDisplaySelected?self->adlsWiring.hdmi:self->tglHDMIWiring.hdmi,32); // Preserve the existing TC2 machine's diagnostic names for old collectors. IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBTopologyApplied",bool(self->tglHDMIWiring.hdmi&2)); if(self->tglHDMIWiring.hdmi&2)IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIBPortConfig",config+6,24); @@ -10960,12 +11591,12 @@ bool XE::tglLoadTCWiring(IOService *provider) { self->tglTCWiringExplicit=property!=nullptr; auto data=OSDynamicCast(OSData,property); self->tglTCWiring=property ? (data?TGLTCWiring::fromProperty(data->getBytesNoCopy(),data->getLength()):TGLTCWiring{}) : TGLTCWiring::fromMasks(0,0,0); - if(!property && self->xeProfile.family==XELP::Family::TigerLake) + if(!property && XELP::displayAdapter(self->xeProfile.family)==XELP::DisplayAdapter::TGL) self->tglTCAutoCandidates=self->firmwarePorts.tglDPCandidates(self->tglAUXParentMask&0x18); IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTCDPCandidates",self->tglTCAutoCandidates,32); // The qualified donor ready switch covers TC1..4. Resolve modular FIA // coordinates under TC-cold before admitting their mode3 transaction. - if(!property && self->xeProfile.family==XELP::Family::TigerLake) + if(!property && XELP::displayAdapter(self->xeProfile.family)==XELP::DisplayAdapter::TGL) self->tglDPAltCandidates=self->firmwarePorts.tglDPAltCandidates(self->tglAUXParentMask&0x78); IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDPAltCandidates",self->tglDPAltCandidates,32); const auto &w=self->tglTCWiring; @@ -10981,6 +11612,19 @@ bool XE::tglLoadTCWiring(IOService *provider) { // overrides firmware. Only unambiguous firmware TMDS uses an admitted backend. bool XE::tglLoadHDMIWiring(IOService *provider) { auto self=callbackXE; + if(self->adlsDisplaySelected) { + if(!self->tglHDMIWiringLoaded) { + self->tglHDMIWiringLoaded=true; + self->tglHDMIB=false; + // The legacy five-word property addresses TGL Combo A/B/C, not ADL-S. + if(provider && provider->getProperty("tgl-hdmi-wiring"))return false; + self->adlsWiring=AlderLakeS::Wiring::fromFirmware(self->firmwarePorts); + self->tglHDMIB=self->adlsWiring.valid && self->adlsWiring.selected(); + uint32_t routes[]={self->adlsWiring.hdmi,self->adlsWiring.dp,self->adlsWiring.excluded,AlderLakeS::ComboBackendMask,self->adlsWiring.panel}; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSFirmwareRoutes",routes,sizeof(routes)); + } + return self->tglHDMIB; + } if(self->tglHDMIWiringLoaded)return self->tglHDMIWiring.supported(self->tglHDMIBackendMask,self->tglHDMIB); self->tglHDMIWiringLoaded=true; auto property=provider?provider->getProperty("tgl-hdmi-wiring"):nullptr; @@ -10990,11 +11634,90 @@ bool XE::tglLoadHDMIWiring(IOService *provider) { const auto &w=self->tglHDMIWiring; self->tglHDMIB=w.valid && w.hdmi && !(w.hdmi&~self->tglHDMIBackendMask); const bool admitted=w.supported(self->tglHDMIBackendMask,self->tglHDMIB); + self->tglComboDPMask=admitted?TGLComboDP::fromFirmware(self->firmwarePorts,w.hdmi):0; + IORegistryEntry::getRegistryRoot()->setProperty("TGLComboDPRoutes",self->tglComboDPMask,32); uint32_t q[]={361,self->tglHDMIWiringExplicit,w.valid,w.hdmi,w.ddc[0],w.ddc[1],w.ddc[2],self->tglHDMIBackendMask,admitted}; IORegistryEntry::getRegistryRoot()->setProperty("TGLHDMIWiring",q,sizeof(q));return admitted; } +XELP::DisplayContract::Mask XE::display12Available() const { + using C=XELP::DisplayContract;C::Mask a=0; + if(tglSonomaSurfaceFramebufferReady)a|=C::Surface; + if(tglPlatformNVRAMReady)a|=C::NVRAM; + if(tglFBIRQReady)a|=C::IRQ; + if(tglProbeReady)a|=C::Probe; + if(tglFramebufferInitReady)a|=C::Framebuffer; + if(tglTopologyReady)a|=C::Topology; + if(tglPanelPowerPropertyReady)a|=C::PanelPower; + if(tglEDPTranscoderReady)a|=C::PanelTranscoder; + if(tglDekelReady)a|=C::Dekel; + if(tglGMBusReady)a|=C::GMBus; + if(tglTrainingLeaseReady)a|=C::Lease; + if(tglEnablePG)a|=C::PowerGet; + if(tglDisablePG)a|=C::PowerPut; + if(tglPortByDDI)a|=C::PortLookup; + if(orgTGLPathLookup)a|=C::PathLookup; + if(orgTGLAddPath)a|=C::PathAdd; + if(orgTGLFreePath)a|=C::PathFree; + if(orgTGLTrainingEnd)a|=C::TrainingEnd; + if(orgTGLTraining)a|=C::Training; + if(orgTGLTrainingBegin)a|=C::TrainingBegin; + if(orgTGLAllocatePathResources)a|=C::PathAllocate; + if(tglSinkFramingReady)a|=C::Framing; + if(tglComboSignalReady)a|=C::ComboSignal; + if(tglComboAUXLifetimeReady)a|=C::ComboAUX; + if(tglAUXParentReady)a|=C::AUXParent; + if(tglTCNativeFunctions.complete())a|=C::TCNative; + if(tglModeResultReady)a|=C::Mode; + if(tglNativeLegacyHPDCaller)a|=C::HPDCaller; + if(tglDPAltFiaReady)a|=C::AltFIA; + if(tglNativeFiaReady)a|=C::NativeFIA; + if(tglModeTransitionLock)a|=C::ModeLock; + if(adlsClockReady)a|=C::ClockAdapter; + if(adlsPowerReady)a|=C::PowerAdapter; + if(adlsPortInitReady)a|=C::PortInit; + if(orgTGLEnableCombo)a|=C::EnableCombo; + if(orgTGLGetOSInformation)a|=C::OSInformation; + if(orgTGLRegisterRead)a|=C::MMIORead; + if(orgTGLRegisterWrite)a|=C::MMIOWrite; + if(tglHPDHookReady)a|=C::HPD; + if(tglClockOffCaller)a|=C::ClockOrder; + if(tglComboAUXGateReady)a|=C::ComboAUXGate; + if(tglPanelDepthReady)a|=C::PanelDepth; + if(tglPanelTimingReady)a|=C::PanelTiming; + if(adlsPanelPWMReady)a|=C::PanelPWM; + return a; +} + +XELP::DisplayContract::Mask XE::display13Available() const { + using C=XELP::DisplayContract;C::Mask a=0; + if(display13FBPowerReferencesReady)a|=C::PowerRefs13; + if(display13FBEngineEnableHookReady)a|=C::Engine13; + if(display13FBLifecycleHooksReady)a|=C::Lifecycle13; + if(display13FBDBUFHooksReady)a|=C::DBUF13; + if(display13FBWatermarkHooksReady)a|=C::WM13; + if(display13FBClockReadoutHooksReady)a|=C::ClockRead13; + if(display13FBClockSetHookReady)a|=C::ClockSet13; + if(display13FBMemoryHooksReady)a|=C::Memory13; + if(display13FBGTTHooksReady)a|=C::GTT13; + if(display13FBABIReady)a|=C::ABI13; + if(display13FBBootHandoffHookReady)a|=C::Handoff13; + if(display13FBIRQReady)a|=C::IRQ13; + if(display13FBWatermarkLayoutReady)a|=C::WMLayout13; + if(display13FBOutputHooksReady)a|=C::Output13; + if(display13FBTopologyHooksReady)a|=C::Topology13; + if(display13FBSSTHooksReady)a|=C::SST13; + if(display13FBAUXHooksReady)a|=C::AUX13; + if(display13DPProbeReady)a|=C::DPProbe13; + // No DPBoot13 until GOP handoff and cold-start have a proven backend. + return a; +} + bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { + if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || + (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady)) { + that->setProperty(callbackXE->adlsDisplaySelected?"ADLSFramebufferPrerequisitesMissing":"XELPSmallGTPrerequisitesMissing",true);return false; + } tglRecordStartOrder(3,that); if(tglTransitionLock)if(auto live=OSSerializer::forTarget(nullptr,tglTransitionSerialize)) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTransitionLive",live);live->release(); @@ -11027,17 +11750,23 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { if(!tglLoadHDMIWiring(provider)) { that->setProperty("TGLStartBlockedByHDMIWiring",true);return false; } - if (!tglSonomaSurfaceFramebufferReady || !callbackXE->tglPlatformNVRAMReady || !callbackXE->tglFBIRQReady || !callbackXE->tglProbeReady || !callbackXE->tglFramebufferInitReady || !callbackXE->tglDekelReady || - (callbackXE->tglHDMIB && !callbackXE->tglGMBusReady) || - !callbackXE->tglTopologyReady || !callbackXE->tglPanelPowerPropertyReady || !callbackXE->tglEDPTranscoderReady || - (callbackXE->tglTrainingLeaseRequested && (!callbackXE->tglTrainingLeaseReady || - !callbackXE->tglEnablePG || !callbackXE->tglDisablePG || - !callbackXE->tglPortByDDI || !callbackXE->orgTGLPathLookup || - !callbackXE->orgTGLAddPath || !callbackXE->orgTGLFreePath || !callbackXE->orgTGLTrainingEnd || - !callbackXE->orgTGLTraining || !callbackXE->orgTGLTrainingBegin || !callbackXE->orgTGLAllocatePathResources))) { - IORegistryEntry::getRegistryRoot()->setProperty("TGLFramebufferStartBlockedByVtable", true); - return false; - } + // Evaluate firmware candidates, not only currently registered/live ports: + // a hotplug route must have the same operations available as a boot route. + auto self=callbackXE; + XELP::DisplayContract::Routes routes; + routes.hdmi=self->tglHDMIB;routes.panel=self->firmwarePorts.panel.valid; + routes.combo=self->adlsDisplaySelected ? self->adlsWiring.dp!=0 : self->tglComboDPMask!=0; + routes.legacy=self->tglTCWiring.legacy || self->tglTCAutoCandidates; + routes.alt=self->tglDPAltCandidates;routes.leaseRequested=self->tglTrainingLeaseRequested; + const auto contract=XELP::DisplayContract::display12(self->display12Available(),routes); + uint64_t contractRecord[]={1,contract.required,contract.available,contract.missing()}; + that->setProperty("WEXDisplayStartupContract",contractRecord,sizeof(contractRecord)); + if(!contract.ready()) { + for(uint64_t bit=1;bit;bit<<=1)if(contract.missing()&bit) + SYSLOG("igfx","Display12 missing operation %s",XELP::DisplayContract::name(bit)); + IORegistryEntry::getRegistryRoot()->setProperty("TGLFramebufferStartBlockedByVtable",true); + return false; + } // r88: use the native provider-workloop mode so IRQ registration does not // depend on a WindowServer framebuffer open after accelerator startup. @@ -11051,6 +11780,9 @@ bool XE::wrapTGLFramebufferStart(IOService *that, IOService *provider) { that->setProperty("TGLHostProviderWorkLoopSelected",ok); if (!ok) return false; } + // A reused controller address must not inherit another start's PPS snapshot. + callbackXE->tglPanelTimingBIOSValid=false; + callbackXE->adlsPanelPWM={}; callbackXE->tglObservationController = that; if (checkKernelArgument("-disablegfxfirmware")) { auto live=OSSerializer::forTarget(nullptr,tglIRQLiveSerialize); @@ -11114,6 +11846,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; if (callbackXE->adlnDetected && adlnNativeRCSProfileRequested && !adlnNativeRCSProfileReady) { SYSLOG("igfx", "AlderLakeN native RCS profile qualification rejected accelerator start"); return false; @@ -11510,6 +12244,8 @@ void XE::tglObserveAccelerator(IOService *that, uint32_t stage, bool done, uint6 } IOService *XE::wrapTGLAcceleratorProbe(IOService *that, IOService *provider, SInt32 *score) { + if((callbackXE->display12SmallGTDetected && !callbackXE->display12SmallGTReady) || + (callbackXE->adlsDisplaySelected && !callbackXE->adlsDisplayHooksReady))return nullptr; if (!callbackXE->tglAcceleratorRequested) return nullptr; auto pci = OSDynamicCast(IOPCIDevice, provider); const uint32_t identity = pci ? pci->configRead32(0) : 0; @@ -11788,7 +12524,7 @@ void XE::wrapTGLHostIRQEnable(void *bridge) { // Native owner enables primary/display IRQ under its existing interrupt lock. // Later enableController skips duplicate setup in mode0; FD4 makes this call // idempotent. Never forge bridge flags, completion stamps, or CSB identities. - if(callbackXE->adlnDetected)wrapDisplay13FBEnableIRQ(static_cast(controller)); + if(callbackXE->display13FramebufferRequested)wrapDisplay13FBEnableIRQ(static_cast(controller)); else wrapTGLFBEnableIRQ(controller); } tglFBIRQEnd(r,0); @@ -13531,6 +14267,16 @@ uint32_t XE::wrapTGLSetPortMode(void *port, uint32_t mode) { if(mode==3 && traceDDI<9 && (self->tglDPAltCandidates&(1U<tglObservationController))return kIOReturnNotReady; auto caller = __builtin_return_address(0); + // TC-labelled fixed DP routes use Combo mode1 on ADL-S at both + // boot and hotplug. Preserve the original caller and immutable record guards. + const auto requestedMode=mode; + mode=self->adlsWiring.hotplugMode(self->adlsDisplaySelected && self->tglModeResultReady, + traceConfig,self->tglPlatformCopy, + reinterpret_cast(caller),self->tglNativeLegacyHPDCaller,mode); + if(mode!=requestedMode) { + uint32_t record[]={traceDDI,requestedMode,mode}; + IORegistryEntry::getRegistryRoot()->setProperty("ADLSDPHotplugMode",record,sizeof(record)); + } if(!self->tglTCModeAllowed(traceDDI,mode) && self->tglRegisterLegacyHPD(traceDDI,mode,reinterpret_cast(caller))) { IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeTCDPRegistered", @@ -13958,8 +14704,9 @@ static bool tglPcodeGateSerialize(void *,void *,OSSerialize *serializer) { const bool result=out->serialize(serializer);out->release();return result; } -// TGL TC AUX depends on PW1/2/3, independently of the AUX-specific well. -// Donor PG argument 3 acquires refs 1..3; pair every AUX reference with it. +// AUX wells also need their parent display power: PW1 for ADL-S DDI A, +// PW1/2/3 for TC AUX. Donor PG argument N acquires refs 1..N. Pair every +// owned AUX reference with its physical parent, including after all pipes stop. // Extra refs are port-local; the donor power object/PG1..3 remain shared. // Preserve the donor caller-serialization contract. This bookkeeping is not // a lock for native power transitions or a claim of concurrent AUX support. @@ -13972,7 +14719,7 @@ void XE::wrapTGLEnableAUXPower(void *power, uint32_t ddi) { auto bytes = static_cast(power); if (hold) { tglAUXRecord(port, 5, false, 0, *reinterpret_cast(bytes + 0x28), *reinterpret_cast(bytes + 0x54 + 4 * ddi), self->tglAUXParentRefs[ddi]); - reinterpret_cast(self->tglEnablePG)(power, 3); + reinterpret_cast(self->tglEnablePG)(power, self->tglAUXParentPG(ddi)); tglAUXRecord(port,9,true,0,*reinterpret_cast(static_cast(power)+0x28), *reinterpret_cast(static_cast(power)+0x54+4*ddi),self->tglAUXParentRefs[ddi],1); ++self->tglAUXParentRefs[ddi]; @@ -13999,7 +14746,7 @@ void XE::wrapTGLDisableAUXPower(void *power, uint32_t ddi) { --self->tglAUXParentRefs[ddi]; tglAUXRecord(port,9,false,0,*reinterpret_cast(static_cast(power)+0x28), *reinterpret_cast(static_cast(power)+0x54+4*ddi),self->tglAUXParentRefs[ddi],0); - reinterpret_cast(self->tglDisablePG)(power, 3); + reinterpret_cast(self->tglDisablePG)(power, self->tglAUXParentPG(ddi)); } tglAUXRecord(port, 6, true, release ? 0 : 1, *reinterpret_cast(bytes + 0x28), *reinterpret_cast(bytes + 0x54 + 4 * ddi), self->tglAUXParentRefs[ddi]); } @@ -14256,6 +15003,18 @@ uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { if(trace)tglTransitionRecord(35,true,access,reg,tglConnectionCaller(__builtin_return_address(0)),value); const auto caller = reinterpret_cast(__builtin_return_address(0)); auto s = callbackXE; + // Only the qualified donor post-ACK fuse reads receive this bounded poll. + if(reg==0x42000) { + const auto mask=XELPDonor::powerWellFuseMask(s->tglPGFuseWaitFunction,caller); + if(mask) { + struct IO { + void *access;uint32_t (*read)(void *,unsigned long); + void delayUs(unsigned us){IODelay(us);} + uint32_t readStatus(){return read(access,0x42000);} + } io {access,reinterpret_cast(s->orgTGLRegisterRead)}; + value=XELPDonor::waitPowerWellFuse(io,value,mask).value; + } + } if (reg == 0x42000 && s->tglPG1FuseReadCaller && caller == s->tglPG1FuseReadCaller) { // Exact donor PG1 fuse check, after its power-state ACK and 5us delay. // Return only real hardware status. A missing bit still terminates; @@ -14336,6 +15095,10 @@ uint32_t XE::wrapTGLRegisterRead(void *access, unsigned long reg) { } uint32_t XE::wrapTGLGMBus(void *controller, uint32_t bus, uint32_t speed, void *request) { + if(callbackXE->adlsDisplaySelected && !callbackXE->tglHDMIDDCEligible(bus)) { + if(request)*reinterpret_cast(static_cast(request)+0x1C)=0xE00002C2; + return 0xE00002C2; + } uint64_t r[24] {}, uptime; static uint32_t count {}; r[0] = __sync_add_and_fetch(&count, 1); @@ -14400,12 +15163,13 @@ uint32_t XE::tglPanelDepthPort(void *port) { auto s=callbackXE;auto p=static_cast(port); auto c=static_cast(s->tglObservationController); if(!p || !c || !s->tglActive || !s->tglPanelDepthReady || !tglAUXLock || - s->xeProfile.family!=XELP::Family::TigerLake)return 9; + (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected))return 9; auto cfg=*reinterpret_cast(p+0x548); if(!cfg || cfg[0]>1 || cfg[2]!=0 || cfg[3]!=0 || p[0x117] || *reinterpret_cast(p+0x578)!=*reinterpret_cast(c+0xC40) || !s->firmwarePorts.valid() || !(s->firmwarePorts.present&(1U<firmwarePorts.port[cfg[0]].internal() || !s->firmwarePorts.port[cfg[0]].dp())return 9; + if(s->adlsDisplaySelected && !s->adlsWiring.boundPanel(s->tglPlatformCopy,cfg))return 9; return cfg[0]; } void XE::wrapTGLModeTiming(void *controller,void *fb,void *timing) { @@ -14648,22 +15412,6 @@ uint32_t XE::wrapTGLAGDCAUX(void *controller, void *request, void *output) { IORegistryEntry::getRegistryRoot()->setProperty(key, failure, sizeof(failure)); } tglTrace(16, id, true, a, b, r, value); - // One read-only receiver-CRC capability probe after a successful late link - // status diagnostic. Reuse the native AGDC AUX entry and its exact request - // contract, outside IRQ context. Do not start a test or write sink state. - static uint32_t crcProbed {}; - uint64_t uptime, ns; - clock_get_uptime(&uptime); absolutetime_to_nanoseconds(uptime, &ns); - if (!r && req && id == 5 && a == 0x200 && !req[13] && req[12] >= 8 && - ns > 30000000000ULL && __sync_bool_compare_and_swap(&crcProbed, 0, 1)) { - uint8_t input[32] {}, out[32] {}; - *reinterpret_cast(input) = 5; - *reinterpret_cast(input + 8) = 0x246; - input[12] = 1; - const auto result = FunctionCast(wrapTGLAGDCAUX, callbackXE->orgTGLAGDCAUX)(controller, input, out); - uint64_t record[] {ns, result, out[14], out[15], out[16], r}; - IORegistryEntry::getRegistryRoot()->setProperty("TGLSinkCRCCapability", record, sizeof(record)); - } return r; } @@ -14675,21 +15423,28 @@ uint32_t XE::wrapTGLAGDCPortStatus(void *controller, void *request, void *output const auto r = FunctionCast(wrapTGLAGDCPortStatus, callbackXE->orgTGLAGDCPortStatus)(controller, request, output); uint64_t value = r == 0 && output ? *reinterpret_cast(static_cast(output) + 8) : 0; auto s = callbackXE; - if (s->tglInactivePorts && s->tglPortByIndex && r == 0 && output && - (id == 6 || id == 7) && controller && controller == s->tglObservationController && + const auto &w=s->tglTCWiring;const auto &v=s->firmwarePorts; + // Explicitly unspecified wiring remains unknown. Firmware absence is useful + // only when every digital child was mapped; do not hide an unparsed output. + const uint32_t excluded=!s->tglTCWiringLoaded || !w.valid ? 0 : s->tglTCWiringExplicit ? w.excluded : + v.valid() && !v.unmapped ? TGLTCWiring::Mask&~v.present : 0; + if (req && s->tglInactivePorts && s->tglPortByIndex && r == 0 && output && + XELP::displayAdapter(s->xeProfile.family)==XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected && + id>=4 && id<=9 && (excluded&(1U<<(id-1))) && controller && controller == s->tglObservationController && *reinterpret_cast(static_cast(controller) + 0xD40) == s->tglPlatformCopy) { auto port = static_cast(reinterpret_cast(s->tglPortByIndex)(id - 1)); auto config = port ? *reinterpret_cast(port + 0x548) : nullptr; - if (config && config[0] == id - 1 && config[1] == id + 5 && config[2] == 1) { + if (config == reinterpret_cast(reinterpret_cast(s->tglPlatformCopy)+0x20+24*(id-1)) && + config[0] == id - 1 && config[1] == id + 5 && config[2] == 1) { const uint32_t mode = *reinterpret_cast(port + 0x550); const uint32_t state = *reinterpret_cast(port + 0x554); const uint32_t sinkCount = port[0xF8]; const uint64_t native = value; // DP1 probePortState resets mode to 0 on D0/AUX failure but leaves // state=2. GetPortStatus tests only state>1, causing AGDC to spend - // seconds reading capabilities of these inactive TC ports. Report - // disconnection only for the measured DDI5/6 mode-0 case. Keep - // native state, HPD, power and all active ports untouched. + // seconds reading capabilities of absent TC ports. The immutable + // wiring excludes this physical port and the exact native record is bound. + // Change only the reported stale result, never native mode/state or power. if (value == 1 && mode == 0 && state == 2 && sinkCount == 0) { *reinterpret_cast(static_cast(output) + 8) = 0; value = 0; @@ -14783,9 +15538,10 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) auto s = callbackXE; auto ctl = static_cast(controller); auto port = static_cast(reinterpret_cast(s->tglPortByDDI)(lease.ddi)); - if (!ctl || !port || - *reinterpret_cast(ctl + 0xD40) != s->tglPlatformCopy || - !s->tglDPSSTMode(lease.ddi,*reinterpret_cast(port + 0x550)) || port[0x117]) return 1; + if (!ctl || !port) return 1; + const auto route=s->tglSSTRoutes().external(lease.ddi,*reinterpret_cast(port + 0x550)); + if (*reinterpret_cast(ctl + 0xD40) != s->tglPlatformCopy || + !route.needsLease() || port[0x117]) return 1; auto access = *reinterpret_cast(ctl + 0xC40); auto power = *reinterpret_cast(ctl + 0xC8); if (!access || !power || *reinterpret_cast(access + 0x60) < 0x16A000 || @@ -14831,8 +15587,9 @@ uint32_t XE::tglPrepareTrainingLease(void *controller, TGLTrainingLease &lease) lease.path = path; lease.port = port; lease.fb = fb; lease.power = power; lease.busy = true; lease.adopted = false; ++lease.generation; lease.clocksActive = false; lease.endCleanupPending = false; lease.pipe = pipe; - // Same power-domain selection as native allocateDisplayPathResources. - lease.pg = pipe ? pipe + 2 : 1; + // Own both consumers. Pipe A alone needs PW1, but a TC-numbered lane + // still needs PW1..3 even when firmware/AUX/other displays hold no refs. + lease.pg = route.powerGroup(pipe); static_cast(lease.fb)->retain(); static_cast(lease.port)->retain(); static_cast(lease.power)->retain(); @@ -15483,9 +16240,9 @@ uint32_t XE::wrapTGLPortTraining(void *port, void *request) { auto p = static_cast(port); auto config = *reinterpret_cast(p + 0x548); auto ctl = static_cast(callbackXE->tglObservationController); - TGLSSTTrainingPort mapping; - if (config && ctl && TGLSSTTrainingPort::resolve(config[0],config[2], - *reinterpret_cast(p + 0x550),callbackXE->tglDPSSTMode(config[0],*reinterpret_cast(p + 0x550)),mapping) && + XELP::Display12SSTRoute mapping; + if (config && ctl && callbackXE->tglSSTRoutes().resolve(config[0],config[2], + *reinterpret_cast(p + 0x550),mapping) && *reinterpret_cast(p + 0x578) && *reinterpret_cast(p + 0x578) == *reinterpret_cast(ctl + 0xC40) && p[0x115] == 1 && p[0x117] == 0 && p[0x10] <= 1 && p[0x11A] <= 1) { @@ -15580,6 +16337,9 @@ 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; + } tglTransitionRecord(32,false,port,reinterpret_cast(controller),reinterpret_cast(otherPort),0); auto s = callbackXE; auto &tx = tglClockOffTransaction; @@ -15634,9 +16394,67 @@ uint32_t XE::wrapTGLLightUpTMDS(void *controller, void *fb, void *path, const vo // The 20A2314a donor nevertheless initializes DDI2 at 0x160000 (EHL PHY C). // r44's exact return PC 0x5a843 proves that this third call caused its panic. // Keep PHY A, including its shared reference role, and PHY B entirely native. +bool XE::adlsClockTarget(void *controller,void *port,void *otherPort) { + auto self=callbackXE; + if(!self->adlsClockReady || !controller || controller!=self->tglObservationController || + !port || (otherPort && otherPort!=port) || + !self->adlsWiring.valid || !self->adlsWiring.selected())return false; + auto p=static_cast(port); + auto config=*reinterpret_cast(p+0x548); + auto access=*reinterpret_cast(static_cast(controller)+0xC40); + if(!config)return false; + AlderLakeS::Port route;uint32_t slot; + return self->adlsWiring.bound(self->tglPlatformCopy,config) && + XELP::ADLSComboBackend::clockSlot(XELP::Donor::C004FC1A,config[0],slot) && + AlderLakeS::Port::resolve(config[0],route) && slot==route.phy && access && + *reinterpret_cast(p+0x578)==access && *reinterpret_cast(p+0x5B8)==route.phyBase()+0x10C && + *reinterpret_cast(access+0x60)>=0x16429C; +} + +void XE::wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params) { + AlderLakeS::HDMIClock encoded; + if(!adlsClockTarget(controller,port,otherPort) || + ((*reinterpret_cast(static_cast(port)+0x548))[2]==2 && + !AlderLakeS::HDMIClock::encode(params,*reinterpret_cast(static_cast(controller)+0xEA8),encoded))) { + IORegistryEntry::getRegistryRoot()->setProperty("ADLSClockEnableRejected",true);return; + } + tglTransitionRecord(71,false,port,encoded.cfg0,encoded.cfg1,0); + FunctionCast(wrapADLSEnableClocks,callbackXE->orgADLSEnableClocks)(controller,port,otherPort,params); + tglTransitionRecord(71,true,port,encoded.cfg0,encoded.cfg1,0); +} + +bool XE::wrapADLSPortInit(void *port,uint32_t *config) { + const bool result=FunctionCast(wrapADLSPortInit,callbackXE->orgADLSPortInit)(port,config); + if(!result || !callbackXE->adlsDisplaySelected || !config || !callbackXE->adlsWiring.matches(config))return result; + auto bytes=static_cast(port); + const bool ready=callbackXE->adlsPortInitReady && + callbackXE->adlsWiring.bound(callbackXE->tglPlatformCopy,config) && + *reinterpret_cast(bytes+0x548)==config && + AlderLakeS::comboRegisters(config[0],reinterpret_cast(bytes+0x580),0x128/4); + char key[40];snprintf(key,sizeof(key),"ADLSComboPort%uReady",config[0]); + IORegistryEntry::getRegistryRoot()->setProperty(key,ready); + return ready; +} + void XE::wrapTGLEnableCombo(void *port) { - auto b = static_cast(port); - auto config = *reinterpret_cast(b + 0x548); + auto b=static_cast(port); + auto config=b?*reinterpret_cast(b+0x548):nullptr; + if(callbackXE->adlsDisplaySelected) { + if(!config || !callbackXE->adlsPortInitReady)return; + // PHY A also supplies the shared reference even with no wired port. + if(config[0]==0) {FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port);return;} + // Native startup enumerates PHY slots 0..2, not physical DDI IDs. + if(config[0]==1 || config[0]==2) { + if(!callbackXE->tglPortByDDI)return; + port=reinterpret_cast(callbackXE->tglPortByDDI)(config[0]+2); + } + b=static_cast(port);config=b?*reinterpret_cast(b+0x548):nullptr; + AlderLakeS::Port route; + if(callbackXE->adlsWiring.bound(callbackXE->tglPlatformCopy,config) && + AlderLakeS::Port::resolve(config[0],route) && *reinterpret_cast(b+0x5B8)==route.phyBase()+0x10C) + FunctionCast(wrapTGLEnableCombo,callbackXE->orgTGLEnableCombo)(port); + return; + } const bool absent = callbackXE->tglActive && config && config[0] == 2 && *reinterpret_cast(b + 0x5B8) == 0x16010C; uint64_t r[20] {}, uptime; @@ -15877,7 +16695,7 @@ void XE::wrapTGLRegisterWrite(void *access, unsigned long reg, uint32_t value) { if (ctl && *reinterpret_cast(ctl + 0xC40) == access) { const auto refCode = *reinterpret_cast(ctl + 0xEA8); const auto nominal = value; - if (TGLComboPLLFraction::adjust(refCode, reg, value)) { + if (TGLComboPLLFraction::adjust(refCode, callbackXE->adlsDisplaySelected && reg==0x164294 ? 0x164284 : reg, value)) { const uint64_t detail[] {refCode, nominal}; tglTransitionRecord(70,true,access,reg,nominal,value,detail,sizeof(detail)); } @@ -16148,21 +16966,17 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { auto ctl = fb ? *reinterpret_cast(fb + 0x1D0) : nullptr; auto access = ctl ? *reinterpret_cast(ctl + 0xC40) : nullptr; const auto transcoder = *reinterpret_cast(static_cast(path) + 0x3640); - TGLSSTTrainingPort mapping; - if (config && TGLSSTTrainingPort::resolve(config[0],config[2], - *reinterpret_cast(pb + 0x550),callbackXE->tglDPSSTMode(config[0],*reinterpret_cast(pb + 0x550)),mapping) && !pb[0x117] && + XELP::Display12SSTRoute mapping; + if (config && callbackXE->tglSSTRoutes().resolve(config[0],config[2], + *reinterpret_cast(pb + 0x550),mapping) && !pb[0x117] && transcoder < 3 && ctl == callbackXE->tglObservationController && access && *reinterpret_cast(pb + 0x578) == access && *reinterpret_cast(fb + 0x4A40) == access) { auto base = *reinterpret_cast(access + 0x50); const auto size = *reinterpret_cast(access + 0x60); if (base && size >= mapping.ddiBuffer + 4 && size >= 0x60544 + 0x1000 * transcoder) { - auto clock = reinterpret_cast(base + 0x46140 + 4 * transcoder); - auto func = reinterpret_cast(base + 0x60400 + 0x1000 * transcoder); auto buffer = reinterpret_cast(base + mapping.ddiBuffer); const auto lanes = *reinterpret_cast(pb + 0x124); - const uint32_t routeClock = (config[0] + 1) << 28; - const uint32_t routePort = (config[0] + 1) << 27; const auto mainPower = *reinterpret_cast(base + 0x45404); const auto ioPower = *reinterpret_cast(base + 0x45454); // The selected port and pipe determine the required TGL power wells. @@ -16171,29 +16985,13 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { const uint32_t ioMask = 3U << (2 * mapping.ddi); const bool powered = mainPower != 0xFFFFFFFFU && ioPower != 0xFFFFFFFFU && (mainPower & mainMask) == mainMask && (ioPower & ioMask) == ioMask; - // Do not sample the route/output bank when its power contract failed. - const auto clockBefore = powered ? *clock : 0U; - const auto funcBefore = powered ? *func : 0U; - const auto bufferBefore = powered ? *buffer : 0U; - uint32_t reason = 0; - if (!powered) reason = 5; - else if (lanes != 1 && lanes != 2 && lanes != 4) reason = 1; - else if ((funcBefore | bufferBefore) & 0x80000000U) reason = 2; - else if ((clockBefore & 0xF0000000U) && (clockBefore & 0xF0000000U) != routeClock) reason = 3; - else if ((funcBefore & 0x78000000U) && (funcBefore & 0x78000000U) != routePort) reason = 4; - const uint32_t clockWanted = (clockBefore & 0x0FFFFFFFU) | routeClock; - const uint32_t funcWanted = (funcBefore & ~0x7F00000EU) | routePort | 0x02000000U | - ((lanes - 1) << 1 & 0xEU); - uint32_t clockAfter = clockBefore, funcAfter = funcBefore; - if (!reason) { - *clock = clockWanted; - clockAfter = *clock; - if (clockAfter == clockWanted) { - *func = funcWanted; - funcAfter = *func; - if (funcAfter != funcWanted) reason = 7; - } else reason = 6; - } + const auto read=[&](uint32_t reg){return *reinterpret_cast(base+reg);}; + const auto write=[&](uint32_t reg,uint32_t value){*reinterpret_cast(base+reg)=value;}; + const auto programmed=XELP::SSTProgram::prepare(read,write,mapping.ddi,transcoder,lanes,powered); + const auto reason=programmed.reason; + const auto clockBefore=programmed.clockBefore,clockWanted=programmed.clockWanted,clockAfter=programmed.clockAfter; + const auto funcBefore=programmed.funcBefore,funcWanted=programmed.funcWanted,funcAfter=programmed.funcAfter; + const auto bufferBefore=programmed.bufferBefore; static uint32_t sequence {}; uint32_t record[] {__sync_add_and_fetch(&sequence, 1), reason, transcoder, lanes, clockBefore, clockWanted, clockAfter, funcBefore, funcWanted, funcAfter, bufferBefore, powered ? *buffer : 0U, @@ -16225,9 +17023,9 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { auto ctl = fb ? *reinterpret_cast(fb + 0x1D0) : nullptr; auto access = ctl ? *reinterpret_cast(ctl + 0xC40) : nullptr; const auto transcoder = *reinterpret_cast(static_cast(path) + 0x3640); - TGLSSTTrainingPort mapping; - if (config && TGLSSTTrainingPort::resolve(config[0],config[2], - *reinterpret_cast(p + 0x550),callbackXE->tglDPSSTMode(config[0],*reinterpret_cast(p + 0x550)),mapping) && + XELP::Display12SSTRoute mapping; + if (config && callbackXE->tglSSTRoutes().resolve(config[0],config[2], + *reinterpret_cast(p + 0x550),mapping) && !p[0x117] && p[0x11A] <= 1 && transcoder < 3 && ctl == callbackXE->tglObservationController && access && *reinterpret_cast(p + 0x578) == access && @@ -16235,7 +17033,6 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { auto base = *reinterpret_cast(access + 0x50); const uint32_t reg = 0x60540 + 0x1000*transcoder; if (base && *reinterpret_cast(access + 0x60) >= reg + 4) { - auto tp = reinterpret_cast(base + reg); const auto mainPower = *reinterpret_cast(base + 0x45404); const auto ioPower = *reinterpret_cast(base + 0x45454); const uint32_t mainMask = mapping.mainPowerMask(transcoder); @@ -16245,18 +17042,16 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { tglObserveActivation(callbackXE->tglObservationController, port, path, 1, true, result); return result; } - const auto before = *tp; - auto wanted = (before & ~0x40000U) | (uint32_t(p[0x11A]) << 18); - // Match the negotiated sink setting, not merely its capability. - // External DP never receives this eDP-only DPCD write. - const bool alternateSR = config[2] == 0 && callbackXE->tglSinkFramingReady && p[0x118] <= 1; - if (alternateSR) wanted = (wanted & ~0x40U) | (uint32_t(p[0x118]) << 6); - const bool activeSST = (before & 0x88000000U) == 0x80000000U; - if (activeSST && before != wanted) *tp = wanted; + const auto read=[&](uint32_t off){return *reinterpret_cast(base+off);}; + const auto write=[&](uint32_t off,uint32_t value){*reinterpret_cast(base+off)=value;}; + const auto framing=XELP::SSTProgram::framing(read,write,transcoder,p[0x11A], + config[2]==0,callbackXE->tglSinkFramingReady,p[0x118],true); + const auto before=framing.before,wanted=framing.wanted; + const auto activeSST=framing.active,alternateSR=framing.alternate; uint64_t record[13] {}, uptime; clock_get_uptime(&uptime); absolutetime_to_nanoseconds(uptime, record); record[1] = pattern; record[2] = p[0x11A]; record[3] = before; - record[4] = wanted; record[5] = *tp; record[6] = activeSST; + record[4] = wanted; record[5] = framing.after; record[6] = activeSST; record[7] = *reinterpret_cast(p + 0x124); record[8] = reinterpret_cast(path); record[9] = transcoder; record[10] = mapping.ddi; record[11] = alternateSR; record[12] = p[0x118]; @@ -16269,12 +17064,43 @@ uint32_t XE::wrapTGLTrainingPattern(void *path, uint32_t pattern) { return result; } +uint8_t *XE::adlsPowerTarget(void *power) { + auto s=callbackXE;auto p=static_cast(power); + auto c=static_cast(s->tglObservationController); + if(!s->adlsDisplaySelected || !s->adlsPowerReady || !p || !c || + !s->adlsWiring.valid || !s->adlsWiring.selected() || + *reinterpret_cast(p+0x10)!=c || *reinterpret_cast(c+0xC8)!=p || + *reinterpret_cast(p+0x78)!=*reinterpret_cast(c+0xC40))return nullptr; + return p; +} + +void XE::wrapADLSPowerPG(void *power,bool enable,uint32_t requested) { + auto p=adlsPowerTarget(power); + const auto target=AlderLakeS::LanePower::target(requested,p && callbackXE->adlsWiring.laneConsumer(reinterpret_cast(p+0x30))); + tglTransitionRecord(73,false,power,requested,target,enable); + FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,enable,target); + tglTransitionRecord(73,true,power,requested,target,enable); +} + void XE::wrapTGLPowerDDIState(void *power, bool enable, uint32_t requested) { auto bytes=static_cast(power); tglTransitionRecord(33,false,power,requested,enable,bytes[0x18],bytes+0x20,52); auto controller = *reinterpret_cast(static_cast(power) + 0x10); tglObserveActivation(controller, nullptr, nullptr, 3, false, requested); + // Acquire parent wells before DDI IO turns on; release after IO turns off. + // Native reference counts remain untouched. The PG target is the union of + // the native pipe consumers and the current TC1/TC2 lane consumers. + auto adls=adlsPowerTarget(power); + const auto laneMask=callbackXE->adlsWiring.laneMask(); + if(adls && (requested&laneMask)) { + const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); + FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,true,AlderLakeS::LanePower::target(native,true)); + } FunctionCast(wrapTGLPowerDDIState, callbackXE->orgTGLPowerDDIState)(power, enable, requested); + if(adls && !(requested&laneMask)) { + const auto native=AlderLakeS::LanePower::pipeRequests(reinterpret_cast(adls+0x20)); + FunctionCast(wrapADLSPowerPG,callbackXE->orgADLSPowerPG)(power,false,native); + } tglTransitionRecord(33,true,power,requested,enable,bytes[0x18],bytes+0x20,52); tglObserveActivation(controller, nullptr, nullptr, 3, true, requested); } @@ -16318,18 +17144,26 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void auto p=static_cast(port); auto ctl=static_cast(self->tglObservationController); if(!p || !ctl || overrideConfig || !self->tglActive || !self->tglComboSignalReady || - self->xeProfile.family!=XELP::Family::TigerLake || !self->firmwarePorts.valid())return false; + (XELP::displayAdapter(self->xeProfile.family)!=XELP::DisplayAdapter::TGL && !self->adlsDisplaySelected) || !self->firmwarePorts.valid())return false; auto config=*reinterpret_cast(p+0x548); const auto &panel=self->firmwarePorts.panel; - if(!config || config[0]>1 || config[2]!=0 || config[3]!=0 || - *reinterpret_cast(p+0x550)!=1 || p[0x117] || - !panel.valid || panel.ddi!=config[0] || (self->firmwarePorts.port[config[0]].flags&0x17))return false; + const bool adls=self->adlsDisplaySelected && self->adlsWiring.boundSST(self->tglPlatformCopy,config); + const bool fixed=!self->adlsDisplaySelected && TGLComboDP::bound(self->tglComboDPMask,self->tglPlatformCopy,config); + if(self->adlsDisplaySelected && !adls)return false; + XELP::Display12SSTRoute route; + const XELP::Display12SSTRoutes routes{adls?self->adlsWiring.dp:fixed?self->tglComboDPMask:0,0,0}; + if(!config || !routes.resolve(config[0],config[2],*reinterpret_cast(p+0x550),route) || + !route.retainComboAUX() || p[0x117])return false; + if(!adls && !fixed && (config[0]>1 || config[2]!=0 || config[3]!=0 || + !panel.valid || panel.ddi!=config[0] || (self->firmwarePorts.port[config[0]].flags&0x17)))return false; auto access=*reinterpret_cast(p+0x578); if(!access || *reinterpret_cast(ctl+0xC40)!=access)return false; TGLComboSignal selected; const auto lanes=*reinterpret_cast(p+0x124); const auto rate=*reinterpret_cast(p+0x128); - if(!selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis))return false; + if(!(adls ? selected.selectADLS(config[0],lanes,rate,swing,emphasis,route.panel,panel.lowSwing): + selected.select(config[0],lanes,rate,panel.lowSwing,panel.hobl,swing,emphasis, + XELP::ComboPolicy::tigerLake(self->xeProfile.identity),!fixed)))return false; // The native fields identify this very bank. No guessed framebuffer index // or cross-port reads; power must already belong to the training caller. if(*reinterpret_cast(p+0x620)!=selected.base+0x804 || @@ -16339,7 +17173,8 @@ bool XE::tglProgramComboSignal(void *port,uint32_t swing,uint32_t emphasis,void const auto main=wrapTGLRegisterRead(access,0x45404); const auto io=wrapTGLRegisterRead(access,0x45454); const uint32_t ioMask=3U<<(2*config[0]); - if(main==~0U || io==~0U || (main&3)!=3 || (io&ioMask)!=ioMask)return false; + const uint32_t mainMask=route.lanePowerMask(); + if(main==~0U || io==~0U || (main&mainMask)!=mainMask || (io&ioMask)!=ioMask)return false; const auto read=[&](uint32_t reg){return wrapTGLRegisterRead(access,reg);}; const auto write=[&](uint32_t reg,uint32_t value){wrapTGLRegisterWrite(access,reg,value);}; const bool programmed=selected.program(read,write); @@ -16669,9 +17504,12 @@ void XE::tglComboAUXReference(void *port,bool acquire) { auto ctl=static_cast(s->tglObservationController); auto &f=s->tglTCNativeFunctions; // shared native AUX API, not TC admission if(!p || !ctl || !s->tglActive || !s->tglComboAUXLifetimeReady || !f.complete() || - s->xeProfile.family!=XELP::Family::TigerLake || *f.controllerSlot!=ctl)return; + (XELP::displayAdapter(s->xeProfile.family)!=XELP::DisplayAdapter::TGL && !s->adlsDisplaySelected) || *f.controllerSlot!=ctl)return; auto config=*reinterpret_cast(p+0x548); - if(!config || config[0]>1)return; + const bool adls=s->adlsDisplaySelected && s->adlsWiring.boundSST(s->tglPlatformCopy,config); + const bool fixed=!s->adlsDisplaySelected && TGLComboDP::bound(s->tglComboDPMask,s->tglPlatformCopy,config); + if(s->adlsDisplaySelected && !adls)return; + if(!config || (config[0]>1 && !adls))return; const auto ddi=config[0]; auto &owner=s->tglComboAUXOwners[ddi]; if(acquire ? owner!=nullptr : owner!=port)return; @@ -16680,14 +17518,17 @@ void XE::tglComboAUXReference(void *port,bool acquire) { if(!power || !access || *reinterpret_cast(power+0x10)!=ctl || *reinterpret_cast(power+0x78)!=access || *reinterpret_cast(p+0x578)!=access || - *reinterpret_cast(access+0x60)<0x64200 || + *reinterpret_cast(access+0x60)<(0x64004U+0x100*ddi) || !*reinterpret_cast(access+0x50))return; const uint32_t buffer=0x64000+0x100*ddi,mask=3U<<(2*ddi); if(*reinterpret_cast(p+0x590)!=buffer)return; - if(acquire && (config[2]!=0 || config[3]!=0 || p[0x117] || - *reinterpret_cast(p+0x550)!=1 || !s->firmwarePorts.valid() || - !(s->firmwarePorts.present&(1U<firmwarePorts.port[ddi].internal() || - !s->firmwarePorts.port[ddi].dp()))return; + 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; const auto io=wrapTGLRegisterRead(access,0x45454); if(io==~0U || (io&mask)!=mask)return; const auto bufferState=wrapTGLRegisterRead(access,buffer); diff --git a/WhateverXe/kern_xe.hpp b/WhateverXe/kern_xe.hpp index fff2f41f..71bd7350 100644 --- a/WhateverXe/kern_xe.hpp +++ b/WhateverXe/kern_xe.hpp @@ -12,10 +12,15 @@ #include "kern_tgl_tc_native.hpp" #include "kern_tgl_native_fia.hpp" #include "kern_tgl_hdmi_wiring.hpp" +#include "kern_tgl_combo_dp.hpp" #include "kern_display13_hdmi_wiring.hpp" #include "kern_xelp_platform.hpp" +#include "kern_xelp_sst_program.hpp" #include "kern_vbt.hpp" +#include "kern_alderlake_s.hpp" +#include "kern_xelp_panel_pwm.hpp" #include "kern_xelp_donor.hpp" +#include "kern_xelp_display_contract.hpp" #include "kern_dvmt.hpp" #include @@ -57,6 +62,8 @@ class XE { bool processKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); private: + XELP::DisplayContract::Mask display12Available() const; + XELP::DisplayContract::Mask display13Available() const; // Do not inline either helper into the small kext-load dispatcher. __attribute__((noinline)) bool processAlderLakeNLoadOnly(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); __attribute__((noinline)) bool processAlderLakeNRejectedAccelerator(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size); @@ -75,6 +82,28 @@ class XE { bool tglActive {false}; bool adlnRequested {false}; bool adlnDetected {false}; + // Render and Display selections are independent; shared PHY/port code + // must not depend on which GT implementation the product uses. + bool display12SmallGTDetected {},display12SmallGTReady {}; + bool adlsDisplaySelected {},adlsDisplayHooksReady {}; + bool adlsPortInitReady {},tglHPDHookReady {}; + bool adlsPLLTableReady {},adlsClockReady {},adlsPowerReady {},adlsPanelPWMReady {}; + XELP::PanelPWM adlsPanelPWM; + mach_vm_address_t orgADLSLightUpEDP {},orgADLSPanelBacklight {}; + static bool adlsPanelPWMTarget(void *controller,void *&access,void *&power); + static bool adlsCapturePanelPWM(void *controller); + static uint32_t wrapADLSLightUpEDP(void *controller,void *fb,void *path,const void *timing); + static uint32_t wrapADLSPanelBacklight(void *controller,uint32_t brightness); + static void wrapADLSPanelPWMEnable(void *access); + mach_vm_address_t orgADLSPowerPG {}; + static uint8_t *adlsPowerTarget(void *power); + static void wrapADLSPowerPG(void *power,bool enable,uint32_t requested); + mach_vm_address_t orgADLSEnableClocks {}; + static bool adlsClockTarget(void *controller,void *port,void *otherPort); + static void wrapADLSEnableClocks(void *controller,void *port,void *otherPort,uint32_t *params); + mach_vm_address_t orgADLSPortInit {}; + static bool wrapADLSPortInit(void *port,uint32_t *config); + XELP::SmallGTResources display12SmallGT; XELP::Profile xeProfile; XELP::VBT::Ports firmwarePorts; void xelpReadFirmware(IOPCIDevice *pci); @@ -83,7 +112,9 @@ class XE { uint32_t adlnNativeSubslices {}, adlnL3Banks {}, adlnNativeEUs {}; bool adlnKernelReady {false}; bool adlnNativeRequested {false}; - bool adlnNativeReady {false}; + // Shared platform preflight: GT fuses, reserved GGTT/GOP, mapper, + // aperture and native boot policy. Both Render and Display consume it. + bool adlnNativeReady {false}; bool display13FramebufferRequested {false}; bool display13FBPowerReferencesReady {false}; bool display13FBClockReadoutHooksReady {false}; @@ -94,10 +125,32 @@ class XE { bool display13FBWatermarkLayoutReady {false}; bool display13FBWatermarkHooksReady {false}; bool display13FBOutputHooksReady {false}; + bool display13FBSSTHooksReady {}; + bool display13FBAUXHooksReady {}; + mach_vm_address_t display13AUXControllerSlot {}; + mach_vm_address_t orgDisplay13ReadAUX {},orgDisplay13WriteAUX {},orgDisplay13I2CAUX {},orgDisplay13RunAUX {}; + bool processDisplay13FramebufferAUX(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static bool display13AUXReady(void *port,bool command); + static uint32_t wrapDisplay13ReadAUX(void *port,uint32_t address,void *data,uint32_t length); + static uint32_t wrapDisplay13WriteAUX(void *port,uint32_t address,void *data,uint32_t length); + static uint32_t wrapDisplay13I2CAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,uint8_t flag); + static uint32_t wrapDisplay13RunAUX(void *port,uint32_t command,uint32_t *packet,uint32_t length,bool flag); + mach_vm_address_t orgDisplay13TrainingPattern {},orgDisplay13SignalLevels {}; + mach_vm_address_t orgDisplay13EnablePort {},orgDisplay13DisablePort {}; + static bool display13MainLinkAUX(void *port,bool acquire); + static uint32_t wrapDisplay13EnablePort(void *port); + static uint32_t wrapDisplay13DisablePort(void *port); + bool processDisplay13FramebufferSST(KernelPatcher &,size_t,mach_vm_address_t,size_t); + static uint8_t *display13SSTPort(void *port); + static bool display13HDMIPort(void *port); + static uint32_t wrapDisplay13TrainingPattern(void *path,uint32_t pattern); + static uint32_t wrapDisplay13SignalLevels(void *port,uint32_t swing,uint32_t emphasis,void *overrideConfig); mach_vm_address_t xelpProviderProbe {},xelpProviderConfigureProbe {}; + bool display13DPProbeReady {}; bool xelpBindProbe(void *controller); bool display13FBTopologyHooksReady {false}; - Display13HDMIWiring adlnHDMIWiring; + Display13OutputWiring adlnOutputWiring; + mach_vm_address_t adlnComboPLLFractionCaller {}; uint64_t adlnPlatform[0x1C0/8] {}; IOService *adlnTopologyController {}; mach_vm_address_t orgAlderLakeNGetOSInformation {},orgAlderLakeNPortState {}; @@ -238,6 +291,13 @@ class XE { mach_vm_address_t orgTGLPlaneSetup {}, orgTGLColorMode {}, orgTGLPixelInfo {}; mach_vm_address_t orgTGLModeTiming {}; bool tglPanelDepthReady {false}; + bool tglPanelTimingReady {}; + mach_vm_address_t orgTGLPanelTiming {},orgTGLPanelConfig {}; + uint32_t tglPanelTimingBIOS[3] {}; + bool tglPanelTimingBIOSValid {}; + static void tglApplyPanelTiming(void *controller,bool apply); + static void wrapTGLPanelConfig(void *controller,uint32_t config); + static uint32_t wrapTGLPanelTiming(void *controller); static uint32_t tglPanelDepthPort(void *port); static void wrapTGLModeTiming(void *controller,void *fb,void *timing); mach_vm_address_t orgTGLGamma10 {}, orgTGLRegisterWrite {}, orgTGLFastRegisterWrite {}; @@ -251,6 +311,7 @@ class XE { static volatile uint8_t *tglClockOrderTarget(void *controller, void *port, void *path, void *fb); static void wrapTGLDisableClocks(void *controller, void *port, void *otherPort); mach_vm_address_t tglPG1FuseReadCaller {}; + mach_vm_address_t tglPGFuseWaitFunction {}; mach_vm_address_t tglComboPLLFractionCaller {}; bool tglSinkFramingReady {}; bool tglComboAUXGateReady {}; @@ -279,11 +340,12 @@ class XE { static bool wrapTGLPanelPowerProperty(IORegistryEntry *framebuffer, const char *, OSObject *value); bool tglHDMIB {false}; TGLHDMIWiring tglHDMIWiring; + AlderLakeS::Wiring adlsWiring; bool tglHDMIWiringLoaded {},tglHDMIWiringExplicit {},tglHDMIWiringApplied {}; uint32_t tglHDMIBackendMask {3}; // TGL-U combo PHY A/B; C is absent static bool tglLoadHDMIWiring(IOService *provider); - bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && tglHDMIWiring.matches(config);} - bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && tglHDMIWiring.usesDDC(pin);} + bool tglHDMIEligible(const uint32_t *config) const {return tglHDMIWiringApplied && (adlsDisplaySelected?adlsWiring.matches(config,false):tglHDMIWiring.matches(config));} + bool tglHDMIDDCEligible(uint32_t pin) const {return tglHDMIWiringApplied && (adlsDisplaySelected?adlsWiring.usesDDC(pin):tglHDMIWiring.usesDDC(pin));} bool tglGMBusReady {false}; mach_vm_address_t orgTGLGMBus {}; @@ -329,7 +391,7 @@ class XE { mach_vm_address_t orgTGLEnablePort {}; mach_vm_address_t orgTGLDisablePort {}; bool tglComboAUXLifetimeReady {}; - void *tglComboAUXOwners[2] {}; + void *tglComboAUXOwners[9] {}; static void tglComboAUXReference(void *port,bool acquire); static uint32_t wrapTGLDisablePort(void *port); mach_vm_address_t orgTGLSignalLevels {}; @@ -388,13 +450,18 @@ class XE { // Native mode setter owns FIA1 bits 0/1 correctly on modular TGL. Later // instances need the new mode backend, regardless of available PHY math. uint32_t tglTCAutoCandidates {},tglTCAutoRegistered {}; - uint32_t tglDPAltCandidates {}; - bool tglDPSSTEligible(uint32_t ddi) const { - return tglLegacyTCEligible(ddi) || (ddi>=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && ddi<9 && (tglDPAltCandidates&(1U<=3 && ddi<9 && - (tglTCAutoCandidates & tglAUXParentMask & __atomic_load_n(&tglTCAutoRegistered,__ATOMIC_ACQUIRE) & (1U<= KernelVersion::Sonoma && v <= KernelVersion::Tahoe; + return v >= KernelVersion::BigSur && v <= KernelVersion::Tahoe; } bool allowed(void *vnode, bool executable) { if (!ready || !vnode) return false; @@ -34,7 +37,17 @@ bool allowed(void *vnode, bool executable) { const auto client = XeDataPolicy::clientForFile(path, executable); if (client == XeDataPolicy::Client::None) return false; auto process = currentProcess(); - auto image = process ? executableVnode(process) : nullptr; + if (!process) return false; + if (client == XeDataPolicy::Client::WebKitGPU) { + // Current process lifetime covers these kernel queries; do not cache by + // PID/path (exec and PID reuse would invalidate such a cache). + return signingIdentity && platformBinary && signatureValid && + XeDataPolicy::webKitGPU(signingIdentity(process), + platformBinary(process) != 0, signatureValid(process) != 0); + } + // The native compiler exception is only needed on Sonoma and later. + if (getKernelVersion() < KernelVersion::Sonoma) return false; + auto image = executableVnode(process); if (!image) return false; length = sizeof(path); const auto error = getPath(image, path, &length); @@ -74,6 +87,14 @@ void XeDataAccess::processKernel(KernelPatcher &p) { p.clearError(); putVnode = reinterpret_cast(p.solveSymbol(KernelPatcher::KernelID, "_vnode_put")); p.clearError(); + signingIdentity = reinterpret_cast(p.solveSymbol(KernelPatcher::KernelID, "_csproc_get_identity")); + p.clearError(); + platformBinary = reinterpret_cast(p.solveSymbol(KernelPatcher::KernelID, "_csproc_get_platform_binary")); + p.clearError(); + signatureValid = reinterpret_cast(p.solveSymbol(KernelPatcher::KernelID, "_cs_valid")); + p.clearError(); + IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeWebKitIdentitySymbols", + signingIdentity && platformBinary && signatureValid); enabled = currentProcess && executableVnode && fileVnode && getPath && putVnode; IORegistryEntry::getRegistryRoot()->setProperty("WhateverXeDataAccessSymbols", enabled); } diff --git a/WhateverXe/kern_xe_data_policy.hpp b/WhateverXe/kern_xe_data_policy.hpp index 0fe3217f..a3982a10 100644 --- a/WhateverXe/kern_xe_data_policy.hpp +++ b/WhateverXe/kern_xe_data_policy.hpp @@ -4,20 +4,17 @@ namespace XeDataPolicy { enum class Client { None, Compiler, WebKitGPU }; +// Identity comes from the running process's kernel code-signing state, never +// Info.plist, a basename, or caller-provided metadata. Safari updates and moves +// must not require enumerating executable locations. +inline bool webKitGPU(const char *identity, bool platformBinary, bool valid) { + return platformBinary && valid && identity && !strcmp(identity, "com.apple.WebKit.GPU"); +} inline bool service(const char *path, Client client = Client::Compiler) { - if (!path || client == Client::None) return false; - // WebKit's executable vnode is on the OS Cryptex on newer systems. - // Accept only the two OS mount roots, followed by the exact service path. - if (client == Client::WebKitGPU) { - constexpr const char os[] = "/System/Volumes/Preboot/Cryptexes/OS"; - constexpr const char incoming[] = "/System/Volumes/Preboot/Cryptexes/Incoming/OS"; - if (!strncmp(path, os, sizeof(os)-1)) path += sizeof(os)-1; - else if (!strncmp(path, incoming, sizeof(incoming)-1)) path += sizeof(incoming)-1; - } - if (client == Client::WebKitGPU) return path && !strcmp(path, - "/System/Library/Frameworks/WebKit.framework/Versions/A/XPCServices/" - "com.apple.WebKit.GPU.xpc/Contents/MacOS/com.apple.WebKit.GPU"); - return path && !strcmp(path, "/System/Library/Frameworks/Metal.framework/Versions/A/" + // Compiler access keeps its original exact-path contract. WebKit has no + // path-based fallback: even a familiar location must pass signature checks. + return client == Client::Compiler && path && !strcmp(path, + "/System/Library/Frameworks/Metal.framework/Versions/A/" "XPCServices/MTLCompilerService.xpc/Contents/MacOS/MTLCompilerService"); } inline bool file(const char *path, bool executable) { diff --git a/WhateverXe/kern_xelp_combo_policy.hpp b/WhateverXe/kern_xelp_combo_policy.hpp new file mode 100644 index 00000000..bf73a92c --- /dev/null +++ b/WhateverXe/kern_xelp_combo_policy.hpp @@ -0,0 +1,247 @@ +// Combo electrical policy, independent of PHY coordinates and port admission. +// Tables and selection rules: Linux v6.12 intel_ddi_buf_trans.c (MIT). +// Reference coefficient tables: Copyright (c) 2020 Intel Corporation. +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +#ifndef KERN_XELP_COMBO_POLICY_HPP +#define KERN_XELP_COMBO_POLICY_HPP +#include +namespace XELP { +struct ComboPolicy { + enum class Platform : uint8_t {Unknown,TGL,TGLUY,RKL,ADLS,ADLP}; + enum class Table : uint8_t {Invalid,TGLHBR,TGLHigh,TGLUYHigh,ICLHigh,LowSwing,HOBL,RKLHBR,RKLHigh,ADLSHigh,ADLSLow,ADLPHBR,ADLPHigh,ADLPLow}; + struct Coefficients {uint8_t swing,scalar,cursor,post2,post1;}; + Table table {Table::Invalid}; + Coefficients coefficients {}; + uint32_t level {}; + bool hobl {}; + // Linux TGL U/Y is the physical GT2 ID set, not WEX's render backend. + // In particular 9A78 uses U/Y electrical values despite its G4 backend. + static Platform tigerLake(uint32_t physicalIdentity) { + if((physicalIdentity&0xFFFF)!=0x8086)return Platform::Unknown; + switch(physicalIdentity>>16) { + case 0x9A60:case 0x9A68:case 0x9A70:return Platform::TGL; + case 0x9A40:case 0x9A49:case 0x9A59:case 0x9A78: + case 0x9AC0:case 0x9AC9:case 0x9AD9:case 0x9AF8:return Platform::TGLUY; + default:return Platform::Unknown; + } + } + static Table choose(Platform platform,uint32_t rate,bool panel,bool lowSwing,bool useHOBL) { + if(platform==Platform::Unknown || platform>Platform::ADLP || + (rate!=6 && rate!=10 && rate!=20 && rate!=30))return Table::Invalid; + if(panel) { + if(rate==30)return platform==Platform::ADLS?Table::ADLSHigh:platform==Platform::ADLP?Table::ADLPHigh:Table::ICLHigh; + if(useHOBL)return Table::HOBL; + if(lowSwing)return platform==Platform::ADLS?Table::ADLSLow:platform==Platform::ADLP?Table::ADLPLow:Table::LowSwing; + } + if(platform==Platform::ADLP)return rate>10?Table::ADLPHigh:Table::ADLPHBR; + if(platform==Platform::RKL)return rate>10?Table::RKLHigh:Table::RKLHBR; + if(rate<=10)return Table::TGLHBR; + return platform==Platform::ADLS?Table::ADLSHigh:platform==Platform::TGLUY?Table::TGLUYHigh:Table::TGLHigh; + } + static bool row(Table table,uint32_t index,Coefficients &out) { + out={}; + static const Coefficients TGLHBR[] { + {0x0A,0x32,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x71,0x2F,0x00,0x10}, + {0x06,0x7D,0x2B,0x00,0x14}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x6C,0x3C,0x00,0x03}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients TGLHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x63,0x2F,0x00,0x10}, + {0x06,0x7F,0x2B,0x00,0x14}, + {0x0A,0x47,0x3F,0x00,0x00}, + {0x0C,0x63,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x61,0x3C,0x00,0x03}, + {0x06,0x7B,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients TGLUYHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x36,0x00,0x09}, + {0x0C,0x60,0x32,0x00,0x0D}, + {0x0C,0x7F,0x2D,0x00,0x12}, + {0x0C,0x47,0x3F,0x00,0x00}, + {0x0C,0x6F,0x36,0x00,0x09}, + {0x06,0x7D,0x32,0x00,0x0D}, + {0x06,0x60,0x3C,0x00,0x03}, + {0x06,0x7F,0x34,0x00,0x0B}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ICLHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x71,0x2F,0x00,0x10}, + {0x06,0x7F,0x2B,0x00,0x14}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x6C,0x3C,0x00,0x03}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients LowSwing[] { + {0x00,0x7F,0x3F,0x00,0x00}, + {0x08,0x7F,0x38,0x00,0x07}, + {0x01,0x7F,0x33,0x00,0x0C}, + {0x09,0x7F,0x31,0x00,0x0E}, + {0x08,0x7F,0x3F,0x00,0x00}, + {0x01,0x7F,0x38,0x00,0x07}, + {0x09,0x7F,0x35,0x00,0x0A}, + {0x01,0x7F,0x3F,0x00,0x00}, + {0x09,0x7F,0x38,0x00,0x07}, + {0x09,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients HOBL[] { + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients RKLHBR[] { + {0x0A,0x2F,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x63,0x2F,0x00,0x10}, + {0x06,0x7D,0x2A,0x00,0x15}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x6E,0x3E,0x00,0x01}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients RKLHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x50,0x38,0x00,0x07}, + {0x0C,0x61,0x33,0x00,0x0C}, + {0x06,0x7F,0x2E,0x00,0x11}, + {0x0A,0x47,0x3F,0x00,0x00}, + {0x0C,0x5F,0x38,0x00,0x07}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x5F,0x3F,0x00,0x00}, + {0x06,0x7E,0x36,0x00,0x09}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ADLSHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x63,0x31,0x00,0x0E}, + {0x06,0x7F,0x2C,0x00,0x13}, + {0x0A,0x47,0x3F,0x00,0x00}, + {0x0C,0x63,0x37,0x00,0x08}, + {0x06,0x73,0x32,0x00,0x0D}, + {0x0C,0x58,0x3F,0x00,0x00}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ADLSLow[] { + {0x09,0x73,0x3D,0x00,0x02}, + {0x09,0x7A,0x3C,0x00,0x03}, + {0x09,0x7F,0x3B,0x00,0x04}, + {0x04,0x6C,0x33,0x00,0x0C}, + {0x02,0x73,0x3A,0x00,0x05}, + {0x02,0x7C,0x38,0x00,0x07}, + {0x04,0x5A,0x36,0x00,0x09}, + {0x04,0x57,0x3D,0x00,0x02}, + {0x04,0x65,0x38,0x00,0x07}, + {0x04,0x6C,0x3A,0x00,0x05}, + }; + static const Coefficients ADLPHBR[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x71,0x31,0x00,0x0E}, + {0x06,0x7F,0x2C,0x00,0x13}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x2F,0x00,0x10}, + {0x0C,0x7C,0x3C,0x00,0x03}, + {0x06,0x7F,0x35,0x00,0x0A}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ADLPHigh[] { + {0x0A,0x35,0x3F,0x00,0x00}, + {0x0A,0x4F,0x37,0x00,0x08}, + {0x0C,0x71,0x30,0x00,0x0F}, + {0x06,0x7F,0x2B,0x00,0x14}, + {0x0A,0x4C,0x3F,0x00,0x00}, + {0x0C,0x73,0x34,0x00,0x0B}, + {0x06,0x7F,0x30,0x00,0x0F}, + {0x0C,0x63,0x3F,0x00,0x00}, + {0x06,0x7F,0x38,0x00,0x07}, + {0x06,0x7F,0x3F,0x00,0x00}, + }; + static const Coefficients ADLPLow[] { + {0x04,0x50,0x38,0x00,0x07}, + {0x04,0x58,0x35,0x00,0x0A}, + {0x04,0x60,0x34,0x00,0x0B}, + {0x04,0x6A,0x32,0x00,0x0D}, + {0x04,0x5E,0x38,0x00,0x07}, + {0x04,0x61,0x36,0x00,0x09}, + {0x04,0x6B,0x34,0x00,0x0B}, + {0x04,0x69,0x39,0x00,0x06}, + {0x04,0x73,0x37,0x00,0x08}, + {0x04,0x7A,0x38,0x00,0x07}, + }; + const Coefficients *entries=nullptr;uint32_t count=0; + switch(table) { + case Table::TGLHBR:entries=TGLHBR;count=10;break; + case Table::TGLHigh:entries=TGLHigh;count=10;break; + case Table::TGLUYHigh:entries=TGLUYHigh;count=10;break; + case Table::ICLHigh:entries=ICLHigh;count=10;break; + case Table::LowSwing:entries=LowSwing;count=10;break; + case Table::HOBL:entries=HOBL;count=9;break; + case Table::RKLHBR:entries=RKLHBR;count=10;break; + case Table::RKLHigh:entries=RKLHigh;count=10;break; + case Table::ADLSHigh:entries=ADLSHigh;count=10;break; + case Table::ADLSLow:entries=ADLSLow;count=10;break; + case Table::ADLPHBR:entries=ADLPHBR;count=10;break; + case Table::ADLPHigh:entries=ADLPHigh;count=10;break; + case Table::ADLPLow:entries=ADLPLow;count=10;break; + default:return false; + } + if(index>=count)return false; + out=entries[index];return true; + } + bool select(Platform platform,uint32_t rate,bool panel,bool lowSwing,bool useHOBL,uint32_t voltage,uint32_t emphasis) { + *this={}; + if(voltage>3 || emphasis>3 || voltage+emphasis>3)return false; + const auto chosen=choose(platform,rate,panel,lowSwing,useHOBL); + if(chosen==Table::Invalid)return false; + static const uint8_t first[]={0,4,7,9}; + const auto requested=first[voltage]+emphasis; + // Linux intel_ddi_level clamps to the final table entry. HOBL has nine + // identical entries and no distinct voltage-3 row. + level=chosen==Table::HOBL && requested>=9?8:requested; + if(!row(chosen,level,coefficients)){*this={};return false;} + table=chosen;hobl=chosen==Table::HOBL;return true; + } +}; +} +#endif diff --git a/WhateverXe/kern_xelp_devices.hpp b/WhateverXe/kern_xelp_devices.hpp index b07c7572..4abc239c 100644 --- a/WhateverXe/kern_xelp_devices.hpp +++ b/WhateverXe/kern_xelp_devices.hpp @@ -8,7 +8,7 @@ namespace XELP { enum class Family : uint32_t { Unknown, TigerLake, AlderLakeN, RocketLake, AlderLakeS, AlderLakeP, RaptorLakeS, RaptorLakeP }; enum class Display : uint32_t { Unknown, XeD = 12, XeLPD = 13 }; -enum class GT : uint32_t { Unknown, TGLGT2, TGLG4, ADLNSmall }; +enum class GT : uint32_t { Unknown, TGLGT2, TGLG4, ADLNSmall, ADLSSmall, TGLGT1 }; enum class Donor : uint32_t { Unknown, C004FC1A = 1 }; struct DeviceEntry { uint16_t device; Family family; Display display; GT backend; @@ -16,13 +16,14 @@ struct DeviceEntry { }; // GT::Unknown means catalogued, without an implemented automatic backend. constexpr DeviceEntry DeviceTable[] { - {0x4680, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 770"}, - {0x4682, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, - {0x4688, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, - {0x468B, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, - {0x4690, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 770"}, - {0x4692, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, - {0x4693, Family::AlderLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 710"}, + {0x4680, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, + {0x4682, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 730"}, + {0x4688, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0x468A, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0x468B, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0x4690, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, + {0x4692, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 730"}, + {0x4693, Family::AlderLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 710"}, {0x46A3, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel UHD Graphics"}, {0x46A6, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, {0x46A8, Family::AlderLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, @@ -38,12 +39,18 @@ constexpr DeviceEntry DeviceTable[] { {0x4C8B, Family::RocketLake, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, {0x9A40, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel Iris Xe Graphics"}, {0x9A49, Family::TigerLake, Display::XeD, GT::TGLGT2, "Intel Iris Xe Graphics"}, - {0x9A60, Family::TigerLake, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, + {0x9A60, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, + {0x9A68, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, + {0x9A70, Family::TigerLake, Display::XeD, GT::TGLGT1, "Intel UHD Graphics"}, {0x9A78, Family::TigerLake, Display::XeD, GT::TGLG4, "Intel UHD Graphics"}, - {0xA780, Family::RaptorLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 770"}, - {0xA782, Family::RaptorLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics 730"}, - {0xA788, Family::RaptorLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, - {0xA78B, Family::RaptorLakeS, Display::XeD, GT::Unknown, "Intel UHD Graphics"}, + {0xA780, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 770"}, + {0xA781, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0xA782, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics 730"}, + {0xA783, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0xA788, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0xA789, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0xA78A, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, + {0xA78B, Family::RaptorLakeS, Display::XeD, GT::ADLSSmall, "Intel UHD Graphics"}, {0xA7A0, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, {0xA7A1, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel Iris Xe Graphics"}, {0xA7A8, Family::RaptorLakeP, Display::XeLPD, GT::Unknown, "Intel UHD Graphics"}, @@ -58,5 +65,16 @@ inline const DeviceEntry *deviceEntry(uint32_t pci) { for(const auto &entry:DeviceTable)if(entry.device==(pci>>16))return &entry; return nullptr; } +inline bool desktopGTDevice(uint32_t pci) { + const auto e=deviceEntry(pci); + return e && (e->family==Family::AlderLakeS || e->family==Family::RaptorLakeS); +} + +inline bool display12SmallGT(GT gt) {return gt==GT::ADLSSmall || gt==GT::TGLGT1;} +inline bool display12SmallGTDevice(uint32_t pci) { + const auto e=deviceEntry(pci); + return e && display12SmallGT(e->backend); +} + } #endif diff --git a/WhateverXe/kern_xelp_display_contract.hpp b/WhateverXe/kern_xelp_display_contract.hpp new file mode 100644 index 00000000..9d21b320 --- /dev/null +++ b/WhateverXe/kern_xelp_display_contract.hpp @@ -0,0 +1,180 @@ +// Startup contracts describe installed operations, separately from physical +// GPU admission, firmware wiring and runtime resource ownership. +#ifndef KERN_XELP_DISPLAY_CONTRACT_HPP +#define KERN_XELP_DISPLAY_CONTRACT_HPP +#include +namespace XELP { +struct DisplayContract { + using Mask=uint64_t; + enum Operation:Mask { + Surface=1ULL<<0, + NVRAM=1ULL<<1, + IRQ=1ULL<<2, + Probe=1ULL<<3, + Framebuffer=1ULL<<4, + Topology=1ULL<<5, + PanelPower=1ULL<<6, + PanelTranscoder=1ULL<<7, + Dekel=1ULL<<8, + GMBus=1ULL<<9, + Lease=1ULL<<10, + PowerGet=1ULL<<11, + PowerPut=1ULL<<12, + PortLookup=1ULL<<13, + PathLookup=1ULL<<14, + PathAdd=1ULL<<15, + PathFree=1ULL<<16, + TrainingEnd=1ULL<<17, + Training=1ULL<<18, + TrainingBegin=1ULL<<19, + PathAllocate=1ULL<<20, + Framing=1ULL<<21, + ComboSignal=1ULL<<22, + ComboAUX=1ULL<<23, + AUXParent=1ULL<<24, + TCNative=1ULL<<25, + Mode=1ULL<<26, + HPDCaller=1ULL<<27, + AltFIA=1ULL<<28, + NativeFIA=1ULL<<29, + ModeLock=1ULL<<30, + ClockAdapter=1ULL<<31, + PowerAdapter=1ULL<<32, + PortInit=1ULL<<33, + EnableCombo=1ULL<<34, + OSInformation=1ULL<<35, + MMIORead=1ULL<<36, + MMIOWrite=1ULL<<37, + HPD=1ULL<<38, + PowerRefs13=1ULL<<39, + Engine13=1ULL<<40, + Lifecycle13=1ULL<<41, + DBUF13=1ULL<<42, + WM13=1ULL<<43, + ClockRead13=1ULL<<44, + ClockSet13=1ULL<<45, + Memory13=1ULL<<46, + GTT13=1ULL<<47, + ABI13=1ULL<<48, + Handoff13=1ULL<<49, + IRQ13=1ULL<<50, + WMLayout13=1ULL<<51, + Output13=1ULL<<52, + Topology13=1ULL<<53, + ClockOrder=1ULL<<54, + ComboAUXGate=1ULL<<55, + PanelDepth=1ULL<<56, + PanelTiming=1ULL<<57, + PanelPWM=1ULL<<58, + SST13=1ULL<<59, + AUX13=1ULL<<60, + DPProbe13=1ULL<<61, + DPBoot13=1ULL<<62, + }; + Mask required {},available {}; + Mask missing() const {return required&~available;} + bool ready() const {return !missing();} + static constexpr Mask Base12=Surface|NVRAM|IRQ|Probe|Framebuffer|Topology|PanelPower|PanelTranscoder|Dekel; + static constexpr Mask Training12=Lease|PowerGet|PowerPut|PortLookup|PathLookup|PathAdd|PathFree|TrainingEnd|Training|TrainingBegin|PathAllocate|ClockOrder; + static constexpr Mask Combo12=Framing|ComboSignal|ComboAUX|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 Power13=PowerRefs13|Engine13|Lifecycle13|DBUF13|WM13; + static constexpr Mask Clock13=ClockRead13|ClockSet13|Lifecycle13; + static constexpr Mask MemoryGroup13=Memory13|GTT13|ABI13|Handoff13; + static constexpr Mask Backend13=Power13|Clock13|MemoryGroup13|IRQ13|WMLayout13|Output13|Topology13; + struct Routes { + bool hdmi {},panel {},combo {},legacy {},alt {},leaseRequested {}; + }; + static DisplayContract display12(Mask installed,const Routes &r) { + // These donor startup hooks are controller-wide even without a panel or + // connected Dekel sink. Route-specific operations are added below. + Mask required=Base12; + if(r.hdmi)required|=GMBus; + if(r.leaseRequested||r.combo||r.legacy||r.alt)required|=Training12; + if(r.panel||r.combo)required|=Combo12; + if(r.panel)required|=PanelDepth|PanelTiming|PowerGet|PowerPut; + if(r.combo)required|=AUXParent; + if(r.legacy)required|=Fixed12; + if(r.legacy||r.alt)required|=Framing|ModeLock; + if(r.alt)required|=Alt12; + return {required,installed}; + } + static DisplayContract adls(Mask installed,bool dp,bool panel=false) { + return {PlatformADLS|(dp?Training12|Combo12|Fixed12:0)| + (panel?Combo12|PanelDepth|PanelTiming|PanelPWM|AUXParent|PowerGet|PowerPut:0),installed}; + } + static constexpr Mask DP13=SST13|AUX13|DPProbe13|DPBoot13; + static DisplayContract display13(Mask installed,bool dp=false) {return {Backend13|(dp?DP13:0),installed};} + static const char *name(Mask bit) { + switch(bit) { + case Surface:return "Surface"; + case NVRAM:return "NVRAM"; + case IRQ:return "IRQ"; + case Probe:return "Probe"; + case Framebuffer:return "Framebuffer"; + case Topology:return "Topology"; + case PanelPower:return "PanelPower"; + case PanelTranscoder:return "PanelTranscoder"; + case Dekel:return "Dekel"; + case GMBus:return "GMBus"; + case Lease:return "Lease"; + case PowerGet:return "PowerGet"; + case PowerPut:return "PowerPut"; + case PortLookup:return "PortLookup"; + case PathLookup:return "PathLookup"; + case PathAdd:return "PathAdd"; + case PathFree:return "PathFree"; + case TrainingEnd:return "TrainingEnd"; + case Training:return "Training"; + case TrainingBegin:return "TrainingBegin"; + case PathAllocate:return "PathAllocate"; + case Framing:return "Framing"; + case ComboSignal:return "ComboSignal"; + case ComboAUX:return "ComboAUX"; + case AUXParent:return "AUXParent"; + case TCNative:return "TCNative"; + case Mode:return "Mode"; + case HPDCaller:return "HPDCaller"; + case AltFIA:return "AltFIA"; + case NativeFIA:return "NativeFIA"; + case ModeLock:return "ModeLock"; + case ClockAdapter:return "ClockAdapter"; + case PowerAdapter:return "PowerAdapter"; + case PortInit:return "PortInit"; + case EnableCombo:return "EnableCombo"; + case OSInformation:return "OSInformation"; + case MMIORead:return "MMIORead"; + case MMIOWrite:return "MMIOWrite"; + case HPD:return "HPD"; + case PowerRefs13:return "PowerRefs13"; + case Engine13:return "Engine13"; + case Lifecycle13:return "Lifecycle13"; + case DBUF13:return "DBUF13"; + case WM13:return "WM13"; + case ClockRead13:return "ClockRead13"; + case ClockSet13:return "ClockSet13"; + case Memory13:return "Memory13"; + case GTT13:return "GTT13"; + case ABI13:return "ABI13"; + case Handoff13:return "Handoff13"; + case IRQ13:return "IRQ13"; + case WMLayout13:return "WMLayout13"; + case Output13:return "Output13"; + case Topology13:return "Topology13"; + case ClockOrder:return "ClockOrder"; + case ComboAUXGate:return "ComboAUXGate"; + case PanelDepth:return "PanelDepth"; + case PanelTiming:return "PanelTiming"; + case PanelPWM:return "PanelPWM"; + case SST13:return "SST13"; + case AUX13:return "AUX13"; + case DPProbe13:return "DPProbe13"; + case DPBoot13:return "DPBoot13"; + default:return "Unknown"; + } + } +}; +} +#endif diff --git a/WhateverXe/kern_xelp_display_resources.hpp b/WhateverXe/kern_xelp_display_resources.hpp new file mode 100644 index 00000000..ad5e7fd0 --- /dev/null +++ b/WhateverXe/kern_xelp_display_resources.hpp @@ -0,0 +1,49 @@ +// Hardware coordinates and donor capacity are separate from firmware wiring. +// A resource description does not grant GPU, protocol or live-owner admission. +#ifndef KERN_XELP_DISPLAY_RESOURCES_HPP +#define KERN_XELP_DISPLAY_RESOURCES_HPP +#include "kern_xelp_devices.hpp" +namespace XELP { +// Select an implemented Display adapter independently of GT admission. Linux +// v6.12 intel_display_device.c binds ADL-S and RPL-S to adl_s_desc; intel_bios.c +// consequently uses the same PHY-based VBT mapping for both. This selector +// grants no physical identity, stepping, fuse or donor qualification. +enum class DisplayAdapter : uint32_t { Unknown, TGL, ADLS, ADLN }; +constexpr DisplayAdapter displayAdapter(Family family) { + switch(family) { + case Family::TigerLake:return DisplayAdapter::TGL; + case Family::AlderLakeS: + case Family::RaptorLakeS:return DisplayAdapter::ADLS; + case Family::AlderLakeN:return DisplayAdapter::ADLN; + default:return DisplayAdapter::Unknown; + } +} +struct ADLSComboResources { + // Linux v6.12: ADL-S Combo PHY A..E map to DDI A, TC1..TC4. + static constexpr uint32_t physicalCount=5; + static constexpr uint32_t ddi(uint32_t phy) {return phy=3 && port<=6?port-2:physicalCount;} + static constexpr uint32_t physicalMask() { + uint32_t mask=0;for(uint32_t i=0;i +#include namespace XELPDonor { inline bool displayPoolRequest(uint32_t enabled,uint32_t bytes) { return enabled && bytes && !(bytes&4095) && bytes<=0x4000000; } struct Poll {uint32_t value {},elapsedUs {},reads {};}; +// hwSetPowerWellStatePG checks PG2/3/4 once, 1 us after the state ACK. +// Linux gen9_wait_for_power_well_fuses polls the actual fuse status instead. +// Match the complete donor read/test instructions, including the call target. +constexpr uint32_t fuseReadSites[] {0x39D,0x53F,0x6D3}; +inline bool powerWellFuseSites(const uint8_t *function,size_t size) { + static const uint8_t expected[][14] { + {0xBE,0x00,0x20,0x04,0x00,0xE8,0x5D,0x29,0x00,0x00,0x0F,0xBA,0xE0,0x19}, + {0xBE,0x00,0x20,0x04,0x00,0xE8,0xBB,0x27,0x00,0x00,0x0F,0xBA,0xE0,0x18}, + {0xBE,0x00,0x20,0x04,0x00,0xE8,0x27,0x26,0x00,0x00,0x0F,0xBA,0xE0,0x17} + }; + if(!function || size=function)for(unsigned i=0;i<3;++i) + if(caller-function==fuseReadSites[i]+10)return 1U<<(25-i); + return 0; +} +template Poll waitPowerWellFuse(IO &io,uint32_t initial,uint32_t mask) { + Poll r {initial,0,0}; + while(mask && r.value!=UINT32_MAX && !(r.value&mask) && r.elapsedUs<1000) { + io.delayUs(10);r.elapsedUs+=10;r.value=io.readStatus();++r.reads; + } + return r; // Never fabricate readiness or change a power request/refcount. +} template Poll waitEDIDReady(IO &io,uint32_t initial) { Poll r {initial,0,0}; while(!(r.value&0xC00) && r.elapsedUs<50000) { diff --git a/WhateverXe/kern_xelp_gt_resources.hpp b/WhateverXe/kern_xelp_gt_resources.hpp new file mode 100644 index 00000000..4d1dc7cc --- /dev/null +++ b/WhateverXe/kern_xelp_gt_resources.hpp @@ -0,0 +1,39 @@ +// Shared Gen12 small-GT resource contract, independent of product identity. +#ifndef KERN_XELP_GT_RESOURCES_HPP +#define KERN_XELP_GT_RESOURCES_HPP +#include +namespace XELP { +struct SmallGTResources { + uint32_t slices {}, dss {}, nativeSubslices {}, eus {}, l3Banks {}, urbKiB {}; +}; + +// Gen12 SSEU: one slice, up to two DSS in these small GT SKUs, one disable +// bit per EU pair. Native subslices are twice the DSS population. Preserve +// the physical LP backend; capacity is independent of the PCI product label. +// Four L3 banks/256KiB is the supported small-GT capacity, not eus*constant. +inline bool smallGTResources(uint32_t slice, uint32_t dss, uint32_t euDisable, + uint32_t l3Disable, SmallGTResources &out) { + out = {}; + if (slice != 1 || !dss || (dss&~0x3FU) || (euDisable&~0xFFU) || l3Disable != 0xF0) + return false; + const auto count=static_cast(__builtin_popcount(dss)); + const auto eus=2*count*(8-static_cast(__builtin_popcount(euDisable))); + if (count>2 || (eus!=16 && eus!=24 && eus!=32)) return false; + out = {1, count, 2*count, eus, 4, 256}; + return true; +} + +// TGL GT1 and ADL/RPL-S use the same Gen12 fuse fields. VCS2 is optional; +// both require VCS0 and VECS0. Product admission is the caller's policy. +inline bool decodeSmallGT(const uint32_t *fuses, SmallGTResources &out) { + out={}; + if(!fuses)return false; + for(unsigned i=0;i<5;++i)if(fuses[i]==UINT32_MAX)return false; + const auto video=(~fuses[4])&0xFF,enhance=((~fuses[4])>>16)&0xF; + if((video!=1 && video!=5) || enhance!=1)return false; + return smallGTResources(fuses[0]&0xFF,fuses[1]&0x3F, + fuses[2]&0xFF,fuses[3]&0xFF,out); +} + +} +#endif diff --git a/WhateverXe/kern_xelp_os.hpp b/WhateverXe/kern_xelp_os.hpp index 63f17a46..ad85a66e 100644 --- a/WhateverXe/kern_xelp_os.hpp +++ b/WhateverXe/kern_xelp_os.hpp @@ -4,6 +4,9 @@ #define KERN_XELP_OS_HPP namespace XELP { enum class OS { Unknown, Donor, BigSur, Monterey, Ventura, Sonoma, Sequoia, Tahoe }; +inline bool hostOS(OS os) { + return os>=OS::BigSur && os<=OS::Tahoe; +} inline OS kernelOS(const char *version) { static const char *const versions[] = { "Darwin Kernel Version 20.0.0: Sat Jun 13 17:58:16 PDT 2020; root:xnu-7090.0.0.111.5~1/RELEASE_X86_64", diff --git a/WhateverXe/kern_xelp_panel_power.hpp b/WhateverXe/kern_xelp_panel_power.hpp new file mode 100644 index 00000000..dcd3ca57 --- /dev/null +++ b/WhateverXe/kern_xelp_panel_power.hpp @@ -0,0 +1,62 @@ +// Panel sequencing facts and a bounded bridge to the exact donor's PPS fields. +// Units/layout checked against Linux v6.12 intel_bios.h/intel_pps.c. No Linux +// implementation is included. Native PPS sequencing remains the single owner. +#ifndef KERN_XELP_PANEL_POWER_HPP +#define KERN_XELP_PANEL_POWER_HPP +#include "kern_vbt.hpp" +namespace XELP { +struct PanelPower { + // The current donor has one panel and addresses PPS/PWM controller 0. + // This qualification does not claim PPS1, AUX backlight or panel admission + // outside the native Combo panel coordinates; each needs its own adapter. + static bool nativePPS0(const VBT::Ports &v,Family family) { + const auto &p=v.panel; + const auto adapter=displayAdapter(family); + const bool backend=(adapter==DisplayAdapter::TGL && p.ddi<2) || + (adapter==DisplayAdapter::ADLS && p.ddi==0); + if(!backend || !v.valid() || v.unmapped || !p.valid || + !p.backlightValid || !p.pwmPresent || p.backlightType!=2 || p.controller)return false; + const auto &port=v.port[p.ddi];const auto aux=p.ddi?0x10U:0x40U; + if(!(v.present&(1U<0x1fff)return p; + if(native[4]>31)return p; + const uint32_t bios[]={on>>16&0x1fff,on&0x1fff,off&0x1fff,off>>16&0x1fff,(control>>4&31)*1000}; + const uint32_t defaults[]={2100,500,500,5000,6100}; + const unsigned index[]={2,0,1,3,4}; + bool any=false;for(auto v:panel.delays)any|=v!=0; + for(unsigned i=0;i<5;++i) { + uint32_t v=panel.delays[i]; + // Linux adjusts VBT T12 once, before merging; never add to a BIOS readback. + if(i==4 && any)v+=1000; + if(v(i==4?31U:0x1fffU))return {}; + p.fields[index[i]]=v; + } + // Unlike i915, the donor owns the hardware backlight waits. Keep T8/T9; + // do not copy i915's value 1, which relies on separate software sleeps. + p.valid=true;return p; + } +}; +} +#endif diff --git a/WhateverXe/kern_xelp_panel_pwm.hpp b/WhateverXe/kern_xelp_panel_pwm.hpp new file mode 100644 index 00000000..742c36c1 --- /dev/null +++ b/WhateverXe/kern_xelp_panel_pwm.hpp @@ -0,0 +1,24 @@ +// CNP/ICP/ADP PWM facts (Linux v6.12 intel_backlight.c/intel_backlight_regs.h). +// Keep initial hardware period/polarity; VBT supplies missing initialization. +#ifndef KERN_XELP_PANEL_PWM_HPP +#define KERN_XELP_PANEL_PWM_HPP +#include +namespace XELP { +struct PanelPWM { + uint32_t period {},polarity {}; + bool valid() const {return period && period!=~0U;} + static PanelPWM select(uint32_t period,uint32_t control,uint32_t strap,uint32_t hz,bool activeLow) { + if(period==~0U || control==~0U)return {}; + if(period)return {period,control&(1U<<29)}; + if(strap==~0U || !hz || hz>65535)return {}; + const auto clock=(strap&256)?24000000U:19200000U; + return {(clock+hz/2)/hz,activeLow?1U<<29:0}; + } + bool duty(uint32_t brightness,uint32_t &value) const { + if(!valid() || brightness>65535)return false; + // The donor's 32-bit multiply overflows for ordinary low-frequency panels. + value=uint32_t(uint64_t(period)*brightness/65535);return true; + } +}; +} +#endif diff --git a/WhateverXe/kern_xelp_platform.hpp b/WhateverXe/kern_xelp_platform.hpp index 8200cac8..d2276056 100644 --- a/WhateverXe/kern_xelp_platform.hpp +++ b/WhateverXe/kern_xelp_platform.hpp @@ -5,6 +5,7 @@ #include #include #include "kern_xelp_devices.hpp" +#include "kern_xelp_display_resources.hpp" namespace XELP { struct Profile { uint32_t identity {}, revision {}; // Physical config-space identity, never spoofed. @@ -14,9 +15,12 @@ struct Profile { Donor donor {Donor::Unknown}; uint32_t effectiveIdentity {}; // Table selector; may come from device-id. bool overridden() const {return identity!=effectiveIdentity;} + // Select the implemented family backend for every PCI revision. Hardware + // validation is independent; a stepping split needs a concrete behavior delta. bool admitted() const {return (identity&0xFFFF)==0x8086 && revision<=255 && donor==Donor::C004FC1A && gt!=GT::Unknown && - (family!=Family::AlderLakeN || revision==0);} + ((!display12SmallGT(gt) && !display12SmallGTDevice(identity)) || + (display12SmallGT(gt) && display12SmallGTDevice(identity) && !overridden()));} }; inline Profile identify(uint32_t physical,uint32_t revision,uint32_t effective) { Profile p;p.identity=physical;p.revision=revision;p.effectiveIdentity=effective; @@ -63,13 +67,27 @@ struct NativePorts { // legacy permits probing for mode4; it does not force mode4 or classify a // USB-C-capable connector as physically fixed DP. The provider still checks FIA. // Type-C intent is represented but not admitted by either current backend. +constexpr uint32_t ProbeABI=3,ProbeCapabilityABI=1,ProbeDisplay13ComboDP=1U; +using QueryProbeCapabilities=uint32_t (*)(uint32_t,uint32_t); +// Optional export: old ABI3 providers remain valid for their existing routes, +// but their lack of a capability reply cannot authorize a new DP route. +inline bool display13ProbeCapable(QueryProbeCapabilities query) { + return query && (query(ProbeCapabilityABI,ProbeABI)&ProbeDisplay13ComboDP); +} struct ProbeConfig { - uint32_t abi {3},bytes {sizeof(ProbeConfig)}; + uint32_t abi {ProbeABI},bytes {sizeof(ProbeConfig)}; Profile profile; uint32_t combo {},legacy {},typeC {},excluded {},comboBackend {},legacyBackend {}; bool valid() const { - if(abi!=3 || bytes!=sizeof(ProbeConfig) || !profile.admitted() || + if(abi!=ProbeABI || bytes!=sizeof(ProbeConfig) || !profile.admitted() || !same(profile,identify(profile.identity,profile.revision,profile.effectiveIdentity)))return false; + // ADL-S firmware routes use the donor's three Combo PHY slots. + // Its TC label grants neither Type-C nor Dekel register access. + if(displayAdapter(profile.family)==DisplayAdapter::ADLS) { + const auto capacity=ADLSComboBackend::mask(profile.donor); + return profile.display==Display::XeD && combo && !(combo&~capacity) && comboBackend==capacity && + !(excluded&~PortMask) && !(excluded&combo) && !legacy && !typeC && !legacyBackend; + } if((combo&~7U) || ((legacy|typeC|excluded|legacyBackend)&~0x1F8U) || (comboBackend&~7U) || (legacy&typeC) || (legacy&excluded) || (typeC&excluded))return false; if(profile.display==Display::XeD)return comboBackend==7; diff --git a/WhateverXe/kern_xelp_probe.hpp b/WhateverXe/kern_xelp_probe.hpp index 3fb1a46e..ace6f8bf 100644 --- a/WhateverXe/kern_xelp_probe.hpp +++ b/WhateverXe/kern_xelp_probe.hpp @@ -29,11 +29,21 @@ template uint32_t legacyTGLMode(IO &io,uint32_t ddi,uint32_t selected) } template uint32_t probeMode(IO &io,const ProbeBinding &b,uintptr_t manager,uintptr_t record,const uint32_t *c) { if(!b.bound(manager,record,c))return 0; + if(displayAdapter(b.config.profile.family)==DisplayAdapter::ADLS) { + if(!(b.config.combo&b.config.comboBackend&(1U<=9 && c[1]<=12)) && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2; + const bool dp=c[1]==(c[0]>=3?c[0]+6:c[0]+1) && c[2]==1 && !c[3] && c[4]==0x400 && c[5]==0x201; + const bool panel=c[0]==0 && !c[1] && !c[2] && !c[3] && c[4]==2 && c[5]==0x18; + return hdmi || dp || panel ? 1 : 0; + } if(b.config.profile.display==Display::XeD) return legacyTGLMode(io,c[0],b.config.legacy&b.config.legacyBackend); - // Display13 fixed Combo HDMI remains the only qualified transport. + // Fixed Combo uses the same native mode for HDMI and SST DP. This is + // classification only: startup separately proves AUX/PHY/boot operations. if(!(b.config.combo&b.config.comboBackend&(1U<=1 && c[1]<=3 && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2 ? 1 : 0; + const bool hdmi=c[1]>=1 && c[1]<=3 && c[2]==2 && !c[3] && c[4]==0x800 && (c[5]&0xB)==2; + const bool dp=c[0]<2 && c[1]==c[0]+1 && c[2]==1 && !c[3] && c[4]==0x400 && c[5]==0x201; + return hdmi || dp ? 1 : 0; } } #endif diff --git a/WhateverXe/kern_xelp_sst_program.hpp b/WhateverXe/kern_xelp_sst_program.hpp new file mode 100644 index 00000000..8e71c5f2 --- /dev/null +++ b/WhateverXe/kern_xelp_sst_program.hpp @@ -0,0 +1,44 @@ +// Display12/13 SST transcoder programming. Platform adapters must retain +// the route owner and prove their own generation's power contract first. +// This layer knows neither power-well numbers nor firmware/connector policy. +#ifndef KERN_XELP_SST_PROGRAM_HPP +#define KERN_XELP_SST_PROGRAM_HPP +#include +namespace XELP { namespace SSTProgram { +struct Prepare { + uint32_t reason {},clockBefore {},clockWanted {},clockAfter {},funcBefore {},funcWanted {},funcAfter {},bufferBefore {}; +}; +template Prepare prepare(Read read,Write write,uint32_t ddi,uint32_t pipe,uint32_t lanes,bool powered) { + Prepare r; + if(ddi>=9 || pipe>=3){r.reason=8;return r;} + if(!powered){r.reason=5;return r;} + const auto clock=0x46140+4*pipe,func=0x60400+0x1000*pipe,buffer=0x64000+0x100*ddi; + const auto routeClock=(ddi+1)<<28,routePort=(ddi+1)<<27; + r.clockBefore=r.clockAfter=read(clock);r.funcBefore=r.funcAfter=read(func);r.bufferBefore=read(buffer); + if(lanes!=1 && lanes!=2 && lanes!=4)r.reason=1; + else if((r.funcBefore|r.bufferBefore)&0x80000000U)r.reason=2; + else if((r.clockBefore&0xF0000000U) && (r.clockBefore&0xF0000000U)!=routeClock)r.reason=3; + else if((r.funcBefore&0x78000000U) && (r.funcBefore&0x78000000U)!=routePort)r.reason=4; + r.clockWanted=(r.clockBefore&0x0FFFFFFFU)|routeClock; + r.funcWanted=(r.funcBefore&~0x7F00000EU)|routePort|0x02000000U|((lanes-1)<<1&0xEU); + if(!r.reason) { + write(clock,r.clockWanted);r.clockAfter=read(clock); + if(r.clockAfter!=r.clockWanted)r.reason=6; + else {write(func,r.funcWanted);r.funcAfter=read(func);if(r.funcAfter!=r.funcWanted)r.reason=7;} + } + return r; +} +struct Framing {uint32_t before {},wanted {},after {};bool active {},alternate {},ready {};}; +template Framing framing(Read read,Write write,uint32_t pipe,uint32_t enhanced, + bool panel,bool sinkReady,uint32_t alternate,bool powered) { + Framing r;if(pipe>=3 || enhanced>1 || !powered)return r; + const auto reg=0x60540+0x1000*pipe; + r.before=read(reg);r.wanted=(r.before&~0x40000U)|(enhanced<<18); + r.alternate=panel && sinkReady && alternate<=1; + if(r.alternate)r.wanted=(r.wanted&~0x40U)|(alternate<<6); + r.active=(r.before&0x88000000U)==0x80000000U; + if(r.active && r.before!=r.wanted)write(reg,r.wanted); + r.after=read(reg);r.ready=true;return r; +} +}} +#endif diff --git a/WhateverXe/kern_xelp_sst_route.hpp b/WhateverXe/kern_xelp_sst_route.hpp new file mode 100644 index 00000000..d47d0ad7 --- /dev/null +++ b/WhateverXe/kern_xelp_sst_route.hpp @@ -0,0 +1,81 @@ +// Display12 SST routing after firmware, physical platform and donor admission. +// A connector label (notably USB-C) is not a PHY or a power-domain selector. +// Domain relationships follow Linux v6.12 intel_display_power_map.c. +#ifndef KERN_XELP_SST_ROUTE_HPP +#define KERN_XELP_SST_ROUTE_HPP +#include + +namespace XELP { +struct Display12SSTRoute { + enum class Transport : uint8_t { None, Combo, DekelLegacy, DekelAlt }; + uint32_t ddi {}, ddiBuffer {}, mode {}, lanePG {}, auxParentPG {}; + Transport transport {Transport::None}; + bool panel {}; + bool valid() const {return transport!=Transport::None;} + bool needsLease() const {return valid() && !panel;} + bool retainComboAUX() const {return transport==Transport::Combo;} + uint32_t lanePowerMask() const {return valid()?(1U<<(2*lanePG))-1:0;} + uint32_t powerGroup(uint32_t pipe) const { + // Pipe A/B/C require PW1/PW3/PW4, independently of lane consumers. + if(!valid() || pipe>=3)return 0; + const uint32_t pipePG[]={1,3,4}; + return lanePG>pipePG[pipe]?lanePG:pipePG[pipe]; + } + uint32_t mainPowerMask(uint32_t pipe) const { + const auto pg=powerGroup(pipe); + return pg?(1U<<(2*pg))-1:0; + } +}; + +// These are qualified backend selections, not raw VBT masks. The platform +// adapter retains responsibility for identity, physical mapping and native ABI. +// Keeping the three transports separate prevents TC-numbered Combo ports from +// accidentally acquiring Dekel/FIA semantics. +struct Display12SSTRoutes { + uint32_t combo {}, legacy {}, alt {}, panel {}; + Display12SSTRoute external(uint32_t ddi,uint32_t mode) const { + Display12SSTRoute r; + if(ddi>=9)return r; + const auto bit=1U< +#include +#include +#include +#include +#include +#include "kern_alderlake_s.hpp" +#include "kern_tgl_tc_boot.hpp" +struct IOService; +struct XE { + bool adlsDisplaySelected=true;AlderLakeS::Wiring adlsWiring; + FIELDS + uintptr_t tglPortByDDI,tglEnablePG,tglDisablePG,orgTGLEnableAUXPower,orgTGLDisableAUXPower; + static void wrapTGLEnableAUXPower(void*,uint32_t); + static void wrapTGLDisableAUXPower(void*,uint32_t); +}; +XE state,*callbackXE=&state; +#define FunctionCast(f,p) reinterpret_cast(p) +templatevoid tglAUXRecord(T...){} +BODY +alignas(8) uint8_t power[0x90],other[0x90]; +uint32_t &ref(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} +std::vector calls; +void *port(unsigned d){assert(d<9);return reinterpret_cast(uintptr_t(d+1));} +void getPG(void *p,unsigned level){assert(level==1||level==3);calls.push_back(10+level);for(unsigned i=1;i<=level;++i)++ref(p,0x1c+4*i);} +void putPG(void *p,unsigned level){assert(level==1||level==3);calls.push_back(20+level);for(unsigned i=1;i<=level;++i){assert(ref(p,0x1c+4*i));--ref(p,0x1c+4*i);}} +void getAUX(void *p,unsigned d){calls.push_back(1);assert(ref(p,0x20));if(d>=3)assert(ref(p,0x28));++ref(p,0x54+4*d);} +void putAUX(void *p,unsigned d){calls.push_back(2);if(ref(p,0x54+4*d))--ref(p,0x54+4*d);} +void reset(unsigned mask){ + state={};state.adlsWiring.valid=true;state.adlsWiring.dp=mask;state.tglAUXParentReady=true; + state.tglPortByDDI=(uintptr_t)port;state.tglEnablePG=(uintptr_t)getPG;state.tglDisablePG=(uintptr_t)putPG; + state.orgTGLEnableAUXPower=(uintptr_t)getAUX;state.orgTGLDisableAUXPower=(uintptr_t)putAUX; + memset(power,0,sizeof(power));memset(other,0,sizeof(other));calls.clear(); +} +void check(){ + const auto tc=state.tglAUXParentRefs[3]+state.tglAUXParentRefs[4]; + assert(ref(power,0x20)==tc+state.tglAUXParentRefs[0]); + assert(ref(power,0x24)==tc&&ref(power,0x28)==tc); +} +int main(){ + unsigned cases=0; + for(unsigned panel=0;panel<2;++panel)for(unsigned mask:{1U,8U,16U,9U,17U,24U,25U}) { + std::vector order;for(unsigned d=0;d<5;++d)if(mask&(1U<{d?13U:11U,1}));check();} + } + for(auto d:release){ + // Cross-object release cannot spend the real owner's PG reference. + ref(other,0x54+4*d)=1;calls.clear();XE::wrapTGLDisableAUXPower(other,d); + assert(calls==std::vector{2});check(); + for(unsigned n=0;n<3;++n){calls.clear();XE::wrapTGLDisableAUXPower(power,d); + assert(calls==(n?std::vector{2,d?23U:21U}:std::vector{2}));check();} + } + assert(!ref(power,0x20));++cases; + }while(std::next_permutation(release.begin(),release.end()));}while(std::next_permutation(order.begin(),order.end())); + } + reset(0);assert(!state.tglAUXParentEligible(0)); + reset(1);state.adlsWiring.valid=false;assert(!state.tglAUXParentEligible(0)); + reset(1);state.adlsDisplaySelected=false;assert(!state.tglAUXParentEligible(0)); + reset(1);state.tglAUXParentReady=false;assert(!state.tglAUXParentEligible(0)); + reset(1);assert(!state.tglAUXParentEligible(9)&&!state.tglAUXParentEligible(~0U)); + printf("PASS %u mixed AUX A/TC parent orderings; nested/inherited refs; foreign owner; admission guards\n",cases); +} +'''.replace('FIELDS',fields).replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='adls-aux-parent-') as d: + d=Path(d);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test')],check=True) diff --git a/tests/adls_clock_guard.py b/tests/adls_clock_guard.py new file mode 100644 index 00000000..6a9dbd9e --- /dev/null +++ b/tests/adls_clock_guard.py @@ -0,0 +1,66 @@ +#!/usr/bin/env python3 +"""Exercise the runtime clock admission boundary against impostor objects.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +function=re.search(r'bool XE::adlsClockTarget\(.*?\n\}',s,re.S).group() +code='''#include "kern_alderlake_s.hpp" +#include +#include +#include +struct XE { + bool adlsClockReady=true;void *tglObservationController;uint8_t tglPlatformCopy[0x1c0] {}; + AlderLakeS::Wiring adlsWiring;static bool adlsClockTarget(void*,void*,void*); +}; +XE state;XE *callbackXE=&state; +FUNCTION +templateT &at(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} +int main(){ + alignas(8) uint8_t ctl[0xF00] {},port[0x700] {},access[0x70] {}; + state.tglObservationController=ctl;state.adlsWiring.valid=true;state.adlsWiring.hdmi=8;state.adlsWiring.pin[3]=9; + uint32_t config[]={3,9,2,0,0x800,0x202};auto c=reinterpret_cast(state.tglPlatformCopy+0x68); + memcpy(c,config,sizeof(config));at(port,0x548)=c;at(port,0x578)=access; + at(ctl,0xC40)=access;at(port,0x5B8)=0x6C10C;at(access,0x60)=0x1000000; + assert(XE::adlsClockTarget(ctl,port,nullptr));assert(XE::adlsClockTarget(ctl,port,port)); + assert(!XE::adlsClockTarget(nullptr,port,nullptr));assert(!XE::adlsClockTarget(ctl,nullptr,nullptr)); + assert(!XE::adlsClockTarget(ctl,port,access));assert(!XE::adlsClockTarget(access,port,nullptr)); + at(port,0x548)=config;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x548)=c; + at(port,0x578)=nullptr;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x578)=access; + at(port,0x5B8)=0;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x5B8)=0x6C10C; + at(access,0x60)=0x100000;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(access,0x60)=0x1000000; + for(unsigned i=0;i<6;++i){c[i]^=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));c[i]^=1;} + state.adlsWiring.hdmi|=1;assert(XE::adlsClockTarget(ctl,port,nullptr));state.adlsWiring.hdmi=8; + state.adlsClockReady=false;assert(!XE::adlsClockTarget(ctl,port,nullptr)); + state.adlsClockReady=true;state.adlsWiring.valid=true;state.adlsWiring.dp=16;state.adlsWiring.pin[4]=10; + uint32_t dp[]={4,10,1,0,0x400,0x201};c=reinterpret_cast(state.tglPlatformCopy+0x80); + memcpy(c,dp,sizeof(dp));at(port,0x548)=c;at(port,0x5B8)=0x16010C; + assert(XE::adlsClockTarget(ctl,port,nullptr));assert(XE::adlsClockTarget(ctl,port,port)); + state.adlsWiring.dp=0;assert(!XE::adlsClockTarget(ctl,port,nullptr));state.adlsWiring.valid=true;state.adlsWiring.dp=16;state.adlsWiring.pin[4]=10; + at(port,0x548)=dp;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x548)=c; + at(port,0x5B8)=0x6C10C;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(port,0x5B8)=0x16010C; + at(access,0x60)=0x164298;assert(!XE::adlsClockTarget(ctl,port,nullptr));at(access,0x60)=0x1000000; + for(unsigned i=0;i<6;++i){c[i]^=1;assert(!XE::adlsClockTarget(ctl,port,nullptr));c[i]^=1;} + assert(!XE::adlsClockTarget(ctl,port,access));assert(!XE::adlsClockTarget(access,port,nullptr)); + for(unsigned ddi:{0u,3u,4u,5u,6u})for(bool isDP:{false,true}) { + state.adlsWiring={};state.adlsWiring.valid=true; + (isDP?state.adlsWiring.dp:state.adlsWiring.hdmi)=1U<(state.tglPlatformCopy+0x20+24*ddi);memcpy(bound,config,sizeof(config)); + at(port,0x548)=bound;at(port,0x5B8)=route.phyBase()+0x10C; + assert(XE::adlsClockTarget(ctl,port,nullptr)==(ddi<=4)); + state.adlsWiring.valid=false;assert(!XE::adlsClockTarget(ctl,port,nullptr)); + } + state={};state.adlsClockReady=true;state.tglObservationController=ctl;state.adlsWiring.valid=true;state.adlsWiring.panel=1; + const uint32_t panel[]={0,0,0,0,2,0x18};auto panelConfig=reinterpret_cast(state.tglPlatformCopy+0x20);memcpy(panelConfig,panel,sizeof(panel)); + at(port,0x548)=panelConfig;at(port,0x578)=access;at(ctl,0xc40)=access; + at(port,0x5b8)=0x16210c;at(access,0x60)=0x200000; + assert(XE::adlsClockTarget(ctl,port,nullptr)); + state.adlsWiring.panel=0;assert(!XE::adlsClockTarget(ctl,port,nullptr)); + puts("PASS ADL-S runtime clock guard: exact owner, immutable TC1/TC2 configurations, mapped Combo B/C and no shared port"); +}'''.replace('FUNCTION',function) +with tempfile.TemporaryDirectory(prefix='adls-clock-guard-') as d: + d=Path(d);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test')],check=True) diff --git a/tests/adls_clock_native.py b/tests/adls_clock_native.py new file mode 100644 index 00000000..41fe45f4 --- /dev/null +++ b/tests/adls_clock_native.py @@ -0,0 +1,23 @@ +#!/usr/bin/env python3 +"""Replay qualified ADL-S Combo clock translations in the real donor code.""" +from pathlib import Path +import hashlib,re,struct,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1] +b=bytearray(Path(sys.argv[1]).read_bytes()) +assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +s=(r/'WhateverXe/kern_xe.cpp').read_text() +for name,off in [('On',0x6cfd3),('Select',0x6d222),('SelectC',0x6d219),('SelectCPLL',0x6d4a2),('Off',0x6dca8),('Bus',0x53b66)]: + def array(suffix): + v=re.search(r'const uint8_t adlsClock'+name+suffix+r'\[\]=\{([^}]+)\}',s).group(1) + return bytes(int(n,0) for n in v.split(',')) + before,after=array('Find'),array('Replace') + assert len(before)==len(after) and b[off:off+len(before)]==before + b[off:off+len(after)]=after +before=[0x164000,0x164080,0x164200,0x164004,0x164084,0x164204] +assert list(struct.unpack_from('<6Q',b,0x9a0e8))==before +struct.pack_into('<6Q',b,0x9a0e8,0x164284,0x16428c,0x164294,0x164288,0x164290,0x164298) +with tempfile.TemporaryDirectory(prefix='adls-clock-') as d: + d=Path(d);(d/'donor').write_bytes(b) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined', + '-fno-omit-frame-pointer','-I'+str(r/'WhateverXe'),str(r/'tests/adls_clock_native_fixture.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test'),str(d/'donor')],check=True) diff --git a/tests/adls_clock_native_fixture.cpp b/tests/adls_clock_native_fixture.cpp new file mode 100644 index 00000000..d3c55547 --- /dev/null +++ b/tests/adls_clock_native_fixture.cpp @@ -0,0 +1,111 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "kern_alderlake_s.hpp" +#include "kern_tgl_port_map.hpp" +templateT &at(void *p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} +void jump(uint8_t *p,uintptr_t t){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&t,8);p[10]=0xff;p[11]=0xe0;} +struct Event {uint32_t kind,reg,value;}; +std::vector events;std::map mmio; +alignas(8) uint8_t ctl[0xF00] {},port[0x700] {},access[0x70] {}; +uintptr_t vtable[64] {};uint32_t config[]={3,9,2,0,0x800,0x202}; +uint8_t *image;unsigned fault,delays,errors; +uint32_t readReg(void *p,unsigned long reg){assert(p==access);assert(mmio.count(reg));return mmio.at(reg);} +void writeReg(void *p,unsigned long reg,uint32_t v){ + assert(p==access && mmio.count(reg)); + if(reinterpret_cast(__builtin_return_address(0))==reinterpret_cast(image+0x6d973)) + TGLComboPLLFraction::adjust(at(ctl,0xEA8),reg==0x164294?0x164284:reg,v); + events.push_back({1,uint32_t(reg),v}); + if(reg==0x46010 || reg==0x46014 || reg==0x46018) { + // Model hardware ACK/LOCK independently from request bits. Failure cases + // exercise the donor's bounded waits, not successful hardware operation. + v&=~0x44000000U; + if((v&0x08000000U) && fault!=1)v|=0x04000000U; + if((v&0x80000000U) && fault!=2)v|=0x40000000U; + } + mmio[reg]=v; +} +void mailbox(void *p,uint32_t command,uint32_t value,bool wait){assert(p==ctl && command==7 && !wait);events.push_back({2,command,value});} +uint32_t voltage(void *p){assert(p==ctl);return 1;} +void getDDI(void *p){assert(p==port && (at(p,0x548)[0]==0 || at(p,0x548)[0]==3 || at(p,0x548)[0]==4));events.push_back({3,3,1});} +void putDDI(void *p){assert(p==port && (at(p,0x548)[0]==0 || at(p,0x548)[0]==3 || at(p,0x548)[0]==4));events.push_back({3,3,0});} +void delay(unsigned n){assert(n==1);++delays;} +void error(const char *,...){++errors;} +void redirectCall(unsigned site,uintptr_t target,unsigned stub){ + assert(image[site]==0xe8);jump(image+stub,target);int32_t d=stub-site-5;memcpy(image+site+1,&d,4); +} +int main(int argc,char **argv){ + assert(argc==2);std::ifstream f(argv[1],std::ios::binary);std::vector bytes((std::istreambuf_iterator(f)),{}); + image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0));assert(image!=MAP_FAILED); + memcpy(image,bytes.data(),bytes.size()); + // Rebase only the four native profiling-pointer tables used on these paths. + for(auto table: {std::pair{0xcd330,3},{0xcd348,6},{0xcd378,3},{0xcd3c0,3}}) + for(unsigned i=0;i(image,table.first+8*i);assert(v>=0xd0000 && v<0x160000);v+=reinterpret_cast(image);} + jump(image+0x5e344,(uintptr_t)readReg);jump(image+0x4691e,(uintptr_t)mailbox); + jump(image+0x6bf6e,(uintptr_t)voltage);jump(image+0x1fefe,(uintptr_t)getDDI);jump(image+0x1ff54,(uintptr_t)putDDI); + for(unsigned site:{0x6d081u,0x6d9ecu,0x6dd6du,0x6de16u})redirectCall(site,(uintptr_t)delay,0x300000); + for(unsigned site:{0x6d54cu,0x6dda6u})redirectCall(site,(uintptr_t)error,0x300020); + vtable[0x130/8]=(uintptr_t)writeReg;at(access,0)=vtable;at(ctl,0xc40)=access; + at(port,0x548)=config; + auto enable=(void(*)(void*,void*,void*,uint32_t*))(image+0x6ce10); + auto disable=(void(*)(void*,void*,void*))(image+0x6dae6); + unsigned cases=0; + for(unsigned ddi:{0u,3u,4u})for(unsigned ref=0;ref<=2;++ref)for(unsigned p:{2u,3u,5u,7u})for(unsigned k:{1u,2u,3u})for(unsigned q:{1u,3u}) { + config[0]=ddi;config[1]=config[2]==0?0:ddi?ddi+6:1; + AlderLakeS::Port route;AlderLakeS::PLL pll;assert(AlderLakeS::Port::resolve(ddi,route));assert(AlderLakeS::PLL::resolve(route.phy,pll)); + fault=0;events.clear();delays=errors=0;at(ctl,0xea8)=ref; + mmio={{pll.enable,0},{pll.cfg0,0},{pll.cfg1,0},{0x164280,0x01e07400}}; + uint32_t params[]={464,2049,p,q,k};AlderLakeS::HDMIClock expected; + assert(AlderLakeS::HDMIClock::encode(params,ref,expected)); + enable(ctl,port,nullptr,params); + assert(!errors&&!delays && mmio[pll.cfg0]==expected.cfg0 && mmio[pll.cfg1]==expected.cfg1); + assert(mmio[pll.enable]==0xcc000000 && mmio[0x164280]==((0x01e07400&~(route.clockOff|(3U<(port,0x114)=1; + for(unsigned kind:{0u,1u})for(unsigned ddi:{0u,3u,4u})for(unsigned ref=0;ref<=2;++ref)for(unsigned rate:{6u,10u,20u,30u}) { + if(kind==0 && ddi!=0)continue;config[2]=kind; + config[4]=kind?0x400:2;config[5]=kind?0x201:0x18; + config[0]=ddi;config[1]=config[2]==0?0:ddi?ddi+6:1; + AlderLakeS::Port route;AlderLakeS::PLL pll;assert(AlderLakeS::Port::resolve(ddi,route));assert(AlderLakeS::PLL::resolve(route.phy,pll)); + fault=0;events.clear();delays=errors=0;at(ctl,0xea8)=ref;at(port,0x128)=rate; + mmio={{pll.enable,0},{pll.cfg0,0},{pll.cfg1,0},{0x164280,0x01e07400}}; + enable(ctl,port,nullptr,nullptr); + assert(!errors&&!delays && config[0]==ddi && mmio[pll.enable]==0xcc000000); + assert(mmio[0x164280]==((0x01e07400&~(route.clockOff|(3U<(request,0x1c)==result); + } + at(request,0x38)=0x200;assert(bus(ctl,9,3,request)==0xe00002c2); + munmap(image,0x400000); + printf("PASS %u real donor Combo PLL enable/disable cases: physical A/TC1/TC2 preserved, PHY A/B/C selection, reference fraction, voltage/power order; 2 bounded timeout paths. MMIO and power hardware mocked.\n",cases); +} diff --git a/tests/adls_donor.py b/tests/adls_donor.py new file mode 100644 index 00000000..96f27c82 --- /dev/null +++ b/tests/adls_donor.py @@ -0,0 +1,83 @@ +"""Compare ADL-S adapters with the exact donor's port and Combo PHY tables.""" +from pathlib import Path +import hashlib,struct,subprocess,sys,tempfile + +repo=Path(__file__).resolve().parents[1] +donor=Path(sys.argv[1]).read_bytes() +assert hashlib.sha256(donor).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' +# PortHAL::init's Combo B branch consists of 25 immediate/table-store pairs. +# Decode only this bounded, qualified shape; no whole-driver execution. +fields={} +for at in range(0x5f2b3,0x5f45b,17): + assert donor[at:at+2]==b'\x48\xb8' and donor[at+10:at+13]==b'\x49\x89\x86' + lo,hi=struct.unpack_from(' +#include +int main() { + const uint32_t aphy[]={APHY};const uint32_t cphy[]={CPHY};const uint32_t phy[]={PHY};const uint32_t ports[]={PORTS}; + uint32_t registers[0x128/4] {};registers[4]=0x64300;registers[(0x64c-0x580)/4]=0x4610c; + assert(AlderLakeS::comboRegisters(3,registers,sizeof(registers)/4)); + assert(!memcmp(registers+(0x5ac-0x580)/4,phy,sizeof(phy))); + memset(registers,0,sizeof(registers));registers[4]=0x64400;registers[(0x64c-0x580)/4]=0x46110; + assert(AlderLakeS::comboRegisters(4,registers,sizeof(registers)/4)); + assert(!memcmp(registers+(0x5ac-0x580)/4,cphy,sizeof(cphy))); + memcpy(registers,aphy,sizeof(aphy)); + assert(AlderLakeS::comboRegisters(0,registers,sizeof(registers)/4)); + assert(registers[(0x640-0x580)/4]==0x162A24); // donor duplicated lane3; restore lane2 + alignas(8) uint8_t platform[0x1c0] {};platform[0x12]=9; + memcpy(platform+0x20,ports,sizeof(ports)); + XELP::VBT::Ports firmware;firmware.status=XELP::VBT::Status::Valid; + firmware.present=8;firmware.port[3]={0x60d2,1,2}; + const auto wiring=AlderLakeS::Wiring::fromFirmware(firmware); + assert(wiring.apply(platform,sizeof(platform))); + assert(wiring.matches(reinterpret_cast(platform+0x68))); + memcpy(platform+0x20,ports,sizeof(ports)); + firmware.present|=1;firmware.port[0]={0x78c6,10,0,0x40,0,0}; + firmware.panel.valid=true;firmware.panel.ddi=0;firmware.panel.backlightValid=true; + firmware.panel.pwmPresent=true;firmware.panel.backlightType=2; + const auto panel=AlderLakeS::Wiring::fromFirmware(firmware); + assert(panel.panel==1 && panel.apply(platform,sizeof(platform))); + assert(!memcmp(platform+0x20,ports,24) && panel.boundPanel(platform,reinterpret_cast(platform+0x20))); + // The same captured predecessor records feed Display13 CPU topology. + for(unsigned assignment=1;assignment<9;++assignment) { + Display13OutputWiring w;w.valid=true;unsigned code=assignment; + for(unsigned d=0;d<2;++d){unsigned kind=code%3;code/=3; + if(kind==1){w.hdmi|=1U< +#include +#include +#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; +struct Call{unsigned kind,target;bool enable;};std::vector calls; +void pg(void*,bool e,uint32_t t){calls.push_back({0,t,e});} +void ddi(void*,bool e,uint32_t t){calls.push_back({1,t,e});} +templatevoid tglTransitionRecord(T...){} +templatevoid tglObserveActivation(T...){} +templateT &at(void*p,unsigned o){return *reinterpret_cast(static_cast(p)+o);} +FUNCTIONS +int main(){ + alignas(8) uint8_t ctl[0xF00]{},power[0x80]{},access[0x70]{}; + state.tglObservationController=ctl;state.adlsWiring.valid=true;state.adlsWiring.hdmi=8; + state.orgADLSPowerPG=reinterpret_cast(pg);state.orgTGLPowerDDIState=reinterpret_cast(ddi); + at(power,0x10)=ctl;at(ctl,0xC8)=power;at(power,0x78)=access;at(ctl,0xC40)=access; + assert(XE::adlsPowerTarget(power)==power);assert(!XE::adlsPowerTarget(nullptr));unsigned count=0; + for(bool dp:{false,true})for(unsigned activeDP:{0U,1U,7U})for(unsigned active:{0U,1U,7U})for(unsigned mask=0;mask<256;++mask)for(bool on:{false,true}){ + state.adlsWiring.dp=dp?16:0;at(power,0x40)=activeDP;at(power,0x3C)=active;calls.clear();XE::wrapADLSPowerPG(power,on,mask); + assert(calls.size()==1&&calls[0].target==(mask|((active || (dp && activeDP))?0x2A:0))&&calls[0].enable==on); + assert(at(power,0x3C)==active);++count; + } + state.adlsWiring.valid=true;state.adlsWiring.dp=16;state.adlsWiring.pin[4]=10; + for(unsigned bits=0;bits<16;++bits)for(unsigned tc:{0U,0x80U,0x200U,0x280U}){ + unsigned expected=0;for(unsigned i=0;i<4;++i){at(power,0x20+4*i)=(bits>>i)&1;if((bits>>i)&1)expected|=2U<<(2*i);} + uint8_t before[sizeof(power)];memcpy(before,power,sizeof(power));calls.clear();XE::wrapTGLPowerDDIState(power,false,tc); + assert(calls.size()==2);assert(calls[tc?0:1].kind==0&&calls[tc?0:1].target==(expected|(tc?0x2A:0))); + assert(calls[tc?0:1].enable==bool(tc));assert(calls[tc?1:0].kind==1&&calls[tc?1:0].target==tc); + assert(!memcmp(before,power,sizeof(power)));++count; + } + // A DP-only route on TC1 must own the same parents as HDMI there. + // Unwired consumers and physical A must not acquire TC lane parents. + for(unsigned selected:{1u,8u,16u,9u,17u,24u,25u})for(unsigned live=0;live<32;++live) { + state.adlsWiring.hdmi=0;state.adlsWiring.dp=selected;unsigned expected=0; + for(unsigned d=0;d<5;++d){at(power,0x30+4*d)=(live>>d)&1; + if(d>=3 && (selected&live&(1U<(ctl,0xC8)=bad==4?nullptr:power;at(power,0x78)=bad==5?nullptr:access; + assert(!XE::adlsPowerTarget(power));calls.clear();XE::wrapADLSPowerPG(power,false,2);assert(calls.size()==1&&calls[0].target==2); + calls.clear();XE::wrapTGLPowerDDIState(power,false,0x80);assert(calls.size()==1&&calls[0].kind==1); + } + printf("PASS %u lane/pipe conditions; acquire/release order; unchanged refs; 6 owner/profile rejections\n",count); +} +'''.replace('FUNCTIONS',s[a:b]) +with tempfile.TemporaryDirectory(prefix='adls-lane-power-') as d: + d=Path(d);(d/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(d/'test.cpp'),'-o',str(d/'test')],check=True) + subprocess.run([str(d/'test')],check=True) diff --git a/tests/adls_panel_pwm.py b/tests/adls_panel_pwm.py new file mode 100644 index 00000000..64a9bba8 --- /dev/null +++ b/tests/adls_panel_pwm.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Run production PWM binding/lifecycle and the exact donor PWM write tail.""" +from pathlib import Path +import hashlib,os,re,subprocess,tempfile,sys +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +names=['adlsPanelPWMTarget','adlsCapturePanelPWM','wrapADLSLightUpEDP','wrapADLSPanelBacklight','wrapADLSPanelPWMEnable'] +functions='\n'.join(re.search(r'(?:void|bool|uint32_t) XE::'+n+r'\(.*?\n\}',s,re.S).group() for n in names) +install=s[s.index(' // ADLS_PANEL_PWM_INSTALL:'):s.index(' // ADLS_PANEL_PWM_INSTALL_END')] +arrays={n:re.search(n+r'\[\]=\{([^}]+)',install).group(1) for n in ['lightEntry','brightnessEntry','enable']} +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + for n,off in [('lightEntry',0x5063e),('brightnessEntry',0x4a56a),('enable',0x507e9)]: + raw=bytes(int(x,0) for x in arrays[n].split(','));assert b[off:off+len(raw)]==raw + # Native start chooses Camellia/Banksia from platform+190 only. + assert b[0x40216:0x4021d]==bytes.fromhex('83b89001000002') + assert b[0x402bb:0x402c2]==bytes.fromhex('83b89001000003') +code=r''' +#include "kern_alderlake_s.hpp" +#include "kern_xelp_panel_pwm.hpp" +#include "kern_tgl_tc_native.hpp" +#include +#include +#include +#include +#include +#include +#define FunctionCast(f,p) reinterpret_cast(p) +constexpr uint32_t kIOReturnNotReady=0xe00002d8; +struct IORegistryEntry {static IORegistryEntry* getRegistryRoot(){static IORegistryEntry o;return &o;}void setProperty(const char*,bool){}}; +struct KernelPatcher { + enum class Error{NoError,Failed};bool fail=false;unsigned routes=0,blocks=0;uintptr_t delta=0; + struct RouteRequest {template RouteRequest(const char*,T,uintptr_t&) {}}; + uintptr_t solveSymbol(unsigned,const char*s,uintptr_t a,size_t){return a+(strstr(s,"LightUp")?0x5063e:0x4a56a)+delta;} + bool routeMultiple(unsigned,RouteRequest*,unsigned,uintptr_t,size_t){++routes;return !fail;} + void routeBlock(uintptr_t a,const uint8_t*b,size_t n){++blocks;memcpy((void*)a,b,n);} + Error getError(){return Error::NoError;}void clearError(){} +}; +struct XE { + static XE*callbackXE; + bool adlsDisplaySelected=true,adlsPanelPWMReady=true,tglActive=true;XELP::PanelPWM adlsPanelPWM; + XELP::VBT::Ports firmwarePorts;AlderLakeS::Wiring adlsWiring; + struct {XELP::Family family=XELP::Family::AlderLakeS;}xeProfile; + TGLTCNativeFunctions tglTCNativeFunctions;void*tglObservationController=nullptr; + uint64_t tglPlatformCopy[0x1c0/8]{}; + uintptr_t orgTGLRegisterRead=0,orgTGLRegisterWrite=0,orgADLSLightUpEDP=0,orgADLSPanelBacklight=0; + static bool adlsPanelPWMTarget(void*,void*&,void*&);static bool adlsCapturePanelPWM(void*); + static uint32_t wrapADLSLightUpEDP(void*,void*,void*,const void*);static uint32_t wrapADLSPanelBacklight(void*,uint32_t); + static void wrapADLSPanelPWMEnable(void*); + static uint32_t wrapTGLRegisterRead(void*,unsigned long);static void wrapTGLRegisterWrite(void*,unsigned long,uint32_t); + void install(KernelPatcher&patcher,unsigned index,uintptr_t address,size_t size){INSTALL} +};XE*XE::callbackXE; +FUNCTIONS +alignas(8) uint8_t ctl[0x1600]{},port[0x600]{},access[0x80]{},power[0x90]{}; +templateT&field(void*p,unsigned n){return *reinterpret_cast(static_cast(p)+n);} +static void*owner=ctl;static uint32_t period=120000,control=1U<<29,duty=0,strap=256,wells=1; +static unsigned get=0,put=0,refs=0,reads=0,writes=0,nativeLight=0,nativeBacklight=0; +static uint32_t base(void*,uint32_t){return 0;}static void aux(void*){}static void*lookup(uint32_t d){assert(d==0);return port;} +static void getPG(void*p,uint32_t n){assert(p==power&&n==1);++get;++refs;}static void putPG(void*p,uint32_t n){assert(p==power&&n==1&&refs);++put;--refs;} +static uint32_t read(void*a,unsigned long reg){assert(a==access&&refs);++reads;switch(reg){case 0x45404:return wells;case 0x42000:return 0x1000000;case 0xc8254:return period;case 0xc8250:return control;case 0xc2014:return strap;default:assert(false);return ~0U;}} +void XE::wrapTGLRegisterWrite(void*a,unsigned long reg,uint32_t v){assert(a==access);++writes; + switch(reg){case 0xc8254:period=v;break;case 0xc8250:control=v;break;case 0xc8258:duty=v;break;default:assert(false);}} +static uint32_t light(void*c,void*,void*,const void*){assert(c==ctl);++nativeLight;return 0x1234;} +static uint32_t backlight(void*c,uint32_t){assert(c==ctl);++nativeBacklight;return 0x5678;} +static void setup(XE&s){ + XE::callbackXE=&s;memset(ctl,0,sizeof(ctl));memset(port,0,sizeof(port));memset(access,0,sizeof(access));memset(power,0,sizeof(power)); + owner=ctl;period=120000;control=1U<<29;duty=0;strap=256;wells=1;get=put=refs=reads=writes=nativeLight=nativeBacklight=0; + s.tglObservationController=ctl;s.tglTCNativeFunctions={base,aux,aux,getPG,putPG,lookup,&owner,true}; + s.orgTGLRegisterRead=(uintptr_t)read;s.orgTGLRegisterWrite=(uintptr_t)XE::wrapTGLRegisterWrite; + s.orgADLSLightUpEDP=(uintptr_t)light;s.orgADLSPanelBacklight=(uintptr_t)backlight; + auto &v=s.firmwarePorts;v.status=XELP::VBT::Status::Valid;v.present=1;v.port[0]={0x78c6,10,0,0x40,0,0}; + v.panel.valid=true;v.panel.ddi=0;v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2;v.panel.pwmHz=200;v.panel.activeLowPWM=true; + s.adlsWiring=AlderLakeS::Wiring::fromFirmware(v);assert(s.adlsWiring.panel==1); + const uint32_t record[]={0,0,0,0,2,0x18};memcpy((uint8_t*)s.tglPlatformCopy+0x20,record,sizeof(record)); + field(ctl,0xc40)=access;field(ctl,0xc8)=power;field(ctl,0xd40)=s.tglPlatformCopy; + field(power,0x10)=ctl;field(power,0x78)=access; + field(port,0x548)=(uint8_t*)s.tglPlatformCopy+0x20;field(port,0x578)=access;field(port,0x550)=1; + field(access,0x50)=access;field(access,0x60)=0x200000; +} +static void patchCall(uint8_t*m,unsigned site,unsigned target){assert(m[site]==0xe8);int32_t d=target-site-5;memcpy(m+site+1,&d,4);} +static void jump(uint8_t*p,uintptr_t target){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&target,8);p[10]=0xff;p[11]=0xe0;} +int main(int argc,char**argv){ + std::vector image(0x51000);const uint8_t l[]={LIGHT},b[]={BRIGHT},e[]={ENABLE}; + for(unsigned n=0;n<6;++n){ + memcpy(image.data()+0x5063e,l,sizeof(l));memcpy(image.data()+0x4a56a,b,sizeof(b));memcpy(image.data()+0x507e9,e,sizeof(e)); + XE s;s.adlsPanelPWMReady=false;KernelPatcher p;size_t size=image.size();if(n==0)size=0x508c3;if(n==1)p.delta=1;if(n==2)image[0x507e9]^=1;if(n==3)image[0x4a56a]^=1;if(n==4)p.fail=true; + s.install(p,0,(uintptr_t)image.data(),size);assert(s.adlsPanelPWMReady==(n==5));assert(p.blocks==(n==5)); + } + for(unsigned bad=0;bad<16;++bad){XE s;setup(s); + if(bad==0)s.adlsPanelPWMReady=false;if(bad==1)s.tglActive=false;if(bad==2)owner=nullptr;if(bad==3)s.firmwarePorts.panel.controller=1; + if(bad==4)field(ctl,0xd0)=port;if(bad==5)field(ctl,0xd40)=nullptr;if(bad==6)field(port,0x548)=nullptr; + if(bad==7)field(power,0x78)=nullptr;if(bad==8)field(access,0x60)=0xc8258; + if(bad==9)wells=0;if(bad==10)period=~0U;if(bad==11){period=0;s.firmwarePorts.panel.pwmHz=0;} + if(bad==14)field(port,0x550)=3;if(bad==15)s.xeProfile.family=XELP::Family::TigerLake; + if(bad==12)control=~0U;if(bad==13){period=0;strap=~0U;} + assert(XE::wrapADLSLightUpEDP(ctl,nullptr,nullptr,nullptr)==kIOReturnNotReady&&nativeLight==0&&writes==0&&get==put&&!refs); + } + XE s;setup(s);assert(XE::wrapADLSLightUpEDP(ctl,nullptr,nullptr,nullptr)==0x1234&&nativeLight==1&&field(ctl,0xe60)==120000); + assert(get==put&&get==1&&!refs);const auto capturedReads=reads; + for(unsigned v:{0U,1U,32768U,65535U}){assert(!XE::wrapADLSPanelBacklight(ctl,v));assert(duty==uint64_t(120000)*v/65535&&field(ctl,0xe58)==v&&get==put&&!refs);} + assert(!nativeBacklight);auto oldWrites=writes;assert(XE::wrapADLSPanelBacklight(ctl,65536)==kIOReturnNotReady&&writes==oldWrites); + XE::wrapADLSPanelPWMEnable(access);assert(control==0xa0000000); + control=0;period=0;assert(XE::adlsCapturePanelPWM(ctl)&&s.adlsPanelPWM.period==120000); // native disable cannot erase snapshot + s.adlsPanelPWM={};strap=0;assert(XE::adlsCapturePanelPWM(ctl)&&s.adlsPanelPWM.period==96000&&s.adlsPanelPWM.polarity==(1U<<29)); + s.adlsWiring.panel=0;assert(XE::wrapADLSPanelBacklight(ctl,7)==0x5678&&nativeBacklight==1); + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);std::vectorraw((std::istreambuf_iterator(f)),{});assert(raw.size()>0xdfb00); + const unsigned stubs=(raw.size()+4095)&~4095U;const size_t length=stubs+4096; + auto mem=(uint8_t*)mmap(nullptr,length,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0);assert(mem!=MAP_FAILED);memcpy(mem,raw.data(),raw.size()); + XE state;setup(state);state.adlsPanelPWMReady=false;KernelPatcher patcher;state.install(patcher,0,(uintptr_t)mem,length);assert(state.adlsPanelPWMReady); + jump(mem+stubs,(uintptr_t)XE::wrapADLSPanelBacklight);patchCall(mem,0x507da,stubs); + // Execute the native PWM tail with the exact controller register convention. + uint8_t entry[]={0x53,0x48,0x89,0xfb,0xe9,0,0,0,0};int32_t d=0x507b6-(stubs+32+sizeof(entry));memcpy(entry+5,&d,4);memcpy(mem+stubs+32,entry,sizeof(entry)); + mem[0x507f9]=0x5b;mem[0x507fa]=0xc3; + assert(mprotect(mem,length,PROT_READ|PROT_WRITE|PROT_EXEC)==0); + uintptr_t vtable[40]{};vtable[0x130/8]=(uintptr_t)XE::wrapTGLRegisterWrite;field(access,0)=vtable; + auto tail=(void(*)(void*))(mem+stubs+32); + state.orgADLSLightUpEDP=(uintptr_t)+[](void*c,void*,void*,const void*)->uint32_t{assert(c==ctl);return 0;}; + for(unsigned cycle=0;cycle<16;++cycle){ + assert(XE::wrapADLSLightUpEDP(ctl,nullptr,nullptr,nullptr)==0); + field(ctl,0xe58)=65535;tail(ctl); + assert(period==120000&&duty==120000&&control==0xa0000000&&get==put&&!refs); + XE::wrapTGLRegisterWrite(access,0xc8250,0);XE::wrapTGLRegisterWrite(access,0xc8258,0); + } + // Real unadapted duty math provides the overflow counterexample. + auto native=(uint32_t(*)(void*,uint32_t))(mem+0x4a56a);native(ctl,65535);assert(duty!=120000); + munmap(mem,length); + } + puts("PASS production ADL-S PWM qualification, faults, balanced power, BIOS/VBT fallback, native PWM tail and 32-bit overflow counterexample"); +} +'''.replace('INSTALL',install).replace('FUNCTIONS',functions).replace('LIGHT',arrays['lightEntry']).replace('BRIGHT',arrays['brightnessEntry']).replace('ENABLE',arrays['enable']) +with tempfile.TemporaryDirectory(prefix='adls-panel-pwm-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True,env=dict(os.environ,UBSAN_OPTIONS='halt_on_error=1')) diff --git a/tests/alderlake_n.cpp b/tests/alderlake_n.cpp index 77e2f462..763a7fa2 100644 --- a/tests/alderlake_n.cpp +++ b/tests/alderlake_n.cpp @@ -191,9 +191,10 @@ int main() { assert(stuck.status==AlderLakeN::GTStatus::ReleaseFailed && stuck.releaseFailedMask==1 && stuck.releasedMask==2); std::puts("AlderLakeN GT forcewake ownership, ACK failure, fuse profile and release fixtures passed"); assert(AlderLakeN::matches(0x46d18086,0)); - for(unsigned id=0x46d0;id<=0x46d4;++id) {assert(AlderLakeN::matches((id<<16)|0x8086,0));assert(!AlderLakeN::matches((id<<16)|0x8086,1));} + for(unsigned id=0x46d0;id<=0x46d4;++id)for(unsigned rev=0;rev<256;++rev) + assert(AlderLakeN::matches((id<<16)|0x8086,rev)); assert(!AlderLakeN::family(0x46cf8086) && !AlderLakeN::family(0x46d58086) && !AlderLakeN::family(0x46d11234)); - assert(!AlderLakeN::matches(0x46d18086,1)); + assert(AlderLakeN::matches(0x46d18086,1)); assert(!AlderLakeN::matches(0x9a498086,0)); AlderLakeN::Resources res; assert(AlderLakeN::resources(1,3,0x88,0xf0,res)); @@ -223,7 +224,12 @@ int main() { uint32_t owner=0; for(auto reg:{0x45400U,0x45408U,0x4540CU,0x45450U,0x45454U,0x4545CU}) assert(AlderLakeN::value(ra,reg,owner) && owner==a.regs[reg]); - for(auto offset:a.reads)assert(offset!=0x61400 && offset!=0x62400); + for(auto offset:a.reads)for(auto pipe:{1U,2U}) { + for(auto reg:{0x60400U,0x60404U,0x60540U,0x60030U,0x60034U,0x60040U,0x60044U}) + assert(offset!=reg+0x1000*pipe); + assert(offset!=0x46140+4*pipe && offset!=0x78000+0x200*pipe && offset!=0x78004+0x200*pipe); + } + for(auto offset:a.reads)assert(offset!=0x60038 && offset!=0x6003C && offset!=0x60048 && offset!=0x6004C); // Linux reference: pipe B -> Combo B, with the ADL-N PW_B at index 6. IO b;b.regs[0x45404]=0x1005;b.regs[0x61400]=0x90000000; auto rb=AlderLakeN::readDisplay(b,true); @@ -237,6 +243,10 @@ int main() { IO shortMap;shortMap.limit=0x40000; assert(AlderLakeN::readDisplay(shortMap,true).status==AlderLakeN::ReadoutStatus::Unavailable); assert(shortMap.reads.empty()); + for(auto reg:{0x60540U,0x60030U,0x60034U,0x60040U,0x60044U,0x46140U,0x78000U,0x78004U}) { + auto bad=a;bad.regs[reg]=UINT32_MAX; + assert(AlderLakeN::readDisplay(bad,true).status==AlderLakeN::ReadoutStatus::Unavailable); + } a.changePower=true;a.reads.clear(); assert(AlderLakeN::readDisplay(a,true).status==AlderLakeN::ReadoutStatus::PowerChanged); auto live=firmware();auto snapshot=AlderLakeN::readDisplay(live,true);AlderLakeN::LinearPlan plan; diff --git a/tests/alderlake_s.cpp b/tests/alderlake_s.cpp new file mode 100644 index 00000000..6297f0b2 --- /dev/null +++ b/tests/alderlake_s.cpp @@ -0,0 +1,177 @@ +#include "kern_alderlake_s.hpp" +#include +#include +#include +#include +using namespace AlderLakeS; +int main() { + uint32_t fuses[]={1,3,0,0xF0,0xE00FA};GT gt; + assert(GT::decode(0x46908086,0xC,fuses,gt) && gt.eus==32 && gt.nativeSubslices==4 && gt.l3Banks==4 && gt.urbKiB==256); + unsigned cases=0; + const unsigned ids[]={0x4680,0x4682,0x4688,0x468A,0x468B,0x4690,0x4692,0x4693, + 0xA780,0xA781,0xA782,0xA783,0xA788,0xA789,0xA78A,0xA78B}; + for(auto id:ids)for(unsigned rev=0;rev<256;++rev) { + assert(GT::decode((id<<16)|0x8086,rev,fuses,gt)); + assert(!GT::decode((id<<16)|0x1234,rev,fuses,gt));++cases; + } + for(unsigned dss=1;dss<64;++dss)for(unsigned eu=0;eu<256;++eu)for(unsigned video:{1U,5U}) { + fuses[1]=dss;fuses[2]=eu;fuses[4]=0xE0000|(0xFF^video); + const unsigned count=__builtin_popcount(dss),eus=2*count*(8-__builtin_popcount(eu)); + const bool supported=count<=2 && (eus==16 || eus==24 || eus==32); + assert(GT::decode(0xA7808086,4,fuses,gt)==supported); + if(supported)assert(gt.dss==count && gt.nativeSubslices==count*2 && gt.eus==eus && gt.l3Banks==4 && gt.urbKiB==256); + else assert(!gt.eus); + ++cases; + } + const uint32_t original[]={1,3,0,0xF0,0xE00FA};memcpy(fuses,original,sizeof(fuses)); + for(unsigned i=0;i<5;++i) { + fuses[i]=UINT32_MAX;assert(!GT::decode(0xA7808086,4,fuses,gt));fuses[i]=original[i]; + fuses[i]|=0x80000000;assert(GT::decode(0xA7808086,4,fuses,gt));fuses[i]=original[i]; + } + for(unsigned i:{0U,3U,4U}) { + fuses[i]^=1;assert(!GT::decode(0xA7808086,4,fuses,gt));fuses[i]=original[i]; + } + assert(!GT::decode(0x46D18086,0,fuses,gt));assert(!GT::decode(0x46908086,256,fuses,gt)); + assert(!GT::decode(0x46908086,0xC,nullptr,gt)); + printf("PASS %u desktop GT identity/step/resource cases\n",cases); + const uint32_t physical[]={0,3,4,5,6},dvo[]={10,1,8,9,11}; + const uint32_t expectedPin[]={1,9,10,11,12}; + const uint32_t expectedHPD[]={0x10000,0x1000000,0x2000000,0x4000000,0x8000000}; + for(unsigned n=0;n<5;++n) { + assert(XELP::VBT::ddi(XELP::Family::AlderLakeS,dvo[n])==physical[n]); + Port port;assert(Port::resolve(physical[n],port)); + assert(port.phy==n && port.buffer==0x64000+physical[n]*256); + assert(port.hpd==expectedHPD[n] && port.connected(expectedHPD[n])); + assert(!port.connected(UINT32_MAX) && !port.connected(~expectedHPD[n])); + assert(gmbus(n+1)==expectedPin[n]); + assert(port.clock==(n<3?0x164280U:0x1642BCU)); + } + for(auto d:{1U,2U,7U,8U,9U,31U,32U,UINT32_MAX}) { + Port port;assert(!Port::resolve(d,port) && !port.buffer && !port.hpd); + } + for(auto pin:{0U,6U,7U,8U,9U,UINT32_MAX})assert(!gmbus(pin)); + // All connector assignments over the supported physical coordinates, + // including DP-only, HDMI-only, mixed and multiple fixed DP routes. + for(unsigned assignment=1;assignment<27;++assignment) { + XELP::VBT::Ports fw;fw.status=XELP::VBT::Status::Valid; + unsigned code=assignment,hdmi=0,dp=0; + for(unsigned n=0;n<3;++n) { + const unsigned kind=code%3;code/=3;if(!kind)continue; + const unsigned d=physical[n];Port route;assert(Port::resolve(d,route)); + fw.present|=1U<=3?d+6:d+1,1,d>=3?1U:0U,0x400,0x201}; + memcpy(platform+0x20+24*d,c,sizeof(c)); + } + uint8_t saved[sizeof(platform)];memcpy(saved,platform,sizeof(saved)); + const unsigned last=fw.present&16?4:fw.present&8?3:0; + platform[0x20+24*last+16]^=1; + uint8_t bad[sizeof(platform)];memcpy(bad,platform,sizeof(bad)); + assert(!w.apply(platform,sizeof(platform)) && !memcmp(platform,bad,sizeof(bad))); + memcpy(platform,saved,sizeof(saved));assert(w.apply(platform,sizeof(platform))); + for(unsigned d=0;d<9;++d)if(fw.present&(1U<(platform+0x20+24*d); + assert(w.matches(c) && w.bound(platform,c)); + const bool isDP=dp&(1U<(platform+0x190)=mode; + uint8_t saved[sizeof(platform)];memcpy(saved,platform,sizeof(saved)); + assert(!panel.apply(platform,sizeof(platform)) && !memcmp(saved,platform,sizeof(saved))); + } + for(unsigned mode:{0U,2U,3U}) { + *reinterpret_cast(platform+0x190)=mode; + assert(panel.apply(platform,sizeof(platform)) && !*reinterpret_cast(platform+0x190)); + // Restore the native external predecessors for the next application. + for(unsigned d:{3U,4U}){const uint32_t rec[]={d,d+6,1,1,0x400,0x201};memcpy(platform+0x20+24*d,rec,sizeof(rec));} + } + assert(panel.apply(platform,sizeof(platform)));auto c=reinterpret_cast(platform+0x20); + assert(panel.boundPanel(platform,c) && panel.boundSST(platform,c) && !panel.boundDP(platform,c)); + assert(!memcmp(c,nativePanel,sizeof(nativePanel)) && panel.hotplugMode(true,c,platform,1,1,4)==4); + for(unsigned field=0;field<6;++field){c[field]^=1;assert(!panel.boundPanel(platform,c));c[field]^=1;} + for(unsigned bad=0;bad<8;++bad){auto invalid=v; + if(bad==0)invalid.panel.controller=1;if(bad==1)invalid.panel.backlightType=5; + if(bad==2)invalid.panel.delays[0]=8192;if(bad==3)invalid.panel.delays[4]=30001; + if(bad==4)invalid.panel.ddi=3;if(bad==5)invalid.port[0].aux=0x10; + if(bad==6)invalid.unmapped=1;if(bad==7)invalid.panel.backlightValid=false; + assert(!Wiring::fromFirmware(invalid).panel); + } + } + puts("PASS ADL-S physical DDI/Combo PHY/HPD/PLL/DDC coordinates and ambiguous HDMI rejection"); +} diff --git a/tests/boot_arguments.py b/tests/boot_arguments.py index 3ded6651..a07e0be7 100644 --- a/tests/boot_arguments.py +++ b/tests/boot_arguments.py @@ -35,6 +35,16 @@ init = xe[xe.index('void XE::init() {'):xe.index('void XE::deinit()')] assert 'BaseDeviceInfo' not in init and 'CPU_MODEL' not in init entry = re.search(r'extern "C" kern_return_t tglStart\(.*?\n\}', provider, re.S).group() +# Execute the production prerequisite expressions at all three entry points. +prerequisites = [] +for method,stop in [('wrapTGLFramebufferStart',' tglRecordStartOrder'), + ('wrapTGLAcceleratorStart',' if (callbackXE->adlnDetected'), + ('wrapTGLAcceleratorProbe',' if (!callbackXE->tglAcceleratorRequested')]: + at=xe.index('XE::'+method+'(') + body=xe[xe.index('{',at)+1:xe.index(stop,at)] + expression=re.search(r'if\((.*?)\)(?:return| \{)',body,re.S).group(1) + prerequisites.append('bool '+method+'Gate() { return !('+expression+'); }') + fixture = r''' #include #include @@ -53,8 +63,9 @@ static struct { void onKextLoadForce(int *) {} } lilu; struct XE { XELP::Profile xeProfile; - bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false; + bool adlnRequested=false, adlnDetected=false, adlnKernelReady=false, adlnNativeRequested=false, adlsDisplaySelected=false, display12SmallGTDetected=false; bool display13FramebufferRequested=false, display13TraceEnabled=false; + bool display12SmallGTReady=false, adlsDisplayHooksReady=false; bool tglRequested=false, tglAcceleratorRequested=false; bool tglInactivePorts=false, tglPG1Poll=false, tglClockOrderRequested=false; bool tglProgramEnabled=false, tglTrainingLeaseRequested=false; @@ -69,12 +80,23 @@ struct kmod_info_t {}; static constexpr int KERN_FAILURE=5, KERN_SUCCESS=0; // ACTUAL_SOURCE +// PREREQUISITES static XE boot(std::initializer_list names, uint32_t pci=0x46D28086,uint32_t effective=0) { args.clear(); for (const auto name:names) args.insert(name); XE xe; xe.init(); xe.selectProfile(XELP::identify(pci,0,effective?effective:pci)); assert(tglStart(nullptr,nullptr)==KERN_SUCCESS); return xe; } int main() { + // Rendering and Display prerequisites can fail independently. The Display + // callbacks themselves are replayed elsewhere without any Render flag. + for(unsigned bits=0;bits<16;++bits) { + XE selected;selected.display12SmallGTDetected=bits&1;selected.adlsDisplaySelected=bits&2; + selected.display12SmallGTReady=bits&4;selected.adlsDisplayHooksReady=bits&8;callbackXE=&selected; + const bool ready=(!(bits&1) || (bits&4)) && (!(bits&2) || (bits&8)); + assert(wrapTGLFramebufferStartGate()==ready); + assert(wrapTGLAcceleratorStartGate()==ready); + assert(wrapTGLAcceleratorProbeGate()==ready); + } // Every implemented ADL-N/Twin ID enables the qualified normal path without args. XE xe; for(uint32_t pci:{0x46D08086U,0x46D18086U,0x46D28086U,0x46D38086U,0x46D48086U}) { @@ -115,6 +137,25 @@ if(id<0x9000)assert(xe.adlnKernelReady && xe.adlnNativeRequested && xe.display13FramebufferRequested); assert(tglStart(nullptr,nullptr)==KERN_SUCCESS); } + // Exercise the complete production selection, including the shared Display + // adapter on registered desktop products and the still-unimplemented ADL-P. + for(auto kernel:allKernels)for(auto id:{0x9A49U,0x9A78U,0x4690U,0x4680U,0xA780U,0x4688U,0x468BU,0xA788U,0xA78BU,0x9A60U,0x9A68U,0x9A70U,0x46A6U,0x46D2U}) + for(auto rev:{0U,4U,12U,255U})for(unsigned option=0;option<4;++option) { + version=kernel;args={}; + if(option&1)args.insert("-wex-tgl-off"); + if(option&2)args.insert("-wex-noaccel"); + XE selected;selected.init();selected.selectProfile(XELP::identify((id<<16)|0x8086,rev)); + const bool desktop=id==0x4690 || id==0x4680 || id==0xA780 || id==0x4688 || id==0x468B || id==0xA788 || id==0xA78B; + const bool gt1=id==0x9A60 || id==0x9A68 || id==0x9A70; + const bool mobile=id==0x9A49 || id==0x9A78 || gt1; + const bool small13=id==0x46D2; + assert(selected.display12SmallGTDetected==(desktop || gt1) && selected.adlsDisplaySelected==desktop); + assert(selected.adlnDetected==small13 && selected.display13FramebufferRequested==small13); + const bool d12=(desktop || mobile) && !(option&1); + assert(selected.tglRequested==(small13 || d12)); + assert(selected.tglAcceleratorRequested==(small13 || (d12 && !(option&2)))); + assert((selected.currentFramebuffer==&kextIntelTGLFb)==(small13 || d12)); + } version="Darwin Kernel Version 21.6.0: Mon Jun 24 00:56:10 PDT 2024; root:xnu-8020.240.18.709.2~1/RELEASE_X86_64"; xe=boot({},0x9A498086);assert(xe.tglRequested && xe.tglAcceleratorRequested); xe=boot({"-wex-noaccel"},0x9A498086); @@ -131,7 +172,7 @@ xe=boot({},pci);assert(!xe.adlnNativeRequested && !xe.display13FramebufferRequested && !adlnNativeRCSProfileRequested); } xe=XE{};xe.selectProfile(XELP::identify(0x46D28086,1)); - assert(!xe.adlnNativeRequested && !xe.display13FramebufferRequested && !adlnNativeRCSProfileRequested); + assert(xe.adlnNativeRequested && xe.display13FramebufferRequested && adlnNativeRCSProfileRequested); auto malformed=XELP::identify(0x46D28086,0);malformed.display=XELP::Display::XeD; xe=XE{};xe.selectProfile(malformed);assert(!xe.adlnRequested && !adlnNativeRCSProfileRequested); args={};version="unqualified"; @@ -142,7 +183,7 @@ xe=XE{};xe.selectProfile(XELP::identify(0x9A498086,0));assert(!xe.tglRequested); } -'''.replace('// ACTUAL_SOURCE', init + entry) +'''.replace('// ACTUAL_SOURCE', init + entry).replace('// PREREQUISITES', '\n'.join(prerequisites)) with tempfile.TemporaryDirectory(prefix='wex-boot-args-') as directory: directory = Path(directory) cpp, exe = directory / 'test.cpp', directory / 'test' diff --git a/tests/device_selection.cpp b/tests/device_selection.cpp index 2b3b80f9..56fb2e1d 100644 --- a/tests/device_selection.cpp +++ b/tests/device_selection.cpp @@ -8,25 +8,28 @@ int main() { unsigned implemented=0; for(const auto &entry:DeviceTable) { const uint32_t pci=uint32_t(entry.device)<<16|0x8086; - auto p=identify(pci,0); + const auto revision=0U; + auto p=identify(pci,revision); assert(p.identity==pci && p.effectiveIdentity==pci && !p.overridden()); assert(p.family==entry.family && p.display==entry.display && p.gt==entry.backend); assert(!strcmp(model(p),entry.model)); assert(p.admitted()==(entry.backend!=GT::Unknown)); implemented+=p.admitted(); const uint32_t value=entry.device; - assert(same(p,select(pci,0,true,&value,4))); - // Every catalogued backend can be selected for an unknown physical ID. - auto f=select(0x12348086,0,true,&value,4); + assert(same(p,select(pci,revision,true,&value,4))); + // Display12 small-GT qualification requires physical identity; established + // TGL/ADL-N explicit override semantics remain unchanged. + auto f=select(0x12348086,revision,true,&value,4); assert(f.overridden() && f.identity==0x12348086 && f.effectiveIdentity==pci); - assert(f.admitted()==p.admitted()); + assert(f.admitted()==(p.admitted() && !display12SmallGT(entry.backend))); assert(!strcmp(model(f),entry.model)); auto nonIntel=select(0x123410DE,0,true,&value,4);assert(!nonIntel.admitted()); } - assert(implemented==8); // Recognition of other table rows adds no native backend. + assert(implemented==27); // Recognition of other table rows adds no native backend. uint32_t id=0x46D2; assert(select(0x12348086,0,true,&id,4).admitted()); - assert(!select(0x12348086,1,true,&id,4).admitted()); // Keep physical stepping guard. + assert(select(0x12348086,1,true,&id,4).admitted()); + assert(!identify(0x46908086,256).admitted()); // Malformed PCI revision width. assert(!select(0x12348086,0,false,nullptr,0).admitted()); for(size_t n:{size_t(0),size_t(1),size_t(2),size_t(3),size_t(5),size_t(8)}) assert(!select(0x9A498086,0,true,&id,n).admitted()); diff --git a/tests/display13_aux.py b/tests/display13_aux.py new file mode 100644 index 00000000..c64106d6 --- /dev/null +++ b/tests/display13_aux.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +"""Actual AUX wrappers + native bridge; optional exact donor caller replay.""" +from pathlib import Path +import hashlib,re,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +functions='\n'.join(re.search(re.escape(sig)+r'.*?\n\}',s,re.S).group() for sig in [ + 'uint8_t *XE::display13SSTPort(', 'bool XE::display13HDMIPort(', 'bool XE::display13MainLinkAUX(', + 'uint32_t XE::wrapDisplay13EnablePort(', 'uint32_t XE::wrapDisplay13DisablePort(', + 'uint32_t XE::wrapDisplay13SignalLevels(', 'uint32_t XE::wrapDisplay13TrainingPattern(', + 'bool XE::display13AUXReady(', 'uint32_t XE::wrapDisplay13ReadAUX(', 'uint32_t XE::wrapDisplay13WriteAUX(', + 'uint32_t XE::wrapDisplay13I2CAUX(', 'uint32_t XE::wrapDisplay13RunAUX(']) +binding=re.search(r'static TGLFBBinding::Readout adlnInspectPathBinding\(.*?\n\}',s,re.S).group() +objects=(r/'tests/tgl_fb_binding.cpp').read_text().split('int main()')[0].replace('port[0x580]','port[0x700]') +base=(r/'tests/display13_sst_fixture.cpp').read_text().split('int main(')[0] +base=base.replace('// NATIVE_OBJECTS',objects).replace('// BINDING',binding).replace('// FUNCTIONS',functions) +base=base.replace('struct OSObject {','void *nativePortObject=nullptr;\nstruct OSObject {').replace('return size;','return this==nativePortObject?0x700:size;') +base=base.replace('bool adlnWriterMappingBound()', 'void adlnFault(bool &fault,unsigned){fault=true;}\nbool adlnWriterMappingBound()') +header=(r/'WhateverXe/kern_xe.hpp').read_text() +extra=header[header.index(' bool display13FBAUXHooksReady'):header.index(' mach_vm_address_t orgDisplay13TrainingPattern')] +extra=extra.replace(' bool processDisplay13FramebufferAUX(KernelPatcher &,size_t,mach_vm_address_t,size_t);','').replace('mach_vm_address_t','uintptr_t') +base=base.replace('struct XE {','struct XE {\n'+extra) +code=base+(r/'tests/display13_aux_fixture.cpp').read_text() +assert 'c.bridge.suspend(io)' in s +engine=s[s.index('void XE::wrapAlderLakeNEngineDisable('):s.index('void XE::wrapAlderLakeNPowerOverride(')] +assert '!c.bridge.auxBusy()' in engine +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + install=s[s.index('bool XE::processDisplay13FramebufferAUX('):s.index('bool XE::display13AUXReady(')] + entry=bytes(int(x,0) for x in re.search(r'entry\[\]=\{([^}]+)',install).group(1).split(',')) + for off in [0x1cc72,0x1ceb0,0x1d2b6,0x61748]:assert b[off:off+len(entry)]==entry + # AUX bank coordinates in both native Combo init branches. + for start,ctl in [(0x5f0e2,0x64010),(0x5f2c2,0x64110)]: + assert (ctl.to_bytes(4,'little')) in b[start:start+130] + print('PASS exact donor AUX hooks, Combo bank coordinates and runtime suspend wiring') +with tempfile.TemporaryDirectory(prefix='display13-aux-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True) diff --git a/tests/display13_aux_fixture.cpp b/tests/display13_aux_fixture.cpp new file mode 100644 index 00000000..8852799c --- /dev/null +++ b/tests/display13_aux_fixture.cpp @@ -0,0 +1,129 @@ +// Uses display13_sst_fixture's real binding/bridge setup. Only the transport +// completion and kernel imports are mocked; optional native callers execute +// their original get/put, retry, buffer-copy and result paths. +bool failGet=false,failPut=false,failCommand=false;unsigned auxCalls=0,auxResult=0; +void getAux(void *power,unsigned ddi){ + assert(!locked&&power==display13FBPower.power);auto &c=display13FBPower;Display13FBPowerIO io{c.map}; + if(failGet)c.map->drop=0x45444; + auto r=c.bridge.getAUX(io,ddi);c.map->drop=0; + if(r.status!=Display13Power::Status::Complete)c.bridge.state.faulted=true; +} +void putAux(void *power,unsigned ddi){ + assert(!locked&&power==display13FBPower.power);auto &c=display13FBPower;Display13FBPowerIO io{c.map}; + if(failPut)c.map->drop=0x45444; + auto r=c.bridge.putAUX(io,ddi);c.map->drop=0; + if(r.status!=Display13Power::Status::Complete)c.bridge.state.faulted=true; +} +unsigned auxTransport(void *port,unsigned,uint32_t *packet,unsigned,bool){ + assert(!locked);++auxCalls;auto &c=display13FBPower;Display13FBPowerIO io{c.map}; + const auto ddi=Display13FBMemory::field(port,0x120); + assert(c.bridge.auxBusy()&&c.bridge.auxHeld(io,ddi)); + const auto writes=c.map->writes.size(); + assert(c.bridge.suspend(io).status==Display13Power::Status::NotReady&&c.map->writes.size()==writes); + assert(c.bridge.putAUX(io,ddi).status==Display13Power::Status::Unbalanced&&c.map->writes.size()==writes); + // Nested peer-native transactions preserve the active command's scope. + assert(c.bridge.getAUX(io,ddi).status==Display13Power::Status::Complete); + assert(c.bridge.putAUX(io,ddi).status==Display13Power::Status::Complete&&c.bridge.auxBusy()); + packet[0]=0;packet[1]=0x12345678; + if(failCommand)c.map->regs[0x45444]=0; + return auxResult; +} +unsigned syntheticFront(void *port,unsigned,void *data,unsigned){ + auto &c=display13FBPower;const auto ddi=Display13FBMemory::field(port,0x120);uint32_t packet[5]{}; + getAux(c.power,ddi);auto r=XE::wrapDisplay13RunAUX(port,0,packet,4,false);putAux(c.power,ddi); + if(!r)memcpy(data,packet+1,4);return r; +} +unsigned syntheticI2C(void *port,unsigned command,uint32_t *data,unsigned length,uint8_t){return syntheticFront(port,command,data,length);} +unsigned unsafeTransport(void*,unsigned,uint32_t *packet,unsigned,bool){++auxCalls;packet[0]=0;packet[1]=0x12345678;return 0;} +void waitAUX(unsigned){assert(!locked);} +void copyAUX(const void *src,void *dst,size_t bytes){memcpy(dst,src,bytes);} +void stackFail(){assert(false);} +void auxSetup(Fixture &f,Map &map,unsigned ddi,bool hdmi,unsigned mode){ + setup(f,map,ddi,0,2,20);auto &c=display13FBPower;state.display13FBAUXHooksReady=true; + if(hdmi){state.adlnOutputWiring.dp=0;state.adlnOutputWiring.hdmi=3;state.adlnOutputWiring.ddc[0]=1;state.adlnOutputWiring.ddc[1]=2; + assert(Display13FBTopology::apply(f.platform,sizeof(f.platform),f.donor,state.adlnOutputWiring));} + state.display13AUXControllerSlot=reinterpret_cast(&c.controller);put(f.ctl,0xC30,c.lock); + for(unsigned n=0;n<6;++n)put(f.port,0x594+4*n,0x64010U+0x100*ddi+4*n); + put(f.port,0x550,mode); + state.orgDisplay13ReadAUX=state.orgDisplay13WriteAUX=reinterpret_cast(syntheticFront); + state.orgDisplay13I2CAUX=reinterpret_cast(syntheticI2C);state.orgDisplay13RunAUX=reinterpret_cast(auxTransport); + failGet=failPut=failCommand=false;auxCalls=auxResult=0; + // Probe must work after all DDI/pipe owners are gone. + Display13FBPowerIO io{&map};assert(c.bridge.putDDI(io,ddi).status==Display13Power::Status::Complete); + assert(c.bridge.putPG(io,1).status==Display13Power::Status::Complete); + assert(!c.bridge.state.refs[0]); +} +int main(int argc,char **argv){ + uint8_t *image=nullptr;uint64_t canary=0x123456789ABCDEF0ULL;uintptr_t portVT[64]{}; + portVT[0x170/8]=reinterpret_cast(XE::wrapDisplay13RunAUX); + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);std::vector bytes((std::istreambuf_iterator(f)),{}); + image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0));assert(image!=MAP_FAILED); + memcpy(image,bytes.data(),bytes.size());put(image,0xC80A8,reinterpret_cast(&canary)); + jump(image+0x5C4BA,reinterpret_cast(getAux));jump(image+0x5AE0A,reinterpret_cast(putAux)); + for(unsigned at:{0x1CDF6U,0x1D0F7U,0x1D3B2U,0x1D46DU,0x1D52AU})redirect(image,at,0x300000,reinterpret_cast(waitAUX)); + for(unsigned at:{0x1CE6CU,0x1D0AFU})redirect(image,at,0x300020,reinterpret_cast(copyAUX)); + for(unsigned at:{0x1CEABU,0x1D19CU})redirect(image,at,0x300040,reinterpret_cast(stackFail)); + } + unsigned cases=0; + for(unsigned ddi=0;ddi<2;++ddi)for(bool hdmi:{false,true})for(unsigned mode:{0U,1U}) + for(unsigned op=0;op<3;++op)for(unsigned fault=0;fault<5;++fault){ + Fixture f(0,0,ddi);Map map;auxSetup(f,map,ddi,hdmi,mode);auto &c=display13FBPower; + if(image){nativePortObject=f.port;put(f.port,0,reinterpret_cast(portVT));put(image,0x138BA8,c.controller); + state.orgDisplay13ReadAUX=reinterpret_cast(image+0x1CC72); + state.orgDisplay13WriteAUX=reinterpret_cast(image+0x1CEB0); + state.orgDisplay13I2CAUX=reinterpret_cast(image+0x1D2B6);} + failGet=fault==1;failPut=fault==2;failCommand=fault==3;auxResult=fault==4?0xE00002BCU:0; + uint32_t data[5]{};auto result=op==0?XE::wrapDisplay13ReadAUX(f.port,0x100,data,4): + op==1?XE::wrapDisplay13WriteAUX(f.port,0x100,data,4):XE::wrapDisplay13I2CAUX(f.port,0,data,4,0); + assert(result==(fault==4?auxResult:fault?kIOReturnNotReady:0)); + assert(!c.bridge.auxBusy() && (fault==4 && image ? auxCalls==7 : auxCalls==(fault==1?0U:1U))); + if(!fault||fault==4){assert(!c.bridge.nativeAUXRefs(ddi)&&!c.bridge.state.refs[0]);} + if(fault==1)assert(c.bridge.state.faulted&&!c.bridge.nativeAUXRefs(ddi)); + if(fault==2||fault==3)assert(c.bridge.state.faulted&&c.bridge.nativeAUXRefs(ddi)==1); + nativePortObject=nullptr;++cases; + } + // All-ones power, retained main-link-only AUX, unbound objects and unknown + // banks must not reach the transport, even when the firmware left AUX on. + for(unsigned bad=0;bad<21;++bad){ + Fixture f(0,0,0);Map map;auxSetup(f,map,0,false,0);auto &c=display13FBPower;Display13FBPowerIO io{&map}; + assert(c.bridge.getAUX(io,0).status==Display13Power::Status::Complete); + if(bad==0)state.display13FramebufferRequested=false;if(bad==1)state.display13FBAUXHooksReady=false; + if(bad==2)state.xeProfile.display=XELP::Display::XeD;if(bad==3)state.adlnTopologyController=nullptr; + if(bad==4)state.display13AUXControllerSlot=0;if(bad==5)bound=false; + if(bad==6)c.bridge.state.faulted=true;if(bad==7)c.bridge.state.suspended=true; + if(bad==8)put(f.ctl,0xD0,static_cast(f.port));if(bad==9)put(f.port,0x550,2U); + if(bad==10)put(f.port,0x578,static_cast(f.port));if(bad==11)put(f.port,0x598,0x64114U); + if(bad==12)put(f.port,0x120,1U);if(bad==13)map.regs[0x45444]=UINT32_MAX; + if(bad==14)c.bridge.auxRefs[0]=0;if(bad==15)c.bridge.auxLease[0].owner=nullptr; + if(bad==16)map.regs[0x45404]=0;if(bad==17)c.bridge.auxCommands[0]=UINT32_MAX; + if(bad==18)put(f.ctl,0xB48,1U);if(bad==19)f.ctl[0x1BD8]=1;if(bad==20)put(f.ctl,0xC30,static_cast(nullptr)); + uint32_t packet[5]{};auto reads=map.reads.size();assert(XE::wrapDisplay13RunAUX(f.port,0,packet,4,false)==kIOReturnNotReady&&!auxCalls); + if(bad<13)assert(map.reads.size()==reads); + } + // Two native callers waiting on the donor channel lock retain independent + // command counts; the first completion cannot release the second caller. + {Fixture f(0,0,0);Map map;auxSetup(f,map,0,false,0);auto &b=display13FBPower.bridge;Display13FBPowerIO io{&map}; + assert(b.getAUX(io,0).status==Display13Power::Status::Complete&&b.beginAUXCommand(io,0)); + assert(b.getAUX(io,0).status==Display13Power::Status::Complete&&b.beginAUXCommand(io,0)); + assert(b.auxCommands[0]==2&&b.endAUXCommand(io,0)); + assert(b.putAUX(io,0).status==Display13Power::Status::Complete&&b.auxBusy()); + assert(b.endAUXCommand(io,0)&&b.putAUX(io,0).status==Display13Power::Status::Complete); + assert(!b.auxBusy()&&!b.state.refs[0]);} + unsigned counterexamples=0; + if(image){ + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned op=0;op<3;++op)for(unsigned fault=1;fault<=2;++fault){ + Fixture f(0,0,ddi);Map map;auxSetup(f,map,ddi,false,0);auto &c=display13FBPower; + nativePortObject=f.port;put(f.port,0,reinterpret_cast(portVT));put(image,0x138BA8,c.controller); + portVT[0x170/8]=reinterpret_cast(unsafeTransport);failGet=fault==1;failPut=fault==2; + uint32_t data[5]{};auto read=reinterpret_cast(image+0x1CC72); + auto write=reinterpret_cast(image+0x1CEB0); + auto i2c=reinterpret_cast(image+0x1D2B6); + const auto result=op==0?read(f.port,0x100,data,4):op==1?write(f.port,0x100,data,4):i2c(f.port,0,data,4,0); + assert(!result&&auxCalls==1&&c.bridge.state.faulted);++counterexamples;nativePortObject=nullptr; + } + assert(counterexamples==12);munmap(image,0x400000); + printf("PASS %u old native false-success counterexamples (failed AUX get/put)\n",counterexamples); + } + printf("PASS Display13 AUX: %u %s transactions; get/put/HAL fault propagation, native errors, no-DDI probe, active-command suspend/put guards, 21 rejected bindings\n",cases,image?"exact-donor":"synthetic"); +} diff --git a/tests/display13_boot.cpp b/tests/display13_boot.cpp index 25b15aef..e46a8135 100644 --- a/tests/display13_boot.cpp +++ b/tests/display13_boot.cpp @@ -6,7 +6,14 @@ struct IO { std::map regs; std::vector writes; + uint32_t pteReads {},badPTE {UINT32_MAX}; + XELPMemory::Layout memory=XELPMemory::decode(0x03C1,0x7A800000,0); + uint64_t readPTE(uint32_t reg) { + assert(reg==0x800000+8*pteReads);++pteReads; + return memory.pte(uint64_t(reg-0x800000)/8*4096)^(reg==badPTE?4096:0); + } bool noFrames {},noStop {},noIdle {},changePeer {}; + uint32_t changeRegister {},failWrite {}; IO() { regs[0x45404]=0xFC0F;regs[0x4438C]=0x1C000700; regs[0x64000]=regs[0x64100]=0x80000000; @@ -23,18 +30,74 @@ struct IO { bool readable(uint32_t reg) {return reg<0x1000000;} uint32_t read(uint32_t reg) {return regs[reg];} void write(uint32_t reg,uint32_t value) { - writes.push_back({reg,value});regs[reg]=value; + writes.push_back({reg,value});if(reg==failWrite)return;regs[reg]=value; if((reg==0x71008 || reg==0x72008) && !(value&0x80000000) && !noStop)regs[reg]&=~0x40000000; if((reg==0x64000 || reg==0x64100 || reg==0x64200) && !(value&0x80000000) && !noIdle)regs[reg]|=0x80; if(writes.size()==1)regs[0x4500C]=0; if(changePeer && writes.size()==1)regs[0x70180]^=8; + if(changeRegister && writes.size()==1)regs[changeRegister]^=1; } void delay() {if(!noFrames) {++regs[0x70040];if(regs[0x71008]&0xC0000000)++regs[0x71040];if(regs[0x72008]&0xC0000000)++regs[0x72040];}} }; const uint32_t prop[] {1,7,1,2,3}; const auto wiring=TGLHDMIWiring::fromProperty(prop,sizeof(prop)); const auto memory=XELPMemory::decode(0x03C1,0x7A800000,0); +static void dp(IO &io,unsigned pipe,unsigned port,unsigned lanes,bool enhanced) { + const auto bank=pipe*0x1000; + io.regs[0x60400+bank]=0x82030000|((port+1)<<27)|((lanes-1)<<1); + io.regs[0x64000+port*0x100]=0x80000000|((lanes-1)<<1); + io.regs[0x60540+bank]=0x80000300|(enhanced?1U<<18:0); + io.regs[0x60030+bank]=0x7E100000;io.regs[0x60034+bank]=0x800000; + io.regs[0x60040+bank]=0x100000;io.regs[0x60044+bank]=0x800000; +} +static Display13OutputWiring protocols(unsigned dpMask) { + Display13OutputWiring w=wiring;w.hdmi=3&~dpMask;w.dp=dpMask; + for(unsigned d=0;d<3;++d)if(!(w.hdmi&(1U<=0x78000?p*0x200:p*0x1000); + auto val=bad.value;if(bad.offset==0x60400)val+=p<<27; + t.regs[reg]=val; + assert(Display13Boot::normalize(t,AlderLakeN::readDisplay(t,true),true,mixed,memory).status==Display13Boot::Status::Topology); + assert(t.writes.empty()); + } + for(auto reg:{0x64000U,0x64100U,0x46140U,0x46144U}) { + IO t;dp(t,0,0,4,true);dp(t,1,1,4,true);t.regs[reg]=0; + assert(Display13Boot::normalize(t,AlderLakeN::readDisplay(t,true),true,mixed,memory).status==Display13Boot::Status::Topology && t.writes.empty()); + } + // Snapshot freshness and peer preservation cover DP metadata as well. + for(auto reg:{0x60540U,0x60030U,0x60034U,0x60040U,0x60044U,0x46140U,0x78004U}) { + IO t;dp(t,0,0,4,true);dp(t,1,1,4,true);const auto boot=AlderLakeN::readDisplay(t,true); + t.regs[reg]^=1; + assert(Display13Boot::normalize(t,boot,true,mixed,memory).status==Display13Boot::Status::Readback && t.writes.empty()); + t.regs[reg]^=1;t.changeRegister=reg; + assert(Display13Boot::normalize(t,boot,true,mixed,memory).status==Display13Boot::Status::PeerChanged && t.writes.size()==2); + } + IO tp;dp(tp,0,0,4,true);dp(tp,1,1,4,true);tp.failWrite=0x61540; + auto failure=Display13Boot::normalize(tp,AlderLakeN::readDisplay(tp,true),true,mixed,memory); + assert(failure.status==Display13Boot::Status::Timeout && failure.stage==4 && tp.writes.size()==7 && tp.regs[0x7127C]); + assert(Display13FBTopology::bootDDI(mixed,tp.regs[0x60400])==UINT32_MAX); // Runtime stays closed. + printf("PASS GOP bootstrap: %u HDMI/DP combinations, STOP/transport/idle ordering, peer/M-N preservation and bounded failures\n",combinations); } diff --git a/tests/display13_boot_aux.py b/tests/display13_boot_aux.py new file mode 100644 index 00000000..5d909dd5 --- /dev/null +++ b/tests/display13_boot_aux.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python3 +"""Compile the actual power/boot MMIO adapters with the DP bootstrap helpers.""" +from pathlib import Path +import subprocess,tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +start=s.index('struct Display13FBPowerIO {') +end=s.index('// Only used after native object/BAR binding',start) +code=(r/'tests/display13_boot_aux_fixture.cpp').read_text().replace('// PRODUCTION_IO',s[start:end]) +with tempfile.TemporaryDirectory(prefix='display13-boot-aux-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display13_boot_aux_fixture.cpp b/tests/display13_boot_aux_fixture.cpp new file mode 100644 index 00000000..2cbffc25 --- /dev/null +++ b/tests/display13_boot_aux_fixture.cpp @@ -0,0 +1,98 @@ +#include "kern_alderlake_n.hpp" +#include +#include +#include +struct IOMemoryMap { + std::vector memory=std::vector(0x400000); + uintptr_t getVirtualAddress() const {return reinterpret_cast(memory.data());} + uint64_t getLength() const {return 0x1000000;} + uint32_t &at(uint32_t reg) {return memory[reg/4];} +}; +void IODelay(unsigned) {} +// PRODUCTION_IO +using namespace Display13Power; +using namespace AlderLakeN; +static void firmware(IOMemoryMap &map,unsigned ddi,bool request) { + map.at(Driver)=0xFC0F;map.at(DDI)=15;map.at(Fuses)=0x8E380000; + map.at(AUX)=(request?3U:1U)<<(2*ddi); + map.at(0x60400)=0x82030006|((ddi+1)<<27);map.at(0x70008)=0xC0000000; + map.at(0x64000+ddi*0x100)=0x80000006;map.at(0x46140)=(ddi+1)<<28; + map.at(0x60540)=0x80040300;map.at(0x60030)=0x7E100000; + map.at(0x60034)=map.at(0x60044)=0x800000;map.at(0x60040)=0x100000; +} +struct FaultIO:Display13FBBootIO { + using Display13FBBootIO::Display13FBBootIO; + bool loseSource {},loseRequest {}; + void write(uint32_t reg,uint32_t value) { + Display13FBBootIO::write(reg,value); + if(reg==AUX) { + if(loseSource)map->at(0x60540)^=1; + if(loseRequest)map->at(AUX)&=~10U; + } + } +}; +int main() { + unsigned cases=0; + for(unsigned ddi=0;ddi<2;++ddi)for(bool requested:{false,true})for(bool peer:{false,true}) { + IOMemoryMap map;firmware(map,ddi,requested);const auto other=1-ddi; + if(peer)map.at(AUX)|=3U<<(2*other); + const auto initial=map.at(AUX);Display13FBBootIO io{&map};NativeBridge bridge;MainLinkAUX link; + assert(bridge.begin(io,ddi,3).status==Status::Complete); + const auto snapshot=readDisplay(io,true);assert(snapshot.status==ReadoutStatus::Complete && snapshot.count==139); + const auto heldResult=holdDPBootAUX(io,snapshot,bridge,link,ddi); + assert(heldResult.status==Status::Complete && heldResult.writes==(requested?0U:1U)); + assert(!link.owner && link.bootPending && link.port==ddi && !bridge.nativeAUXRefs(ddi)); + assert(link.lease.owner==&bridge.state && bridge.state.refs[ddi?6:8]==1); + assert(link.begin(io,bridge,7,ddi).status==Status::NotReady && link.end(io,bridge,7,ddi).status==Status::NotReady); + assert(holdDPBootAUX(io,readDisplay(io,true),bridge,link,ddi).status==Status::NotReady); + assert(bridge.getAUX(io,ddi).status==Status::Complete && bridge.putAUX(io,ddi).status==Status::Complete); + assert(bridge.state.refs[ddi?6:8]==1 && !bridge.nativeAUXRefs(ddi)); + const auto after=readDisplay(io,true);const auto requestBefore=map.at(AUX); + assert(adoptDPBootAUX(io,after,bridge,link,7,ddi).status==Status::Complete); + assert(link.owner==7 && !link.bootPending && !bridge.nativeAUXRefs(ddi) && map.at(AUX)==requestBefore); + assert(adoptDPBootAUX(io,after,bridge,link,8,ddi).status==Status::NotReady); + assert(bridge.adoptBootPath(9,0,ddi).status==Status::Complete); + assert(link.begin(io,bridge,7,ddi).status==Status::Complete); + assert(link.end(io,bridge,7,ddi).status==Status::NotReady); // Live GOP cannot release. + map.at(0x64000+ddi*0x100)=0x86; + assert(link.end(io,bridge,7,ddi).status==Status::Complete && !link.owner && !link.lease.owner); + assert(map.at(AUX)==initial); // Borrowed firmware/peer requests survive; only our request clears. + assert(bridge.putDDI(io,ddi).status==Status::Complete && bridge.putPG(io,1).status==Status::Complete); + for(auto refs:bridge.state.refs)assert(!refs); + io.write(0x60540,0);assert(map.at(0x60540)==0x80040300); // Boot IO cannot write link registers. + assert(!io.readable(0x1000000) && !Display13FBBootIO{nullptr}.readable(0)); + ++cases; + } + for(unsigned fault=0;fault<9;++fault) { + IOMemoryMap map;firmware(map,0,false);Display13FBBootIO io{&map};NativeBridge b;MainLinkAUX l; + assert(b.begin(io,0,3).status==Status::Complete);auto boot=readDisplay(io,true); + if(fault==0)map.at(AUX)=0; + if(fault==1)map.at(0x60540)=0x80000000; + if(fault==2)b.bootPGProtected=false; + if(fault==3)b.bootDDIProtected=false; + if(fault==4)b.bootAdopted=true; + if(fault==5)b.bootPort=1; + if(fault==6)b.ddiRefs[0]=0; + if(fault==7)b.state.suspended=true; + if(fault==8)b.state.faulted=true; + assert(holdDPBootAUX(io,boot,b,l,0).status==Status::NotReady && !l.lease.owner && !l.bootPending); + } + for(unsigned fault=0;fault<7;++fault) { + IOMemoryMap map;firmware(map,0,false);Display13FBBootIO io{&map};NativeBridge b;MainLinkAUX l; + assert(b.begin(io,0,3).status==Status::Complete && holdDPBootAUX(io,readDisplay(io,true),b,l,0).status==Status::Complete); + auto boot=readDisplay(io,true);uintptr_t target=7;unsigned port=0; + if(fault==0)target=0;if(fault==1)port=1;if(fault==2)map.at(AUX)=0; + if(fault==3)map.at(0x60540)^=1;if(fault==4)b.bootPGProtected=false; + if(fault==5)b.bootAdopted=true;if(fault==6)b.state.suspended=true; + assert(adoptDPBootAUX(io,boot,b,l,target,port).status==Status::NotReady && l.bootPending && !l.owner && l.lease.owner); + } + {IOMemoryMap map;firmware(map,0,false);FaultIO io{&map};NativeBridge b;MainLinkAUX l; + assert(b.begin(io,0,3).status==Status::Complete);io.loseSource=true; + assert(holdDPBootAUX(io,readDisplay(io,true),b,l,0).status==Status::RequestLost); + assert(b.state.faulted && l.bootPending && l.lease.owner && map.at(AUX)==3 && !b.nativeAUXRefs(0));} + {IOMemoryMap map;firmware(map,0,false);FaultIO io{&map};NativeBridge b;MainLinkAUX l; + assert(b.begin(io,0,3).status==Status::Complete);io.loseRequest=true; + assert(holdDPBootAUX(io,readDisplay(io,true),b,l,0).status==Status::RequestLost); + assert(b.state.faulted && !l.bootPending && !l.lease.owner && map.at(AUX)==1);} + printf("PASS DP boot AUX: %u real-IO ownership lifecycles, transaction independence, refusal/fault retention and power-only writes\n",cases); +} diff --git a/tests/display13_combo_clock.py b/tests/display13_combo_clock.py new file mode 100644 index 00000000..35056a18 --- /dev/null +++ b/tests/display13_combo_clock.py @@ -0,0 +1,105 @@ +#!/usr/bin/env python3 +"""Production Display13 fractional write boundary and optional native PLL replay.""" +from pathlib import Path +import hashlib,os,re,struct,subprocess,sys,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +guard=s[s.index(' // DISPLAY13_COMBO_FRACTION:'):s.index(' // DISPLAY13_COMBO_FRACTION_END')] +guard=guard.replace('reinterpret_cast(__builtin_return_address(0))','caller') +install=s[s.index('bool XE::processDisplay13FramebufferOutput('):s.index('uint32_t XE::wrapDisplay13GMBus(')] +def array(name): + text=re.search(r'const uint(?:8|64)_t '+name+r'\[\] \{([^}]+)\}',install).group(1) + return [int(x.strip(),0) for x in text.split(',')] +code=r''' +#include "kern_xelp_platform.hpp" +#include "kern_tgl_port_map.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +struct IOService; +struct State { + bool display13FramebufferRequested=true;struct {XELP::Display display=XELP::Display::XeLPD;}xeProfile; + uintptr_t adlnComboPLLFractionCaller=0x1000;IOService *adlnTopologyController=nullptr; +}state,*callbackXE=&state; +uint32_t adjust(void *access,uint64_t reg,uint32_t value,uintptr_t caller) {GUARD return value;} +templateT &field(void*p,unsigned off){return *reinterpret_cast(static_cast(p)+off);} +alignas(8) uint8_t ctl[0xf00]{},port[0x700]{},access[0x80]{}; +uint32_t config[6]{};std::map mmio; +uint8_t *image;unsigned delays=0,errors=0,get=0,put=0,fault=0; +bool correction=true; +void jump(uint8_t*p,uintptr_t to){p[0]=0x48;p[1]=0xb8;memcpy(p+2,&to,8);p[10]=0xff;p[11]=0xe0;} +void redirect(unsigned at,uintptr_t to,unsigned stub){assert(image[at]==0xe8);jump(image+stub,to);int32_t d=stub-at-5;memcpy(image+at+1,&d,4);} +uint32_t read(void*p,unsigned long reg){assert(p==access&&mmio.count(reg));return mmio.at(reg);} +void write(void*p,unsigned long reg,uint32_t value){assert(p==access&&mmio.count(reg)); + if(correction)value=adjust(p,reg,value,(uintptr_t)__builtin_return_address(0)); + if(reg==0x46010 || reg==0x46014){value&=~0x44000000U; + if((value&0x08000000)&&fault!=1)value|=0x04000000; + if((value&0x80000000)&&fault!=2)value|=0x40000000;} + mmio[reg]=value; +} +void mailbox(void*p,uint32_t command,uint32_t,bool wait){assert(p==ctl&&command==7&&!wait);} +uint32_t voltage(void*p){assert(p==ctl);return 1;} +void getDDI(void*p){assert(p==port);++get;}void putDDI(void*p){assert(p==port);++put;} +void delay(unsigned n){assert(n==1);++delays;}void error(const char*,...){++errors;} +int main(int argc,char**argv){ + state.adlnTopologyController=reinterpret_cast(ctl);field(ctl,0xc40)=access;field(ctl,0xea8)=2; + for(unsigned bad=0;bad<9;++bad){State saved=state; + if(bad==0)state.display13FramebufferRequested=false;if(bad==1)state.xeProfile.display=XELP::Display::XeD; + if(bad==2)state.adlnComboPLLFractionCaller=0;if(bad==3)state.adlnTopologyController=nullptr; + if(bad==4)field(ctl,0xea8)=1;if(bad==8)field(ctl,0xc40)=port; + assert(adjust(bad==5?nullptr:access,bad==6?0x164288:0x164284,0x1c001a5,bad==7?0x1001:0x1000)==0x1c001a5); + state=saved;field(ctl,0xea8)=2;field(ctl,0xc40)=access; + } + assert(adjust(access,0x164284,0x1c001a5,0x1000)==0xe001a5); + assert(adjust(access,0x16428c,0x1c001a5,0x1000)==0xe001a5); + assert(adjust(access,0x164294,0x1c001a5,0x1000)==0x1c001a5); + if(argc>1){ + std::ifstream f(argv[1],std::ios::binary);std::vectorraw((std::istreambuf_iterator(f)),{}); + image=(uint8_t*)mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0);assert(image!=MAP_FAILED); + memcpy(image,raw.data(),raw.size()); + for(auto table:{std::pair{0xcd330,3},{0xcd348,6},{0xcd378,3},{0xcd3c0,3}}) + for(unsigned i=0;i(image,table.first+8*i);assert(v>=0xd0000&&v<0x160000);v+=(uintptr_t)image;} + jump(image+0x5e344,(uintptr_t)read);jump(image+0x4691e,(uintptr_t)mailbox);jump(image+0x6bf6e,(uintptr_t)voltage); + jump(image+0x1fefe,(uintptr_t)getDDI);jump(image+0x1ff54,(uintptr_t)putDDI); + for(unsigned at:{0x6d081U,0x6d9ecU,0x6dd6dU,0x6de16U})redirect(at,(uintptr_t)delay,0x300000); + for(unsigned at:{0x6d54cU,0x6dda6U})redirect(at,(uintptr_t)error,0x300020); + uintptr_t vt[64]{};vt[0x130/8]=(uintptr_t)write;field(access,0)=vt;field(port,0x548)=config; + state.adlnComboPLLFractionCaller=(uintptr_t)image+0x6d973; + auto enable=(void(*)(void*,void*,void*,uint32_t*))(image+0x6ce10);auto disable=(void(*)(void*,void*,void*))(image+0x6dae6); + unsigned cases=0,counterexamples=0; + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned ref=0;ref<3;++ref) + for(unsigned rate:{6U,10U,20U,30U})for(bool hdmi:{false,true})for(bool fixed:{false,true}) { + if(hdmi && rate!=6)continue; + correction=fixed;get=put=delays=errors=0;field(ctl,0xea8)=ref; + const uint32_t record[]={ddi,ddi+1,hdmi?2U:1U,0,hdmi?0x800U:0x400U,hdmi?0x202U:0x201U};memcpy(config,record,sizeof(record)); + field(port,0x114)=hdmi?0:1;field(port,0x128)=rate; + const auto pll=0x46010+4*ddi,cfg0=0x164284+8*ddi,cfg1=cfg0+4,off=1U<<(10+ddi); + mmio={{pll,0},{cfg0,0},{cfg1,0},{0x164280,0x01e07400}}; + uint32_t params[]={464,2049,2,3,2};enable(ctl,port,nullptr,hdmi?params:nullptr); + assert(!errors&&!delays&&get==1&&mmio[pll]==0xcc000000); + assert(mmio[0x164280]==((0x01e07400&~(off|(3U<<(2*ddi))))|(ddi<<(2*ddi)))); + const auto nominal=hdmi?(464|(2049<<10)):ref==0?0x1000151U:0x1c001a5U; + const auto expected=ref==2?(nominal&~0x1fffc00U)|(((((nominal>>10)&32767)+1)/2)<<10):nominal; + if(fixed)assert(mmio[cfg0]==expected);else if(ref==2){assert(mmio[cfg0]!=expected);++counterexamples;} + assert(mmio[cfg1]==(hdmi?0xe84U:rate==6?0x90U:rate==10?0x88U:rate==20?0x48U:0x44U)); + disable(ctl,port,nullptr);assert(!errors&&!delays&&put==1&&!mmio[pll]&&(mmio[0x164280]&off));++cases; + } + assert(cases==60 && counterexamples==10);munmap(image,0x400000); + printf("PASS %u exact native Display13 Combo A/B DP/HDMI clock cycles, %u old 38.4 MHz counterexamples\n",cases,counterexamples); + } + puts("PASS production Display13 nominal-write owner/caller/reference/register guards"); +} +'''.replace('GUARD',guard) +with tempfile.TemporaryDirectory(prefix='display13-combo-clock-') as d: + p=Path(d);(p/'test.cpp').write_text(code);args=[] + if len(sys.argv)>1: + b=bytearray(Path(sys.argv[1]).read_bytes());assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + assert list(struct.unpack_from('<6Q',b,0x9a0e8))==array('before') + assert b[0x6d95d:0x6d973]==bytes(array('fractionWrite')) + struct.pack_into('<6Q',b,0x9a0e8,*array('after'));(p/'donor').write_bytes(b);args=[str(p/'donor')] + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+args,check=True,env=dict(os.environ,UBSAN_OPTIONS='halt_on_error=1')) diff --git a/tests/display13_fb_topology.cpp b/tests/display13_fb_topology.cpp index 9a4bf4ac..9f4f0e62 100644 --- a/tests/display13_fb_topology.cpp +++ b/tests/display13_fb_topology.cpp @@ -91,6 +91,38 @@ int main() { 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));} } + // All fixed Combo HDMI/DP assignments compose CPU topology/resources. + // DP runtime/boot admission stays closed until the hardware hooks exist. + for(unsigned assignment=1;assignment<9;++assignment) { + Display13OutputWiring w;w.valid=true;unsigned code=assignment; + for(unsigned d=0;d<2;++d){unsigned kind=code%3;code/=3; + if(kind==1){w.hdmi|=1U< #include +#include #include "kern_display13_fb_topology.hpp" using namespace Display13FBTopology; +static_assert(!std::is_convertible::value,"Do not silently drop DP policy through a legacy HDMI API"); int main() { for(unsigned mask=1;mask<=7;++mask) { const uint32_t legacy[] {1,mask,(mask&1)?1U:0U,(mask&2)?2U:0U,(mask&4)?3U:0U}; diff --git a/tests/display13_linear_dp.cpp b/tests/display13_linear_dp.cpp new file mode 100644 index 00000000..d39357af --- /dev/null +++ b/tests/display13_linear_dp.cpp @@ -0,0 +1,82 @@ +#include "kern_alderlake_n.hpp" +#include +#include +#include +#include +using namespace AlderLakeN; +struct IO { + std::map regs; + std::vector writes; + unsigned pipe {},ticks {},changeTick {}; + uint32_t changeReg {}; + bool failEnable {},noFrames {}; + bool readable(uint32_t reg) {return reg<0x1000000;} + uint32_t read(uint32_t reg) {return reg==0x70040+pipe*0x1000 ? ticks : regs[reg];} + void write(uint32_t reg,uint32_t v) { + writes.push_back({reg,v}); + if(failEnable && reg==0x70180+pipe*0x1000 && v==0x94000008){failEnable=false;return;} + regs[reg]=v; + } + void delay() { + if(!noFrames)++ticks; + if(changeTick && ticks==changeTick)regs[changeReg]^=1; + } +}; +static IO firmware(unsigned pipe,unsigned ddi,unsigned lanes,bool enhanced,unsigned width=1920) { + IO io;io.pipe=pipe;const auto b=pipe*0x1000; + io.regs={{0x45404,0xFC0F},{0x45454,3U<<(2*ddi)},{0x45444,0},{0x42000,0x8E380000}, + {0x60400+b,0x82030000|((ddi+1)<<27)|((lanes-1)<<1)}, + {0x60540+b,0x80000300|(enhanced?1U<<18:0)}, + {0x64000+ddi*0x100,0x80000000|((lanes-1)<<1)},{0x46140+4*pipe,(ddi+1)<<28}, + {0x60030+b,0x7E100000},{0x60034+b,0x800000},{0x60040+b,0x100000},{0x60044+b,0x800000}, + {0x60000+b,((width+279)<<16)|(width-1)},{0x6000C+b,0x04640437},{0x70008+b,0xC0000000}, + {0x70028+b,0xA0F},{0x70180+b,0x84000008},{0x70188+b,(width*4+63)/64}, + {0x70190+b,0x04370000|(width-1)}}; + return io; +} +int main() { + unsigned cases=0; + for(unsigned pipe=0;pipe<3;++pipe)for(unsigned ddi=0;ddi<2;++ddi)for(unsigned lanes:{1U,2U,4U}) + for(bool enhanced:{false,true})for(unsigned width:{1280U,1920U,2560U,3840U}) { + auto io=firmware(pipe,ddi,lanes,enhanced,width);auto boot=readDisplay(io,true);const auto before=io.regs; + LinearPlan plan; + assert(linearPlan(boot,plan,ddi)==LinearStatus::NotSingleHDMI); + assert(linearPlan(boot,plan,ddi,GOPProtocol::DP)==LinearStatus::Ready && plan.pipe==pipe); + auto result=reinitializeLinear(io,boot,true,ddi,GOPProtocol::DP); + assert(result.status==LinearStatus::Success && result.writes==10 && result.framesAfter>=6); + assert(dpSourceUnchanged(io,boot,pipe,ddi)); + const uint32_t b=pipe*0x1000; + for(auto w:io.writes)assert(w.offset==0x42084 || w.offset==0x70028+b || w.offset==0x70180+b || + w.offset==0x70188+b || w.offset==0x7018C+b || w.offset==0x70190+b || w.offset==0x701A4+b || w.offset==0x7019C+b); + for(auto r:before)if(r.first!=0x42084 && r.first!=0x70028+b && r.first!=0x70180+b) + assert(io.regs[r.first]==r.second); + ++cases; + } + const uint32_t link[] {0x60400,0x60404,0x60540,0x60030,0x60034,0x60040,0x60044,0x70008, + 0x46140,0x64000,0x78000,0x78004,0x45404,0x45454,0x45444,0x42000}; + for(auto reg:link) { + auto io=firmware(0,0,4,true);auto boot=readDisplay(io,true);io.regs[reg]^=1; + auto r=reinitializeLinear(io,boot,true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::StateChanged && io.writes.empty()); + // Loss before first write, after plane stop, and after plane restart. + // Do not attempt rollback through a changed power/link configuration. + for(unsigned tick:{1U,3U,5U}) { + auto io=firmware(0,0,4,true);auto boot=readDisplay(io,true);io.changeReg=reg;io.changeTick=tick; + auto r=reinitializeLinear(io,boot,true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==(tick==1?0U:tick==3?2U:10U)); + } + } + auto bad=firmware(0,0,4,true);bad.regs[0x60540]=0x80000000; + auto r=reinitializeLinear(bad,readDisplay(bad,true),true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::InvalidDPSource && bad.writes.empty()); + auto stopped=firmware(0,0,4,true);stopped.noFrames=true; + r=reinitializeLinear(stopped,readDisplay(stopped,true),true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::NoVblank && stopped.writes.empty()); + auto rollback=firmware(0,0,4,true);const auto boot=readDisplay(rollback,true);rollback.failEnable=true; + r=reinitializeLinear(rollback,boot,true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::EnableTimeout && r.restored && dpSourceUnchanged(rollback,boot,0,0)); + auto lost=firmware(0,0,4,true);lost.failEnable=true;lost.changeReg=0x60540;lost.changeTick=7; + r=reinitializeLinear(lost,readDisplay(lost,true),true,0,GOPProtocol::DP); + assert(r.status==LinearStatus::StateChanged && !r.restored && r.writes==12); + printf("PASS DP plane bootstrap: %u route/lane/framing/geometry cases, source/power drift and bounded rollback\n",cases); +} diff --git a/tests/display13_power.cpp b/tests/display13_power.cpp index e1070fd1..0a802c51 100644 --- a/tests/display13_power.cpp +++ b/tests/display13_power.cpp @@ -15,13 +15,18 @@ struct IO { {0x45400,0}, {0x45408,0}, {0x4540C,0}, {0x45450,0}, {0x4545C,0}}; std::vector> writes; uint32_t deny {}, allones {}, failedEnable {}, failedEnableReg {}, stickyRelease {}; - unsigned time {}, delayEnable {}; + unsigned time {}, delayEnable {}, delayFuse {}, fuseUnavailableAt {UINT32_MAX}; + uint32_t delayedFuse {}; bool loseDDIAfterWrite {}; bool readable(uint32_t reg) { return reg != deny && regs.count(reg); } uint32_t read(uint32_t reg) { assert(regs.count(reg)); if (reg == allones || (loseDDIAfterWrite && reg==0x45454 && writes.size()>=3)) return UINT32_MAX; update(); + if(reg==0x42000) { + if(time>=fuseUnavailableAt)return UINT32_MAX; + return regs[reg] & ~(time #include #include "kern_display13_power_bridge.hpp" +#include "kern_display13_output_wiring.hpp" using namespace Display13Power; struct IO { std::map regs {{0x45404,0xFC0F},{0x45454,0xC},{0x45444,0},{0x42000,0x8E380000}}; @@ -29,6 +30,28 @@ static void slots(const NativeBridge &b, uint32_t a,uint32_t two,uint32_t three, b.nativeRefs(3)==three && b.nativeRefs(4)==four); } int main() { + // Resource leases depend on selected physical ports, not HDMI labels. + // Start cold, drop the peer before the boot route, and require all counts + // to return to zero for each fixed HDMI/DP assignment and boot choice. + for(unsigned assignment=1;assignment<9;++assignment) { + Display13OutputWiring w;w.valid=true;unsigned code=assignment; + for(unsigned d=0;d<2;++d){const unsigned kind=code%3;code/=3; + if(kind==1){w.hdmi|=1U<1: + b=Path(sys.argv[1]).read_bytes();assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + install=s[s.index('bool XE::processDisplay13FramebufferSST('):s.index('uint8_t *XE::display13SSTPort(')] + for name,offset in [('patternEntry',0x955b8),('signalEntry',0x60ae0),('enableEntry',0x5f9f4),('disableEntry',0x5fc56),('pcsABI',0x60eb9),('lanesABI',0x61468),('successABI',0x61478)]: + a=re.search(name+r'\[\]=\{([^}]+)\}',install).group(1);v=bytes(int(x,0) for x in a.split(',')) + assert b[offset:offset+len(v)]==v + print('PASS exact donor Display13 SST hook and Combo register-field preflight') +with tempfile.TemporaryDirectory(prefix='display13-sst-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True) diff --git a/tests/display13_sst_fixture.cpp b/tests/display13_sst_fixture.cpp new file mode 100644 index 00000000..ab8b8a44 --- /dev/null +++ b/tests/display13_sst_fixture.cpp @@ -0,0 +1,298 @@ +#include +#include +#include +#include +#include +#include +#include "kern_display13_power_bridge.hpp" +#include "kern_tgl_combo_signal.hpp" +#include "kern_xelp_sst_program.hpp" +// NATIVE_OBJECTS +std::map objectRefs; +extern bool locked; +struct OSObject {unsigned size;const OSObject *getMetaClass() const{return this;}unsigned getClassSize() const{return size;} + void retain(){assert(locked);++objectRefs[this];}void release(){assert(!locked&&objectRefs[this]);if(!--objectRefs[this])objectRefs.erase(this);}}; +struct IOService:OSObject {}; +constexpr unsigned kIOReturnNotReady=0xE00002D8; +#define FunctionCast(fn,ptr) reinterpret_cast(ptr) +bool locked=false,bound=true,loseAfterNative=false;unsigned calls=0,nativeResult=0; +void IOLockLock(void*){assert(!locked);locked=true;}void IOLockUnlock(void*){assert(locked);locked=false;} +struct Map { + std::map regs; + std::vector reads,writes; + uint32_t base=0,drop=0,delay=0,idleAfter=0,idleReg=0; + Map():regs{{0x45404,0},{0x45454,0},{0x45444,0},{0x42000,0x8E380000}}{} + uint32_t read(uint32_t reg){reads.push_back(reg);return regs.at(reg);} + void write(uint32_t reg,uint32_t v){writes.push_back(reg);if(reg==drop)return; + if(reg==0x45404)v=(v&~0x5405U)|((v&0xA80A)>>1); + if(reg==0x45454||reg==0x45444)v=(v&~21U)|((v&42)>>1); + if(reg==base+0x694)for(unsigned n=0;n<4;++n)regs.at(base+0x894+0x100*n)=v; + regs.at(reg)=v; + } +}; +struct Context { + Display13Power::NativeBridge bridge;void *lock=reinterpret_cast(1),*controller=nullptr,*access=nullptr; + Display13Power::MainLinkAUX mainLink[2];alignas(void *) uint8_t powerBytes[0x80]{};void *power=powerBytes; + Map *map=nullptr;bool clockFaulted=false,memoryFaulted=false,clockStopped=false; +}display13FBPower; +struct Display13FBPowerIO { + Map *map;bool readable(uint32_t r){return map&&map->regs.count(r);}uint32_t read(uint32_t r){return map->read(r);} + void write(uint32_t r,uint32_t v){map->write(r,v);}void delayUs(unsigned us){map->delay+=us;if(map->idleReg&&map->delay>=map->idleAfter)map->regs.at(map->idleReg)|=0x80;} +}; +struct Display13FBSSTIO { + Map *map;uint32_t read(uint32_t r){assert(locked);return map->read(r);} + void write(uint32_t r,uint32_t v){assert(locked);map->write(r,v);} +}; +bool adlnWriterMappingBound(){return bound;} +uintptr_t display13FBExpectedOwner(void*){return 0;} +struct XE { + bool display13FramebufferRequested=true,display13FBSSTHooksReady=true;XELP::Profile xeProfile; + Display13OutputWiring adlnOutputWiring;void *adlnPlatform=nullptr,*adlnTopologyController=nullptr; + uintptr_t orgDisplay13TrainingPattern=0,orgDisplay13SignalLevels=0; + uintptr_t orgDisplay13EnablePort=0,orgDisplay13DisablePort=0; + static bool display13MainLinkAUX(void*,bool); + static uint32_t wrapDisplay13EnablePort(void*);static uint32_t wrapDisplay13DisablePort(void*); + static uint8_t *display13SSTPort(void*);static bool display13HDMIPort(void*); + static uint32_t wrapDisplay13SignalLevels(void*,uint32_t,uint32_t,void*); + static uint32_t wrapDisplay13TrainingPattern(void*,uint32_t); +}state,*callbackXE=&state; +// BINDING +// FUNCTIONS +unsigned nativeSignal(void*,unsigned,unsigned,void*){assert(!locked);++calls;return nativeResult;} +unsigned linkCalls=0,linkResult=0;bool changeBuffer=true,bufferIdle=true,loseLinkPower=false; +unsigned (*realEnable)(void*)=nullptr,(*realDisable)(void*)=nullptr;void *currentPath=nullptr;unsigned portReplays=0; +unsigned nativeEnable(void *port){assert(!locked);++linkCalls; + auto &c=display13FBPower;const auto ddi=Display13FBMemory::field(port,0x120); + Display13FBPowerIO io{c.map};assert(Display13Power::held(io,c.bridge.state,Display13Power::auxDomain(ddi))); + unsigned result=linkResult; + if(realEnable&&changeBuffer&&!linkResult){++portReplays;result=realEnable(port);} + else if(changeBuffer)c.map->regs.at(0x64000+0x100*ddi)=0x80000000; + if(loseLinkPower)c.map->regs.at(0x45444)=0; + return result;} +unsigned nativeDisable(void *port){assert(!locked);++linkCalls; + const auto ddi=Display13FBMemory::field(port,0x120); + if(realDisable&&changeBuffer&&!linkResult){++portReplays;return realDisable(port);} + if(changeBuffer)display13FBPower.map->regs.at(0x64000+0x100*ddi)=bufferIdle?0x80:0;return linkResult;} +uint32_t (*realPattern)(void *,uint32_t)=nullptr; +void nativeWrite(void *access,uint64_t reg,uint32_t value){ + assert(!locked&&access==display13FBPower.access);display13FBPower.map->write(static_cast(reg),value); +} +unsigned nativeRead(void *access,uint64_t reg){assert(!locked&&access==display13FBPower.access);return display13FBPower.map->read(static_cast(reg));} +unsigned portRead(void *port,uint32_t reg){assert(!locked&®==Display13FBMemory::field(port,0x590));return display13FBPower.map->read(reg);} +void portWrite(void *port,uint32_t reg,uint32_t value){ + assert(!locked&®==Display13FBMemory::field(port,0x590)); + // Hardware model: DDI buffer enable clears idle; stop may acknowledge late. + value=(value&~0x80U)|(!(value&0x80000000U)&&bufferIdle?0x80U:0); + display13FBPower.map->write(reg,value); +} +void *firstPath(void *port){return Display13FBMemory::field(currentPath,0x3648)==port?currentPath:nullptr;} +void nativeDelay(unsigned us){assert(us==5||us==8);} +void nativeLog(const char*,...){} +void jump(uint8_t *to,uintptr_t target){to[0]=0x48;to[1]=0xB8;memcpy(to+2,&target,8);to[10]=0xFF;to[11]=0xE0;} +void redirect(uint8_t *image,unsigned at,unsigned stub,uintptr_t target){assert(image[at]==0xE8);jump(image+stub,target);const int32_t displacement=static_cast(stub-at-5);memcpy(image+at+1,&displacement,4);} +unsigned nativePattern(void *path,unsigned pattern){ + assert(!locked);++calls; + const auto pipe=Display13FBMemory::field(path,0x3640); + if(!nativeResult) { + if(realPattern)assert(realPattern(path,pattern)==0); + else {const uint32_t patterns[]={0x200,0,0x100,0x400,0x500,0x300}; + assert(pattern<6);display13FBPower.map->regs[0x60540+0x1000*pipe]=0x80000000|patterns[pattern];} + } + if(loseAfterNative)display13FBPower.map->regs[0x45454]=0; + return nativeResult; +} +void setup(Fixture &f,Map &map,unsigned ddi,unsigned pipe,unsigned lanes,unsigned rate){ + display13FBPower.~Context();new(&display13FBPower)Context;state={};calls=nativeResult=0;bound=true;loseAfterNative=false; + assert(objectRefs.empty());linkCalls=linkResult=0;changeBuffer=bufferIdle=true;loseLinkPower=false; + auto &c=display13FBPower;c.controller=f.ctl;c.access=f.access;c.map=↦ + currentPath=f.path; + put(f.ctl,0xC8,c.power);put(c.power,0x10,static_cast(f.ctl));put(c.power,0x78,static_cast(f.access)); + f.wiring.hdmi=1U<<(1-ddi);f.wiring.dp=1U<(nativePattern);state.orgDisplay13SignalLevels=reinterpret_cast(nativeSignal); + state.orgDisplay13EnablePort=reinterpret_cast(nativeEnable);state.orgDisplay13DisablePort=reinterpret_cast(nativeDisable); + static uintptr_t vtable[64]{};vtable[0x130/8]=reinterpret_cast(nativeWrite);put(f.access,0,static_cast(vtable)); + put(f.ctl,0,unsigned(sizeof(f.ctl)));put(f.fb,0,unsigned(sizeof(f.fb)));put(f.path,0,unsigned(sizeof(f.path))); + put(f.plane,0,unsigned(sizeof(f.plane)));put(f.port,0,unsigned(sizeof(f.port))); + put(f.port,0x578,static_cast(f.access));put(f.port,0x550,1U);put(f.port,0x124,lanes);put(f.port,0x128,rate);f.port[0x11A]=1; + map.base=ddi?0x6C000:0x162000; + put(f.port,0x620,map.base+0x804);put(f.port,0x5C8,map.base+0x28);put(f.access,0x60,0x200000U); + put(f.port,0x590,0x64000+0x100*ddi); + for(auto reg:{0x804U,0x14U,0x28U,0x894U,0x604U,0x694U})map.regs[map.base+reg]=0; + for(unsigned n=0;n<4;++n)for(auto reg:{0x888U,0x890U,0x894U,0x89CU})map.regs[map.base+0x100*n+reg]=0; + map.regs[0x46140+4*pipe]=0;map.regs[0x60400+0x1000*pipe]=0;map.regs[0x64000+0x100*ddi]=0;map.regs[0x60540+0x1000*pipe]=0; + Display13FBPowerIO io{&map};using Display13Power::Status; + assert(c.bridge.begin(io,1-ddi,3).status==Status::Complete); + const auto pg=pipe?pipe+2:1; + assert(c.bridge.getPG(io,pg).status==Status::Complete&&c.bridge.getDDI(io,ddi).status==Status::Complete); + assert(c.bridge.getAUX(io,ddi).status==Status::Complete&&c.bridge.putAUX(io,ddi).status==Status::Complete); + // Remove boot peer first: training must depend only on its actual resources. + assert(c.bridge.adoptBootPath(1,0,1-ddi).status==Status::Complete); + assert(c.bridge.putDDI(io,1-ddi).status==Status::Complete&&c.bridge.putPG(io,1).status==Status::Complete); + assert(c.bridge.outputHeld(io,ddi,pipe));map.reads.clear();map.writes.clear(); +} +int main(int argc,char **argv){ + uint8_t *image=nullptr; + if(argc>1){ + std::ifstream input(argv[1],std::ios::binary);std::vector bytes((std::istreambuf_iterator(input)),{}); + assert(!bytes.empty()&&bytes.size()<0x400000); + image=static_cast(mmap(nullptr,0x400000,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_ANON|MAP_PRIVATE,-1,0));assert(image!=MAP_FAILED); + memcpy(image,bytes.data(),bytes.size());realPattern=reinterpret_cast(image+0x955B8); + realEnable=reinterpret_cast(image+0x5F9F4);realDisable=reinterpret_cast(image+0x5FC56); + assert(Display13FBMemory::field(image,0xC95A0)==0x1B446&&Display13FBMemory::field(image,0xC95A8)==0x1B456); + put(image,0xC95A0,reinterpret_cast(image+0x1B446));put(image,0xC95A8,reinterpret_cast(image+0x1B456)); + jump(image+0x1FE8E,reinterpret_cast(portRead));jump(image+0x1FEA8,reinterpret_cast(portWrite)); + jump(image+0x5E344,reinterpret_cast(nativeRead));jump(image+0x91BCA,reinterpret_cast(firstPath)); + for(unsigned at:{0x5FA33U,0x5FAC4U,0x5FBEBU,0x5FC4EU})redirect(image,at,0x300000,reinterpret_cast(nativeLog)); + for(unsigned at:{0x5FBCBU,0x5FC99U})redirect(image,at,0x300020,reinterpret_cast(nativeDelay)); + } + unsigned count=0,replays=0; + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned lanes:{1U,2U,4U}) + for(unsigned rate:{6U,10U,20U,30U})for(unsigned swing=0;swing<4;++swing)for(unsigned emphasis=0;emphasis<4-swing;++emphasis){ + Fixture f(2-pipe,pipe,ddi);Map map;setup(f,map,ddi,pipe,lanes,rate); + assert(!XE::wrapDisplay13SignalLevels(f.port,swing,emphasis,nullptr)&&!calls); + XELP::ComboPolicy policy;assert(policy.select(XELP::ComboPolicy::Platform::ADLP,rate,false,false,false,swing,emphasis)); + assert(((map.regs[map.base+0x89C]>>24)&0x7F)==policy.coefficients.scalar); + assert(!XE::wrapDisplay13TrainingPattern(f.path,1)&&calls==1); + assert(map.regs[0x46140+4*pipe]==((ddi+1)<<28)); + assert(map.regs[0x60400+0x1000*pipe]==(((ddi+1)<<27)|0x02000000|((lanes-1)<<1))); + assert(map.regs[0x60540+0x1000*pipe]==0x80040000); + assert(!XE::wrapDisplay13TrainingPattern(f.path,2)&&calls==2); + assert(!XE::wrapDisplay13TrainingPattern(f.path,0)&&calls==3); + // Actual native instruction replay: all six pattern values lose enhanced + // framing. The adapter restores only the negotiated bit, preserving the + // pattern and SST mode for both enhanced-framing settings. + if(realPattern){ + const uint32_t patterns[]={0x200,0,0x100,0x400,0x500,0x300}; + for(unsigned enhanced=0;enhanced<2;++enhanced)for(unsigned pattern=0;pattern<6;++pattern){ + f.port[0x11A]=enhanced; + assert(realPattern(f.path,pattern)==0&&map.regs[0x60540+0x1000*pipe]==(0x80000000|patterns[pattern])); + assert(!XE::wrapDisplay13TrainingPattern(f.path,pattern)); + assert(map.regs[0x60540+0x1000*pipe]==(0x80000000|patterns[pattern]|(enhanced<<18)));++replays; + } + } + const auto completedCalls=calls; + Display13FBPowerIO io{&map};using Display13Power::Status; + auto &bridge=display13FBPower.bridge;const auto auxIndex=ddi?6:8; + assert(!XE::wrapDisplay13EnablePort(f.port)&&linkCalls==1&&objectRefs[f.port]==1); + assert(bridge.state.refs[auxIndex]==1&&!bridge.nativeAUXRefs(ddi)); + assert(!XE::wrapDisplay13EnablePort(f.port)&&linkCalls==2&&objectRefs[f.port]==1&&bridge.state.refs[auxIndex]==1); + // Transactions retain/release their own lease while the link stays active. + for(unsigned transaction=0;transaction<3;++transaction){ + assert(bridge.getAUX(io,ddi).status==Status::Complete&&bridge.state.refs[auxIndex]==2); + assert(bridge.putAUX(io,ddi).status==Status::Complete&&bridge.state.refs[auxIndex]==1); + assert(map.regs[0x45444]&(ddi?8:2)); + } + assert(!XE::wrapDisplay13DisablePort(f.port)&&linkCalls==3&&objectRefs.empty()&&!bridge.state.refs[auxIndex]); + assert(display13FBPower.bridge.putDDI(io,ddi).status==Status::Complete); + assert(display13FBPower.bridge.putPG(io,pipe?pipe+2:1).status==Status::Complete); + for(auto refs:display13FBPower.bridge.state.refs)assert(!refs); + // Deliberately leave BIOS-like enabled/request bits. No native ownership. + map.regs[0x45404]=0xFC0F;map.regs[0x45454]=15;map.writes.clear();map.reads.clear(); + assert(XE::wrapDisplay13TrainingPattern(f.path,1)==kIOReturnNotReady&&calls==completedCalls&&map.writes.empty()&&map.reads.empty()); + ++count; + } + for(unsigned fault=0;fault<25;++fault){ + Fixture f(2,1,1);Map map;setup(f,map,1,1,4,20);auto &c=display13FBPower; + if(fault==0)c.bridge.state.suspended=true;if(fault==1)c.bridge.state.faulted=true; + if(fault==2)c.bridge.ddiRefs[1]=0;if(fault==3)c.bridge.ddiLease[1].owner=nullptr; + if(fault==4)map.regs[0x45454]&=~8U;if(fault==5)map.regs[0x45454]&=~4U; + if(fault==6)map.regs[0x45404]&=~0x2000U;if(fault==7)map.regs[0x45404]&=~0x1000U; + if(fault==8)map.regs[0x42000]&=~0x100000U;if(fault==9)map.regs[0x45454]=~0U; + if(fault==10)c.bridge.pgRefs[2]=0;if(fault==11)bound=false; + if(fault==12)c.clockStopped=true;if(fault==13)put(f.port,0x548,reinterpret_cast(1)); + if(fault==14)f.port[0x117]=1;if(fault==15)put(f.port,0x578,static_cast(f.ctl)); + if(fault==16)put(f.path,0x3640,0U);if(fault==17)put(f.plane,0x7C,0U); + if(fault==18)state.display13FramebufferRequested=false;if(fault==19)state.display13FBSSTHooksReady=false; + if(fault==20)map.drop=0x46144;if(fault==21)map.drop=0x61400; + if(fault==22)map.regs[0x46144]=0x10000000; + if(fault==23)map.regs[0x61400]=0x88000000; + if(fault==24)c.bridge.state.refs[0]=0; + assert(XE::wrapDisplay13TrainingPattern(f.path,1)==kIOReturnNotReady&&!calls); + if(fault!=20&&fault!=21)assert(map.writes.empty()); + } + for(unsigned fault=0;fault<14;++fault){ + Fixture f(2,0,0);Map map;setup(f,map,0,0,4,30);auto &c=display13FBPower; + if(fault==0)c.bridge.ddiRefs[0]=0;if(fault==1)c.bridge.state.suspended=true; + if(fault==2)c.bridge.state.refs[0]=0;if(fault==3)map.regs[0x45454]&=~2U; + if(fault==4)map.regs[0x42000]&=~0x4000000U; + if(fault==5)put(f.port,0x620,map.base+0x804+4);if(fault==6)put(f.port,0x5C8,map.base+0x28+4); + if(fault==7)put(f.access,0x60,map.base+0xB9C); + if(fault==8)map.regs[map.base+0xA9C]=~0U; + if(fault==9)put(f.port,0x124,3U);if(fault==10)put(f.port,0x128,12U); + if(fault==11)map.drop=map.base+0x694; + const auto result=XE::wrapDisplay13SignalLevels(f.port,fault==12?4:0,0,fault==13?f.path:nullptr); + assert(result==kIOReturnNotReady&&!calls);if(fault!=11)assert(map.writes.empty()); + } + {Fixture f(2,0,0);Map map;setup(f,map,0,0,2,10);nativeResult=42; + assert(XE::wrapDisplay13TrainingPattern(f.path,1)==42&&calls==1); + map.writes.clear();loseAfterNative=true;nativeResult=0; + assert(XE::wrapDisplay13TrainingPattern(f.path,2)==kIOReturnNotReady&&calls==2); + if(realPattern)assert(map.writes.size()==1&&map.writes[0]==0x60540);else assert(map.writes.empty());} + // HDMI peer in a mixed wiring must retain native behavior and result. + {Fixture f(2,0,0);Map map;setup(f,map,0,0,2,10);put(f.port,0x548,static_cast(f.platform+0x38));nativeResult=17; + assert(XE::wrapDisplay13SignalLevels(f.port,0,0,nullptr)==17&&calls==1); + assert(XE::wrapDisplay13TrainingPattern(f.path,1)==17&&calls==2&&map.writes.empty());} + // Partial enable keeps AUX until a real stop; failure before enabling can + // unwind immediately. Late idle succeeds; absent idle retains the owner. + for(unsigned ddi=0;ddi<2;++ddi)for(unsigned fault=0;fault<6;++fault){ + Fixture f(0,0,ddi);Map map;setup(f,map,ddi,0,2,10);auto &c=display13FBPower;const auto buffer=0x64000+0x100*ddi; + if(fault<2){linkResult=42;changeBuffer=fault==0;map.regs[buffer]=0x80; + assert(XE::wrapDisplay13EnablePort(f.port)==42&&linkCalls==1); + assert(bool(c.mainLink[ddi].owner)==(fault==0));} + else { + assert(!XE::wrapDisplay13EnablePort(f.port)); + if(fault==2){bufferIdle=false;map.idleReg=buffer;map.idleAfter=4;} + if(fault==3)bufferIdle=false; + if(fault==4)changeBuffer=false; + if(fault==5)linkResult=42; + const auto result=XE::wrapDisplay13DisablePort(f.port); + assert(result==(fault==3||fault==4?kIOReturnNotReady:fault==5?42:0)); + assert(bool(c.mainLink[ddi].owner)==(fault==3||fault==4)); + if(fault==2)assert(map.delay==4);if(fault==3)assert(map.delay==8); + } + changeBuffer=bufferIdle=true;linkResult=0; + if(c.mainLink[ddi].owner)assert(!XE::wrapDisplay13DisablePort(f.port)); + assert(objectRefs.empty()&&!c.mainLink[ddi].owner); + } + // Alien power owner/bank/record must never acquire a main-link reference. + for(unsigned fault=0;fault<6;++fault){ + Fixture f(0,0,0);Map map;setup(f,map,0,0,4,20);auto &c=display13FBPower; + if(fault==0)put(f.ctl,0xC8,static_cast(f.port)); + if(fault==1)put(c.power,0x10,static_cast(f.port)); + if(fault==2)put(c.power,0x78,static_cast(f.port)); + if(fault==3)put(f.port,0x590,0x64100U); + if(fault==4)map.regs[0x64000]=0x80000000; + if(fault==5)map.regs[0x64000]=~0U; + assert(XE::wrapDisplay13EnablePort(f.port)==kIOReturnNotReady&&!linkCalls&&objectRefs.empty()&&map.writes.empty()); + } + // A native success with a lost AUX request is not a successful enable. + {Fixture f(0,0,0);Map map;setup(f,map,0,0,4,20);loseLinkPower=true; + assert(XE::wrapDisplay13EnablePort(f.port)==kIOReturnNotReady&&linkCalls==1&&objectRefs[f.port]==1); + assert(display13FBPower.mainLink[0].owner==reinterpret_cast(f.port)); + loseLinkPower=false;map.regs[0x45444]=3; + assert(!XE::wrapDisplay13DisablePort(f.port)&&objectRefs.empty());} + // Two live Combo DP links share PW1, but neither owns the other's AUX. + for(unsigned first=0;first<2;++first){ + Fixture f(0,0,0);Map map;setup(f,map,0,0,4,20);auto &c=display13FBPower;Display13FBPowerIO io{&map}; + state.adlnOutputWiring.hdmi=0;state.adlnOutputWiring.dp=3; + for(auto &pin:state.adlnOutputWiring.ddc)pin=0; + assert(Display13FBTopology::apply(f.platform,sizeof(f.platform),f.donor,state.adlnOutputWiring)); + alignas(void *) uint8_t peer[sizeof(f.port)];memcpy(peer,f.port,sizeof(peer)); + put(peer,0x120,1U);put(peer,0x548,static_cast(f.platform+0x38));put(peer,0x590,0x64100U);map.regs[0x64100]=0x80; + using Display13Power::Status;assert(c.bridge.getDDI(io,1).status==Status::Complete); + void *ports[]={f.port,peer}; + assert(!XE::wrapDisplay13EnablePort(ports[0])&&!XE::wrapDisplay13EnablePort(ports[1])); + assert(c.bridge.state.refs[8]==1&&c.bridge.state.refs[6]==1&&objectRefs.size()==2); + assert(!XE::wrapDisplay13DisablePort(ports[first])&&c.bridge.putDDI(io,first).status==Status::Complete); + const auto survivor=1-first; + assert(c.mainLink[survivor].owner==reinterpret_cast(ports[survivor])); + assert(map.regs[0x45444]&(survivor?8:2)); + assert(c.bridge.getAUX(io,survivor).status==Status::Complete&&c.bridge.putAUX(io,survivor).status==Status::Complete); + assert(!XE::wrapDisplay13DisablePort(ports[survivor])&&objectRefs.empty()); + } + printf("PASS production Display13 signal/SST: %u compositions after peer release, 39 admission/power/MMIO faults, native error/power-loss/HDMI preservation\n",count); + puts("PASS main-link AUX: transaction independence, 720 repeated-enable/stop cycles, partial failure/idle timeout/foreign owner guards"); + if(image){munmap(image,0x400000);printf("PASS %u exact native pattern/framing calls and unmodified donor counterexamples; %u native port enable/disable calls\n",replays,portReplays);} +} diff --git a/tests/display_backend_dispatch.py b/tests/display_backend_dispatch.py new file mode 100644 index 00000000..cad926a2 --- /dev/null +++ b/tests/display_backend_dispatch.py @@ -0,0 +1,122 @@ +#!/usr/bin/env python3 +"""Replay the production kext dispatcher with independent Render/Display inputs.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('\t// Display dispatch is independent') +b=s.index('\nstatic bool adlnWriterMappingBound();',a) +body=s[a:b].rstrip()[:-1] +kernel_start=s.index('\tif (adlnDetected || display13FramebufferRequested) {') +kernel_end=s.index('\n\tif (tglRequested) {',kernel_start) +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] +code=r''' +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +struct KernelPatcher {}; +struct Kext {size_t loadIndex;}; +bool loadOnly=false; +bool checkKernelArgument(const char *arg) {assert(!strcmp(arg,"-wex-adln-load-only"));return loadOnly;} +struct XE { + bool display13FramebufferRequested=false,adlnRequested=false,adlnDetected=false; + bool adlnNativeRequested=false,adlnNativeReady=false,tglActive=false; + Kext *currentFramebuffer=nullptr,*currentGraphics=nullptr; + enum Call {None,Display13,Render,Rejected,LoadOnly,Display12};Call call=None; + bool processDisplay13Framebuffer(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Display13;return true;} + bool processTGLAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Render;return true;} + bool processAlderLakeNRejectedAccelerator(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Rejected;return true;} + bool processAlderLakeNLoadOnly(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=LoadOnly;return true;} + bool processKextFull(KernelPatcher&,size_t,mach_vm_address_t,size_t){call=Display12;return true;} + int firmware=0,pool=0,bootstrap=0,vm=0,mmio=0; + unsigned tglDeviceId=0; + struct {bool enabled=true;}tglVM; + bool tglMMIOUCRouted=true,tglMMIOUCReady=true; + struct GPU { + unsigned configRead16(unsigned r){assert(r==2);return 0x46d0;} + void setProperty(const char*,bool){} + struct Map {void release(){}}map; + Map *mapDeviceMemoryWithRegister(unsigned r){assert(r==0x10);return ↦} + }gpu; + struct {void processKernel(KernelPatcher&,void*){}}modDVMTCalcFix; + void xelpReadFirmware(GPU*){++firmware;} + void xelpLoadDisplayPool(GPU*){++pool;} + void adlnBootstrap(GPU*){++bootstrap;} + void tglVMPrepare(KernelPatcher&,GPU*){++vm;} + void tglPrepareMMIOUC(KernelPatcher&,GPU*){++mmio;} + void tglUpdateMMIOMap(GPU::Map*){} + void kernel(KernelPatcher&patcher) {auto physicalGPU=&gpu;void *info=nullptr;KERNEL} + bool dispatch(KernelPatcher&patcher,size_t index,mach_vm_address_t address,size_t size) {BODY} +}; +namespace AlderLakeN {enum class GTStatus {Complete,Failed};} +void certificates() { + unsigned args=0; + const auto checkKernelArgument=[&](const char *name) { + if(!strcmp(name,"-allow3d"))return bool(args&1); + if(!strcmp(name,"-disablegfxfirmware"))return bool(args&2); + if(!strcmp(name,"-wex-adln-scanout"))return bool(args&4); + assert(!strcmp(name,"-wex-adln-gpu"));return bool(args&8); + }; + struct PCI {unsigned command;unsigned configRead16(unsigned r){assert(r==4);return command;}}device; + auto pci=&device; + struct Aperture {uint64_t length;uint64_t getLength(){return length;}}bar; + struct {AlderLakeN::GTStatus status;}gt; + for(unsigned mask=0;mask<4096;++mask) { + gt.status=(mask&1)?AlderLakeN::GTStatus::Complete:AlderLakeN::GTStatus::Failed; + unsigned edram=(mask&2)?0:1; + bool prefix=mask&4; + auto aperture=(mask&8)?&bar:nullptr;bar.length=(mask&16)?0x10000000:0x8000000; + unsigned page_size=(mask&32)?4096:16384; + bool physicalOptions=mask&64;device.command=(mask&128)?6:2; + args=mask>>8; + const bool renderReady=RENDER; + const bool memoryReady=MEMORY; + const bool adlnNativeReady=COMBINED; + // All eight platform prerequisites + both required arguments, no diagnostics. + assert(adlnNativeReady==(mask==0x3ff)); + } + puts("PASS 4096 shared preflight combinations retain every admission prerequisite"); +} +int main() { + certificates(); + Kext fb{1},graphics{2};KernelPatcher patcher;unsigned cases=0; + // Removing/rejecting Render must not redirect a selected Display13 kext + // through the Display12 frame, nor make a graphics kext run Display code. + for(unsigned render=0;render<4;++render)for(bool display:{false,true}) + for(bool framebuffer:{false,true})for(unsigned index=1;index<=3;++index) { + XE x;x.currentFramebuffer=framebuffer?&fb:nullptr;x.currentGraphics=&graphics; + x.display13FramebufferRequested=display;x.adlnRequested=x.adlnDetected=x.adlnNativeRequested=render!=0; + x.adlnNativeReady=x.tglActive=render>=2;loadOnly=render==3; + const bool result=x.dispatch(patcher,index,0x1000,0x2000); + if(display && framebuffer && index==1)assert(result && x.call==XE::Display13); + else if(!render)assert(result && x.call==XE::Display12); + else if(index!=2)assert(!result && x.call==XE::None); + else assert(result && x.call==(render==1?XE::Rejected:render==2?XE::Render:XE::LoadOnly)); + ++cases; + } + for(bool render:{false,true})for(bool display:{false,true})for(bool ready:{false,true}) { + XE x;x.adlnDetected=render;x.display13FramebufferRequested=display; + x.adlnNativeRequested=render;x.adlnNativeReady=ready;x.kernel(patcher); + assert(x.firmware==int(render||display) && x.pool==x.firmware && x.bootstrap==x.firmware); + assert(x.vm==int(render&&ready) && x.mmio==x.vm); + } + // Missing graphics object and missing native request never bypass rejection. + for(bool detected:{false,true}) { + XE x;x.adlnRequested=true;x.adlnDetected=detected;x.adlnNativeRequested=true; + assert(!x.dispatch(patcher,2,0x1000,0x2000) && x.call==XE::None); + x.currentGraphics=&graphics;x.adlnNativeRequested=false; + assert(!x.dispatch(patcher,2,0x1000,0x2000) && x.call==XE::None); + } + printf("PASS %u independent Display/Render dispatch cases, native preflight rejection and load-only routing\n",cases); +} +'''.replace('BODY',body).replace('KERNEL',kernel).replace('RENDER',expression('renderReady')).replace('MEMORY',expression('memoryReady')).replace('COMBINED',expression('adlnNativeReady')).replace('#include ','#include \n#include ') +with tempfile.TemporaryDirectory(prefix='wex-display-dispatch-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-g','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/display_contract_adapter.py b/tests/display_contract_adapter.py new file mode 100644 index 00000000..a1800d1b --- /dev/null +++ b/tests/display_contract_adapter.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Exercise production hook inventory and route-to-contract adapter, without IOKit.""" +from pathlib import Path +import re,subprocess,tempfile +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +methods=[re.search(r'XELP::DisplayContract::Mask XE::display'+n+r'Available\(\) const \{.*?\n\}',s,re.S).group().replace('XE::','') for n in ('12','13')] +fields=re.findall(r' if\((\w+)\)a\|=', '\n'.join(methods)) +# Real callback invocation inside start, through to the immutable contract. +begin=s.index(' // Evaluate firmware candidates, not only currently registered/live ports:') +end=s.index(' uint64_t contractRecord[]',begin) +adapter=s[begin:end].replace('auto self=callbackXE;','auto self=this;')+'return contract;' +code=r''' +#include "kern_xelp_display_contract.hpp" +#include +#include +using C=XELP::DisplayContract; +struct State { +FIELDS + struct Native {bool valid=true;bool complete()const{return valid;}}tglTCNativeFunctions; + bool tglHDMIB=false,adlsDisplaySelected=false,tglTrainingLeaseRequested=true; + struct {struct {bool valid=false;}panel;}firmwarePorts; + struct {unsigned dp=0;}adlsWiring; + struct {unsigned legacy=0;}tglTCWiring; + unsigned tglTCAutoCandidates=0,tglDPAltCandidates=0,tglComboDPMask=0; +METHODS + C start() const {ADAPTER} +}; +int main() { + State s; + assert(s.display12Available()==((1ULL<<39)-1|C::ClockOrder|C::ComboAUXGate|C::PanelDepth|C::PanelTiming|C::PanelPWM)); + assert(s.display13Available()==(C::Backend13|C::SST13|C::AUX13|C::DPProbe13)); + assert(C::display13(s.display13Available(),true).missing()==C::DPBoot13); + s.display13DPProbeReady=false; + assert(C::display13(s.display13Available()).ready()); // Existing provider/HDMI stays valid. + assert(C::display13(s.display13Available(),true).missing()==(C::DPBoot13|C::DPProbe13)); + s.display13DPProbeReady=true;s.display13FBAUXHooksReady=false; + assert(C::display13(s.display13Available(),true).missing()==(C::DPBoot13|C::AUX13)); + s.display13FBAUXHooksReady=true;s.display13FBSSTHooksReady=false; + assert(C::display13(s.display13Available(),true).missing()==(C::DPBoot13|C::SST13)); + s.display13FBSSTHooksReady=true; + // TGL: candidate not yet registered, native donor setup is complete. + // Missing framing, AUX, mode lock or cleanup must fail before any hotplug. + s.tglTCAutoCandidates=8;assert(s.start().ready()); + s.tglSinkFramingReady=false;assert(s.start().missing()==C::Framing);s.tglSinkFramingReady=true; + s.tglAUXParentReady=false;assert(s.start().missing()==C::AUXParent);s.tglAUXParentReady=true; + s.tglModeTransitionLock=false;assert(s.start().missing()==C::ModeLock);s.tglModeTransitionLock=true; + s.tglClockOffCaller=false;assert(s.start().missing()==C::ClockOrder);s.tglClockOffCaller=true; + s.tglTCAutoCandidates=0;s.tglDPAltCandidates=8; + s.tglDPAltFiaReady=false;assert(s.start().missing()==C::AltFIA);s.tglDPAltFiaReady=true; + s.tglAUXParentReady=false;assert(s.start().ready()); // TC owns DP-alt AUX power. + s.tglAUXParentReady=true;s.tglDPAltCandidates=0; + s.adlsDisplaySelected=true;s.adlsWiring.dp=1; + s.tglComboSignalReady=false;assert(s.start().missing()==C::ComboSignal); + s.tglComboSignalReady=true; + s.adlsDisplaySelected=false;s.adlsWiring.dp=0;s.firmwarePorts.panel.valid=true; + s.tglTCNativeFunctions.valid=false;assert(s.start().missing()==C::TCNative);s.tglTCNativeFunctions.valid=true; + s.tglComboAUXGateReady=false;assert(s.start().missing()==C::ComboAUXGate);s.tglComboAUXGateReady=true; + s.tglPanelDepthReady=false;assert(s.start().missing()==C::PanelDepth);s.tglPanelDepthReady=true; + s.tglPanelTimingReady=false;assert(s.start().missing()==C::PanelTiming); + return 0; +} +''' +code=code.replace('FIELDS','\n'.join(' bool '+f+'=true;' for f in fields)).replace('METHODS','\n'.join(methods)).replace('ADAPTER',adapter) +with tempfile.TemporaryDirectory(prefix='display-contract-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) +print('PASS production startup inventory: future hotplug, framing, AUX, mode, cleanup and DP-alt dependency failures') diff --git a/tests/firmware_ports.cpp b/tests/firmware_ports.cpp index d77e23bf..206cab7e 100644 --- a/tests/firmware_ports.cpp +++ b/tests/firmware_ports.cpp @@ -20,7 +20,8 @@ std::vector table(const std::vector&ports){ } int main(int argc,char **argv){ if(argc==3){std::ifstream f(argv[2],std::ios::binary);assert(f);std::vectorb{std::istreambuf_iterator(f),{}}; - auto r=parse(b.data(),b.size(),!strcmp(argv[1],"12")?Family::TigerLake:Family::AlderLakeN);auto w=r.hdmi(3); + const auto family=!strcmp(argv[1],"adls")?Family::AlderLakeS:!strcmp(argv[1],"12")?Family::TigerLake:Family::AlderLakeN; + auto r=parse(b.data(),b.size(),family);auto w=r.hdmi(3); printf("status=%u bdb=%u present=%x unmapped=%u ambiguous=%x hdmi=%x pins=%u,%u,%u valid=%u\n",unsigned(r.status),r.version,r.present,r.unmapped,r.ambiguous,w.hdmi,w.ddc[0],w.ddc[1],w.ddc[2],w.valid); for(unsigned d=0;d<9;++d)if(r.present&(1U< #include #include "kern_xelp_platform.hpp" -std::vector events;bool publish=true,published=false;uint32_t identity=0x9a498086; +std::vector events;bool publish=true,published=false;uint32_t identity=0x9a498086;uint8_t revision=0; struct Object {virtual ~Object()=default;}; struct OSData:Object {uint32_t value=0;void *getBytesNoCopy(){return &value;}size_t getLength(){return 4;}}; OSData fake;Object customModel;bool hasModel=false;unsigned modelWrites=0;Object *deviceProperty=nullptr; struct IOPCIDevice { const char *name="GFX0";unsigned renames=0; uint32_t configRead32(int){assert(published);events.push_back(2);return identity;} - uint8_t configRead8(int){assert(published);events.push_back(3);return 0;} + uint8_t configRead8(int){assert(published);events.push_back(3);return revision;} uint16_t configRead16(int){assert(published);events.push_back(4);return 0x300;} const char *getName(){return name;}Object *getProperty(const char *key){return !strcmp(key,"device-id")?deviceProperty:!strcmp(key,"model")&&hasModel?&customModel:nullptr;} void setProperty(const char *,const char *){}void setProperty(const char *key,const void *value,unsigned size){if(!strcmp(key,"model")){assert(value && size>1);++modelWrites;}}void setProperty(const char *,uint64_t,unsigned){} @@ -63,6 +63,13 @@ else if(test==2 || test==5){assert(!host.active&&graphics.off);assert(events.size()==4);} else {assert(host.active&&!graphics.off);assert(events.front()==1);assert(events[events.size()-2]==8);} } +for(unsigned rev:{0U,12U,255U})for(bool apple:{false,true}) { + DeviceInfo::value.videoBuiltin=&gpu;DeviceInfo::value.managementEngine=nullptr; + DeviceInfo::value.firmwareVendor=apple?DeviceInfo::FirmwareVendor::Apple:DeviceInfo::FirmwareVendor::Other; + publish=true;published=false;deviceProperty=nullptr;identity=0x46908086;revision=rev;events.clear(); + XeHost host;host.processKernel(patcher);assert(host.active); + assert(host.backlightEnabled==!apple);assert(host.backlightGPU==(!apple?&gpu:nullptr)); +} puts("PASS actual host admission: absent/unpublished/unsupported GPUs never enter Xe; published PCI reads precede selected-driver dispatch");} ''' with tempfile.TemporaryDirectory(prefix='wxe-host-') as d: diff --git a/tests/kernel_os_updates.cpp b/tests/kernel_os_updates.cpp index 7c464324..faf7d820 100644 --- a/tests/kernel_os_updates.cpp +++ b/tests/kernel_os_updates.cpp @@ -3,6 +3,10 @@ int main() { using namespace XELP; + assert(!hostOS(OS::Unknown) && !hostOS(OS::Donor)); + for (unsigned os=unsigned(OS::BigSur);os<=unsigned(OS::Tahoe);++os) + assert(hostOS(static_cast(os))); + assert(!hostOS(static_cast(unsigned(OS::Tahoe)+1))); assert(kernelOS(nullptr)==OS::Unknown); assert(kernelOS("")==OS::Unknown); assert(kernelOS("Darwin Kernel Version 24.6.1: update/RELEASE_X86_64")==OS::Sequoia); diff --git a/tests/provider_binding.py b/tests/provider_binding.py index ff23ed1c..f67ba41d 100644 --- a/tests/provider_binding.py +++ b/tests/provider_binding.py @@ -22,5 +22,5 @@ "-fno-omit-frame-pointer", "-I" + str(root.parent / "AppleIntelTGLCompat"), "-I" + str(root.parent / "WhateverXe"), str(cpp), "-o", str(exe)], check=True) - for mode in ["tgl", "tgl-9a40", "tgl-g4", "adln", "adln-16eu", "adln-abi1", "unknown", "adln-revision", "adln-fake", "tgl-fake", "adln-abi1-fake"]: + for mode in ["tgl", "tgl-9a40", "tgl-g4", "adln", "adln-16eu", "adln-abi1", "unknown", "adln-revision", "adln-fake", "tgl-fake", "adln-abi1-fake", "adls", "adls-revision", "rpls", "rpls-revision"]: subprocess.run([str(exe), mode], check=True) diff --git a/tests/provider_binding_fixture.cpp b/tests/provider_binding_fixture.cpp index b941097b..64720428 100644 --- a/tests/provider_binding_fixture.cpp +++ b/tests/provider_binding_fixture.cpp @@ -25,13 +25,24 @@ struct IOMemoryMap:OSObject {uintptr_t address {};uint64_t bytes {}; #define IOLog(...) ((void)0) // PROVIDER_SOURCE int main(int argc,char **argv) { + assert(XELP::display13ProbeCapable(xelpProbeCapabilities)); + assert(!XELP::display13ProbeCapable(nullptr)); + for(unsigned schema=0;schema<5;++schema)for(unsigned abi=0;abi<6;++abi) + assert(xelpProbeCapabilities(schema,abi)==(schema==1&&abi==3?XELP::ProbeDisplay13ComboDP:0)); + assert(!XELP::display13ProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 0;})); + assert(!XELP::display13ProbeCapable(+[](uint32_t,uint32_t)->uint32_t{return 2;})); assert(argc==2);const bool adln=!strncmp(argv[1],"adln",4); + const bool rpls=!strncmp(argv[1],"rpls",4); + const bool adls=!strncmp(argv[1],"adls",4) || rpls; const bool legacy=!strncmp(argv[1],"adln-abi1",9); const bool fake=strstr(argv[1],"fake")!=nullptr; - const bool rejected=!strcmp(argv[1],"unknown") || !strcmp(argv[1],"adln-revision"); + const bool rejected=!strcmp(argv[1],"unknown"); IOService controller;IOPCIDevice pci;OSObject access,port,foreign;IOMemoryMap map; std::vector mmio(0x1000000);alignas(8) uint8_t platform[0x1C0] {}; pci.identity=adln?0x46D18086:0x9A498086; + if(adls){pci.identity=0x46908086;pci.revision=12;} + if(rpls){pci.identity=0xA7808086;pci.revision=!strcmp(argv[1],"rpls-revision")?8:4;} + if(!strcmp(argv[1],"adls-revision"))pci.revision=0; if(!strcmp(argv[1],"tgl-g4"))pci.identity=0x9A788086; if(!strcmp(argv[1],"tgl-9a40"))pci.identity=0x9A408086; if(!strcmp(argv[1],"adln-16eu"))pci.identity=0x46D28086; @@ -51,17 +62,21 @@ int main(int argc,char **argv) { field(&port,0x548)=initial; assert(!tglProbePort(&port)); assert(!hardwareAllowed(nullptr));IOService orphan;assert(!hardwareAllowed(&orphan)); - const bool physicalTGL=!adln && !rejected; - assert(hardwareAllowed(&controller)==physicalTGL); + const bool physicalTGL=!adln && !adls && !rejected; + assert(hardwareAllowed(&controller)==(!adln && !rejected)); assert(tglProbePort(&port)==(physicalTGL?1U:0U)); field(&port,0x578)=&foreign;assert(!tglProbePort(&port));field(&port,0x578)=&access; XELP::ProbeConfig config;config.profile=XELP::deviceProfile(&pci); xelpSelectedGPU=&pci;xelpSelectedProfile=config.profile; assert(xelpSelectedDevice(&pci)==config.profile.admitted()); assert(xelpAlderLakeNDevice(&pci)==(adln && !rejected)); + assert(xelpDisplay13Device(&pci)==(adln && !rejected)); IOPCIDevice foreignPCI;foreignPCI.identity=pci.identity;foreignPCI.revision=pci.revision;foreignPCI.property=pci.property; assert(!xelpSelectedDevice(&foreignPCI)); + assert(!xelpDisplay13Device(&foreignPCI)); config.combo=3;config.comboBackend=adln?3:7;config.legacy=adln?0:0x1F8;config.legacyBackend=adln?0:0x1F8; + if(adls){config.combo=8;config.comboBackend=0x19;config.legacy=config.legacyBackend=0;} + if(adls && !rejected){assert(hardwareAllowed(&controller));assert(!tglProbePort(&port));} // The contract derives generation from physical identity, not boot args. assert(!xelpConfigurePortProbe(&controller,2,3,3)); if(!adln)assert(!xelpConfigurePortProbe(&controller,1,3,3)); @@ -92,14 +107,19 @@ int main(int argc,char **argv) { malformed=config;malformed.combo=1;assert(!xelpConfigureDisplay(&controller,&malformed)); if(adln){assert(xelpConfigurePortProbe(&controller,1,3,3));assert(!xelpConfigurePortProbe(&controller,2,3,3));} for(unsigned d=0;d<9;++d){auto rec=reinterpret_cast(platform+0x20+24*d); - const uint32_t values[]={d,d+1,2,0,0x800,0x202};memcpy(rec,values,sizeof(values)); + const uint32_t values[]={d,adls?9:d+1,2,0,0x800,0x202};memcpy(rec,values,sizeof(values)); field(&port,0x548)=rec; if(d>=3){*reinterpret_cast(mmio.data()+0xC4000)=1U<<(24+d-3); *reinterpret_cast(mmio.data()+0x1638A0)=0x10; const uint32_t bases[]={0x163000,0x16E000,0x16F000};const auto base=bases[(d-3)/2]; *reinterpret_cast(mmio.data()+base+0x8A0)=0x10; *reinterpret_cast(mmio.data()+base+0x890)=1U<<((d-3)%2);} - assert(tglProbePort(&port)==(adln?(d<2?1U:0U):(d<3?1U:4U))); + assert(tglProbePort(&port)==(adls?(d==3?1U:0U):adln?(d<2?1U:0U):(d<3?1U:4U))); + if(adln){ + const uint32_t dp[]={d,d+1,1,0,0x400,0x201};memcpy(rec,dp,sizeof(dp)); + assert(tglProbePort(&port)==(d<2?1U:0U)); + if(d<2)for(unsigned n=0;n<6;++n){rec[n]^=1;assert(!tglProbePort(&port));rec[n]^=1;} + } field(&port,0x578)=&foreign;assert(!tglProbePort(&port));field(&port,0x578)=&access; field(&port,0x548)=reinterpret_cast(1);assert(!tglProbePort(&port)); } diff --git a/tests/small_gt_urb.py b/tests/small_gt_urb.py new file mode 100644 index 00000000..88dd24d5 --- /dev/null +++ b/tests/small_gt_urb.py @@ -0,0 +1,64 @@ +#!/usr/bin/env python3 +"""Replay production URB patch selection and its native store on CPU, no GPU.""" +import hashlib,re,subprocess,sys,tempfile +from pathlib import Path +r=Path(__file__).resolve().parents[1] +s=(r/'WhateverXe/kern_xe.cpp').read_text() +a=s.index('static const uint8_t urbFind[]');b=s.index('auto gpuInfo=',a) +body=s[a:b] +if len(sys.argv)>1: + donor=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest()=='bd1a21f5e622aadabdab6441f6b5c38ed73fa3053c783fdf0fb7e4b309f273d0' + old=bytes(int(x,0) for x in re.search(r'urbFind\[\] \{([^}]+)',body)[1].split(',')) + assert donor[0x28781:0x28781+len(old)]==old +code=r'''#include "kern_xelp_platform.hpp" +#include "kern_xelp_gt_resources.hpp" +#include +#include +#include +#include +#include +void check(unsigned id,unsigned rev,unsigned dss,unsigned eu,unsigned media) { + auto xeProfile=XELP::identify((id<<16)|0x8086,rev); + bool display12SmallGTDetected=XELP::display12SmallGT(xeProfile.gt); + XELP::SmallGTResources display12SmallGT; + uint32_t fuses[]={1,dss,eu,0xf0,media}; + assert(xeProfile.admitted() && display12SmallGTDetected && XELP::decodeSmallGT(fuses,display12SmallGT)); + bool adlnDetected=false;unsigned adlnNativeSubslices=0,adlnL3Banks=0; + BODY + assert(urbReplacement==urbReplaceAlderLakeN); + auto program=(unsigned char *)mmap(nullptr,4096,PROT_READ|PROT_WRITE|PROT_EXEC,MAP_PRIVATE|MAP_ANON,-1,0); + assert(program!=MAP_FAILED);memset(program,0xcc,4096); + const unsigned char prefix[]={0x41,0x57,0x49,0x89,0xff}; // save R15, destination from RDI + const unsigned char done[]={0x41,0x5f,0xc3}; + memcpy(program,prefix,5);memcpy(program+5,urbReplacement,sizeof(urbFind)); + memcpy(program+5+sizeof(urbFind),done,3);memcpy(program+5+94,done,3); + unsigned char state[0x1200]; + for(unsigned slice:{0U,1U,2U})for(unsigned ss:{0U,2U,4U,6U,10U,12U}) { + memset(state,0xa5,sizeof(state)); + reinterpret_cast(program)(state,slice,ss); + unsigned result;memcpy(&result,state+0x1164,4); + assert(result==((slice==1 && ss==display12SmallGT.nativeSubslices)?4U:0xa5a5a5a5U)); + for(unsigned i=0;i=0x1168)assert(state[i]==0xa5); + } + munmap(program,4096); +} +int main() { + // Three GT1 identities and 16/32-EU HX identities use fuse-derived topology. + for(auto id:{0x9a60U,0x9a68U,0x9a70U,0x4688U,0x468bU,0xa788U,0xa78bU}) + for(auto rev:{0U,1U,4U,12U,255U})for(auto video:{0xe00feU,0xe00faU}) { + check(id,rev,1,0,video); // 16 EU, one DSS, two native subslices + check(id,rev,3,0xc0,video); // 24 EU, two DSS + check(id,rev,0x30,0,video); // 32 EU, sparse DSS mask + } + XELP::SmallGTResources resource; + assert(!XELP::decodeSmallGT(nullptr,resource)); + for(auto banks:{0U,0xe0U,0xfcU,0xffU}) { + uint32_t f[]={1,3,0,banks,0xe00fe};assert(!XELP::decodeSmallGT(f,resource)); + } + puts("PASS TGL GT1/HX production patch selection, fuse shapes and 3780 native URB stores/rejections"); +}'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='small-gt-urb-') as tmp: + p=Path(tmp);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/tgl_agdc_aux_observer.py b/tests/tgl_agdc_aux_observer.py new file mode 100644 index 00000000..0ada9aeb --- /dev/null +++ b/tests/tgl_agdc_aux_observer.py @@ -0,0 +1,48 @@ +#!/usr/bin/env python3 +"""The production AUX observer must never inject a port-specific transaction.""" +from pathlib import Path +import re, subprocess, tempfile, sys +r=Path(__file__).resolve().parents[1] +s=(Path(sys.argv[1]) if len(sys.argv)>1 else r/'WhateverXe/kern_xe.cpp').read_text() +body=re.search(r'uint32_t XE::wrapTGLAGDCAUX\(.*?\n\}',s,re.S).group() +code=r''' +#include +#include +#include +#include +struct XE {uintptr_t orgTGLAGDCAUX;static uint32_t wrapTGLAGDCAUX(void*,void*,void*);}; +XE state,*callbackXE=&state; +#define FunctionCast(f,p) reinterpret_cast(p) +struct IORegistryEntry { + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry e;return &e;} + templatevoid setProperty(A...){} +}; +templatevoid tglTrace(A...){} +void clock_get_uptime(uint64_t *v){*v=60000000000ULL;} +void absolutetime_to_nanoseconds(uint64_t v,uint64_t *n){*n=v;} +unsigned calls,result;void *expectedRequest,*expectedOutput; +uint32_t native(void *c,void *request,void *output){ + assert(c==&state && request==expectedRequest && output==expectedOutput); + assert(++calls==1);return result; +} +BODY +int main(){ + state.orgTGLAGDCAUX=reinterpret_cast(native); + for(unsigned ddi=0;ddi<10;++ddi)for(unsigned write=0;write<2;++write) + for(unsigned error=0;error<2;++error)for(unsigned length=0;length<32;++length){ + alignas(8) uint8_t request[64]{},output[64]{},before[64]{}; + request[0]=ddi;request[9]=2;request[12]=length;request[13]=write; + memcpy(before,request,sizeof(request));expectedRequest=request;expectedOutput=output; + calls=0;result=error?0xe00002d6:0; + assert(XE::wrapTGLAGDCAUX(&state,request,output)==result); + assert(calls==1&&!memcmp(before,request,sizeof(request))); + } + calls=0;expectedRequest=expectedOutput=nullptr; + assert(XE::wrapTGLAGDCAUX(&state,nullptr,nullptr)==result&&calls==1); + puts("PASS 1280 AUX observer cases: one native call, original arguments/result, no port-specific probe"); +} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='tgl-aux-observer-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined',str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/tgl_agdc_port_status.py b/tests/tgl_agdc_port_status.py new file mode 100644 index 00000000..98c521fa --- /dev/null +++ b/tests/tgl_agdc_port_status.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +"""Production AGDC stale status policy across physical ports and wiring sources.""" +from pathlib import Path +import re,subprocess,tempfile,sys,hashlib +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(b).hexdigest()=='c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + # Exact native GetPortStatus: lookup(id-1), state>1, write output+8. + for offset,expected in [(0x6a9fa,'8b3effcfe89907fbff'),(0x6aa11,'83b854050000010f9fc1'),(0x6aa22,'894b08')]: + assert b[offset:offset+len(bytes.fromhex(expected))]==bytes.fromhex(expected) +body=re.search(r'uint32_t XE::wrapTGLAGDCPortStatus\(.*?\n\}',s,re.S).group() +code=r''' +#include "kern_vbt.hpp" +#include "kern_tgl_tc_boot.hpp" +#include +#include +#include +struct XE { + uintptr_t orgTGLAGDCPortStatus{},tglPortByIndex{};bool tglInactivePorts=true,adlsDisplaySelected=false; + bool tglTCWiringLoaded=true,tglTCWiringExplicit=false; + TGLTCWiring tglTCWiring=TGLTCWiring::fromMasks(0,0,0);XELP::VBT::Ports firmwarePorts; + struct {XELP::Family family=XELP::Family::TigerLake;}xeProfile; + void *tglObservationController{};alignas(8) uint8_t tglPlatformCopy[0x200]{}; + static uint32_t wrapTGLAGDCPortStatus(void*,void*,void*); +} state,*callbackXE=&state; +#define FunctionCast(f,p) reinterpret_cast(p) +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry e;return &e;} + templatevoid setProperty(A...){} }; +templatevoid tglTrace(A...){} +void clock_get_uptime(uint64_t *v){*v=1;}void absolutetime_to_nanoseconds(uint64_t v,uint64_t *n){*n=v;} +alignas(8) uint8_t ports[9][0x600]{},ctl[0xD50]{}; +unsigned calls=0,lookups=0,nativeStatus=1,nativeResult=0; +void *get(unsigned index){assert(index<9);++lookups;return ports[index];} +uint32_t native(void*,void*,void *out){++calls;if(out)memcpy(static_cast(out)+8,&nativeStatus,4);return nativeResult;} +templatevoid put(void *base,unsigned at,T value){memcpy(static_cast(base)+at,&value,sizeof(value));} +BODY +int main() { + unsigned cases=0; + for(unsigned ddi=0;ddi<9;++ddi)for(unsigned mode=0;mode<5;++mode)for(unsigned status=0;status<4;++status) + for(unsigned sinks=0;sinks<2;++sinks)for(unsigned wiring=0;wiring<8;++wiring){ + state={};memset(ports,0,sizeof(ports));state.orgTGLAGDCPortStatus=reinterpret_cast(native); + state.tglPortByIndex=reinterpret_cast(get);state.tglObservationController=ctl; + state.firmwarePorts.status=XELP::VBT::Status::Valid; + state.firmwarePorts.present=1; // declared panel; no other child. + if(wiring==1)state.firmwarePorts.present|=1U<(state.tglPlatformCopy+0x20+24*ddi); + c[0]=ddi;c[1]=ddi+6;c[2]=1;put(ports[ddi],0x548,c);put(ports[ddi],0x550,mode);put(ports[ddi],0x554,status); + ports[ddi][0xF8]=sinks;put(ctl,0xD40,state.tglPlatformCopy+0); + alignas(8) uint32_t req[4]={ddi+1},out[4]={}; + uint8_t before[sizeof(ports)];memcpy(before,ports,sizeof(ports));calls=lookups=0; + assert(!XE::wrapTGLAGDCPortStatus(ctl,req,out)&&calls==1); + const bool absent=ddi>=3&&(wiring==0||wiring==5); + assert(out[2]==(absent&&mode==0&&status==2&&!sinks?0u:1u)); + assert(!memcmp(before,ports,sizeof(ports))); // no mode/state/power mutations. + assert(lookups==unsigned(absent));++cases; + } + // Exact controller/config association is mandatory even for excluded wiring. + for(unsigned bad=0;bad<7;++bad) { + state={};state.orgTGLAGDCPortStatus=reinterpret_cast(native);state.tglPortByIndex=reinterpret_cast(get); + state.tglObservationController=ctl;state.tglTCWiringExplicit=true;state.tglTCWiring=TGLTCWiring::fromMasks(0,0,0x1f8); + auto c=reinterpret_cast(state.tglPlatformCopy+0x20+24*5);c[0]=5;c[1]=11;c[2]=1; + memset(ports[5],0,sizeof(ports[5]));put(ports[5],0x548,c);put(ports[5],0x554,2u);put(ctl,0xD40,state.tglPlatformCopy+0); + uint32_t copy[6];memcpy(copy,c,sizeof(copy)); + if(bad==0)put(ports[5],0x548,copy+0); + if(bad==1)state.tglObservationController=nullptr; + if(bad==2)state.xeProfile.family=XELP::Family::AlderLakeN; + if(bad==3)c[0]=4; + if(bad==4)c[1]=10; + if(bad==5)c[2]=0; + if(bad==6)state.tglInactivePorts=false; + uint32_t req[4]={6},out[4]={};calls=0; + assert(!XE::wrapTGLAGDCPortStatus(ctl,req,out)&&out[2]==1&&calls==1); + } + printf("PASS %u port/mode/state/sink/wiring combinations plus binding guards; native state untouched\n",cases); +} +'''.replace('BODY',body) +with tempfile.TemporaryDirectory(prefix='agdc-status-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/tgl_clock_order.py b/tests/tgl_clock_order.py index 799ee4d3..5eb8a550 100644 --- a/tests/tgl_clock_order.py +++ b/tests/tgl_clock_order.py @@ -13,7 +13,9 @@ #define FunctionCast(f,p) reinterpret_cast(p) void *current_thread(){return (void*)1;}unsigned delays=0,writes=0,natives=0;uint32_t lastReg; void IODelay(unsigned n){assert(n==8);++delays;} +struct IORegistryEntry {static IORegistryEntry *getRegistryRoot(){static IORegistryEntry v;return &v;}void setProperty(const char*,bool){} }; struct XE { + bool adlsDisplaySelected=false;static bool adlsClockTarget(void*,void*,void*){return false;} void *tglObservationController;uintptr_t tglClockOffCaller=1,orgTGLPathLookup,orgTGLDisableClocks; bool tglDPSSTMode(unsigned d,unsigned mode){return d>=3&&d<=8&&(mode==3||mode==4);} static volatile uint8_t *tglClockOrderTarget(void*,void*,void*,void*); @@ -50,6 +52,7 @@ tglClockOffTransaction.path=d;tglClockOffTransaction.access=a;tglClockOffTransaction.clockReg=0x46148; tglClockOffTransaction.value=0x123;tglClockOffTransaction.pending=true; writes=0;XE::wrapTGLDisableClocks(c,p,nullptr);assert(writes==1&®(0x46148)==0x123&&!tglClockOffTransaction.pending); + obj.adlsDisplaySelected=true;natives=writes=0;XE::wrapTGLDisableClocks(c,p,nullptr);assert(!natives&&!writes); printf("PASS %u actual clock-order port/pipe/idle cases; invalid pipe and deferred native-write fallback\n",cases); } '''.replace('TX',tx).replace('FUNCTIONS',f) diff --git a/tests/tgl_combo_aux_lifetime.py b/tests/tgl_combo_aux_lifetime.py index b0e1065e..c14e19e0 100644 --- a/tests/tgl_combo_aux_lifetime.py +++ b/tests/tgl_combo_aux_lifetime.py @@ -7,55 +7,68 @@ if len(sys.argv)>1: b=Path(sys.argv[1]).read_bytes();assert b[0x5fc56:0x5fc5f]==bytes.fromhex('554889e553504889fb');assert b[0x5fc6d:0x5fc73]==bytes.fromhex('8bb790050000') code=r''' -#include "kern_vbt.hpp" +#include "kern_alderlake_s.hpp" +#include "kern_tgl_combo_dp.hpp" +#include "kern_xelp_sst_route.hpp" #include #include #include struct Functions {bool ready=true;void **controllerSlot=nullptr;void(*getAux)(void*)=nullptr;void(*putAux)(void*)=nullptr;bool complete(){return ready;}}; -struct State {void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; +struct State {uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{};void *tglObservationController=nullptr;bool tglActive=true,tglComboAUXLifetimeReady=true; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; - XELP::VBT::Ports firmwarePorts;Functions tglTCNativeFunctions;void *tglComboAUXOwners[2]{}; + XELP::VBT::Ports firmwarePorts;Functions tglTCNativeFunctions;void *tglComboAUXOwners[9]{}; } state,*callbackXE=&state; std::map regs;unsigned getCount,putCount,reads;bool missingAck;uint32_t *refs; uint32_t wrapTGLRegisterRead(void *,unsigned long reg){++reads;return regs[reg];} void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==74);} FUNCTION int main(){ - for(unsigned bad=0;bad<15;++bad){ - alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},power[0x100]{},access[0x100]{};uint32_t config[4]{}; + for(unsigned testDDI:{0u,1u,3u,4u})for(unsigned kind=0;kind<4;++kind)for(unsigned bad=0;bad<15;++bad){ + const bool adls=kind==1||kind==3,fixed=kind==2;const bool panel=kind==3;if(panel && testDDI!=0)continue;if(adls?testDDI==1:testDDI>1)continue; + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},power[0x100]{},access[0x100]{};uint32_t local[6]{};uint32_t *config=local; + unsigned ddi=testDDI,mask=3U<<(2*ddi),buffer=0x64000+256*ddi; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; - state={};state.tglObservationController=ctl;void *global=ctl; + state={};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)); + } + if(panel){state.adlsWiring.dp=0;state.adlsWiring.panel=1;const uint32_t c[]={0,0,0,0,2,0x18};memcpy(config,c,sizeof(c));} + if(fixed) { + state.tglComboDPMask=1U<(state.tglPlatformCopy+0x20+24*ddi); + const uint32_t c[]={ddi,ddi+1,1,0,0x400,0x201};memcpy(config,c,sizeof(c)); + } else if(!adls)config[0]=ddi; + state.tglObservationController=ctl;void *global=ctl; state.tglTCNativeFunctions.controllerSlot=&global; - state.firmwarePorts.status=XELP::VBT::Status::Valid;state.firmwarePorts.present=1;state.firmwarePorts.port[0].type=0x1806; - store(p,0x548,config+0);store(p,0x550,1U);store(p,0x578,access+0);store(p,0x590,0x64000U); + state.firmwarePorts.status=XELP::VBT::Status::Valid;state.firmwarePorts.present=1U<(power+0x54);*refs=2;getCount=putCount=reads=0;missingAck=bad==14; - regs.clear();regs[0x45454]=3;regs[0x64000]=0x81; - state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?2:3;}; + refs=reinterpret_cast(power+0x54+4*ddi);*refs=2;getCount=putCount=reads=0;missingAck=bad==14; + regs.clear();regs[0x45454]=mask;regs[buffer]=0x81; + state.tglTCNativeFunctions.getAux=[](void*){++*refs;++getCount;regs[0x45444]=missingAck?0:regs[0x45454];}; state.tglTCNativeFunctions.putAux=[](void*){--*refs;++putCount;}; if(bad==1)state.tglActive=false; if(bad==2)state.tglComboAUXLifetimeReady=false; if(bad==3)state.tglTCNativeFunctions.ready=false; if(bad==4)global=nullptr; - if(bad==5)config[0]=3; + if(bad==5)config[0]=2; if(bad==6)config[2]=2; if(bad==7)state.firmwarePorts.present=0; if(bad==8)store(power,0x78,ctl+0); - if(bad==9)store(p,0x590,0x64100U); + if(bad==9)store(p,0x590,buffer+0x100); if(bad==10)regs[0x45454]=0; - if(bad==11)regs[0x64000]=0x80000001; - if(bad==12)state.xeProfile.family=XELP::Family::AlderLakeN; + if(bad==11)regs[buffer]=0x80000001; + if(bad==12){state.xeProfile.family=XELP::Family::AlderLakeN;state.adlsDisplaySelected=false;} if(bad==13)*refs=~0U; auto before=*refs;run(p,true); - if(bad){assert(*refs==before&&!state.tglComboAUXOwners[0]);assert(getCount==(bad==14));assert(putCount==(bad==14));continue;} - assert(getCount==1&&*refs==3&&state.tglComboAUXOwners[0]==p); + if(bad){assert(*refs==before&&!state.tglComboAUXOwners[ddi]);assert(getCount==(bad==14));assert(putCount==(bad==14));continue;} + assert(getCount==1&&*refs==3&&state.tglComboAUXOwners[ddi]==p); run(p,true);assert(getCount==1); // duplicate enable is idempotent // Native per-transaction ref pairs cannot consume the held main-link ref. ++*refs;--*refs;assert(*refs==3); run(nullptr,false);assert(putCount==0); - regs[0x64000]=0x80000001;run(p,false);assert(putCount==0); // active main link - regs[0x64000]=0x81;run(p,false);assert(putCount==1&&*refs==2&&!state.tglComboAUXOwners[0]); + regs[buffer]=0x80000001;run(p,false);assert(putCount==0); // active main link + regs[buffer]=0x81;run(p,false);assert(putCount==1&&*refs==2&&!state.tglComboAUXOwners[ddi]); run(p,false);assert(putCount==1);run(p,true);assert(getCount==2);run(p,false);assert(putCount==2&&*refs==2); } puts("PASS main-link AUX acquire/duplicate/native transaction/disable/retry, inherited refs, ownership/power/ACK guards"); diff --git a/tests/tgl_combo_dp.cpp b/tests/tgl_combo_dp.cpp new file mode 100644 index 00000000..72679420 --- /dev/null +++ b/tests/tgl_combo_dp.cpp @@ -0,0 +1,44 @@ +#include "kern_tgl_combo_dp.hpp" +#include +#include +#include +#include +int main() { + for(unsigned mask=0;mask<4;++mask)for(unsigned hdmi=0;hdmi<4;++hdmi) { + XELP::VBT::Ports v;v.status=XELP::VBT::Status::Valid;v.present=mask; + v.port[0]={0x60d6,10,0,0x40,0xc0,0};v.port[1]={0x60d6,7,0,0x10,0xc0,0}; + const auto selected=TGLComboDP::fromFirmware(v,hdmi);assert(selected==(mask&~hdmi)); + alignas(8) uint8_t p[0x1c0]{};p[0x12]=9; + const uint32_t panel[]={0,0,0,0,2,0x18},dp[]={1,2,1,0,0x400,0x201}; + memcpy(p+0x20,panel,24);memcpy(p+0x38,dp,24); + uint8_t before[sizeof(p)];memcpy(before,p,sizeof(p)); + assert(TGLComboDP::apply(selected,p,sizeof(p))); + for(unsigned d=0;d<9;++d) { + auto config=reinterpret_cast(p+0x20+24*d); + if(selected&(1U< #include #include -struct State { bool tglActive=true; uintptr_t tglComboPLLFractionCaller=0x1000; void *tglObservationController=nullptr; } state,*callbackXE=&state; +struct State { bool adlsDisplaySelected=false; bool tglActive=true; uintptr_t tglComboPLLFractionCaller=0x1000; void *tglObservationController=nullptr; } state,*callbackXE=&state; unsigned records=0; void tglTransitionRecord(unsigned stage,bool done,void *,unsigned long reg,uint32_t nominal,uint32_t actual,const uint64_t *detail,unsigned size) { assert(stage==70 && done && reg==0x164284 && nominal==0x1c001a5 && actual==0xe001a5); diff --git a/tests/tgl_combo_signal.cpp b/tests/tgl_combo_signal.cpp index 327117db..73d68900 100644 --- a/tests/tgl_combo_signal.cpp +++ b/tests/tgl_combo_signal.cpp @@ -8,8 +8,8 @@ int main() { for(unsigned rate:{6,10,20,30})for(bool low:{false,true})for(bool hobl:{false,true}) for(unsigned vs=0;vs<5;++vs)for(unsigned pre=0;pre<5;++pre) { TGLComboSignal s; - bool valid=ddi<2&&(lanes==1||lanes==2||lanes==4)&&!hobl&& - (rate==6||rate==10||(low&&rate==20))&&vs<4&&pre<4&&vs+pre<=3; + bool valid=ddi<2&&(lanes==1||lanes==2||lanes==4)&& + (rate==6||rate==10||rate==20||rate==30)&&vs<4&&pre<4&&vs+pre<=3; assert(s.select(ddi,lanes,rate,low,hobl,vs,pre)==valid); if(!valid)continue; std::map regs; @@ -30,11 +30,38 @@ int main() { for(unsigned n=0;n<4;++n) { auto value=regs[s.base+0x890+0x100*n]; assert((value&0x7FFC0000)==0x12340000); - assert(bool(value&0x80000000)==(n==1||n==2||(lanes==4&&n==3))); + assert(bool(value&0x80000000)==(rate!=30&&(n==1||n==2||(lanes==4&&n==3)))); } // Missing bank declines before the first write. writes.clear();regs[s.base+0xA9C]=~0U;assert(!s.program(read,write)&&writes.empty()); } + for(unsigned ddi:{0u,3u,4u})for(unsigned lanes:{0u,1u,2u,3u,4u})for(unsigned rate:{0u,6u,10u,12u,20u,30u}) + for(unsigned v=0;v<5;++v)for(unsigned e=0;e<5;++e) { + TGLComboSignal s;bool ok=(lanes==1||lanes==2||lanes==4)&&(rate==6||rate==10||rate==20||rate==30)&&v<4&&e<4&&v+e<=3; + assert(s.selectADLS(ddi,lanes,rate,v,e)==ok);if(!ok)continue; + AlderLakeS::Port route;assert(AlderLakeS::Port::resolve(ddi,route)); + assert(s.base==route.phyBase()&&!s.low); + std::map regs; + // HBR3 must clear even inherited loadgen on every lane (Linux >600 MHz). + for(unsigned n=0;n<4;++n)regs[s.base+0x890+0x100*n]=0x80000000U; + assert(s.program([&](unsigned r){return regs[r];},[&](unsigned r,unsigned value){ + assert(r>=s.base&&r regs; + assert(!hbr3.program([&](unsigned r){return regs[r];},[&](unsigned r,unsigned value){ + if(r==hbr3.base+0x694)for(unsigned n=0;n<4;++n)regs[hbr3.base+0x894+0x100*n]=value; + for(unsigned n=0;n<4;++n)if(r==hbr3.base+0x890+0x100*n)value|=0x80000000U; + regs[r]=value; + })); + } TGLComboSignal s;assert(s.select(0,1,10,false,false,0,0)); assert(s.swing==0xA&&s.scalar==0x32&&s.cursor==0x3F&&!s.post1); // Silent dropped coefficient write must not report successful programming. diff --git a/tests/tgl_combo_signal_guard.py b/tests/tgl_combo_signal_guard.py index 23b01bca..91dffe70 100644 --- a/tests/tgl_combo_signal_guard.py +++ b/tests/tgl_combo_signal_guard.py @@ -12,27 +12,32 @@ expected=bytes(int(x.strip(),0) for x in re.search(name+r'\[\] \{([^}]+)',s).group(1).split(',')) assert b[at:at+len(expected)]==expected code=r''' -#include "kern_vbt.hpp" +#include "kern_alderlake_s.hpp" +#include "kern_tgl_combo_dp.hpp" +#include "kern_xelp_sst_route.hpp" #include "kern_tgl_combo_signal.hpp" #include #include #include struct State { + uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr;bool tglActive=true,tglComboSignalReady=true; - struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; + struct {uint32_t identity=0x9A498086;XELP::Family family=XELP::Family::TigerLake;} xeProfile; XELP::VBT::Ports firmwarePorts; } state,*callbackXE=&state; constexpr uint32_t kIOReturnNotReady=0xe00002d8; std::map registers;unsigned reads=0,writes=0,records=0; uint32_t wrapTGLRegisterRead(void *,unsigned long reg){++reads;return registers[reg];} void wrapTGLRegisterWrite(void *,unsigned long reg,uint32_t value){++writes; + if(reg==0x6C694)for(unsigned n=0;n<4;++n)registers[0x6C894+0x100*n]=value; + if(reg==0x160694)for(unsigned n=0;n<4;++n)registers[0x160894+0x100*n]=value; if(reg==0x162694)for(unsigned n=0;n<4;++n)registers[0x162894+0x100*n]=value; registers[reg]=value; } void tglTransitionRecord(unsigned stage,bool,void *,unsigned,unsigned,unsigned,const void *,unsigned){assert(stage==73);++records;} FUNCTION int main(){ - for(unsigned bad=0;bad<22;++bad){ + for(unsigned bad=0;bad<23;++bad){ alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{};uint32_t config[8]{}; auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; state={};state.tglObservationController=ctl;state.firmwarePorts.status=XELP::VBT::Status::Valid; @@ -53,13 +58,14 @@ if(bad==10)state.firmwarePorts.port[0].flags=2; if(bad==11)store(p,0x578,ctl+0); if(bad==12)store(p,0x124,3U); - if(bad==13)store(p,0x128,20U); - if(bad==14)state.firmwarePorts.panel.hobl=true; + if(bad==13)store(p,0x128,12U); + if(bad==14)state.xeProfile.identity=0x9A491234; if(bad==15)store(p,0x620,0x6C804U); if(bad==16)store(access,0x60,0x100000U); if(bad==17)registers[0x45404]=0; if(bad==18)registers[0x45454]=~0U; if(bad==19)registers[0x162A9C]=~0U; + if(bad==22)state.xeProfile.identity=0xFFFF8086; uint32_t result=99; bool applied=run(bad==20?nullptr:p,0,0,bad==21?p:nullptr,result); assert(applied==(bad==0||bad==19));assert((writes!=0)==(bad==0)); @@ -67,6 +73,92 @@ if(bad==19)assert(result==kIOReturnNotReady); if(bad!=0&&bad!=17&&bad!=18&&bad!=19)assert(reads==0); } + + // Known physical subplatforms must select electrical policy independently + // of a render backend. HBR2 U/Y differs at voltage0/emphasis1; HBR3 eDP + // uses the ICL table regardless of VBT low-swing/HOBL flags. + for(unsigned id:{0x9A498086u,0x9A788086u,0x9A608086u})for(unsigned rate:{20u,30u}) + for(bool low:{false,true})for(bool hobl:{false,true}) { + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{};uint32_t config[8]{}; + auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; + state={};state.xeProfile.identity=id;state.tglObservationController=ctl; + state.firmwarePorts.status=XELP::VBT::Status::Valid;state.firmwarePorts.panel={true,low,hobl,0,0}; + store(p,0x548,config+0);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); + store(p,0x124,4U);store(p,0x128,rate);store(p,0x620,0x162804U);store(p,0x5C8,0x162028U); + store(access,0x60,0x200000U);store(access,0x50,ctl+0); + registers.clear();registers[0x45404]=3;registers[0x45454]=3;reads=writes=records=0; + uint32_t result=99;assert(run(p,0,1,nullptr,result)); + assert(result==0); + const unsigned post=rate==30?8:low?7:id==0x9A608086?8:9; + assert(((registers[0x162890]>>12)&63)==post); + } + + // External TGL Combo uses DP electrical values even if a different panel + // advertises low-swing/HOBL. It requires an exact immutable port record. + for(unsigned ddi:{0u,1u})for(unsigned rate:{6u,10u,20u,30u})for(unsigned bad=0;bad<5;++bad) { + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; + auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; + state={};state.tglComboDPMask=1U<(state.tglPlatformCopy+0x20+24*ddi);memcpy(config,c,sizeof(c)); + const auto base=ddi?0x6c000U:0x162000U; + store(p,0x548,config);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); + store(p,0x124,4U);store(p,0x128,rate);store(p,0x620,base+0x804);store(p,0x5C8,base+0x28); + store(access,0x60,0x200000U);store(access,0x50,ctl+0); + registers.clear();registers[0x45404]=3;registers[0x45454]=3U<<(2*ddi);reads=writes=records=0; + if(bad==1)state.tglComboDPMask=0; + if(bad==2)store(p,0x548,c+0); + if(bad==3)config[1]^=1; + if(bad==4)store(p,0x550,3U); + uint32_t result=99;assert(run(p,0,1,nullptr,result)==(bad==0)); + assert(bool(writes)==(bad==0)); + if(!bad){assert(result==0);assert(((registers[base+0x890]>>12)&63)==(rate>10?9u:8u));} + } + for(unsigned ddi:{0u,3u,4u})for(unsigned bad=0;bad<12;++bad) { + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; + auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; + state={};state.adlsDisplaySelected=true;state.adlsWiring.valid=true;state.adlsWiring.dp=1U<(state.tglPlatformCopy+0x20+24*ddi);memcpy(config,c,sizeof(c)); + store(p,0x548,config);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); + store(p,0x124,4U);store(p,0x128,20U);store(p,0x620,base+0x804);store(p,0x5C8,base+0x28); + store(access,0x60,0x200000U);store(access,0x50,ctl+0); + registers.clear();registers[0x45404]=0x3f;registers[0x45454]=3U<<(2*ddi);reads=writes=records=0; + if(bad==1)state.adlsWiring.dp=0; + if(bad==2)store(p,0x548,c+0); + if(bad==3)store(p,0x550,4U); + if(bad==4)store(p,0x128,12U); + if(bad==5)registers[0x45404]=0; + if(bad==6)registers[0x45454]=0; + if(bad==7)store(p,0x620,base+0x904); + if(bad>=8)config[bad-8]^=1; + uint32_t result=99;assert(run(p,0,0,nullptr,result)==(bad==0)); + assert((writes!=0)==(bad==0));if(!bad)assert(!result && records==1 && registers[base+0x89c]==0x35000000); + } + // ADL-S panel uses the same programming sequence with panel electrical + // policy, never the TGL table or the external-DP policy by accident. + for(unsigned rate:{6U,10U,20U,30U})for(unsigned low=0;low<2;++low)for(unsigned hobl=0;hobl<2;++hobl) { + alignas(8) uint8_t p[0x700]{},ctl[0xD00]{},access[0x100]{}; + auto store=[](void *b,unsigned o,auto v){memcpy(static_cast(b)+o,&v,sizeof(v));}; + state={};state.adlsDisplaySelected=true;state.xeProfile.family=XELP::Family::AlderLakeS; + state.tglObservationController=ctl;auto &v=state.firmwarePorts;v.status=XELP::VBT::Status::Valid;v.present=1;v.port[0]={0x78C6,10,0,0x40,0,0}; + v.panel.valid=true;v.panel.ddi=0;v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2;v.panel.lowSwing=low;v.panel.hobl=hobl; + state.adlsWiring=AlderLakeS::Wiring::fromFirmware(v);assert(state.adlsWiring.panel==1); + auto config=reinterpret_cast(state.tglPlatformCopy+0x20);const uint32_t record[]={0,0,0,0,2,0x18};memcpy(config,record,sizeof(record)); + store(p,0x548,config);store(p,0x550,1U);store(p,0x578,access+0);store(ctl,0xC40,access+0); + store(p,0x124,2U);store(p,0x128,rate);store(p,0x620,0x162804U);store(p,0x5C8,0x162028U); + store(access,0x60,0x200000U);store(access,0x50,ctl+0); + registers.clear();registers[0x45404]=3;registers[0x45454]=3;reads=writes=records=0; + uint32_t result=99;assert(run(p,0,0,nullptr,result)&&!result&&records==1); + XELP::ComboPolicy expected;assert(expected.select(XELP::ComboPolicy::Platform::ADLS,rate,true,low,false,0,0)); + assert((registers[0x16289C]>>24&127)==expected.coefficients.scalar); + assert((registers[0x162890]&0x3ffff)==(expected.coefficients.post1<<12|expected.coefficients.cursor)); + assert(!(registers[0x162028]&12)); + state.adlsWiring.panel=0;writes=0;assert(!run(p,0,0,nullptr,result)&&!writes); + } puts("PASS production combo guard: family, port, firmware, ownership, power and missing bank"); } '''.replace('FUNCTION',fn) diff --git a/tests/tgl_fb_binding.cpp b/tests/tgl_fb_binding.cpp index 5967efdf..b4c496c5 100644 --- a/tests/tgl_fb_binding.cpp +++ b/tests/tgl_fb_binding.cpp @@ -6,7 +6,7 @@ template void put(void *p,size_t off,T value) {memcpy(static_castinspect().ddi]==7);paths[p]=saved;owners[p]=owner; } } + // Mixed HDMI/DP topology feeds the same native binding and withdrawal + // map. Change protocol, FB index and pipe independently of physical DDI. + for(unsigned dp=1;dp<4;++dp)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned fb=0;fb<3;++fb) { + for(unsigned ddi=0;ddi<2;++ddi) { + Fixture x(fb,pipe,ddi);x.wiring.hdmi=3&~dp;x.wiring.dp=dp; + for(unsigned d=0;d<3;++d)x.wiring.ddc[d]=(x.wiring.hdmi&(1U<(x.fb); + assert(publish(paths[pipe],owners[pipe],reinterpret_cast(x.path),r,pipe)); + auto m=powerPipes(paths,owners,x.wiring.selected());assert(m.valid&&m.known==(1U< #include "kern_tgl_hdmi_wiring.hpp" #include "kern_vbt.hpp" +#include "kern_alderlake_s.hpp" +#include "kern_tgl_combo_dp.hpp" #include "kern_xelp_donor.hpp" struct OSObject {virtual ~OSObject()=default;}; struct OSData:OSObject {std::vector data;void *getBytesNoCopy(){return data.data();}uint32_t getLength(){return data.size();}}; @@ -26,6 +28,7 @@ void clock_get_uptime(uint64_t *p){*p=0;}void absolutetime_to_nanoseconds(uint64 void *current_thread(){return reinterpret_cast(1);} struct XE { bool tglHDMIB=true,tglStolenMemoryReady=false; + uint32_t tglComboDPMask=0;bool adlsDisplaySelected=false; struct {uint32_t fStolenMemorySize=0;} framebufferPatch; XELP::VBT::Ports firmwarePorts; // FIELDS @@ -60,6 +63,41 @@ void expectUnchangedExcept(const uint8_t *after,std::initializer_list assert(!memcmp(after,expected.data(),sizeof(original))); } int main(){ + // ADL-S uses the same real entry points, while preserving the physical + // TC1 record and converting its PHY classification from Dekel to Combo. + setup();object.adlsDisplaySelected=true; + object.firmwarePorts.status=XELP::VBT::Status::Valid;object.firmwarePorts.present=8; + object.firmwarePorts.port[3]={0x60d2,1,2}; + reinterpret_cast(original+0x68)[1]=9;IOService adlsProvider; + assert(XE::tglLoadHDMIWiring(&adlsProvider));assert(XE::wrapTGLGetOSInformation(controller)); + auto adlsConfig=reinterpret_cast(platform()+0x68); + assert(object.tglHDMIEligible(adlsConfig) && object.tglHDMIDDCEligible(9) && !object.tglHDMIDDCEligible(2)); + assert(adlsConfig[0]==3 && adlsConfig[1]==9 && adlsConfig[3]==0); + expectUnchangedExcept(platform(),{0x70,0x74,0x78,0x7C}); + for(unsigned bus=0;bus<=14;++bus) { + uint32_t request[16] {};request[1]=1;request[3]=0xA0;request[14]=128; + nativeGMBusCalls=0; + auto result=XE::wrapTGLGMBus(controller,bus,0,request); + assert(nativeGMBusCalls==unsigned(bus==9)); + assert(result==(bus==9?0x91u:0xE00002C2u)); + if(bus!=9)assert(request[7]==result); + } + setup();object.adlsDisplaySelected=true;auto foreignWiring=property(2,0,2,0);IOService wrongADLS{&foreignWiring}; + assert(!XE::tglLoadHDMIWiring(&wrongADLS) && !XE::tglLoadHDMIWiring(&wrongADLS)); + // Panel + external HDMI/DP use the real firmware loader and topology copy. + for(unsigned kind:{1U,2U})for(unsigned tcon:{0U,2U,3U}) { + setup();object.adlsDisplaySelected=true;auto &v=object.firmwarePorts; + v.status=XELP::VBT::Status::Valid;v.present=9;v.port[0]={0x78c6,10,0,0x40,0,0}; + v.panel.valid=true;v.panel.ddi=0;v.panel.backlightValid=true;v.panel.pwmPresent=true;v.panel.backlightType=2; + v.port[3]={kind==1?0x60d2U:0x60d6U,kind==1?1U:7U,2,kind==1?0U:0x10U,0,0}; + reinterpret_cast(original+0x68)[1]=9;*reinterpret_cast(original+0x190)=tcon; + IOService provider;assert(XE::tglLoadHDMIWiring(&provider)&&object.adlsWiring.panel==1); + assert(XE::wrapTGLGetOSInformation(controller)); + assert(object.adlsWiring.boundPanel(platform(),reinterpret_cast(platform()+0x20))); + assert(object.adlsWiring.matches(reinterpret_cast(platform()+0x68))); + assert(!*reinterpret_cast(platform()+0x190)); + expectUnchangedExcept(platform(),{0x70,0x74,0x78,0x7c,0x190}); + } // The native setup caller supplies an interior output pointer as arg0. // Any object/config dereference on an unqualified caller must fault here. setup(); @@ -93,6 +131,20 @@ int main(){ assert(XE::tglLoadHDMIWiring(&firmware)&&!object.tglHDMIWiringExplicit&&object.tglHDMIWiring.hdmi==2); assert(XE::wrapTGLGetOSInformation(controller)); assert(reinterpret_cast(platform()+0x38)[1]==2); + // Fixed Combo DP composes with HDMI and a panel-free A/B native topology. + for(unsigned mask:{1u,2u,3u}) { + setup();object.firmwarePorts.status=XELP::VBT::Status::Valid;object.firmwarePorts.present=3; + for(unsigned d=0;d<2;++d)object.firmwarePorts.port[d]=mask&(1U<(platform()+0x20+24*d); + assert(config[2]==(mask&(1U< #include "kern_xelp_os.hpp" +#include "kern_xelp_platform.hpp" #include #include -namespace XELP { enum class Family {TigerLake,Other}; } struct Profile { bool ok; XELP::Family family; bool admitted() const {return ok;} }; const char *version; -bool noaccel,off; +bool noaccel,off,adlsDisplaySelected; bool checkKernelArgument(const char *arg) {return !strcmp(arg,"-wex-noaccel")?noaccel:!strcmp(arg,"-wex-tgl-off")?off:false;} bool tglRequested,tglInactivePorts,tglPG1Poll,tglClockOrderRequested,tglTrainingLeaseRequested,tglProgramEnabled,tglAcceleratorRequested; int kextIntelTGL,kextIntelTGLFb;int *currentGraphics,*currentFramebuffer; @@ -35,10 +35,11 @@ "Darwin Kernel Version 23.6.0: unqualified", "Darwin Kernel Version 24.6.0: unqualified"}; unsigned cases=0; -for(unsigned k=0;k<11;++k)for(unsigned n=0;n<2;++n)for(unsigned o=0;o<2;++o)for(unsigned physical=0;physical<2;++physical)for(unsigned family=0;family<2;++family){ +for(unsigned k=0;k<11;++k)for(unsigned n=0;n<2;++n)for(unsigned o=0;o<2;++o)for(unsigned physical=0;physical<2;++physical)for(unsigned family=0;family<3;++family){ version=versions[k];noaccel=n;off=o;currentGraphics=currentFramebuffer=nullptr; -select({bool(physical),family?XELP::Family::TigerLake:XELP::Family::Other}); -bool expected=physical&&family&&!o&&(k<7); +adlsDisplaySelected=family==2; +select({bool(physical),family==2?XELP::Family::AlderLakeS:family?XELP::Family::TigerLake:XELP::Family::Unknown}); +bool expected=physical&&family&&!o&&(k<7)&&(family!=2 || k>=1); assert(tglRequested==expected);assert(tglAcceleratorRequested==(expected&&!n)); assert((currentFramebuffer==&kextIntelTGLFb)==expected);++cases; }printf("PASS: %u actual TGL admission cases, including Tahoe acceleration, Sequoia acceleration, Sonoma acceleration, Ventura acceleration, noaccel and unknown-kernel rejection\n",cases); diff --git a/tests/tgl_mode_native_fixture.cpp b/tests/tgl_mode_native_fixture.cpp index e1aac6fd..4ce03f5f 100644 --- a/tests/tgl_mode_native_fixture.cpp +++ b/tests/tgl_mode_native_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" #include @@ -27,6 +28,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; static void tglRecordStartOrder(unsigned,void*,unsigned=9,uint64_t=0,uint64_t=0,uint64_t=0) {} static void tglRecordNativeModeBinding(void *,uint32_t,uint32_t) {} @@ -101,6 +103,18 @@ int main(int argc,char **argv){ reset(3,1);instance.tglDPAltCandidates=8;coldReady=false; assert(XE::wrapTGLSetPortMode(port,3)==kIOReturnNotReady && !reads && !writes && !powerGets); coldReady=true;instance.tglDPAltCandidates=0; + // Qualified ADL-S HPD mode4 must execute native Combo mode1, without FIA. + for(unsigned ddi:{0u,3u,4u}) { + reset(ddi);instance.adlsDisplaySelected=true;instance.adlsWiring.valid=true;instance.adlsWiring.dp=1U<(instance.tglPlatformCopy+0x20+24*ddi); + const uint32_t fixed[]={ddi,ddi?ddi+6:1,1,0,0x400,0x201};memcpy(bound,fixed,sizeof(fixed)); + *reinterpret_cast(port+0x548)=bound; + instance.tglNativeLegacyHPDCaller=testCaller=0x74a59; + assert(XE::wrapTGLSetPortMode(port,4)==0 && mode()==1); + assert(!reads && !writes && !powerGets && !powerPuts && !sleeps); + } + instance.adlsDisplaySelected=false;instance.adlsWiring={};testCaller=0;instance.tglNativeLegacyHPDCaller=0; + *reinterpret_cast(port+0x548)=config; unsigned cases=0; for(bool skipPower:{false,true})for(unsigned d=3;d<=8;++d)for(unsigned requested:{3u,4u})for(bool isReady:{false,true}){ controller[0xc58]=skipPower?2:0;const unsigned powerCalls=skipPower?0:1; diff --git a/tests/tgl_mode_serial_fixture.cpp b/tests/tgl_mode_serial_fixture.cpp index f3212562..91a53551 100644 --- a/tests/tgl_mode_serial_fixture.cpp +++ b/tests/tgl_mode_serial_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include "kern_tgl_native_fia.hpp" #include @@ -26,6 +27,7 @@ void tglAUXRecord(void*,unsigned,bool,uint32_t,uint32_t,uint32_t){} bool coldReady=true; bool tglDPAltColdReady(void*){return coldReady;} struct XE { + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; uint32_t tglDPAltCandidates=0;void *tglObservationController=nullptr; uint32_t tglTCAutoRegistered=0,tglTCAutoCandidates=0x10,tglAUXParentMask=0x18; // LEGACY_HPD_POLICY diff --git a/tests/tgl_panel_depth_route.py b/tests/tgl_panel_depth_route.py index c7a6c705..47660fac 100644 --- a/tests/tgl_panel_depth_route.py +++ b/tests/tgl_panel_depth_route.py @@ -28,11 +28,12 @@ end=s.index('uint32_t XE::wrapTGLPortDDC(',start) code=r''' #include "kern_tgl_panel_depth.hpp" +#include "kern_alderlake_s.hpp" #include #include #include using mach_vm_address_t=uintptr_t; -namespace XELP {enum class Family {TigerLake,Other};} + unsigned calls=0,published=0;uint64_t record[5]; struct Root {void setProperty(const char *,void *p,size_t n){assert(n==40);memcpy(record,p,n);++published;}} root; struct IORegistryEntry {static Root *getRegistryRoot(){return &root;}}; @@ -40,7 +41,8 @@ void IOSimpleLockLock(void *){} void IOSimpleLockUnlock(void *){} struct Ports {struct Port {bool internal(){return true;}bool dp(){return true;}} port[2];unsigned present=1;bool valid(){return true;}}; struct XE { - bool tglActive=true,tglPanelDepthReady=true; + bool tglActive=true,tglPanelDepthReady=true,adlsDisplaySelected=false; + AlderLakeS::Wiring adlsWiring;alignas(8) uint8_t tglPlatformCopy[0x1c0]{}; void *tglObservationController=nullptr; struct {XELP::Family family=XELP::Family::TigerLake;} xeProfile; Ports firmwarePorts;mach_vm_address_t orgTGLModeTiming; @@ -67,12 +69,22 @@ for(unsigned test=0;test<7;++test){ put(timing,0x8A,uint16_t(2));unsigned before=published; state.tglActive=test!=0;state.tglPanelDepthReady=test!=1; - state.xeProfile.family=test==2?XELP::Family::Other:XELP::Family::TigerLake; + state.xeProfile.family=test==2?XELP::Family::Unknown:XELP::Family::TigerLake; cfg[2]=test==3?2:0;cfg[0]=test==4?2:0;port[0x117]=test==5; put(port,0x578,test==6?nullptr:access); XE::wrapTGLModeTiming(ctl,fb,timing); assert(published==before && *reinterpret_cast(timing+0x8A)==2); } + // Same depth contract on qualified ADL-S panel; retained record is required. + state.tglActive=state.tglPanelDepthReady=true;state.adlsDisplaySelected=true; + state.xeProfile.family=XELP::Family::AlderLakeS;port[0x117]=0;put(port,0x578,access); + state.adlsWiring.valid=true;state.adlsWiring.panel=1; + const uint32_t panelRecord[]={0,0,0,0,2,0x18};memcpy(state.tglPlatformCopy+0x20,panelRecord,sizeof(panelRecord)); + put(port,0x548,static_cast(state.tglPlatformCopy+0x20)); + put(timing,0x8A,uint16_t(2));auto before=published; + XE::wrapTGLModeTiming(nullptr,fb,timing);assert(published==before+1 && *reinterpret_cast(timing+0x8A)==1); + state.adlsWiring.panel=0;put(timing,0x8A,uint16_t(2));before=published; + XE::wrapTGLModeTiming(nullptr,fb,timing);assert(published==before && *reinterpret_cast(timing+0x8A)==2); puts("PASS: production owner/port guards and unused-controller argument"); } '''.replace('PRODUCTION',s[start:end]) diff --git a/tests/tgl_panel_timing.py b/tests/tgl_panel_timing.py new file mode 100644 index 00000000..a0759abf --- /dev/null +++ b/tests/tgl_panel_timing.py @@ -0,0 +1,199 @@ +#!/usr/bin/env python3 +"""Run the actual timing adapter and optionally execute the exact donor consumer.""" +from pathlib import Path +import os,re,subprocess,tempfile,sys +r=Path(__file__).resolve().parents[1];s=(r/'WhateverXe/kern_xe.cpp').read_text() +wrapper='\n'.join(re.search(r'(?:uint32_t|void) XE::'+name+r'\(.*?\n\}',s,re.S).group() for name in ['tglApplyPanelTiming','wrapTGLPanelConfig','wrapTGLPanelTiming']) +start=s[s.index(' // A reused controller address'):s.index(' if (checkKernelArgument("-disablegfxfirmware")) {',s.index(' // A reused controller address'))] +install=s[s.index(' // PANEL_TIMING_INSTALL:'):s.index(' // PANEL_TIMING_INSTALL_END')] +entry=re.search(r'entry\[\]=\{([^}]+)',install).group(1) +call=re.search(r'call\[\]=\{([^}]+)',install).group(1) +if len(sys.argv)>1: + b=Path(sys.argv[1]).read_bytes() + assert b[0x4848c:0x4848c+20]==bytes(int(x,0) for x in entry.split(',')) + assert b[0x437d2:0x437d2+11]==bytes(int(x,0) for x in call.split(',')) + for at,key in [(0xa9951,b'AAPL00,PanelPowerUp'),(0xa9965,b'AAPL00,PanelPowerOn'),(0xa9979,b'AAPL00,PanelPowerDown'),(0xa998f,b'AAPL00,PanelPowerOff'),(0xa99a4,b'AAPL00,PanelCycleDelay')]: + assert b[at:at+len(key)+1]==key+b'\0' +code=r''' +#include "kern_xelp_panel_power.hpp" +#include "kern_alderlake_s.hpp" +#include "kern_xelp_panel_pwm.hpp" +#include "kern_tgl_tc_native.hpp" +#include +#include +#include +#include +#include +#include +using mach_vm_address_t=uintptr_t; +#define FunctionCast(fn,addr) reinterpret_cast(addr) +struct IORegistryEntry { + void setProperty(const char *,bool){} + void setProperty(const char *,void *,size_t){} + static IORegistryEntry *getRegistryRoot(){static IORegistryEntry r;return &r;} +}; +struct KernelPatcher { + unsigned routes=0;bool fail=false;uintptr_t delta=0; + struct RouteRequest {template RouteRequest(const char*,F,uintptr_t&) {}}; + uintptr_t solveSymbol(unsigned,const char *s,uintptr_t a,size_t){return a+(strstr(s,"GetPanel")?0x4848c:0x437a6)+delta;} + bool routeMultiple(unsigned,RouteRequest*,unsigned,uintptr_t,size_t){++routes;return !fail;} + void clearError(){} +}; +struct XE { + static XE *callbackXE; + bool tglActive=true,tglPanelTimingReady=true,adlsDisplaySelected=false; + AlderLakeS::Wiring adlsWiring;XELP::PanelPWM adlsPanelPWM; + uintptr_t orgTGLPanelTiming=0,orgTGLRegisterRead=0,orgTGLPanelConfig=0; + uint32_t tglPanelTimingBIOS[3]{};bool tglPanelTimingBIOSValid=false; + XELP::VBT::Ports firmwarePorts; + struct {XELP::Family family=XELP::Family::TigerLake;}xeProfile; + TGLTCNativeFunctions tglTCNativeFunctions; + void *tglObservationController=nullptr; + uint64_t tglPlatformCopy[0x1c0/8]{}; + static uint32_t wrapTGLPanelTiming(void *); + static void wrapTGLPanelConfig(void *,uint32_t); + static void tglApplyPanelTiming(void *,bool); + void start(void *that){START} + static uint32_t wrapTGLRegisterRead(void *,unsigned long); + void install(KernelPatcher &patcher,unsigned index,uintptr_t address,size_t size){INSTALL} +}; +XE *XE::callbackXE; +WRAPPER +alignas(8) uint8_t ctl[0x1600]{},port[0x630]{},access[0x80]{},power[0x90]{}; +static void *owner=ctl; +static uint32_t input[5]{},result=0,on=0,off=0,control=0,wells=1,fuse=0x1000000; +static unsigned calls=0,refs=0,get=0,put=0,reads=0,writes=0; +template T& at(void *p,unsigned n){return *reinterpret_cast(static_cast(p)+n);} +static uint32_t original(void *c){++calls;memcpy(static_cast(c)+0x1548,input,sizeof(input));return result;} +static void *lookup(uint32_t d){assert(d<2);return port;} +static void pgGet(void *p,uint32_t n){assert(p==power&&n==1);++get;++refs;} +static void pgPut(void *p,uint32_t n){assert(p==power&&n==1&&refs==1);++put;--refs;} +static uint32_t read(void *a,unsigned long reg){ + assert(a==access&&refs==1);++reads; + switch(reg){case 0x45404:return wells;case 0x42000:return fuse;case 0xC7208:return on;case 0xC720C:return off;case 0xC7204:return control;default:assert(false);return ~0U;} +} +static void write(void *a,unsigned long reg,uint32_t value){ + assert(a==access&&refs==0);++writes; + switch(reg){case 0xC7208:on=value;break;case 0xC720C:off=value;break;default:assert(false);} +} +static uint32_t base(void *,uint32_t){return 0;} +static void aux(void *){} +struct Datum {uintptr_t *vtable;uint32_t value;}; +static Datum properties[5]{};static unsigned propertyCalls=0; +static void *property(void *,const char *key){ + const char *keys[]={"AAPL00,PanelPowerUp","AAPL00,PanelPowerOn","AAPL00,PanelPowerDown","AAPL00,PanelPowerOff","AAPL00,PanelCycleDelay"}; + for(unsigned i=0;i<5;++i)if(!strcmp(key,keys[i])){++propertyCalls;return &properties[i];} + assert(false);return nullptr; +} +static void *bytes(Datum *d){return &d->value;} +static void *cast(void *object,void *){return object;} +static void jump(uint8_t *p,uintptr_t target){const uint8_t code[]={0x48,0xb8,0,0,0,0,0,0,0,0,0xff,0xe0};memcpy(p,code,sizeof(code));memcpy(p+2,&target,sizeof(target));} +static void patchCall(uint8_t *mem,unsigned at,unsigned target){const int32_t displacement=target-at-5;mem[at]=0xe8;memcpy(mem+at+1,&displacement,4);} + +static void setup(XE &s){ + XE::callbackXE=&s;memset(ctl,0,sizeof(ctl));memset(port,0,sizeof(port));memset(access,0,sizeof(access));memset(power,0,sizeof(power)); + memset(input,0,sizeof(input));calls=get=put=reads=writes=refs=result=on=off=control=0;wells=1;fuse=0x1000000;owner=ctl; + s.orgTGLPanelTiming=(uintptr_t)original;s.orgTGLRegisterRead=(uintptr_t)read;s.tglObservationController=ctl; + s.tglTCNativeFunctions={base,aux,aux,pgGet,pgPut,lookup,&owner,true}; + auto &v=s.firmwarePorts;v.status=XELP::VBT::Status::Valid;v.present=1;v.port[0]={0x78C6,10,0,0x40,0,0}; + auto &p=v.panel;p.valid=true;p.ddi=0;p.backlightValid=true;p.pwmPresent=true;p.backlightType=2; + const uint16_t delay[]={2500,700,800,5500,5000};memcpy(p.delays,delay,sizeof(delay)); + at(ctl,0xc40)=access;at(ctl,0xc8)=power;at(ctl,0xd40)=s.tglPlatformCopy; + at(port,0x548)=reinterpret_cast(s.tglPlatformCopy)+0x20;at(port,0x550)=1;at(port,0x578)=access; + at(power,0x10)=ctl;at(power,0x78)=access;at(access,0x50)=access;at(access,0x60)=0x200000; +} +int main(int argc,char **argv){ + // Execute the production installer, including short image/symbol/byte/route failures. + std::vector image(0x49000);const uint8_t entry[]={ENTRY},call[]={CALL}; + memcpy(image.data()+0x4848c,entry,sizeof(entry));memcpy(image.data()+0x437d2,call,sizeof(call)); + for(unsigned n=0;n<6;++n){ + XE s;KernelPatcher p;size_t size=image.size(); + if(n==0)size=0x48650;if(n==1)p.delta=1;if(n==2)image[0x4848c]^=1;if(n==3)image[0x437d2]^=1;if(n==4)p.fail=true; + s.install(p,0,(uintptr_t)image.data(),size);assert(s.tglPanelTimingReady==(n==5)); + assert(p.routes==(n>=4?1U:0U)); + memcpy(image.data()+0x4848c,entry,sizeof(entry));memcpy(image.data()+0x437d2,call,sizeof(call)); + } + for(unsigned n=0;n<25;++n){ + XE s;setup(s);auto &p=s.firmwarePorts.panel; + switch(n){ + case 0:s.tglActive=false;break;case 1:s.tglPanelTimingReady=false;break;case 2:s.tglObservationController=nullptr;break; + case 3:s.xeProfile.family=XELP::Family::AlderLakeN;break;case 4:p.controller=1;break;case 5:p.backlightType=5;break; + case 6:p.backlightValid=false;break;case 7:s.tglTCNativeFunctions.qualified=false;break;case 8:owner=nullptr;break; + case 9:s.orgTGLRegisterRead=0;break;case 10:ctl[0x918]=1;break;case 11:at(ctl,0xd0)=port;break; + case 12:at(ctl,0xd40)=nullptr;break;case 13:port[0x117]=1;break;case 14:at(port,0x548)=ctl;break; + case 15:at(port,0x550)=0;break;case 16:at(port,0x578)=nullptr;break; + case 17:at(power,0x10)=nullptr;break;case 18:power[0x18]=1;break;case 19:at(power,0x78)=nullptr;break; + case 20:at(access,0x50)=nullptr;break;case 21:at(access,0x60)=0xc720c;break; + case 22:result=0xe00002bc;break;case 23:s.firmwarePorts.unmapped=1;break;case 24:s.firmwarePorts.port[0].aux=0x10;break; + } + assert(XE::wrapTGLPanelTiming(ctl)==result && calls==1 && get==0 && reads==0 && writes==0); + assert(!memcmp(ctl+0x1548,input,sizeof(input))); + } + for(unsigned n=0;n<7;++n){ + XE s;setup(s); + if(n==0)wells=0;if(n==1)wells=~0U;if(n==2)fuse=0;if(n==3)fuse=~0U; + if(n==4)on=~0U;if(n==5)s.firmwarePorts.panel.delays[3]=9000; + assert(XE::wrapTGLPanelTiming(ctl)==0&&calls==1&&get==1&&put==1&&refs==0&&writes==0); + assert(reads==(n<4?2U:5U)); + const uint32_t expected[]={700,800,2500,5500,6}; + assert(!memcmp(ctl+0x1548,n==6?expected:input,sizeof(input))); + } + // Optional exact native hwSetPanelPowerConfig replay. Only its property + // callsite, imported services and virtual register writer are replaced; the encoding/masking, + // property conversions, defaults, cached fields and write order are real donor code. + for(unsigned platform=0;platform<2;++platform)if(argc>1){ + propertyCalls=0; + std::ifstream f(argv[1],std::ios::binary);std::vectorb((std::istreambuf_iterator(f)),{});assert(b.size()>0xde4e0); + const auto stubs=unsigned((b.size()+4095)&~4095U);const size_t length=stubs+4096; + auto mem=(uint8_t*)mmap(nullptr,length,PROT_READ|PROT_WRITE,MAP_PRIVATE|MAP_ANON,-1,0);assert(mem!=MAP_FAILED);memcpy(mem,b.data(),b.size()); + jump(mem+stubs,(uintptr_t)XE::wrapTGLPanelTiming);patchCall(mem,0x437d2,stubs); + jump(mem+stubs+16,(uintptr_t)memset);patchCall(mem,0x484b1,stubs+16); + jump(mem+stubs+32,(uintptr_t)cast); + for(unsigned site: {0x484ddU,0x48524U,0x48568U,0x485afU,0x485f6U})patchCall(mem,site,stubs+32); + uintptr_t meta=0;const uintptr_t metaPointer=(uintptr_t)&meta;memcpy(mem+0xc8050,&metaPointer,sizeof(metaPointer)); + assert(mprotect(mem,length,PROT_READ|PROT_WRITE|PROT_EXEC)==0); + auto nativeConfig=reinterpret_cast(mem+0x437a6); + uintptr_t vtable[40]{};vtable[0x130/8]=(uintptr_t)write; + uintptr_t providerTable[90]{},dataTable[50]{};providerTable[0x2b8/8]=(uintptr_t)property;dataTable[0x178/8]=(uintptr_t)bytes; + auto provider=providerTable;for(auto &p:properties)p.vtable=dataTable; + XE s;setup(s);at(access,0)=vtable;s.orgTGLPanelConfig=(uintptr_t)nativeConfig; + s.orgTGLPanelTiming=(uintptr_t)(mem+0x4848c);at(ctl,0xc60)=&provider; + const uint32_t milliseconds[]={300,100,600,90,800};for(unsigned i=0;i<5;++i)properties[i].value=milliseconds[i]; + if(platform){ + s.adlsDisplaySelected=true;s.xeProfile.family=XELP::Family::AlderLakeS; + s.adlsWiring=AlderLakeS::Wiring::fromFirmware(s.firmwarePorts);assert(s.adlsWiring.panel==1); + const uint32_t record[]={0,0,0,0,2,0x18};memcpy(reinterpret_cast(s.tglPlatformCopy)+0x20,record,sizeof(record)); + } + const auto config=XE::wrapTGLPanelConfig; + control=0xabcd0080;at(ctl,0xd80)=control; + on=4000U<<16|1200;off=8000U<<16|1100; + // First config disables PPS and overwrites the hardware with short delays. + // The wrapper must capture BIOS before that first write, not at first ON. + config(ctl,0);assert(on==0&&off==3000&&get==1&&put==1&&calls==0); + writes=0; + // BIOS 800ms cycle beats VBT 600ms; native property 900ms then beats both. + for(unsigned n=0;n<16;++n){ + s.firmwarePorts.panel.type=n; + properties[4].value=n*100+1;const auto expectedCycle=n+2<8?8:n+2; + config(ctl,1); + assert(on==(4000U<<16|1200)&&off==(8000U<<16|1100)); + assert(at(ctl,0xd78)==on&&at(ctl,0xd7c)==off); + assert(at(ctl,0xd80)==(0xabcd0000|(expectedCycle<<4))); + assert(refs==0&&get==1&&put==1&&propertyCalls==5*(n+1)&&writes==4*n+2); + config(ctl,0);assert(on==0&&off==3000); + } + config(ctl,0);assert(on==0&&off==3000&&at(ctl,0xd80)==0xabcd0000); + assert(propertyCalls==80&&get==1&&put==1); + // Execute the actual startup invalidation, even for a reused address. + s.start(ctl);assert(!s.tglPanelTimingBIOSValid); + on=5000U<<16|1300;off=8100U<<16|1150; + config(ctl,1);assert(on==(5000U<<16|1300)&&off==(8100U<<16|1150)&&get==2&&put==2); + munmap(mem,length); + } + puts("PASS production PPS timing installer, ownership/method guards, balanced PG, native consumer replay"); +} +'''.replace('INSTALL',install).replace('START',start).replace('WRAPPER',wrapper).replace('ENTRY',entry).replace('CALL',call) +with tempfile.TemporaryDirectory(prefix='panel-timing-') as d: + p=Path(d);(p/'test.cpp').write_text(code) + subprocess.run(['clang++','-arch','x86_64','-std=c++17','-O1','-g','-fsanitize=address,undefined','-I'+str(r/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')]+sys.argv[1:],check=True,env=dict(os.environ,UBSAN_OPTIONS="halt_on_error=1")) diff --git a/tests/tgl_port_map_fixture.cpp b/tests/tgl_port_map_fixture.cpp index 721a53bf..a7154748 100644 --- a/tests/tgl_port_map_fixture.cpp +++ b/tests/tgl_port_map_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include #include @@ -17,6 +18,7 @@ struct IORegistryEntry { void *getProperty(const char *) { return nullptr; } }; struct XE { + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // FIELDS IOSimpleLock *tglDekelLock; uintptr_t orgTGLReadDekel,orgTGLWriteDekel; diff --git a/tests/tgl_power_fuses.py b/tests/tgl_power_fuses.py new file mode 100644 index 00000000..681de9a8 --- /dev/null +++ b/tests/tgl_power_fuses.py @@ -0,0 +1,84 @@ +#!/usr/bin/env python3 +"""Replay the production PG2/3/4 poll with delayed, absent and failed MMIO.""" +from pathlib import Path +import hashlib, subprocess, sys, tempfile + +root = Path(__file__).resolve().parents[1] +source = (root/'WhateverXe/kern_xe.cpp').read_text() +begin = source.index('\t// Only the qualified donor post-ACK fuse reads') +body = source[begin:source.index('\tif (reg == 0x42000 && s->tglPG1FuseReadCaller', begin)] +sites = [0x39D, 0x53F, 0x6D3] +expected = [bytes.fromhex(x) for x in [ + 'be00200400e85d2900000fbae019', 'be00200400e8bb2700000fbae018', + 'be00200400e8272600000fbae017']] +function = bytearray(0x700) +for off, seq in zip(sites, expected): + function[off:off+len(seq)] = seq +if len(sys.argv) > 1: + donor = Path(sys.argv[1]).read_bytes() + assert hashlib.sha256(donor).hexdigest() == 'c004fc1ac9ee4157e2e12736f40fa6639bde025a279326beeb9dd48c9b445731' + function = donor[0x5B640:0x5BD40] + for off, seq in zip(sites, expected): + assert function[off:off+14] == seq +code = r''' +#include "kern_xelp_donor.hpp" +#include +#include +#include +#include +static unsigned elapsed,reads,readyAt,failAt; +static uint32_t statusMask; +static void *expectedAccess; +static void IODelay(unsigned us){assert(us==10);elapsed+=us;} +static uint32_t read(void *access,unsigned long reg){ + assert(access==expectedAccess && reg==0x42000);++reads; + if(elapsed>=failAt)return UINT32_MAX; + return 0x80000000U | (elapsed>=readyAt ? statusMask : 0); +} +struct State {uintptr_t tglPGFuseWaitFunction=0x100000;uintptr_t orgTGLRegisterRead=reinterpret_cast(read);} state; +uint32_t run(void *access,unsigned long reg,uintptr_t caller,uint32_t value){ + auto s=&state; + BODY + return value; +} +int main(){ + uint8_t function[]={BYTES}; + assert(XELPDonor::powerWellFuseSites(function,sizeof(function))); + assert(!XELPDonor::powerWellFuseSites(nullptr,sizeof(function))); + for(unsigned n=0;n<0x6E1;++n)assert(!XELPDonor::powerWellFuseSites(function,n)); + // Every instruction byte, including the callee displacement, is guarded. + for(auto off:XELPDonor::fuseReadSites)for(unsigned j=0;j<14;++j){ + function[off+j]^=1;assert(!XELPDonor::powerWellFuseSites(function,sizeof(function)));function[off+j]^=1; + } + expectedAccess=function;unsigned cases=0; + for(unsigned pg=0;pg<3;++pg)for(unsigned delay=0;delay<=1010;delay+=10) + for(unsigned failure:{UINT32_MAX,20U}) { + elapsed=reads=0;readyAt=delay;failAt=failure;statusMask=1U<<(25-pg); + uint32_t initial=0x80000000U|(delay==0?statusMask:0); + auto result=run(expectedAccess,0x42000,0x100000+XELPDonor::fuseReadSites[pg]+10,initial); + unsigned end=delay<1000?delay:1000;if(failure=failure?UINT32_MAX:0x80000000U|(end>=delay?statusMask:0))); + ++cases; + } + for(uintptr_t caller=0x100000;caller<0x100700;++caller){ + auto mask=XELPDonor::powerWellFuseMask(0x100000,caller); + for(auto reg:{0x42000UL,0x45404UL})for(auto initial:{0U,UINT32_MAX,0xFFFFFFFFU ^ mask}){ + elapsed=reads=0;readyAt=failAt=UINT32_MAX; + auto result=run(expectedAccess,reg,caller,initial); + const bool wait=reg==0x42000 && mask && initial!=UINT32_MAX; + assert(elapsed==(wait?1000U:0U) && reads==elapsed/10); + assert(result==(wait?0x80000000U:initial));++cases; + } + } + state.tglPGFuseWaitFunction=0;elapsed=reads=0; + assert(run(expectedAccess,0x42000,0x1003A7,0)==0 && !reads && !elapsed); + assert(!XELPDonor::powerWellFuseMask(0x100000,0xFFFFF)); + printf("PASS: %u production fuse poll cases; exact sites, timeout, read failure, unrelated callers\n",cases); +} +'''.replace('BODY', body).replace('BYTES', ','.join(map(str, function))) +with tempfile.TemporaryDirectory() as tmp: + p=Path(tmp); (p/'test.cpp').write_text(code) + subprocess.run(['clang++','-std=c++17','-O1','-fsanitize=address,undefined', + '-I'+str(root/'WhateverXe'),str(p/'test.cpp'),'-o',str(p/'test')],check=True) + subprocess.run([str(p/'test')],check=True) diff --git a/tests/tgl_probe_fixture.cpp b/tests/tgl_probe_fixture.cpp index f9561610..0888b8d6 100644 --- a/tests/tgl_probe_fixture.cpp +++ b/tests/tgl_probe_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" #include #include #include @@ -30,6 +31,7 @@ void tglAUXRecord(void *,uint32_t,bool,uint32_t,uint32_t,uint32_t) {} uint32_t providerProbe(void *); uint32_t nativeHPD(void *) {return 1;} struct XE { + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; uint32_t tglModularProbed=0; uint64_t tglPlatformCopy[0x1c0/8] {}; bool tglActive=true; diff --git a/tests/tgl_sink_framing.py b/tests/tgl_sink_framing.py index d5561329..f254e02b 100644 --- a/tests/tgl_sink_framing.py +++ b/tests/tgl_sink_framing.py @@ -12,7 +12,7 @@ text=re.search(rf'{name}\[\] \{{([^}}]+)\}}',install).group(1) expected=bytes(int(x,0) for x in text.split(','));assert b[offset:offset+len(expected)]==expected code=r''' -#include "kern_tgl_port_map.hpp" +#include "kern_xelp_sst_route.hpp" #include #include #include @@ -20,7 +20,8 @@ struct State { bool tglActive=true,tglSinkFramingReady=true,eligible=true; void *tglObservationController=nullptr; - bool tglDPSSTMode(uint32_t,uint32_t mode){return eligible && (mode==3 || mode==4);} + XELP::Display12SSTRoutes tglSSTRoutes(){return kind==3?XELP::Display12SSTRoutes{3,0,0}:kind==2?XELP::Display12SSTRoutes{0,0,8}:XELP::Display12SSTRoutes{0,8,0};} + unsigned kind=0; } state,*callbackXE=&state; unsigned records; void tglTransitionRecord(unsigned stage,bool done,void *,uint32_t ddi,uint64_t before,uint8_t after,const uint64_t *d,unsigned size){ @@ -29,13 +30,13 @@ void run(void *port){GUARD} int main(){ alignas(8) uint8_t p[0x600],ctl[0xD00];uint32_t config[4]; - for(unsigned kind=0;kind<3;++kind)for(unsigned cap=0;cap<3;++cap)for(unsigned requested=0;requested<3;++requested) + for(unsigned kind=0;kind<4;++kind)for(unsigned cap=0;cap<3;++cap)for(unsigned requested=0;requested<3;++requested) for(unsigned bad=0;bad<10;++bad){ memset(p,0,sizeof(p));memset(ctl,0,sizeof(ctl));memset(config,0,sizeof(config)); - state={};state.tglObservationController=ctl; - config[0]=kind?3:0;config[2]=kind?1:0; + state={};state.kind=kind;state.tglObservationController=ctl; + config[0]=kind==3?1:kind?3:0;config[2]=kind?1:0; uint32_t *cp=config;memcpy(p+0x548,&cp,sizeof(cp)); - uint32_t backend=kind==2?3:(kind?4:1);memcpy(p+0x550,&backend,4); + uint32_t backend=kind==3?1:kind==2?3:(kind?4:1);memcpy(p+0x550,&backend,4); void *access=ctl+8;memcpy(p+0x578,&access,sizeof(access));memcpy(ctl+0xC40,&access,sizeof(access)); p[0x115]=1;p[0x10]=cap;p[0x11A]=requested;p[0xC]=0x11; if(bad==1)state.tglActive=false; diff --git a/tests/tgl_training_leases_fixture.cpp b/tests/tgl_training_leases_fixture.cpp index 5360fd90..ec6a4478 100644 --- a/tests/tgl_training_leases_fixture.cpp +++ b/tests/tgl_training_leases_fixture.cpp @@ -1,3 +1,4 @@ +#include "kern_alderlake_s.hpp" templatevoid tglTransitionRecord(T...){ } // Native boundaries are mocks; LEASE_FUNCTIONS is extracted unchanged from production. #include @@ -44,6 +45,7 @@ template void tglObserveResource(T...) {} void tglAUXPublish() {} void tglDPCDPublish() {} struct XE { + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // TC_WIRING_FIELDS // LEASE_FIELDS IORecursiveLock *tglPathPoolLock; @@ -77,7 +79,7 @@ bool used[4] {}, existingPath, badPipe, failAdoption, stopCalled; int pgRefs[8] {}; std::atomic nativeCalls {0}; std::function nativeBoundary; -uint32_t nativeEndResult; +uint32_t nativeEndResult,nativeTrainingResult; void *protectedMMIO; bool protectOnPowerDrop, bufferIdlePending; std::atomic portDisables{0}, clockDisables{0}; @@ -99,7 +101,8 @@ void freePath(void *p) { } assert(false); } -void enablePG(void *p, uint32_t pg) { std::lock_guard lock(refLock); assert(p==power.data() && pg<8); ++pgRefs[pg]; } +unsigned expectedPG=0; +void enablePG(void *p, uint32_t pg) { std::lock_guard lock(refLock); assert(p==power.data() && pg<8); if(expectedPG)assert(pg==expectedPG); ++pgRefs[pg]; } void disablePG(void *p, uint32_t pg) { std::lock_guard lock(refLock); assert(p==power.data() && pg<8 && pgRefs[pg]>0); --pgRefs[pg]; if (protectOnPowerDrop && pgRefs[pg]==0) assert(mprotect(protectedMMIO, sizeof(mmio), PROT_NONE)==0); } uint32_t disablePort(void *p) { ++portDisables; @@ -125,7 +128,7 @@ void disableClocks(void *, void *port, void *) { } void nativeStop(void *, IOService *) { stopCalled=true; } uint32_t beginEnd(void *, void *) { ++nativeCalls; if(nativeBoundary) nativeBoundary(); return nativeEndResult; } -uint32_t train(void *, void *, void *) { ++nativeCalls; if(nativeBoundary) nativeBoundary(); return 0; } +uint32_t train(void *, void *, void *) { ++nativeCalls; if(nativeBoundary) nativeBoundary(); return nativeTrainingResult; } bool allocateResources(void *, uint32_t, uint32_t) { ++nativeCalls; if(nativeBoundary) nativeBoundary(); @@ -147,7 +150,7 @@ void setup() { obj.orgTGLTrainingBegin=(uintptr_t)beginEnd; obj.orgTGLTrainingEnd=(uintptr_t)beginEnd; obj.orgTGLTraining=(uintptr_t)train; obj.orgTGLAllocatePathResources=(uintptr_t)allocateResources; ctl={}; access={}; power={}; mmio={}; - existingPath=badPipe=failAdoption=stopCalled=false; nativeCalls=0; nativeEndResult=0; + existingPath=badPipe=failAdoption=stopCalled=false; nativeCalls=0; nativeEndResult=nativeTrainingResult=0; nativeBoundary={}; clockBoundary={}; protectedMMIO=nullptr; protectOnPowerDrop=bufferIdlePending=false;portDisables=clockDisables=0; std::fill(std::begin(used),std::end(used),false); std::fill(std::begin(pgRefs),std::end(pgRefs),0); ctl.at(0xd40)=obj.tglPlatformCopy; ctl.at(0xc40)=access.data(); ctl.at(0xc8)=power.data(); @@ -342,6 +345,73 @@ void endCompletion() { } puts("End completion: all 3 pipes retain failed cleanup and return Busy; retries consume native End once, training/adoption blocked, later End/Begin finishes; native error/commit semantics preserved"); } + +void routeLifecycleMatrix() { + unsigned sequences=0; + for(unsigned adapter:{0u,1u,3u,4u})for(unsigned ddi=0;ddi<9;++ddi)for(unsigned pipe=0;pipe<3;++pipe) + for(unsigned initial=0;initial<3;++initial) { + const unsigned mode=adapter?adapter:1; + if(adapter==0 ? ddi>1 : adapter==1 ? (ddi!=0&&ddi!=3&&ddi!=4) : ddi<3)continue; + setup(); + if(adapter==0){obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.tglComboDPMask=1U<(0x550)=mode; + for(unsigned i=0;i(func)=savedFunc;mmio.at(tp)=savedTP; + uint32_t request[20]={ddi+1},output[20]={}; + if(initial==2) { + // An inherited native scanout remains owned by native. A training call + // must not borrow its pipe/power or restore over its register state. + existingPath=true;mmio.at(func)|=0x80000000; + mmio.at(tp)|=0x80000000;mmio.at(buffer)=0x80000000; + assert(XE::wrapTGLTrainingBegin(ctl.data(),request)==0); + assert(XE::wrapTGLTraining(ctl.data(),request,output)==0); + request[2]=1;assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==0);request[2]=0; + assert(!obj.tglTrainingLeases[ddi].path && !obj.tglTrainingLeases[ddi].powerOwned); + assert(mmio.at(func)==(savedFunc|0x80000000)); + for(int refs:pgRefs)assert(!refs); + // Model the native owner's completed disconnect; not an OS sleep/wake. + existingPath=false;mmio.at(func)=savedFunc;mmio.at(tp)=savedTP;mmio.at(buffer)=0x80; + } + auto &lease=obj.tglTrainingLeases[ddi]; + // Repeat without resetting controller, pool or mode. Successful training, + // native failure and delayed abort completion all precede a reconnection. + for(unsigned cycle=0;cycle<4;++cycle) { + const auto generation=lease.generation; + assert(XE::wrapTGLTrainingBegin(ctl.data(),request)==0); + nativeTrainingResult=cycle==1?0xe00002bc:0; + assert(XE::wrapTGLTraining(ctl.data(),request,output)==nativeTrainingResult); + assert(lease.path && lease.pipe==pipe && lease.generation==generation+1); + assert(lease.powerOwned && !lease.busy && pgRefs[lease.pg]==1); + assert(mmio.at(func)==savedFunc); + if(cycle==2) { + mmio.at(buffer)=0; + assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==kIOReturnBusy); + const auto calls=nativeCalls.load(); + assert(lease.endCleanupPending && lease.powerOwned); + assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==kIOReturnBusy && nativeCalls==calls); + assert(XE::wrapTGLTraining(ctl.data(),request,output)==kIOReturnBusy && nativeCalls==calls); + mmio.at(buffer)=0x80; + assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==0 && nativeCalls==calls); + } else assert(XE::wrapTGLTrainingEnd(ctl.data(),request)==0); + assert(!lease.path && !lease.busy && !lease.powerOwned && !lease.endCleanupPending); + assert(mmio.at(func)==savedFunc && mmio.at(tp)==savedTP); + for(int refs:pgRefs)assert(!refs); + for(unsigned i=0;i(0x49e8)==1);used[0]=false;clean(); ports[3].at(0x550)=2; assert(XE::tglPrepareTrainingLease(ctl.data(),obj.tglTrainingLeases[3])==1); // no TBT + // ADL-S Combo A/B/C use the same pool and mode1 contract. Allocate all + // three simultaneously and vary both allocation and teardown order. + unsigned comboOrders=0,ins[]={0,3,4}; + do {unsigned outs[]={0,3,4};do { + setup();obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDisplaySelected=true; + obj.adlsWiring.valid=true;obj.adlsWiring.dp=0x19;obj.tglAUXParentReady=true; + for(auto d:ins){ + assert(XE::tglTrainingLeaseForDDI(d)&&!obj.tglLegacyTCEligible(d)&&obj.tglAUXParentEligible(d)); + ports[d].at(0x550)=1;reserve(d); + } + for(auto d:outs)release(d); + clean();++comboOrders; + }while(std::next_permutation(outs,outs+3));}while(std::next_permutation(ins,ins+3)); + assert(comboOrders==36); + std::puts("PASS ADL-S Combo A/B/C: 36 three-port allocation/teardown orders using the shared lease implementation"); + // Each reservation must own the union of lane and pipe domains itself. + // No inherited GOP/AUX refs or another monitor are available to mask a gap. + unsigned powerCases=0; + for(unsigned mode:{1u,3u,4u})for(unsigned d=0;d<9;++d)for(unsigned pipe=0;pipe<3;++pipe){ + if(mode==1 ? (d!=0 && d!=3 && d!=4) : d<3)continue; + setup(); + if(mode==1){obj.tglTCWiring=TGLTCWiring::fromMasks(0,0,0);obj.adlsDisplaySelected=true;obj.adlsWiring.valid=true;obj.adlsWiring.dp=1U<(0x550)=mode; + for(unsigned i=0;i=3?3:1); + auto &lease=reserve(d);assert(lease.pipe==pipe&&lease.pg==expectedPG); + release(d);expectedPG=0; + for(unsigned i=0;i #include #include @@ -23,6 +25,7 @@ void clock_get_uptime(uint64_t *p){*p=1;} void absolutetime_to_nanoseconds(uint64_t t,uint64_t *p){*p=t;} unsigned observations,nativeCalls; struct XE { + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; // FIELDS bool tglActive=true,tglSinkFramingReady=true;void *tglObservationController{};uintptr_t orgTGLTrainingPattern{}; static uint32_t wrapTGLTrainingPattern(void *,uint32_t); @@ -91,6 +94,34 @@ int main(){ else {assert(r.pre.empty());assert(reg(0x46140+4*p)==0x3456&®(0x60400+0x1000*p)==0x01000020);} ++cases; } + // Reuse the actual wrapper for every admitted ADL-S Combo route on every + // pipe, independent of which connector/pipe a physical fixture happened to use. + for(bool adls:{false,true})for(unsigned d:{0u,1u,3u,4u})for(unsigned p=0;p<3;++p) + for(unsigned lanes:{1u,2u,4u})for(unsigned framing=0;framing<2;++framing) + for(unsigned pat=0;pat<6;++pat){ + if(adls?d==1:d>1)continue; + reset(d,p,pat);object.tglTCWiring=TGLTCWiring::fromMasks(0,0,0); + object.adlsDisplaySelected=adls;object.adlsWiring.valid=adls;object.adlsWiring.dp=adls?1U< #include #include @@ -20,6 +21,7 @@ struct IORegistryEntry { #define OSDynamicCast(type,p) dynamic_cast(p) #define FunctionCast(f,p) reinterpret_cast(p) struct XE { + bool adlsDisplaySelected=false;AlderLakeS::Wiring adlsWiring; bool tglActive=true; XELP::Profile xeProfile=XELP::identify(0x9a498086,1);XELP::VBT::Ports firmwarePorts; // FIELDS diff --git a/tests/xe_data_access.py b/tests/xe_data_access.py new file mode 100644 index 00000000..2674db32 --- /dev/null +++ b/tests/xe_data_access.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python3 +"""Exercise the production access decision with mocked kernel signing queries.""" +import pathlib, subprocess, tempfile +root = pathlib.Path(__file__).resolve().parents[1] +source = (root/'WhateverXe/kern_xe_data_access.cpp').read_text() +functions = source[source.index('bool knownOS()'):source.index('int checkOpen(')] +harness = r''' +#include "kern_xe_data_policy.hpp" +#include +#include +#include +namespace KernelVersion { enum { Catalina=19, BigSur, Monterey, Ventura, Sonoma, Sequoia, Tahoe }; } +int version=20; int getKernelVersion() { return version; } +bool ready=true, processPresent=true, platform=true, valid=true, pathError=false; +const char *identity="com.apple.WebKit.GPU"; +const char *target="/Library/GPUBundles/AppleIntelTGLGraphicsVADriver.bundle/Contents/MacOS/TGLVA"; +const char *imagePath="/arbitrary/updated/Safari/WebKitGPU"; +int imageReads=0, releases=0; +void *currentProcess() { return processPresent ? (void*)2 : nullptr; } +void *executableVnode(void*) { ++imageReads; return (void*)3; } +int getPath(void *node,char *path,int*) { if(pathError)return 1; strcpy(path,node==(void*)1?target:imagePath);return 0; } +int putVnode(void*) { ++releases; return 0; } +const char *getIdentity(void*) { return identity; } +int getPlatform(void*) { return platform; } +int getValid(void*) { return valid; } +const char *(*signingIdentity)(void*)=getIdentity; +int (*platformBinary)(void*)=getPlatform; +int (*signatureValid)(void*)=getValid; +'''+functions+r''' +int main() { + for(version=19;version<=26;++version) { + assert(knownOS()==(version>=20&&version<=25)); + if(!knownOS())continue; + for(const char *location : {"/System/Library/Frameworks/WebKit.framework/service", + "/System/Volumes/Preboot/Cryptexes/App/staged/service", "/Applications/Safari Copy.app/service", "/tmp/service"}) { + imagePath=location; + assert(allowed((void*)1,true)); + assert(imageReads==0); // WebKit's executable path is never queried. + } + platform=false; assert(!allowed((void*)1,true)); platform=true; + valid=false; assert(!allowed((void*)1,true)); valid=true; + identity="com.apple.WebKit.WebContent"; assert(!allowed((void*)1,true)); + identity=nullptr; assert(!allowed((void*)1,true)); identity="com.apple.WebKit.GPU"; + signingIdentity=nullptr; assert(!allowed((void*)1,true)); signingIdentity=getIdentity; + platformBinary=nullptr; assert(!allowed((void*)1,true)); platformBinary=getPlatform; + signatureValid=nullptr; assert(!allowed((void*)1,true)); signatureValid=getValid; + assert(!allowed((void*)1,false)); + } + ready=false; assert(!allowed((void*)1,true)); ready=true; + assert(!allowed(nullptr,true)); + processPresent=false; assert(!allowed((void*)1,true)); processPresent=true; + pathError=true; assert(!allowed((void*)1,true)); pathError=false; + target="/Library/GPUBundles/AppleIntelTGLGraphicsVADriver.bundle/Contents/MacOS/unlisted"; + assert(!allowed((void*)1,true)); + target="/Library/GPUBundles/XeCompilerAdapter.bundle/Contents/MacOS/adapter.dylib"; + imagePath="/System/Library/Frameworks/Metal.framework/Versions/A/XPCServices/MTLCompilerService.xpc/Contents/MacOS/MTLCompilerService"; + for(version=20;version<=25;++version) assert(allowed((void*)1,true)==(version>=23)); + imagePath="/tmp/MTLCompilerService"; assert(!allowed((void*)1,true)); + assert(imageReads==releases); + puts("PASS: production access decision, six OS gates, relocation, trust failures, compiler boundary"); +} +''' +harness = harness.replace('#include ', '#include \n#include ') +with tempfile.TemporaryDirectory() as temp: + cpp=pathlib.Path(temp)/'test.cpp'; exe=pathlib.Path(temp)/'test' + cpp.write_text(harness) + subprocess.run(['clang++','-std=c++17','-fsanitize=address,undefined','-I'+str(root/'WhateverXe'),str(cpp),'-o',str(exe)],check=True) + subprocess.run([str(exe)],check=True) diff --git a/tests/xe_data_policy.cpp b/tests/xe_data_policy.cpp index 2b0c58c2..106342e6 100644 --- a/tests/xe_data_policy.cpp +++ b/tests/xe_data_policy.cpp @@ -38,15 +38,21 @@ int main() { } for (auto name : {"", "other", "TGLVA.old", "../Info.plist"}) assert(clientForFile((va+name).c_str(), true) == Client::None); - assert(service(web, Client::WebKitGPU)); - for (auto root : {"/System/Volumes/Preboot/Cryptexes/OS", "/System/Volumes/Preboot/Cryptexes/Incoming/OS"}) { - assert(service((std::string(root)+web).c_str(), Client::WebKitGPU)); - assert(!service((std::string(root)+web).c_str(), Client::Compiler)); - assert(!service((std::string(root)+metal).c_str(), Client::WebKitGPU)); - assert(!service((std::string(root)+web+".old").c_str(), Client::WebKitGPU)); + // Same signing identifier alone is not authority: both kernel trust and + // current signature validity are required. Other Apple services are denied. + for (const char *identity : {static_cast(nullptr), "", "com.apple.WebKit.GPU", + "com.apple.WebKit.GPU.extra", "com.apple.WebKit.WebContent", + "com.apple.WebKit.Networking", "com.apple.Safari", "MTLCompilerService"}) { + for (bool platform : {false, true}) for (bool valid : {false, true}) + assert(webKitGPU(identity, platform, valid) == + (identity && std::string(identity) == "com.apple.WebKit.GPU" && platform && valid)); } - for (auto root : {"/tmp", "/System/Volumes/Data", "/System/Volumes/Preboot/Cryptexes/OS-extra", - "/System/Volumes/Preboot/Cryptexes/Incoming/OS/..", "/System/Volumes/Preboot/Cryptexes/App"}) + // No path-only fallback, including formerly accepted OS/App Cryptex roots, + // updated/staged layouts, and arbitrary relocated or renamed copies. + for (const char *root : {"", "/System/Volumes/Preboot/Cryptexes/OS", + "/System/Volumes/Preboot/Cryptexes/Incoming/OS", + "/System/Volumes/Preboot/Cryptexes/App", "/System/Cryptexes/App", + "/Applications/Relocated Safari.app", "/tmp"}) assert(!service((std::string(root)+web).c_str(), Client::WebKitGPU)); assert(!service(nullptr, Client::WebKitGPU)); const std::string mtl = "/Library/GPUBundles/AppleIntelTGLGraphicsMTLDriver.bundle/Contents/MacOS/"; diff --git a/tests/xelp_combo_policy.cpp b/tests/xelp_combo_policy.cpp new file mode 100644 index 00000000..79b1d87e --- /dev/null +++ b/tests/xelp_combo_policy.cpp @@ -0,0 +1,51 @@ +#include "kern_xelp_combo_policy.hpp" +#include +#include +#include +using Policy=XELP::ComboPolicy;using Platform=Policy::Platform;using Table=Policy::Table; +int main() { + for(auto id:{0x9A40,0x9A49,0x9A59,0x9A78,0x9AC0,0x9AC9,0x9AD9,0x9AF8}) + assert(XELP::ComboPolicy::tigerLake((uint32_t(id)<<16)|0x8086)==XELP::ComboPolicy::Platform::TGLUY); + for(auto id:{0x9A60,0x9A68,0x9A70}) + assert(XELP::ComboPolicy::tigerLake((uint32_t(id)<<16)|0x8086)==XELP::ComboPolicy::Platform::TGL); + assert(XELP::ComboPolicy::tigerLake(0x46908086)==XELP::ComboPolicy::Platform::Unknown); + assert(XELP::ComboPolicy::tigerLake(0x9A491234)==XELP::ComboPolicy::Platform::Unknown); + + // FNV-1a of all five-byte rows in Linux v6.12 intel_ddi_buf_trans.c, + // in Table enum order. This also checks the post_cursor_2 field. + const uint32_t expected[]={0x9C7E3F56U,0x004ED0CBU,0x49762BB3U,0x16ACF609U,0xEB4FB3B0U,0xBE6E1ADFU,0x231944E5U,0x67165B6FU,0x52DE18D6U,0x2DFE14A9U,0x4A81F931U,0xE7F29A4EU,0x9C126509U}; + for(unsigned table=1;table<=13;++table) { + uint32_t hash=2166136261U;const unsigned count=table==6?9:10; + for(unsigned i=0;i10]; + if(panel) { + if(rate==30)want=platform==Platform::ADLS?Table::ADLSHigh:platform==Platform::ADLP?Table::ADLPHigh:Table::ICLHigh; + else if(hobl)want=Table::HOBL; + else if(low)want=platform==Platform::ADLS?Table::ADLSLow:platform==Platform::ADLP?Table::ADLPLow:Table::LowSwing; + } + assert(p.table==want&&p.hobl==(want==Table::HOBL)); + const unsigned levels[][4]={{0,1,2,3},{4,5,6,0},{7,8,0,0},{9,0,0,0}}; + assert(p.level==(want==Table::HOBL&&vs==3?8:levels[vs][pre]));++cases; + } + for(auto rate:{0U,12U,19U,31U,~0U}){Policy p;assert(!p.select(Platform::TGL,rate,true,false,false,0,0));} + for(auto platform:{Platform::Unknown,Platform(255)}){Policy p;assert(!p.select(platform,10,true,false,false,0,0));} + Policy p;assert(p.select(Platform::ADLS,20,false,false,false,0,0)); + assert(!p.select(Platform::ADLS,20,false,false,false,4,0)&&p.table==Table::Invalid); + std::printf("PASS 129 Linux coefficient rows and %u platform/protocol/rate/panel/training selections\n",cases); +} diff --git a/tests/xelp_display_contract.cpp b/tests/xelp_display_contract.cpp new file mode 100644 index 00000000..616af9c2 --- /dev/null +++ b/tests/xelp_display_contract.cpp @@ -0,0 +1,57 @@ +#include "kern_xelp_display_contract.hpp" +#include +#include +#include +using C=XELP::DisplayContract; +static void faults(C c) { + assert(c.required && c.ready()); + // Every dependency must independently close admission. An unrelated failed + // operation must not disable this route. Reinstalling restores admission. + for(unsigned i=0;i<63;++i) { + auto broken=c;auto bit=1ULL< +#include +#include +using namespace XELP; +static_assert(ADLSComboResources::physicalMask()==0x79,"physical ADL-S ports"); +static_assert(ADLSComboBackend::mask(Donor::C004FC1A)==0x19,"native Combo capacity"); +int main() { + const uint32_t physical[]={0,3,4,5,6}; + for(unsigned n=0;n<5;++n) { + assert(ADLSComboResources::ddi(n)==physical[n]);assert(ADLSComboResources::phy(physical[n])==n); + AlderLakeS::Port p;assert(AlderLakeS::Port::resolve(physical[n],p) && p.phy==n); + } + assert(ADLSComboResources::ddi(5)==9 && ADLSComboResources::ddi(~0U)==9); + for(unsigned d=0;d<32;++d)for(auto donor:{Donor::Unknown,Donor::C004FC1A,Donor(2)}) { + uint32_t slot=0;const bool known=donor==Donor::C004FC1A&&(d==0||d==3||d==4); + assert(ADLSComboBackend::clockSlot(donor,d,slot)==known); + // The native index replacement is d>>1, only within the selected capacity. + assert(slot==(known?d>>1:~0U)); + } + // All masks over the nine donor port records, independent of VBT protocol. + // Physical D/E remain recognizable but never become usable native slots. + for(unsigned mask=0;mask<512;++mask) { + ProbeConfig c;c.profile=identify(0x46908086,12);c.combo=mask; + c.comboBackend=ADLSComboBackend::mask(c.profile.donor); + assert(c.valid()==(mask && !(mask&~0x19U))); + c.comboBackend=ADLSComboResources::physicalMask();assert(!c.valid()); + } + assert(!ADLSComboBackend::mask(Donor::Unknown)); + puts("PASS physical PHY mapping separate from donor slots: 96 slot cases and 512 provider masks, no D/E resource inference"); +} diff --git a/tests/xelp_panel_power.cpp b/tests/xelp_panel_power.cpp new file mode 100644 index 00000000..50d5aa11 --- /dev/null +++ b/tests/xelp_panel_power.cpp @@ -0,0 +1,53 @@ +#include "kern_xelp_panel_power.hpp" +#include +#include +#include +using P=XELP::PanelPower; +int main() { + XELP::VBT::Ports v;v.status=XELP::VBT::Status::Valid;v.present=1; + v.port[0]={0x78C6,10,0,0x40,0,0}; + auto &panel=v.panel;panel.valid=true;panel.ddi=0; + panel.backlightValid=true;panel.pwmPresent=true;panel.backlightType=2; + assert(P::nativePPS0(v,XELP::Family::TigerLake)); + for(unsigned controller=0;controller<16;++controller)for(unsigned type=0;type<16;++type) { + panel.controller=controller;panel.backlightType=type; + assert(P::nativePPS0(v,XELP::Family::TigerLake)==(!controller && type==2)); + } + panel.controller=0;panel.backlightType=2; + assert(P::nativePPS0(v,XELP::Family::AlderLakeS)); + assert(P::nativePPS0(v,XELP::Family::RaptorLakeS)); + v.present|=2;v.port[1]=v.port[0];assert(!P::nativePPS0(v,XELP::Family::TigerLake));v.present=1; + const uint32_t zero[5]={}; + auto plan=P::select(panel,zero,0,0,0); + const uint32_t defaults[]={500,500,2100,5000,7}; + assert(plan.valid && !memcmp(plan.fields,defaults,sizeof(defaults))); + // Each source wins independently; rounding never shortens T12. BIOS cycle + // readback is already encoded, so repeated power calls cannot grow it. + unsigned cases=0; + for(unsigned i=0;i<5;++i)for(unsigned n: {0U,1U,999U,1000U,1001U,6000U,8191U,8192U,30000U,30001U,65535U}) { + auto p=panel;p.delays[i]=n; + const auto q=P::select(p,zero,0,0,0); + const unsigned index[]={2,0,1,3,4}; + const auto expected=i==4 ? (n+1000+999)/1000 : n; + assert(q.valid==(i==4 ? expected<=31 : n<=8191)); + if(q.valid) { + if(n)assert(q.fields[index[i]]==expected); + uint32_t on=(q.fields[2]<<16)|q.fields[0],off=(q.fields[3]<<16)|q.fields[1]; + auto again=P::select(p,zero,on,off,q.fields[4]<<4); + assert(again.valid && !memcmp(q.fields,again.fields,sizeof(q.fields))); + } + ++cases; + } + uint32_t native[]={3000,2000,7000,8000,20}; + panel.delays[0]=7100;panel.delays[1]=4000;panel.delays[2]=1;panel.delays[3]=2;panel.delays[4]=5000; + plan=P::select(panel,native,(6000U<<16)|100,100U<<16|2500,12<<4); + const uint32_t mixed[]={4000,2500,7100,8000,20}; + assert(plan.valid && !memcmp(plan.fields,mixed,sizeof(mixed))); + for(unsigned i=0;i<5;++i) {auto saved=native[i];native[i]=i==4?32:8192;assert(!P::select(panel,native,0,0,0).valid);native[i]=saved;} + assert(!P::select(panel,native,~0U,0,0).valid); + assert(!P::select(panel,native,0,~0U,0).valid); + assert(!P::select(panel,native,0,0,~0U).valid); + // An explicit property remains effective even if the donor's T8 is absent. + native[0]=0;plan=P::select(panel,native,0,0,0);assert(plan.valid && plan.fields[3]==8000); + printf("PASS panel controller/method qualification; %u delay boundaries, source merging, overflow and idempotence\n",cases); +} diff --git a/tests/xelp_panel_pwm.cpp b/tests/xelp_panel_pwm.cpp new file mode 100644 index 00000000..9bd481ab --- /dev/null +++ b/tests/xelp_panel_pwm.cpp @@ -0,0 +1,21 @@ +#include "kern_xelp_panel_pwm.hpp" +#include +#include +#include +using P=XELP::PanelPWM; +int main(){ + for(unsigned strap:{0U,256U})for(unsigned hz:{1U,200U,1000U,65535U})for(bool low:{false,true}) { + const auto p=P::select(0,0,strap,hz,low); + assert(p.valid() && p.period==((strap?24000000U:19200000U)+hz/2)/hz && p.polarity==(low?1U<<29:0)); + } + for(auto period:{1U,65535U,65537U,96000U,120000U,24000000U,0xfffffffeU})for(bool low:{false,true}) { + const auto p=P::select(period,low?1U<<29:0,~0U,0,!low);assert(p.period==period&&p.polarity==(low?1U<<29:0)); + uint32_t last=0; + for(uint32_t brightness=0;brightness<=65535;++brightness){uint32_t duty=~0U;assert(p.duty(brightness,duty)); + assert(duty==uint64_t(period)*brightness/65535 && duty>=last && duty<=period);last=duty;} + assert(last==period);assert(!p.duty(65536,last)); + } + assert(!P::select(0,0,~0U,200,false).valid());assert(!P::select(0,0,0,0,false).valid()); + assert(!P::select(~0U,0,0,200,false).valid());assert(!P::select(1,~0U,0,200,false).valid()); + puts("PASS 917504 PWM brightness cases; BIOS/VBT period and polarity, rawclk, monotonic wide arithmetic and invalid inputs"); +} diff --git a/tests/xelp_platform.cpp b/tests/xelp_platform.cpp index 997fae6d..8fd2fa17 100644 --- a/tests/xelp_platform.cpp +++ b/tests/xelp_platform.cpp @@ -12,10 +12,22 @@ struct IO { uint32_t read(uint32_t r){reads.push_back(r);return regs.at(r);} }; int main() { - for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,255U}) { + static_assert(sizeof(Profile)==28 && sizeof(ProbeConfig)==60,"Keep existing provider ABI3 layout"); + for(auto id:{0xA7808086U,0xA7828086U,0xA7888086U,0xA78B8086U}) { + auto p=identify(id,12); + assert(displayAdapter(p.family)==DisplayAdapter::ADLS && p.gt==GT::ADLSSmall && p.admitted()); + ProbeConfig c;c.profile=p;c.combo=0x19;c.comboBackend=0x19; + assert(c.valid()); // Registered desktop GT uses the common Display adapter. + assert(!identify(id,12,0x46908086).admitted()); + } + + for(uint32_t id=0;id<65536;++id)for(unsigned rev:{0U,1U,4U,8U,12U,255U}) { const auto p=identify((id<<16)|0x8086,rev); - const bool tgl=id==0x9A40 || id==0x9A49 || id==0x9A78,adln=id>=0x46D0 && id<=0x46D4; - assert(p.admitted()==(tgl || (adln && rev==0))); + 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)); + if(adls||rpls)assert(!identify((id<<16)|0x8086,rev,0x9A498086).admitted()); assert(!adln || p.display==Display::XeLPD); assert(!identify((id<<16)|0x1234,rev).admitted()); } @@ -51,6 +63,41 @@ int main() { const uint32_t v[]={d,d+1,2,0,0x800,0x202};__builtin_memcpy(r,v,sizeof(v)); assert(probeMode(io,b,2,reinterpret_cast(r),r)==(d<2?1U:0U));} assert(io.reads.empty());auto other=b;other.access=3;assert(!b.sameBinding(other));assert(b.sameBinding(b)); + // Display13 Combo protocol/port arrangements compose without FIA reads. + unsigned dpCases=0; + for(unsigned selected=1;selected<4;++selected)for(unsigned dpMask=0;dpMask<4;++dpMask){ + if(dpMask&~selected)continue; + c.combo=selected;c.comboBackend=3;b.config=c; + for(unsigned d=0;d<2;++d){ + auto rec=reinterpret_cast(platform+0x20+24*d);const bool dp=dpMask&(1U<(rec),rec)==((selected&(1U<(rec),rec));rec[field]^=1;} + ++dpCases; + } + } + assert(dpCases==16&&io.reads.empty()); + c={};c.profile=identify(0x46908086,12);c.combo=8;c.comboBackend=0x19;b.config=c;assert(c.valid()); + for(unsigned d=0;d<9;++d){auto r=reinterpret_cast(platform+0x20+24*d); + const uint32_t v[]={d,9,2,0,0x800,0x202};memcpy(r,v,sizeof(v)); + assert(probeMode(io,b,2,reinterpret_cast(r),r)==(d==3?1U:0U));} + c.combo=24;c.comboBackend=0x19;b.config=c;assert(c.valid()); + auto dp=reinterpret_cast(platform+0x80);const uint32_t dpc[]={4,10,1,0,0x400,0x201};memcpy(dp,dpc,sizeof(dpc)); + assert(probeMode(io,b,2,reinterpret_cast(dp),dp)==1); + for(unsigned i=0;i<6;++i){dp[i]^=1;assert(!probeMode(io,b,2,reinterpret_cast(dp),dp));dp[i]^=1;} + c.combo=8;c.comboBackend=0x19;b.config=c;assert(!probeMode(io,b,2,reinterpret_cast(dp),dp)); + assert(io.reads.empty()); // ADL-S must never probe TGL FIA for its Combo TC1. + for(unsigned bad=0;bad<5;++bad){auto x=c;if(bad==0)x.combo=2;if(bad==1)x.comboBackend=0x79; + if(bad==2)x.legacy=8;if(bad==3)x.typeC=8;if(bad==4)x.profile.revision=256;assert(!x.valid());} + assert(!identify(0x46D18086,12,0x46908086).admitted()); + assert(!identify(0x46908086,12,0x46808086).admitted()); + // Panel mode requires selected Combo A and the exact native panel record. + c.combo=1;b.config=c;auto panel=reinterpret_cast(platform+0x20); + const uint32_t panelRecord[]={0,0,0,0,2,0x18};memcpy(panel,panelRecord,sizeof(panelRecord)); + assert(probeMode(io,b,2,reinterpret_cast(panel),panel)==1 && io.reads.empty()); + for(unsigned field=0;field<6;++field){panel[field]^=1;assert(!probeMode(io,b,2,reinterpret_cast(panel),panel));panel[field]^=1;} + c.combo=8;b.config=c;assert(!probeMode(io,b,2,reinterpret_cast(panel),panel)); // CPU association and power-consumer accounting are independent of transport. NativePorts ports {platform,(1U<<0)|(1U<<4)|(1U<<6)}; assert(ports.bound(platform+0x20+24*4,4));assert(!ports.bound(platform+0x20+24*3,4)); diff --git a/tests/xelp_sst_program.cpp b/tests/xelp_sst_program.cpp new file mode 100644 index 00000000..e98df247 --- /dev/null +++ b/tests/xelp_sst_program.cpp @@ -0,0 +1,57 @@ +#include +#include +#include +#include +#include "kern_xelp_sst_program.hpp" +struct IO { + std::map regs; + std::vector reads,writes; + uint32_t drop {}; + uint32_t read(uint32_t reg){reads.push_back(reg);return regs.at(reg);} + void write(uint32_t reg,uint32_t value){writes.push_back(reg);if(reg!=drop)regs.at(reg)=value;} +}; +int main(){ + unsigned count=0; + for(unsigned ddi=0;ddi<9;++ddi)for(unsigned pipe=0;pipe<3;++pipe)for(unsigned lanes:{1U,2U,4U}) { + const auto clock=0x46140+4*pipe,func=0x60400+0x1000*pipe,buffer=0x64000+0x100*ddi; + for(unsigned fault=0;fault<9;++fault){ + IO io;io.regs={{clock,0x12345},{func,0xA05670},{buffer,0x40}}; + const auto read=[&](uint32_t r){return io.read(r);};const auto write=[&](uint32_t r,uint32_t v){io.write(r,v);}; + if(fault==1)io.regs[func]|=0x80000000; + if(fault==2)io.regs[buffer]|=0x80000000; + if(fault==3)io.regs[clock]|=((ddi+1)%9+1)<<28; + if(fault==4)io.regs[func]|=((ddi+1)%9+1)<<27; + if(fault==5)io.drop=clock; + if(fault==6)io.drop=func; + const auto r=XELP::SSTProgram::prepare(read,write,ddi,pipe,fault==7?3:lanes,fault!=8); + const unsigned reason[]={0,2,2,3,4,6,7,1,5};assert(r.reason==reason[fault]); + if(!fault){assert(io.regs[clock]==(((ddi+1)<<28)|0x12345)); + assert(io.regs[func]==(((ddi+1)<<27)|0x02000000|0xA05670|((lanes-1)<<1))); + assert(io.writes.size()==2&&io.writes[0]==clock&&io.writes[1]==func);} + else if(fault==5)assert(io.writes.size()==1&&io.writes[0]==clock); + else if(fault==6)assert(io.writes.size()==2); + else assert(io.writes.empty()); + if(fault==8)assert(io.reads.empty()); + assert(io.regs[buffer]==(fault==2?0x80000040:0x40));++count; + } + } + for(unsigned pipe=0;pipe<3;++pipe)for(unsigned mode:{0U,0x80000000U,0x88000000U}) + for(unsigned enhanced=0;enhanced<2;++enhanced)for(unsigned panel=0;panel<2;++panel) + for(unsigned sink=0;sink<2;++sink)for(unsigned alt=0;alt<3;++alt)for(unsigned powered=0;powered<2;++powered){ + const auto reg=0x60540+0x1000*pipe;IO io;io.regs[reg]=mode|0x40040; + const auto r=XELP::SSTProgram::framing([&](uint32_t x){return io.read(x);},[&](uint32_t x,uint32_t v){io.write(x,v);},pipe,enhanced,panel,sink,alt,powered); + assert(r.ready==bool(powered)); + if(!powered)assert(io.reads.empty()&&io.writes.empty()); + else if(mode!=0x80000000U)assert(io.writes.empty()&&io.regs[reg]==(mode|0x40040)); + else {assert((io.regs[reg]&0x40000)==(enhanced<<18)); + assert((io.regs[reg]&0x40)==((panel&&sink&&alt<=1)?alt<<6:0x40));} + ++count; + } + IO io;const auto read=[&](uint32_t r){return io.read(r);};const auto write=[&](uint32_t r,uint32_t v){io.write(r,v);}; + assert(XELP::SSTProgram::prepare(read,write,9,0,4,true).reason==8); + assert(XELP::SSTProgram::prepare(read,write,0,3,4,true).reason==8); + assert(!XELP::SSTProgram::framing(read,write,3,1,false,false,0,true).ready); + assert(!XELP::SSTProgram::framing(read,write,0,2,false,false,0,true).ready); + assert(io.reads.empty()&&io.writes.empty()); + printf("PASS common SST programming: %u route/framing/fault cases; no unpowered or foreign-route writes\n",count); +} diff --git a/tests/xelp_sst_route.cpp b/tests/xelp_sst_route.cpp new file mode 100644 index 00000000..83128a76 --- /dev/null +++ b/tests/xelp_sst_route.cpp @@ -0,0 +1,80 @@ +#include "kern_xelp_sst_route.hpp" +#include "kern_tgl_tc_boot.hpp" +#include +#include +#include +using namespace XELP; + +int main() { + // Exercise composition, not machine connector lists: all 4^9 assignments + // of absent / Combo / Dekel legacy / DP-alt, then each pipe independently. + unsigned checked=0; + for(unsigned assignment=0;assignment<(1U<<18);++assignment) { + Display12SSTRoutes routes; + for(unsigned d=0;d<9;++d) { + const auto type=(assignment>>(2*d))&3; + if(type==1)routes.combo|=1U<>(2*d))&3; + const unsigned expectedMode[]={0,1,4,3}; + const auto r=routes.external(d,expectedMode[type]); + const bool valid=type && (type==1 || d>=3); + assert(r.valid()==valid); + if(!valid)continue; + assert(r.needsLease() && !r.panel); + assert(r.ddi==d && r.ddiBuffer==0x64000+0x100*d); + assert(r.retainComboAUX()==(type==1)); + // A fixed DP AUX transaction must work with all pipes stopped. + // DP-alt deliberately delegates that ownership to the TC backend. + assert((r.auxParentPG!=0)==(type!=3)); + assert(r.auxParentPG==(type==3?0U:d<3?1U:3U)); + for(unsigned mode=0;mode<6;++mode) { + Display12SSTRoute resolved; + assert(routes.resolve(d,1,mode,resolved)==(mode==expectedMode[type])); + assert(resolved.valid()==(mode==expectedMode[type])); + assert(!routes.resolve(d,2,mode,resolved)); // HDMI is not SST. + } + // Reference domain unions: PW1 / PW1..3 / PW1..4. + const unsigned masks[2][3]={{3,0x3f,0xff},{0x3f,0x3f,0xff}}; + for(unsigned pipe=0;pipe<3;++pipe)assert(r.mainPowerMask(pipe)==masks[d>=3][pipe]); + assert(!r.mainPowerMask(3) && !r.mainPowerMask(~0U)); + ++checked; + } + } + for(unsigned d=0;d<9;++d) { + const auto bit=1U<=3)); + assert(tc.external(d,4).valid()==(d>=3)); + assert(!tc.external(d,1).valid()); + Display12SSTRoute panel; + assert(Display12SSTRoutes{}.resolve(d,0,1,panel)==(d<2)); + if(d<2)assert(panel.panel && !panel.needsLease() && !panel.auxParentPG && panel.retainComboAUX()); + } + assert((!Display12SSTRoutes{~0U,0,0}.eligible(9))); + assert((!Display12SSTRoutes{~0U,0,0}.eligible(~0U))); + // Firmware intent and qualified backend are an intersection. Automatic + // legacy registration supplies a route only after the native HPD decision. + for(unsigned wiring=0;wiring<64;++wiring)for(unsigned backend=0;backend<64;++backend) { + auto w=TGLTCWiring::fromMasks(wiring<<3,0,0); + for(unsigned d=3;d<9;++d) { + assert(w.sstRoutes(backend<<3,0,0).external(d,4).valid()==bool((wiring&backend)&(1U<<(d-3)))); + assert(w.sstRoutes(0,0,0).external(d,4).valid()==false); + } + } + // Panel AUX owns core power, but the panel path never becomes an + // external training-lease candidate. Mixed external ports stay separate. + for(unsigned d=0;d<2;++d) { + Display12SSTRoutes routes{0x18,0,0,1U<