From c7c8dc70381af3b48a4b65c177acf8835da080eb Mon Sep 17 00:00:00 2001 From: FireWolf <10460478+0xFireWolf@users.noreply.github.com> Date: Sat, 15 Jul 2023 05:29:26 -0700 Subject: [PATCH] Extend the Backlight Registers Alternative Fix (BLT) submodule to support Kaby Lake platforms (#116) --- Changelog.md | 4 + Manual/FAQ.IntelHD.cn.md | 8 +- Manual/FAQ.IntelHD.en.md | 8 +- README.md | 2 +- WhateverGreen/kern_igfx.hpp | 494 ++++++++++++- WhateverGreen/kern_igfx_backlight.cpp | 973 +++++++++++++++++++------- 6 files changed, 1198 insertions(+), 291 deletions(-) diff --git a/Changelog.md b/Changelog.md index 87119f56..3f4b0895 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,5 +1,9 @@ WhateverGreen Changelog ======================= +#### v1.6.6 +- Extended the Backlight Registers Alternative Fix (BLT) submodule to support both KBL and CFL platforms. (by @0xFireWolf) +- Revised the Backlight Registers Fix (BLR) submodule to make it compatible with the Backlight Smoother (BLS) on KBL platforms. (by @0xFireWolf) + #### v1.6.5 - Added constants for macOS 14 support - Added a new boot argument `-igfxblt` to revert the optimizations done by the compiler in backlight related functions, fixing the 3-minute dark screen issue and making Backlight Smoother (BLS) work on mobile Coffee Lake platforms running macOS 13.4 or later. (by @0xFireWolf) diff --git a/Manual/FAQ.IntelHD.cn.md b/Manual/FAQ.IntelHD.cn.md index 68a61a0c..72da523a 100644 --- a/Manual/FAQ.IntelHD.cn.md +++ b/Manual/FAQ.IntelHD.cn.md @@ -2021,13 +2021,13 @@ igfx: @ (DBG) BLS: [COMM] Processing the request: Current = 0x00014ead; Target = -## 修复在 Coffee Lake 平台上运行 macOS 13.4 或以上版本的笔记本开机持续3分钟暗屏问题 +## 修复在 Kaby Lake/Coffee Lake 平台上运行 macOS 13.4 或以上版本的笔记本开机持续3分钟暗屏问题 -如果你之前使用“亮度寄存器修复”(也就是 `-igfxblr` 这个启动参数)来解决笔记本开机持续3分钟左右暗屏的问题,在升级到 macOS 13.4 或之后的版本后你会发现该补丁失效了。这是因为苹果简化了显卡驱动中读写寄存器相关的函数,导致编译器优化了函数调用的汇编代码,进而导致“亮度寄存器修复”以及“亮度丝滑器”注册的钩子失效。从 v1.6.5 开始,*WEG* 提供了新的补丁来撤销编译器对亮度调节相关函数的优化以及为 Coffee Lake 平台的笔记本重写了调节亮度的函数,从而解决开机持续3分钟暗屏以及“亮度丝滑器”失效的问题。 +如果你之前使用“亮度寄存器修复”(也就是 `-igfxblr` 这个启动参数)来解决笔记本开机持续3分钟左右暗屏的问题,在升级到 macOS 13.4 或之后的版本后你会发现该补丁失效了。这是因为苹果简化了显卡驱动中读写寄存器相关的函数,导致编译器优化了函数调用的汇编代码,进而导致“亮度寄存器修复”以及“亮度丝滑器”注册的钩子失效。从 v1.6.5 开始,*WEG* 提供了新的补丁来撤销编译器对亮度调节相关函数的优化以及为 Coffee Lake 平台的笔记本重写了调节亮度的函数,从而解决开机持续3分钟暗屏以及“亮度丝滑器”失效的问题。从 v1.6.6 开始,*WEG* 支持 Kaby Lake 平台。 -请注意这个新补丁仅适用于使用 macOS 13.4 以及以上的 Coffee Lake 核显驱动的笔记本用户。你可以为核显添加 `enable-backlight-registers-alternative-fix` 属性或者直接使用 `-igfxblt` 启动参数来启用这个新的补丁。与此同时,你可以删除原“亮度寄存器修复”的 `enable-backlight-registers-fix` 设备属性或者 `-igfxblr` 启动参数。如果你想在 macOS 13.4 或以上系统中使用“亮度丝滑器”,你需要添加 `-igfxblt` 以及 `-igfxbls` 这两个启动参数。 +请注意这个新补丁仅适用于使用 macOS 13.4 以及以上的 Kaby Lake 或者 Coffee Lake 核显驱动的笔记本用户。你可以为核显添加 `enable-backlight-registers-alternative-fix` 属性或者直接使用 `-igfxblt` 启动参数来启用这个新的补丁。与此同时,你可以删除原“亮度寄存器修复”的 `enable-backlight-registers-fix` 设备属性或者 `-igfxblr` 启动参数。如果你想在 macOS 13.4 或以上系统中使用“亮度丝滑器”,你需要添加 `-igfxblt` 以及 `-igfxbls` 这两个启动参数。 -Ice Lake 平台的笔记本用户不受此问题影响,然而 Kaby Lake 平台的笔记本用户可能在 macOS 13.4 或以上系统中遇到类似的3分钟暗屏问题。由于没有足够空间来覆盖读取亮度相关寄存器的汇编指令,此新补丁暂不支持 Kaby Lake 平台。 +请注意 Ice Lake 平台的笔记本用户不受此问题影响。 ## 修复 Ice Lake 平台上笔记本开机持续花屏7到15秒的问题 diff --git a/Manual/FAQ.IntelHD.en.md b/Manual/FAQ.IntelHD.en.md index 44f725cc..e3349423 100644 --- a/Manual/FAQ.IntelHD.en.md +++ b/Manual/FAQ.IntelHD.en.md @@ -2671,13 +2671,13 @@ igfx: @ (DBG) BLS: [COMM] Processing the request: Current = 0x00014ead; Target = -## Fix the 3-minute black screen issue on CFL platforms running macOS 13.4 or later +## Fix the 3-minute black screen issue on KBL/CFL platforms running macOS 13.4 or later -If you have a CFL-based laptop and rely on the Backlight Registers Fix (BLR) to fix the 3-minute black screen issue, you may notice that BLR (`-igfxblr`) no longer work on macOS 13.4 or later. This is because Apple has simplified the implementation of the functions, `ReadRegister32` and `WriteRegister32`, in Coffee Lake's framebuffer driver shipped by macOS 13.4, so the compiler chose to inline invocations of those functions as many as possible. As a result, the `WriteRegister32` hooks registered by the Backlight Registers Fix (BLR) and the Backlight Smoother (BLS) submodules no longer work. Starting from v1.6.5, WEG can revert the optimizations done by the compiler in backlight related functions, provide an alternative to BLR and make BLS work properly on macOS 13.4 or later. +If you have a KBL/CFL-based laptop and rely on the Backlight Registers Fix (BLR) to fix the 3-minute black screen issue, you may notice that BLR (`-igfxblr`) no longer work on macOS 13.4 or later. This is because Apple has simplified the implementation of the functions, `ReadRegister32` and `WriteRegister32`, in Kaby/Coffee Lake's framebuffer drivers shipped by macOS 13.4, so the compiler chose to inline invocations of those functions as many as possible. As a result, the `WriteRegister32` hooks registered by the Backlight Registers Fix (BLR) and the Backlight Smoother (BLS) submodules no longer work. Starting from v1.6.5, WEG can revert the optimizations done by the compiler in backlight related functions, provide an alternative to BLR and make BLS work properly on macOS 13.4 or later. Starting from v1.6.6, WEG supports Kaby Lake platforms. -Note that this alternative fix is only available for users who have laptops using Coffee Lake's graphics driver and running macOS 13.4 or later. You can add the property `enable-backlight-registers-alternative-fix` to `IGPU` or use the boot argument `-igfxblt` to enable this new fix and remove the boot argument `-igfxblr` and/or the device property `enable-backlight-registers-fix`. If you wish to use the Backlight Smoother on macOS 13.4 or later, you need to add both `-igfxblt` and `-igfxbls` to the boot arguments. +Note that this alternative fix is only available for users who have laptops using Kaby Lake's or Coffee Lake's graphics driver and running macOS 13.4 or later. You can add the property `enable-backlight-registers-alternative-fix` to `IGPU` or use the boot argument `-igfxblt` to enable this new fix and remove the boot argument `-igfxblr` and/or the device property `enable-backlight-registers-fix`. If you wish to use the Backlight Smoother on macOS 13.4 or later, you need to add both `-igfxblt` and `-igfxbls` to the boot arguments. -Note that Ice Lake platforms are not affected because `WriteRegister32` is not inlined in backlight related functions, while Kaby Lake platforms may be affected but are not supported by this new fix at this moment, because it is hard to fix the write operation on the register `0xC8250` due to the space limit. +Note that Ice Lake platforms are not affected because `WriteRegister32` is not inlined in backlight related functions. ## Fix the issue that the builtin display remains garbled after the system boots on ICL platforms diff --git a/README.md b/README.md index 6bbe8bca..85aaa9a6 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,7 @@ Read [FAQs](./Manual/) and avoid asking any questions. No support is provided fo |--- |--- |--- | | `-igfxblr` | `enable-backlight-registers-fix` property on IGPU | Fix backlight registers on KBL, CFL and ICL platforms | | `-igfxbls` | `enable-backlight-smoother` property on IGPU | Make brightness transitions smoother on IVB+ platforms. [Read the manual](./Manual/FAQ.IntelHD.en.md#customize-the-behavior-of-the-backlight-smoother-to-improve-your-experience) | -| `-igfxblt` | `enable-backlight-registers-alternative-fix` property on IGPU | An alternative to the Backlight Registers Fix and make Backlight Smoother work on CFL platform running macOS 13.4 or later. [Read the manual](./Manual/FAQ.IntelHD.en.md#fix-the-3-minute-black-screen-issue-on-cfl-platforms-running-macos-134-or-later) | +| `-igfxblt` | `enable-backlight-registers-alternative-fix` property on IGPU | An alternative to the Backlight Registers Fix and make Backlight Smoother work on KBL/CFL platforms running macOS 13.4 or later. [Read the manual](./Manual/FAQ.IntelHD.en.md#fix-the-3-minute-black-screen-issue-on-cfl-platforms-running-macos-134-or-later) | | `-igfxcdc` | `enable-cdclk-frequency-fix` property on IGPU | Support all valid Core Display Clock (CDCLK) frequencies on ICL platforms. [Read the manual](./Manual/FAQ.IntelHD.en.md#support-all-possible-core-display-clock-cdclk-frequencies-on-icl-platforms) | | `-igfxdbeo` | `enable-dbuf-early-optimizer` property on IGPU | Fix the Display Data Buffer (DBUF) issues on ICL+ platforms. [Read the manual](./Manual/FAQ.IntelHD.en.md#fix-the-issue-that-the-builtin-display-remains-garbled-after-the-system-boots-on-icl-platforms) | | `-igfxdump` | N/A | Dump IGPU framebuffer kext to `/var/log/AppleIntelFramebuffer_X_Y` (available in DEBUG binaries) | diff --git a/WhateverGreen/kern_igfx.hpp b/WhateverGreen/kern_igfx.hpp index bd9fdf14..b9d6359a 100644 --- a/WhateverGreen/kern_igfx.hpp +++ b/WhateverGreen/kern_igfx.hpp @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -1601,18 +1602,47 @@ class IGFX { } modBacklightRegistersFix; /** - * A submodule to revert invocations of backlight-related functions and patch backlight register values on macOS 13.4. + * A submodule to revert invocations of backlight-related functions and patch backlight register values on macOS 13.4 and later. * - * @note Supported Platforms: CFL. + * @note This submodule supports both KBL and CFL platforms. + * @note This class defines the interface for concrete BLT submodules, each of which fixes a particular platform. */ class BacklightRegistersAltFix: public PatchSubmodule { + protected: /** * Fallback PWM frequency if the system was initialized with a frequency of 0 */ static constexpr uint32_t kFallbackBacklightFrequency = 120000; /** - * Record the location of the inlined invocation of `hwSetBacklight()` and the register that stores the controller instance + * The maximum number of instructions to be analyzed when probing the offset of each member field and position of each inlined invocation + */ + static constexpr size_t kMaxNumInstructions = 512; + + /** + * Record the offset of each required member field in the framebuffer controller + */ + struct ProbeContext { + /** + * Record the offset of the member field in the framebuffer controller that stores the PWM frequency divider + */ + size_t offsetFrequencyDivider {0}; + + /** + * Record the offset of the member field in the framebuffer controller that stores the current brightness level + */ + size_t offsetBrightnessLevel {0}; + + /** + * Validate this invocation context + */ + bool isValid() const { + return this->offsetFrequencyDivider != 0 && this->offsetBrightnessLevel != 0; + } + }; + + /** + * Record the location of an inlined invocation of `hwSetBacklight()` and the register that stores the controller instance * so that this patch submodule can revert the inlined invocation in the function of interest */ struct InvocationContext { @@ -1648,64 +1678,296 @@ class IGFX { } }; + /** + * Describe a function that contains an inlined invocation of `hwSetBacklight()` and thus will be analyzed and patched + */ + struct FunctionDescriptor { + /** + * Type of prober that finds the location of an inlined invocation of `hwSetBacklight()` in this function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + */ + using InlinedInvocationProber = InvocationContext (*)(const FunctionDescriptor&, const ProbeContext&); + + /** + * Type of reverter that reverts the inlined invocation of `hwSetBacklight()` in this function + * + * @param descriptor A descriptor that describes the function to be patched + * @param probeContext A probe context specifying the offset of each required member field in the framebuffer controller + * @param invocationContext An invocation context indicating where to patch this function and which register stores the controller instance + * @param patcher The kernel patcher + * @param orgHwSetBacklight The address of the original `hwSetBacklight()` function + * @return `true` if the function at the given address has been patched to invoke `hwSetBacklight()` explicitly, `false` otherwise. + */ + using InlinedInvocationReverter = bool (*)(const FunctionDescriptor&, const ProbeContext&, const InvocationContext&, KernelPatcher&, mach_vm_address_t); + + /** + * User-friendly name of this function + */ + const char *name {nullptr}; + + /** + * Symbol of this function + */ + const char *symbol {nullptr}; + + /** + * Address of this function + */ + mach_vm_address_t address {0}; + + /** + * A prober that finds the location of an inlined invocations of `hwSetBacklight()` in this function + */ + InlinedInvocationProber prober {nullptr}; + + /** + * A reverter that reverts the inlined invocation of `hwSetBacklight()` in this function + */ + InlinedInvocationReverter reverter {nullptr}; + + /** + * Set `true` if this function, after patched, will invoke `hwSetBacklight()` with the brightness level stored in the framebuffer controller instead of 0 + */ + bool useCurrentBrightnessLevel {true}; + + /** + * [Convenient] Find the location of an inlined invocations of `hwSetBacklight()` in this function + * + * @param context A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + */ + InvocationContext probe(const ProbeContext &context) const { + return this->prober(*this, context); + } + + /** + * [Convenient] Revert the inlined invocation of `hwSetBacklight()` in this function + * + * @param probeContext A probe context specifying the offset of each required member field in the framebuffer controller + * @param invocationContext An invocation context indicating where to patch this function and which register stores the controller instance + * @param patcher The kernel patcher + * @param orgHwSetBacklight The address of the original `hwSetBacklight()` function + * @return `true` if the function at the given address has been patched to invoke `hwSetBacklight()` explicitly, `false` otherwise. + */ + bool revert(const ProbeContext &probeContext, const InvocationContext &invocationContext, KernelPatcher &patcher, mach_vm_address_t orgHwSetBacklight) const { + return this->reverter(*this, probeContext, invocationContext, patcher, orgHwSetBacklight); + } + }; + + /** + * A namespace in which common assembly patterns are defined + */ + struct Patterns { + // Pattern: movq %rdi, %r?? + // Checks: MOV, 64-bit, Direct Mode, Source Register is %rdi + static bool movqArg0(const Disassembler::hde_t &handle) { + return handle.opcode == 0x89 && handle.rex_w == 1 && handle.modrm_mod == 3 && (handle.rex_r << 3 | handle.modrm_reg) == 7; + } + + // Pattern: movl %esi, %r?? + // Checks: MOV, 32-bit, Direct Mode, Source Register is %esi + static bool movlArg1(const Disassembler::hde_t &handle) { + return handle.opcode == 0x89 && handle.rex_w == 0 && handle.modrm_mod == 3 && (handle.rex_r << 3 | handle.modrm_reg) == 6; + } + + // Pattern: movl (%r), %r?? + // Checks: MOV, 32-bit, Memory Mode, Source register is identical to the given one + static bool movlFromMemory(const Disassembler::hde_t &handle, uint32_t source) { + return handle.opcode == 0x8B && handle.rex_w == 0 && handle.modrm_mod == 2 && (handle.rex_b << 3 | handle.modrm_rm) == source; + } + + // Pattern: movl (%r??), %r?? + // Checks: MOV, 32-bit, Memory Mode, The offset is identical to the given one + static bool movlFromMemoryWithOffset(const Disassembler::hde_t &handle, size_t offset) { + return handle.opcode == 0x8B && handle.rex_w == 0 && handle.modrm_mod == 2 && handle.disp.disp32 == offset; + } + + // Pattern: movl %r, (%r) + // Checks: MOV, 32-bit, Memory Mode, + // Source register is identical to the given `source`, + // Destination register is identical to the given `destination` + static bool movlToMemory(const Disassembler::hde_t &handle, uint32_t source, uint32_t destination) { + return handle.opcode == 0x89 && handle.rex_w == 0 && handle.modrm_mod == 2 && + (handle.rex_r << 3 | handle.modrm_reg) == source && + (handle.rex_b << 3 | handle.modrm_rm) == destination; + } + + // Pattern: movl $??????????, (%r??) + // Checks: MOV, 32-bit, Memory Mode, The offset is identical to the given one + static bool movlImm32ToMemoryWithOffset(const Disassembler::hde_t &handle, size_t offset) { + return handle.opcode == 0xC7 && handle.rex_w == 0 && handle.modrm_mod == 2 && handle.disp.disp32 == offset; + } + + // Pattern: leal (%r??), %r?? + // Checks: LEA, 32-bit, Memory Mode, The offset is identical to the given one + static bool lealWithOffset(const Disassembler::hde_t &handle, size_t offset) { + return handle.opcode == 0x8D && handle.rex_w == 0 && handle.modrm_mod == 2 && handle.disp.disp32 == offset; + } + + // Pattern: addl $, %r?? + // Checks: ADD, 32-bit, Direct Mode, The immediate operand is identical to the given one + static bool addlWithImm32(const Disassembler::hde_t &handle, uint32_t imm32) { + return (handle.opcode == 0x05 || handle.opcode == 0x81) && handle.rex_w == 0 && handle.imm.imm32 == imm32; + } + + // Pattern: imull %r??, %r?? + // Checks: IMUL, 32-bit, Direct Mode + static bool imull(const Disassembler::hde_t &handle) { + return handle.opcode == 0x0F && handle.opcode2 == 0xAF && handle.rex_w == 0 && handle.modrm_mod == 3; + } + + // Pattern: divl (%r) + // Checks: DIV, 32-bit, Memory Mode, The source register is identical to the given one + static bool divlByMemory(const Disassembler::hde_t &handle, uint32_t source) { + return handle.opcode == 0xF7 && handle.rex_w == 0 && handle.modrm_mod == 2 && (handle.rex_b << 3 | handle.modrm_rm) == source; + } + + // Pattern: divl (%r??) + // Checks: DIV, 32-bit, Memory Mode, The offset is identical to the given one + static bool divlByMemoryWithOffset(const Disassembler::hde_t &handle, size_t offset) { + return handle.opcode == 0xF7 && handle.rex_w == 0 && handle.modrm_mod == 2 && handle.disp.disp32 == offset; + } + }; + /** * Record the PWM frequency initialized by the system firmware + * + * @note This is needed on both KBL and CFL platforms. */ uint32_t firmwareBacklightFrequency {0}; /** - * Record the offset of the member field in the framebuffer controller that stores the PWM frequency divider + * Record the offset of each required member field in the framebuffer controller */ - size_t offsetFrequencyDivider {0}; + ProbeContext probeContext {}; /** - * Record the offset of the member field in the framebuffer controller that stores the current brightness level + * Fetch and preserve the PWM frequency set by the system firmware + * + * @param controller The framebuffer controller instance + */ + void fetchFirmwareBacklightFrequencyIfNecessary(void *controller) { + if (this->firmwareBacklightFrequency == 0) { + // Guard: The system should be initialized with a non-zero PWM frequency + if (auto bootValue = callbackIGFX->readRegister32(controller, BXT_BLC_PWM_FREQ1); bootValue != 0) { + DBGLOG("igfx", "BLT: [COMM] System initialized with a PWM frequency of 0x%x.", bootValue); + this->firmwareBacklightFrequency = bootValue; + } else { + SYSLOG("igfx", "BLT: [COMM] System initialized with a PWM frequency of 0. Will use the fallback frequency."); + this->firmwareBacklightFrequency = kFallbackBacklightFrequency; + } + } + } + + /** + * Get the PWM frequency divider from the given framebuffer controller instance + * + * @param controller The framebuffer controller instance + * @return The PWM frequency divider used by Apple. + */ + uint32_t getFrequencyDivider(void *controller) const { + return getMember(controller, this->probeContext.offsetFrequencyDivider); + } + + /** + * Save the given new brightness level to the given framebuffer controller instance + * + * @param controller The framebuffer controller instance + * @param brightness The new brightness level + */ + void setBrightnessLevel(void *controller, uint32_t brightness) const { + getMember(controller, this->probeContext.offsetBrightnessLevel) = brightness; + } + + /** + * Calculate the duty cycle for the given brightness level + * + * @param controller The framebuffer controller instance + * @param brightness The new brightness level + * @return The duty cycle that will be written to the register `BXT_BLC_PWM_DUTY1` + */ + uint32_t calcDutyCycle(void *controller, uint32_t brightness) const { + uint64_t frequency = this->firmwareBacklightFrequency; + uint64_t divider = this->getFrequencyDivider(controller); + return static_cast(frequency * brightness / divider); + } + + /** + * Write the given frequency value to the register `BXT_BLC_PWM_FREQ1` + * + * @param controller The framebuffer controller instance + * @param frequency The new frequency value + */ + void writeFrequency(void *controller, uint32_t frequency) const { + callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_FREQ1, frequency); + } + + /** + * Write the given duty cycle to the register `BXT_BLC_PWM_DUTY1` + * + * @param controller The framebuffer controller instance + * @param dutyCycle The new duty cycle value + * @note This function will pass the given value to the smoother if the user has enabled the BLS submodule. */ - size_t offsetBrightnessLevel {0}; + void writeDutyCycle(void *controller, uint32_t dutyCycle) const { + if (callbackIGFX->modBacklightSmoother.enabled) { + // Need to pass the scaled value to the smoother + DBGLOG("igfx", "BLS: [COMM] Will pass the new duty cycle value 0x%08x to the smoother version.", dutyCycle); + IGFX::BacklightSmoother::smoothCFLWriteRegisterPWMDuty1(controller, BXT_BLC_PWM_DUTY1, dutyCycle); + } else { + // Otherwise invoke the original function + DBGLOG("igfx", "BLT: [COMM] Will pass the new duty cycle value 0x%08x to the original version.", dutyCycle); + callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_DUTY1, dutyCycle); + } + } /** - * Find the offset of the member fields that store the PWM frequency divider and the current brightness level + * Analyze `hwSetBacklight()` to find the offset of each required member field in the framebuffer controller * * @param address The address of `hwSetBacklight()` to be analyzed * @param instructions The maximum number of instructions to be analyzed - * @return The offset of the member field that stores the PWM frequency divider and - * the offset of the member field that stores the current brightness level. + * @return A context object that stores the offset of each required member field in the framebuffer controller */ - ppair probeMemberOffsets(mach_vm_address_t address, size_t instructions) const; + virtual ProbeContext probeMemberOffsets(mach_vm_address_t address, size_t instructions) const { + return {}; + } /** - * Find the location of the inlined invocation of `hwSetBacklight()` in the function that starts at the given address + * Get all functions that contain an inlined invocation of `hwSetBacklight()` and thus will be analyzed and patched * - * @param address The address of the function to be analyzed - * @param instructions The maximum number of instructions to be analyzed - * @return A pair of `` offsets, relative to the given address, indicating the location of inlined invocation of `hwSetBacklight()`, - * and the register that stores the implicit framebuffer controller instance. + * @return A null-terminated array of descriptor, each of which describes a function to be analyzed and patched. */ - InvocationContext probeInlinedInvocation(mach_vm_address_t address, size_t instructions) const; + virtual FunctionDescriptor* getFunctionDescriptors() const { + return nullptr; + } /** - * Revert the inlined invocation of `hwSetBacklight()` in the function that starts at the given address + * Get the address of the wrapper function that sets the backlight compatible with the current machine * - * @param patcher The kernel patcher - * @param address The address of the function to be patched - * @param orgHwSetBacklight The address of the original `hwSetBacklight()` - * @param context The invocation context indicating where to patch the function and which register stores the controller instance - * @return `true` if the function at the given address has been patched to invoke `hwSetBacklight()` explicitly, `false` otherwise. + * @return The address of the wrapper function. */ - bool revertInlinedInvocation(KernelPatcher &patcher, mach_vm_address_t address, mach_vm_address_t orgHwSetBacklight, const InvocationContext &context) const; + virtual mach_vm_address_t getHwSetBacklightWrapper() const { + return 0; + } /** - * Wrapper to set the backlight compatible with the current machine + * Revert the inlined invocation of `hwSetBacklight()` in the given function * - * @param controller The implicit framebuffer controller - * @param brightness The new brightness level - * @return `kIOReturnSuccess`. - * @note When this function returns, the given brightness level should be stored into the given framebuffer controller. - * @note Unlike the original fix implemented in BLR, BLR-ALT computes the value of the frequency register and the duty register - * compatible with the current machine and commit those values using the original version of `WriteRegister32()`. - * Apple's implementation will not be used by this submodule. + * @param descriptor A descriptor that describes the function to be patched + * @param probeContext A probe context specifying the offset of each required member field in the framebuffer controller + * @param invocationContext An invocation context indicating where to patch this function and which register stores the controller instance + * @param patcher The kernel patcher + * @param orgHwSetBacklight The address of the original `hwSetBacklight()` function + * @return `true` if the function at the given address has been patched to invoke `hwSetBacklight()` explicitly, `false` otherwise. */ - static IOReturn wrapHwSetBacklight(void *controller, uint32_t brightness); + static bool revertInlinedInvocation(const FunctionDescriptor &descriptor, const ProbeContext &probeContext, const InvocationContext &invocationContext, KernelPatcher &patcher, mach_vm_address_t orgHwSetBacklight); /** * Get the name of the register at the given index @@ -1723,11 +1985,171 @@ class IGFX { public: // MARK: Patch Submodule IMP - void init() override; - void processKernel(KernelPatcher &patcher, DeviceInfo *info) override; - void processFramebufferKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) override; + void init() final; + void processKernel(KernelPatcher &patcher, DeviceInfo *info) final; + void processFramebufferKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) final; } modBacklightRegistersAltFix; + /** + * A concrete submodule to revert invocations of backlight-related functions and patch backlight register values on KBL platforms running macOS 13.4 and later. + */ + class BacklightRegistersAltFixKBL: public BacklightRegistersAltFix { + /** + * Record the PWM control value initialized by the system firmware + * + * @note This is needed on KBL platform only. + */ + uint32_t firmwareBacklightControl {0}; + + /** + * Fetch and preserve the PWM control value set by the system firmware + * + * @param controller The framebuffer controller instance + */ + void fetchFirmwareBacklightControlIfNecessary(void *controller) { + if (this->firmwareBacklightControl == 0) { + this->firmwareBacklightControl = callbackIGFX->readRegister32(controller, BXT_BLC_PWM_CTL1); + DBGLOG("igfx", "BLT: [KBL ] System initialized with a PWM control value of 0x%x.", this->firmwareBacklightControl); + } + } + + /** + * Write the given PWM control value to the register `BXT_BLC_PWM_CTL1` + * + * @param controller The framebuffer controller instance + * @param value The new control value + */ + void writePWMControl(void *controller, uint32_t value) const { + callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_CTL1, value); + } + + /** + * Analyze `hwSetBacklight()` to find the offset of each required member field in the framebuffer controller + * + * @param address The address of `hwSetBacklight()` to be analyzed + * @param instructions The maximum number of instructions to be analyzed + * @return A context object that stores the offset of each required member field in the framebuffer controller + */ + ProbeContext probeMemberOffsets(mach_vm_address_t address, size_t instructions) const override; + + /** + * Get all functions that contain an inlined invocation of `hwSetBacklight()` and thus will be analyzed and patched + * + * @return A null-terminated array of descriptor, each of describes a function to be analyzed and patched. + */ + FunctionDescriptor* getFunctionDescriptors() const override; + + /** + * Get the address of the wrapper function that sets the backlight compatible with the current machine + * + * @return The address of the wrapper function. + */ + mach_vm_address_t getHwSetBacklightWrapper() const override; + + /** + * Find the location of an inlined invocation of `hwSetBacklight()` in the given function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + * @note This function is specialized for `LightUpEDP()`. + */ + static InvocationContext probeInlinedInvocation_LightUpEDP(const FunctionDescriptor &descriptor, const ProbeContext &probeContext); + + /** + * Find the location of an inlined invocation of `hwSetBacklight()` in the given function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + * @note This function is specialized for `DisableDisplay()`. + */ + static InvocationContext probeInlinedInvocation_DisableDisplay(const FunctionDescriptor &descriptor, const ProbeContext &probeContext); + + /** + * Find the location of an inlined invocation of `hwSetBacklight()` in the given function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + * @note This function is specialized for `hwSetPanelPower()`. + */ + static InvocationContext probeInlinedInvocation_HwSetPanelPower(const FunctionDescriptor &descriptor, const ProbeContext &probeContext); + + /** + * Wrapper to set the backlight compatible with the current machine + * + * @param controller The implicit framebuffer controller + * @param brightness The new brightness level + * @return `kIOReturnSuccess`. + * @note When this function returns, the given brightness level should be stored into the given framebuffer controller. + * @note Unlike the original fix implemented in BLR, BLT computes the value of the frequency register and the duty register + * compatible with the current machine and commit those values using the original version of `WriteRegister32()`. + * Additionally, this function updates the PWM control register at 0xC8250. + * Apple's implementation will not be used by this submodule. + */ + static IOReturn wrapHwSetBacklight(void *controller, uint32_t brightness); + } modBacklightRegistersAltFixKBL; + + /** + * A concrete submodule to revert invocations of backlight-related functions and patch backlight register values on CFL platforms running macOS 13.4 and later. + */ + class BacklightRegistersAltFixCFL: public BacklightRegistersAltFix { + /** + * Analyze `hwSetBacklight()` to find the offset of each required member field in the framebuffer controller + * + * @param address The address of `hwSetBacklight()` to be analyzed + * @param instructions The maximum number of instructions to be analyzed + * @return A context object that stores the offset of each required member field in the framebuffer controller + */ + ProbeContext probeMemberOffsets(mach_vm_address_t address, size_t instructions) const override; + + /** + * Get all functions that contain an inlined invocation of `hwSetBacklight()` and thus will be analyzed and patched + * + * @return A null-terminated array of descriptor, each of describes a function to be analyzed and patched. + */ + FunctionDescriptor* getFunctionDescriptors() const override; + + /** + * Get the address of the wrapper function that sets the backlight compatible with the current machine + * + * @return The address of the wrapper function. + */ + mach_vm_address_t getHwSetBacklightWrapper() const override; + + /** + * Find the location of an inlined invocation of `hwSetBacklight()` in the given function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + * @note This function is specialized for `LightUpEDP()` and `hwSetPanelPower()`. + */ + static InvocationContext probeInlinedInvocation(const FunctionDescriptor &descriptor, const ProbeContext &probeContext); + + /** + * Wrapper to set the backlight compatible with the current machine + * + * @param controller The implicit framebuffer controller + * @param brightness The new brightness level + * @return `kIOReturnSuccess`. + * @note When this function returns, the given brightness level should be stored into the given framebuffer controller. + * @note Unlike the original fix implemented in BLR, BLT computes the value of the frequency register and the duty register + * compatible with the current machine and commit those values using the original version of `WriteRegister32()`. + * Apple's implementation will not be used by this submodule. + */ + static IOReturn wrapHwSetBacklight(void *controller, uint32_t brightness); + } modBacklightRegistersAltFixCFL; + /** * Brightness request event source needs access to the original WriteRegister32 function */ diff --git a/WhateverGreen/kern_igfx_backlight.cpp b/WhateverGreen/kern_igfx_backlight.cpp index b1f5c101..58f5b39e 100644 --- a/WhateverGreen/kern_igfx_backlight.cpp +++ b/WhateverGreen/kern_igfx_backlight.cpp @@ -125,7 +125,15 @@ void IGFX::BacklightRegistersFix::wrapKBLWriteRegisterPWMFreq1(void *controller, callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_FREQ1, frequency ? self->targetBacklightFrequency : 0); // Finish by writing the duty cycle. - callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_DUTY1, rescaledValue); + if (callbackIGFX->modBacklightSmoother.enabled) { + // Need to pass the scaled value to the smoother + DBGLOG("igfx", "BLS: [KBL ] Will pass the rescaled value 0x%08x to the smoother version.", rescaledValue); + IGFX::BacklightSmoother::smoothCFLWriteRegisterPWMDuty1(controller, BXT_BLC_PWM_DUTY1, rescaledValue); + } else { + // Otherwise invoke the original function + DBGLOG("igfx", "BLR: [KBL ] Will pass the rescaled value 0x%08x to the original version.", rescaledValue); + callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_DUTY1, rescaledValue); + } } void IGFX::BacklightRegistersFix::wrapKBLWriteRegisterPWMCtrl1(void *controller, uint32_t reg, uint32_t value) { @@ -255,214 +263,87 @@ void IGFX::BacklightRegistersAltFix::processFramebufferKext(KernelPatcher &patch // - 2023.06 // static constexpr const char* kHwSetBacklightSymbol = "__ZN31AppleIntelFramebufferController14hwSetBacklightEj"; - static constexpr const char* kLightUpEDPSymbol = "__ZN31AppleIntelFramebufferController10LightUpEDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"; - static constexpr const char* kHwSetPanelPowerSymbol = "__ZN31AppleIntelFramebufferController15hwSetPanelPowerEj"; - - // Guard: Only CFL is supported at this moment (ICL driver does not have this issue, KBL driver is hard to fix) - if (auto framebuffer = callbackIGFX->getRealFramebuffer(index); framebuffer != &kextIntelCFLFb) { - SYSLOG("igfx", "BLT: Aborted: Only CFL is supported at this moment."); - return; - } // Guard: Verify the kernel major version if (getKernelVersion() < KernelVersion::Ventura) { - SYSLOG("igfx", "BLT: Aborted: This patch submodule is only available as of macOS 13.4."); + SYSLOG("igfx", "BLT: [COMM] Aborted: This patch submodule is only available as of macOS 13.4."); return; } else if (getKernelVersion() == KernelVersion::Ventura && getKernelMinorVersion() < 5) { - SYSLOG("igfx", "BLT: Aborted: This patch submodule is only available as of macOS 13.4."); + SYSLOG("igfx", "BLT: [COMM] Aborted: This patch submodule is only available as of macOS 13.4."); return; } else { - DBGLOG("igfx", "BLT: Running on Darwin %d.%d. Assuming that BLT is compatible with the current macOS release.", + DBGLOG("igfx", "BLT: [COMM] Running on Darwin %d.%d. Assuming that BLT is compatible with the current macOS release.", getKernelVersion(), getKernelMinorVersion()); } - // Guard: Find the address of each function to be analyzed - mach_vm_address_t orgHwSetBacklight, orgLightUpEDP, orgHwSetPanelPower; - KernelPatcher::SolveRequest requests[] = { - {kHwSetBacklightSymbol, orgHwSetBacklight}, - {kLightUpEDPSymbol, orgLightUpEDP}, - {kHwSetPanelPowerSymbol, orgHwSetPanelPower}, - }; - if (!patcher.solveMultiple(index, requests, address, size)) { - SYSLOG("igfx", "BLT: Error: Unable to find the address of the function to be analyzed."); + // Guard: Verify the current framebuffer driver to select the concrete submodule + auto framebuffer = callbackIGFX->getRealFramebuffer(index); + IGFX::BacklightRegistersAltFix *self = nullptr; + if (framebuffer == &kextIntelKBLFb) { + DBGLOG("igfx", "BLT: [COMM] Will set up the fix for the KBL platform."); + self = &callbackIGFX->modBacklightRegistersAltFixKBL; + } else if (framebuffer == &kextIntelCFLFb) { + DBGLOG("igfx", "BLT: [COMM] Will set up the fix for the CFL platform."); + self = &callbackIGFX->modBacklightRegistersAltFixCFL; + } else { + SYSLOG("igfx", "BLT: [COMM] Aborted: Only KBL and CFL platforms are supported."); + return; + } + + // Guard: Resolve the address of `hwSetBacklight()` + auto orgHwSetBacklight = patcher.solveSymbol(index, kHwSetBacklightSymbol, address, size); + if (orgHwSetBacklight == 0) { + SYSLOG("igfx", "BLT: [COMM] Error: Unable to find the address of hwSetBacklight()."); + patcher.clearError(); return; } - // Guard: Analyze `hwSetBacklight()` to probe the offset of each member field needed by this submodule - if (auto&& [divider, brightness] = this->probeMemberOffsets(orgHwSetBacklight, 64); divider != 0 && brightness != 0) { - this->offsetFrequencyDivider = divider; - this->offsetBrightnessLevel = brightness; - } else { - SYSLOG("igfx", "BLT: Error: Unable to find the offset of one of the required member fields."); + // Guard: Analyze `hwSetBacklight()` to find the offset of each required member field in the framebuffer controller + auto probeContext = self->probeMemberOffsets(orgHwSetBacklight, kMaxNumInstructions); + if (!probeContext.isValid()) { + SYSLOG("igfx", "BLT: [COMM] Error: Failed to find the offset of one of the required member field."); return; } - // Analyze each function to find the position of the inlined invocation of `hwSetBacklight()` - ppair addresses[] = {{"LightUpEDP", orgLightUpEDP}, {"hwSetPanelPower", orgHwSetPanelPower}}; - for (auto&& [name, address] : addresses) { - // Guard: Identify the offsets and the register that stores the controller instance - auto context = this->probeInlinedInvocation(address, 512); - if (!context.isValid()) { - SYSLOG("igfx", "BLT: Error: Unable to find the position of the inlined invocation of hwSetBacklight() in %s().", name); + // Analyze and patch each function that contains an inlined invocation of `hwSetBacklight()` + for (auto descriptor = self->getFunctionDescriptors(); descriptor->name != nullptr; descriptor += 1) { + // Guard: Resolve the symbol of the current function + descriptor->address = patcher.solveSymbol(index, descriptor->symbol, address, size); + if (descriptor->address == 0) { + SYSLOG("igfx", "BLT: [COMM] Error: Failed to find the address of %s().", descriptor->name); + patcher.clearError(); return; } - // Guard: Patch the function to invoke `hwSetBacklight()` explicitly - if (!this->revertInlinedInvocation(patcher, address, orgHwSetBacklight, context)) { - SYSLOG("igfx", "BLT: Error: Failed to patch the function %s().", name); + // Guard: Identify the location of the inlined invocation and the register that stores the controller instance + auto invocationContext = descriptor->probe(probeContext); + if (!invocationContext.isValid()) { + SYSLOG("igfx", "BLT: [COMM] Error: Unable to find the position of the inlined invocation of hwSetBacklight() in %s().", descriptor->name); return; - } else { - DBGLOG("igfx", "BLT: Reverted the inlined invocation of hwSetBacklight() in %s() sucessfully.", name); - } - } - - // Guard: Replace the implementation of `hwSetBacklight()` - KernelPatcher::RouteRequest request(kHwSetBacklightSymbol, wrapHwSetBacklight); - SYSLOG_COND(!patcher.routeMultiple(index, &request, 1, address, size), "igfx", "BLT: Error: Failed to route hwSetBacklight()."); -} - -ppair IGFX::BacklightRegistersAltFix::probeMemberOffsets(mach_vm_address_t address, size_t instructions) const { - DBGLOG("igfx", "BLT: Analyzing the function at 0x%016llx to probe the offset of each required member field.", address); - hde64s handle; - - // Record which register stores the implicit controller instance - // By default, %rdi stores the implicit controller instance (i.e., the 1st argument) - uint32_t registerController = 7; - - // Record which register stores the given brightness level - // By default, %esi stores the given brightness level (i.e., the 2nd argument) - uint32_t registerBrightnessLevel = 6; - - // Record the offset of the member field that stores the frequency divider - size_t offsetFrequencyDivider = 0; - - // Record the offset of the member field that will store the given brightness level - size_t offsetBrightnessLevel = 0; - - // Analyze at most the given number instructions to find the offsets - for (size_t index = 0; index < instructions; index += 1) { - // Guard: Should be able to disassemble the current instruction - address += Disassembler::hdeDisasm(address, &handle); - if (handle.flags & F_ERROR) { - SYSLOG("igfx", "BLT: Error: Cannot disassemble the instruction."); - break; - } - - // Guard: Step 1: Identify which register stores the controller instance - // Pattern: movq %rdi, %r?? - // Checks: MOV, 64-bit, Direct Mode, Source Register is %rdi - if (handle.opcode == 0x89 && handle.rex_w == 1 && handle.modrm_mod == 3 && (handle.rex_r << 3 | handle.modrm_reg) == 7) { - registerController = handle.rex_b << 3 | handle.modrm_rm; - DBGLOG("igfx", "BLT: Found the instruction movq: Register %s now stores the controller instance.", registerName(registerController)); - continue; - } - - // Guard: Step 2: Identify which register stores the given brightness level - // Pattern: movl %esi, %r?? - // Checks: MOV, 32-bit, Direct Mode, Source Register is %esi - if (handle.opcode == 0x89 && handle.rex_w == 0 && handle.modrm_mod == 3 && (handle.rex_r << 3 | handle.modrm_reg) == 6) { - registerBrightnessLevel = handle.rex_b << 3 | handle.modrm_rm; - DBGLOG("igfx", "BLT: Found the instruction movl: Register %s now stores the new brightness level.", registerName(registerBrightnessLevel)); - continue; } - // Guard: Step 3: Verify that the given brightness level is stored in the same register - // Pattern: imull %r??, %r?? where the source register stores the given brightness level and the destination register stores the PWM frequency - // Checks: IMUL, 32-bit, Direct Mode - if (handle.opcode == 0x0F && handle.opcode2 == 0xAF && handle.rex_w == 0 && handle.modrm_mod == 3) { - if (uint32_t source = handle.rex_b << 3 | handle.modrm_rm; source != registerBrightnessLevel) { - DBGLOG("igfx", "BLT: Found the instruction imull: Register %s instead of %s now stores the new brightness level.", - registerName(source), registerName(registerBrightnessLevel)); - registerBrightnessLevel = source; - } - continue; - } - - // Guard: Step 4: Identify the offset of the member field in the controller that stores the frequency divider - // Pattern: divl (%r??) - // Note that even though Apple initializes this field with a hard coded value of `0xFFFF` in `AppleIntelFramebufferController::getOSInformation()`, - // we cannot assume that it is always set to `0xFFFF` in future macOS releases, so we will fetch the divider from the controller. - // Checks: DIV, 32-bit, Memory Mode, Register is identical to the one found in previous steps - if (handle.opcode == 0xF7 && handle.rex_w == 0 && handle.modrm_mod == 2 && (handle.rex_b << 3 | handle.modrm_rm) == registerController) { - offsetFrequencyDivider = handle.disp.disp32; - DBGLOG("igfx", "BLT: Found the instruction divl: The frequency divider is stored at offset 0x%zx.", offsetFrequencyDivider); - continue; - } - - // Guard: Step 5: Identify the offset of the member field in the controller that will store the given brightness level - // Pattern: movl %r??, (%r??) - // Checks: MOV, 32-bit, Memory Mode, - // Source register is identical to the one that stores the brightness level, - // Destination register is identical to the one that stores the controller - if (handle.opcode == 0x89 && handle.rex_w == 0 && handle.modrm_mod == 2 && - (handle.rex_r << 3 | handle.modrm_reg) == registerBrightnessLevel && - (handle.rex_b << 3 | handle.modrm_rm) == registerController) { - offsetBrightnessLevel = handle.disp.disp32; - DBGLOG("igfx", "BLT: Found the instruction movl: The brightness level is stored at offset 0x%zx.", offsetBrightnessLevel); - break; + // Guard: Patch the function to invoke `hwSetBacklight()` explicitly + if (!descriptor->revert(probeContext, invocationContext, patcher, orgHwSetBacklight)) { + SYSLOG("igfx", "BLT: [COMM] Error: Failed to patch the function %s().", descriptor->name); + return; + } else { + DBGLOG("igfx", "BLT: [COMM] Reverted the inlined invocation of hwSetBacklight() in %s() sucessfully.", descriptor->name); } } - - // All done - return {offsetFrequencyDivider, offsetBrightnessLevel}; -} -IGFX::BacklightRegistersAltFix::InvocationContext IGFX::BacklightRegistersAltFix::probeInlinedInvocation(mach_vm_address_t address, size_t instructions) const { - DBGLOG("igfx", "BLT: Analyzing the function at 0x%016llx to identify the position of the inlined invocation of hwSetBacklight().", address); - hde64s handle; - - // The address of the current instruction - auto current = address; - - // The context of the inlined invocation - InvocationContext context; - - // Analyze at most the given number instructions to find the location of inlined invocation of `hwSetBacklight()` - for (size_t index = 0; index < instructions; index += 1) { - // Guard: Should be able to disassemble the current instruction - size_t size = Disassembler::hdeDisasm(current, &handle); - if (handle.flags & F_ERROR) { - SYSLOG("igfx", "BLT: Error: Cannot disassemble the instruction."); - break; - } - - // Guard: Step 1: Find the start address, relative to the given address, of the inlined invocation of `hwSetBacklight()` - // Pattern: leal 0xfff37da7(%r??), %r?? where the source register stores the base address of the MMIO region - // Note that the start address found in `LightUpEDP()` is after the invocation of `CamelliaBase::SetDPCDBacklight()` - // and the retrieval of the base address of the MMIO region - if (handle.opcode == 0x8D && handle.rex_w == 0 && handle.modrm_mod == 2 && handle.disp.disp32 == 0xFFF37DA7) { - context.start = current - address; - DBGLOG("igfx", "BLT: Found the instruction leal: The relative start address of the inlined invocation is 0x%zx.", context.start); - current += size; - continue; - } - - // Guard: Step 2: Identify which register stores the controller instance - // Pattern: divl (%r??) - // where the offset is identical to the one found in `hwSetBacklight()` - if (handle.opcode == 0xF7 && handle.rex_w == 0 && handle.modrm_mod == 2 && handle.disp.disp32 == this->offsetFrequencyDivider) { - context.registerController = handle.rex_b << 3 | handle.modrm_rm; - DBGLOG("igfx", "BLT: Found the instruction divl: Register %s stores the controller instance.", registerName(context.registerController)); - current += size; - continue; - } - - // Guard: Step 3: Find the end address, relative to the given address, of the inlined invocation of `hwSetBacklight()` - // Pattern 2: addl $0xfff37dab, %r?? - if ((handle.opcode == 0x05 || handle.opcode == 0x81) && handle.rex_w == 0 && handle.imm.imm32 == 0xFFF37DAB) { - context.end = current - address; - DBGLOG("igfx", "BLT: Found the instruction addl: The relative end address of the inlined invocation is 0x%zx.", context.end); - break; - } - - current += size; - } + // Guard: Replace the implementation of `hwSetBacklight()` + KernelPatcher::RouteRequest request(kHwSetBacklightSymbol, self->getHwSetBacklightWrapper()); + SYSLOG_COND(!patcher.routeMultiple(index, &request, 1, address, size), "igfx", "BLT: [COMM] Error: Failed to route hwSetBacklight()."); - // All done - return context; + // Save the probe context which is needed by the hwSetBacklight wrapper + self->probeContext = probeContext; } -bool IGFX::BacklightRegistersAltFix::revertInlinedInvocation(KernelPatcher &patcher, mach_vm_address_t address, mach_vm_address_t orgHwSetBacklight, const InvocationContext &context) const { +bool IGFX::BacklightRegistersAltFix::revertInlinedInvocation(const FunctionDescriptor &descriptor, + const ProbeContext &probeContext, + const InvocationContext &invocationContext, + KernelPatcher &patcher, + mach_vm_address_t orgHwSetBacklight) { // // Sample Patch 1: Revert inlined hwSetBacklight() invocation // @@ -537,6 +418,12 @@ bool IGFX::BacklightRegistersAltFix::revertInlinedInvocation(KernelPatcher &patc // 0x41, 0x8B, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00 }; + static constexpr uint8_t kSetArg1_Zero[] = { + // + // Template: xorl %esi, %esi + // + 0x31, 0xF6 + }; // Step 3: [Template] Set the controller instance which will be the 1st argument static constexpr uint8_t kSetArg0[] = { @@ -590,46 +477,54 @@ bool IGFX::BacklightRegistersAltFix::revertInlinedInvocation(KernelPatcher &patc static constexpr size_t kMaxPatchSize = sizeof(kPreserve) + sizeof(kSetArg1_r12) + sizeof(kSetArg0) + sizeof(kCall) + sizeof(kRestore) + sizeof(kJump); // Guard: Ensure that there is enough space to revert the inlined invocation - if (auto freeSpace = context.freeSpace(); freeSpace < kMaxPatchSize) { - SYSLOG("igfx", "BLT: Error: The function does not have enough space to revert the inlined invocation. Required = %zu; Free = %zu.", kMaxPatchSize, freeSpace); + if (auto freeSpace = invocationContext.freeSpace(); freeSpace < kMaxPatchSize) { + SYSLOG("igfx", "BLT: [COMM] Error: %s() does not have enough space to revert the inlined invocation. Required = %zu; Free = %zu.", + descriptor.name, kMaxPatchSize, freeSpace); return false; } // Build the patch step by step uint8_t patch[kMaxPatchSize] = {}; uint8_t* current = patch; - DBGLOG("igfx", "BLT: Building the assembly patch for the function at 0x%016llx to revert the inlined invocation of hwSetBacklight().", address); - DBGLOG("igfx", "BLT: Invocation context: Start Offset = 0x%zu, End Offset = 0x%zu, Free Space = %zu Bytes, Framebuffer controller stored in register %s.", - context.start, context.end, context.freeSpace(), registerName(context.registerController)); + DBGLOG("igfx", "BLT: [COMM] Building the assembly patch for %s() at 0x%016llx to revert the inlined invocation of hwSetBacklight().", descriptor.name, descriptor.address); + DBGLOG("igfx", "BLT: [COMM] Invocation context: Start Offset = %zu, End Offset = %zu, Free Space = %zu Bytes, Framebuffer controller stored in register %s.", + invocationContext.start, invocationContext.end, invocationContext.freeSpace(), registerName(invocationContext.registerController)); // Step 1: Preserve all caller-saved registers to avoid analyzing register liveness lilu_os_memcpy(current, kPreserve, sizeof(kPreserve)); current += sizeof(kPreserve); // Step 2: Fetch the new brightness level which will be the 2nd argument - // Step 2.1: Select the instruction based upon the register that stores the controller instance - if (context.registerController < 8) { - // %rax, %rcx, %rdx, %rbx, /* %rsp, %rbp */, %rsi, %rdi - lilu_os_memcpy(current, kSetArg1 + 1, sizeof(kSetArg1) - 1); - current[1] += context.registerController % 8; - current += sizeof(kSetArg1) - 1; - } else if (context.registerController != 12) { - // %r8, %r9, %r10, %r11, %r13, %r14, %r15 - lilu_os_memcpy(current, kSetArg1, sizeof(kSetArg1)); - current[2] += context.registerController % 8; - current += sizeof(kSetArg1); + if (descriptor.useCurrentBrightnessLevel) { + // Step 2.1: Select the instruction based upon the register that stores the controller instance + if (invocationContext.registerController < 8) { + // %rax, %rcx, %rdx, %rbx, /* %rsp, %rbp */, %rsi, %rdi + lilu_os_memcpy(current, kSetArg1 + 1, sizeof(kSetArg1) - 1); + current[1] += invocationContext.registerController % 8; + current += sizeof(kSetArg1) - 1; + } else if (invocationContext.registerController != 12) { + // %r8, %r9, %r10, %r11, %r13, %r14, %r15 + lilu_os_memcpy(current, kSetArg1, sizeof(kSetArg1)); + current[2] += invocationContext.registerController % 8; + current += sizeof(kSetArg1); + } else { + // %r12 + lilu_os_memcpy(current, kSetArg1_r12, sizeof(kSetArg1_r12)); + current += sizeof(kSetArg1_r12); + } + // Step 2.2: Set the offset of the member field that stores the new brightness level + *reinterpret_cast(current - sizeof(uint32_t)) = static_cast(probeContext.offsetBrightnessLevel); + DBGLOG("igfx", "BLT: [COMM] Patched %s() to invoke hwSetBacklight() with the current brightness level.", descriptor.name); } else { - // %r12 - lilu_os_memcpy(current, kSetArg1_r12, sizeof(kSetArg1_r12)); - current += sizeof(kSetArg1_r12); + lilu_os_memcpy(current, kSetArg1_Zero, sizeof(kSetArg1_Zero)); + current += sizeof(kSetArg1_Zero); + DBGLOG("igfx", "BLT: [COMM] Patched %s() to invoke hwSetBacklight() with a brightness level of zero.", descriptor.name); } - // Step 2.2: Set the offset of the member field that stores the new brightness level - *reinterpret_cast(current - sizeof(uint32_t)) = static_cast(this->offsetBrightnessLevel); // Step 3: Set the controller instance which will be the 1st argument lilu_os_memcpy(current, kSetArg0, sizeof(kSetArg0)); - current[2] += (context.registerController % 8) * 8; - if (context.registerController >= 8) { + current[2] += (invocationContext.registerController % 8) * 8; + if (invocationContext.registerController >= 8) { // %r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15 current[0] += 4; } @@ -640,7 +535,7 @@ bool IGFX::BacklightRegistersAltFix::revertInlinedInvocation(KernelPatcher &patc lilu_os_memcpy(current, kCall, sizeof(kCall)); current += sizeof(kCall); // Step 4.2: Calculate the address of the next instruction (after `call` returns) - mach_vm_address_t next = address + context.start + (current - patch); + mach_vm_address_t next = descriptor.address + invocationContext.start + (current - patch); // Step 4.3: Calculate and set the offset for the call instruction *reinterpret_cast(current - sizeof(uint32_t)) = static_cast(orgHwSetBacklight - next); @@ -653,83 +548,669 @@ bool IGFX::BacklightRegistersAltFix::revertInlinedInvocation(KernelPatcher &patc lilu_os_memcpy(current, kJump, sizeof(kJump)); current += sizeof(kJump); // Step 6.2: Calculate and set the offset for the jump instruction - size_t offset = context.end - (context.start + current - patch); - PANIC_COND(offset > UINT8_MAX, "igfx", "BLR: The offset for the jump instruction exceeds 255, which should not happen."); + size_t offset = invocationContext.end - (invocationContext.start + current - patch); + PANIC_COND(offset > UINT8_MAX, "igfx", "BLT: [COMM] The offset for the jump instruction exceeds 255, which should not happen."); *(current - sizeof(uint8_t)) = static_cast(offset); // The patch has been built for the given function size_t patchSize = current - patch; - DBGLOG("igfx", "BLT: Built the assembly patch (%zu bytes) for the function at 0x%016llx: ", patchSize, address); + DBGLOG("igfx", "BLT: [COMM] Built the assembly patch (%zu bytes) for %s() at 0x%016llx: ", patchSize, descriptor.name, descriptor.address); for (size_t index = 0; index < patchSize; index += 1) { - DBGLOG("igfx", "BLT: [%04lu] 0x%02x", index, patch[index]); + DBGLOG("igfx", "BLT: [COMM] [%04lu] 0x%02x", index, patch[index]); } // Guard: Apply the assembly patch - if (patcher.routeBlock(address + context.start, patch, patchSize) != 0) { - SYSLOG("igfx", "BLT: Failed to apply the assembly patch to the function at 0x%016llx.", address); + if (patcher.routeBlock(descriptor.address + invocationContext.start, patch, patchSize) != 0) { + SYSLOG("igfx", "BLT: [COMM] Failed to apply the assembly patch to %s() at 0x%016llx.", descriptor.name, descriptor.address); patcher.clearError(); return false; } // All done - DBGLOG("igfx", "BLT: Patched the function at 0x%016llx successfully.", address); + DBGLOG("igfx", "BLT: [COMM] Patched %s() at 0x%016llx successfully.", descriptor.name, descriptor.address); return true; } -IOReturn IGFX::BacklightRegistersAltFix::wrapHwSetBacklight(void *controller, uint32_t brightness) { +/** + * Analyze `hwSetBacklight()` to find the offset of each required member field in the framebuffer controller + * + * @param address The address of `hwSetBacklight()` to be analyzed + * @param instructions The maximum number of instructions to be analyzed + * @return A context object that stores the offset of each required member field in the framebuffer controller + */ +IGFX::BacklightRegistersAltFixKBL::ProbeContext IGFX::BacklightRegistersAltFixKBL::probeMemberOffsets(mach_vm_address_t address, size_t instructions) const { + DBGLOG("igfx", "BLT: [KBL ] Analyzing the function at 0x%016llx to probe the offset of each required member field.", address); + hde64s handle; + + // Record which register stores the implicit controller instance + // By default, %rdi stores the implicit controller instance (i.e., the 1st argument) + uint32_t registerController = 7; + + // Record which register stores the given brightness level + // By default, %esi stores the given brightness level (i.e., the 2nd argument) + uint32_t registerBrightnessLevel = 6; + + // Record the offset of the member field that stores the frequency divider + size_t offsetFrequencyDivider = 0; + + // Record the offset of the member field that will store the given brightness level + size_t offsetBrightnessLevel = 0; + + // Analyze at most the given number instructions to find the offsets + for (size_t index = 0; index < instructions; index += 1) { + // Guard: Should be able to disassemble the current instruction + address += Disassembler::hdeDisasm(address, &handle); + if (handle.flags & F_ERROR) { + SYSLOG("igfx", "BLT: [KBL ] Error: Cannot disassemble the instruction."); + break; + } + + // Guard: Step 1: Identify which register stores the controller instance + // Pattern: movq %rdi, %r?? + // Checks: MOV, 64-bit, Direct Mode, Source Register is %rdi + if (Patterns::movqArg0(handle)) { + registerController = handle.rex_b << 3 | handle.modrm_rm; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movq: Register %s now stores the controller instance.", registerName(registerController)); + continue; + } + + // Guard: Step 2: Identify which register stores the given brightness level + // Pattern: movl %esi, %r?? + // Checks: MOV, 32-bit, Direct Mode, Source Register is %esi + if (Patterns::movlArg1(handle)) { + registerBrightnessLevel = handle.rex_b << 3 | handle.modrm_rm; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movl: Register %s now stores the new brightness level.", registerName(registerBrightnessLevel)); + continue; + } + + // Guard: Step 3: Identify the offset of the member field in the controller that stores the frequency divider + // Pattern: movl (%r??), %r?? + // Checks: MOV, 32-bit, Memory Mode, Source register is identical to the one that stores the controller + if (Patterns::movlFromMemory(handle, registerController)) { + offsetFrequencyDivider = handle.disp.disp32; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movl: The frequency divider is stored at offset 0x%zx.", offsetFrequencyDivider); + continue; + } + + // Guard: Step 4: Identify the offset of the member field in the controller that will store the given brightness level + // Pattern: movl %r??, (%r??) + // Checks: MOV, 32-bit, Memory Mode, + // Source register is identical to the one that stores the brightness level, + // Destination register is identical to the one that stores the controller + if (Patterns::movlToMemory(handle, registerBrightnessLevel, registerController)) { + offsetBrightnessLevel = handle.disp.disp32; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movl: The brightness level is stored at offset 0x%zx.", offsetBrightnessLevel); + break; + } + } + + // All done + return {offsetFrequencyDivider, offsetBrightnessLevel}; +} + +/** + * Get all functions that contain an inlined invocation of `hwSetBacklight()` and thus will be analyzed and patched + * + * @return A null-terminated array of descriptor, each of describes a function to be analyzed and patched. + */ +IGFX::BacklightRegistersAltFixKBL::FunctionDescriptor* IGFX::BacklightRegistersAltFixKBL::getFunctionDescriptors() const { + static constexpr const char* kLightUpEDPSymbol = "__ZN31AppleIntelFramebufferController10LightUpEDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"; + static constexpr const char* kDisableDisplaySymbol = "__ZN31AppleIntelFramebufferController14DisableDisplayEP21AppleIntelFramebufferP21AppleIntelDisplayPathbh"; + static constexpr const char* kHwSetPanelPowerSymbol = "__ZN31AppleIntelFramebufferController15hwSetPanelPowerEj"; + + static FunctionDescriptor kFunctionDescriptors[] = { + { + "AppleIntelFramebufferController::LightUpEDP", + kLightUpEDPSymbol, + 0, + IGFX::BacklightRegistersAltFixKBL::probeInlinedInvocation_LightUpEDP, + IGFX::BacklightRegistersAltFixKBL::revertInlinedInvocation, + true, + }, + { + "AppleIntelFramebufferController::DisableDisplay", + kDisableDisplaySymbol, + 0, + IGFX::BacklightRegistersAltFixKBL::probeInlinedInvocation_DisableDisplay, + IGFX::BacklightRegistersAltFixKBL::revertInlinedInvocation, + false, + }, + { + "AppleIntelFramebufferController::hwSetPanelPower", + kHwSetPanelPowerSymbol, + 0, + IGFX::BacklightRegistersAltFixKBL::probeInlinedInvocation_HwSetPanelPower, + IGFX::BacklightRegistersAltFixKBL::revertInlinedInvocation, + true, + }, + { + nullptr, + nullptr, + 0, + nullptr, + nullptr, + false, + } + }; + + return kFunctionDescriptors; +} + +/** + * Get the address of the wrapper function that sets the backlight compatible with the current machine + * + * @return The address of the wrapper function. + */ +mach_vm_address_t IGFX::BacklightRegistersAltFixKBL::getHwSetBacklightWrapper() const { + return reinterpret_cast(&IGFX::BacklightRegistersAltFixKBL::wrapHwSetBacklight); +} + +/** + * Find the location of an inlined invocation of `hwSetBacklight()` in the given function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + * @note This function is specialized for `LightUpEDP()`. + */ +IGFX::BacklightRegistersAltFixKBL::InvocationContext IGFX::BacklightRegistersAltFixKBL::probeInlinedInvocation_LightUpEDP(const FunctionDescriptor &descriptor, const ProbeContext &probeContext) { + DBGLOG("igfx", "BLT: [KBL ] Analyzing %s() at 0x%016llx to identify the position of the inlined invocation of hwSetBacklight().", descriptor.name, descriptor.address); + hde64s handle; + + // The address of the current instruction + auto current = descriptor.address; + + // The context of the inlined invocation + InvocationContext context; + + // Analyze at most the given number instructions to find the location of inlined invocation of `hwSetBacklight()` + for (size_t index = 0; index < kMaxNumInstructions; index += 1) { + // Guard: Should be able to disassemble the current instruction + size_t size = Disassembler::hdeDisasm(current, &handle); + if (handle.flags & F_ERROR) { + SYSLOG("igfx", "BLT: [KBL ] Error: Cannot disassemble the instruction at 0x%016llx.", current); + break; + } + + // Guard: Step 1: Find the start address, relative to the given address, of the inlined invocation of `hwSetBacklight()` + // Pattern: leal 0xfff37da7(%r??), %r?? where the source register stores the base address of the MMIO region + // Note that the start address found in `LightUpEDP()` is after the invocation of `CamelliaBase::SetDPCDBacklight()` + // and the retrieval of the base address of the MMIO region + if (Patterns::lealWithOffset(handle, 0xFFF37DA7)) { + context.start = current - descriptor.address; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction leal: The relative start address of the inlined invocation is 0x%zx.", context.start); + current += size; + continue; + } + + // Guard: Step 2: Identify which register stores the controller instance + // Pattern: movl (%r??), %r?? + // where the offset is identical to the one found in `hwSetBacklight()` + if (Patterns::movlFromMemoryWithOffset(handle, probeContext.offsetFrequencyDivider)) { + context.registerController = handle.rex_b << 3 | handle.modrm_rm; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movl: Register %s stores the controller instance.", registerName(context.registerController)); + current += size; + continue; + } + + // Guard: Step 3: Find the end address, relative to the given address, of the inlined invocation of `hwSetBacklight()` + // Pattern: movl $??????????, 0xc8250(%r??) + if (Patterns::movlImm32ToMemoryWithOffset(handle, 0xC8250)) { + context.end = current + size - descriptor.address; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movl: The relative end address of the inlined invocation is 0x%zx.", context.end); + break; + } + + current += size; + } + + // All done + return context; +} + +/** + * Find the location of an inlined invocation of `hwSetBacklight()` in the given function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + * @note This function is specialized for `DisableDisplay()`. + */ +IGFX::BacklightRegistersAltFixKBL::InvocationContext IGFX::BacklightRegistersAltFixKBL::probeInlinedInvocation_DisableDisplay(const FunctionDescriptor &descriptor, const ProbeContext &probeContext) { + DBGLOG("igfx", "BLT: [KBL ] Analyzing %s() at 0x%016llx to identify the position of the inlined invocation of hwSetBacklight().", descriptor.name, descriptor.address); + hde64s handle; + + // The address of the current instruction + auto current = descriptor.address; + + // The context of the inlined invocation + InvocationContext context; + + // Analyze at most the given number instructions to find the location of inlined invocation of `hwSetBacklight()` + for (size_t index = 0; index < kMaxNumInstructions; index += 1) { + // Guard: Should be able to disassemble the current instruction + size_t size = Disassembler::hdeDisasm(current, &handle); + if (handle.flags & F_ERROR) { + SYSLOG("igfx", "BLT: [KBL ] Error: Cannot disassemble the instruction at 0x%016llx.", current); + break; + } + + // Guard: Step 1: Identify which register stores the controller instance + // Pattern: movq %rdi, %r?? + if (Patterns::movqArg0(handle)) { + context.registerController = handle.rex_b << 3 | handle.modrm_rm; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movq: Register %s stores the controller instance.", registerName(context.registerController)); + current += size; + continue; + } + + // Guard: Step 2: Find the start address, relative to the given address, of the inlined invocation of `hwSetBacklight()` + // Pattern: leal 0xfff37da7(%r??), %r?? where the source register stores the base address of the MMIO region + // Note that the start address found in `LightUpEDP()` is after the invocation of `CamelliaBase::SetDPCDBacklight()` + // and the retrieval of the base address of the MMIO region + if (Patterns::lealWithOffset(handle, 0xFFF37DA7)) { + context.start = current - descriptor.address; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction leal: The relative start address of the inlined invocation is 0x%zx.", context.start); + current += size; + continue; + } + + // Guard: Step 3: Find the end address, relative to the given address, of the inlined invocation of `hwSetBacklight()` + // Pattern: movl $??????????, 0xc8250(%r??) + if (Patterns::movlImm32ToMemoryWithOffset(handle, 0xC8250)) { + context.end = current + size - descriptor.address; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movl: The relative end address of the inlined invocation is 0x%zx.", context.end); + break; + } + + current += size; + } + + // All done + return context; +} + +/** + * Find the location of an inlined invocation of `hwSetBacklight()` in the given function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + * @note This function is specialized for `hwSetPanelPower()`. + */ +IGFX::BacklightRegistersAltFixKBL::InvocationContext IGFX::BacklightRegistersAltFixKBL::probeInlinedInvocation_HwSetPanelPower(const FunctionDescriptor &descriptor, const ProbeContext &probeContext) { + DBGLOG("igfx", "BLT: [KBL ] Analyzing %s() at 0x%016llx to identify the position of the inlined invocation of hwSetBacklight().", descriptor.name, descriptor.address); + hde64s handle; + + // The address of the current instruction + auto current = descriptor.address; + + // The context of the inlined invocation + InvocationContext context; + + // Analyze at most the given number instructions to find the location of inlined invocation of `hwSetBacklight()` + for (size_t index = 0; index < kMaxNumInstructions; index += 1) { + // Guard: Should be able to disassemble the current instruction + size_t size = Disassembler::hdeDisasm(current, &handle); + if (handle.flags & F_ERROR) { + SYSLOG("igfx", "BLT: [KBL ] Error: Cannot disassemble the instruction at 0x%016llx.", current); + break; + } + + // Guard: Step 1: Identify which register stores the controller instance + // Pattern: movq %rdi, %r?? + if (Patterns::movqArg0(handle)) { + context.registerController = handle.rex_b << 3 | handle.modrm_rm; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movq: Register %s now stores the controller instance.", registerName(context.registerController)); + current += size; + continue; + } + + // Guard: Step 2: Find the start address, relative to the given address, of the inlined invocation of `hwSetBacklight()` + // Unlike the Coffee Lake driver, the Kaby Lake driver writes to the register 0xC8250 without calling hwSetBacklight(). + // However, we will use our custom implementation of hwSetBacklight() which updates both the backlight and the register 0xC8250. + // Pattern: addl $0xfff37dab, %r?? + if (Patterns::addlWithImm32(handle, 0xFFF37DAB)) { + context.start = current - descriptor.address; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction addl: The relative start address of the inlined invocation is 0x%zx.", context.start); + current += size; + continue; + } + + // Guard: Step 3: Find the end address, relative to the given address, of the inlined invocation of `hwSetBacklight()` + // Pattern: movl $??????????, 0xc8250(%r??) + if (Patterns::movlImm32ToMemoryWithOffset(handle, 0xC8250)) { + context.end = current + size - descriptor.address; + DBGLOG("igfx", "BLT: [KBL ] Found the instruction movl: The relative end address of the inlined invocation is 0x%zx.", context.end); + break; + } + + current += size; + } + + // All done + return context; +} + +/** + * Wrapper to set the backlight compatible with the current machine + * + * @param controller The implicit framebuffer controller + * @param brightness The new brightness level + * @return `kIOReturnSuccess`. + * @note When this function returns, the given brightness level should be stored into the given framebuffer controller. + * @note Unlike the original fix implemented in BLR, BLT computes the value of the frequency register and the duty register + * compatible with the current machine and commit those values using the original version of `WriteRegister32()`. + * Additionally, this function updates the PWM control register at 0xC8250. + * Apple's implementation will not be used by this submodule. + */ +IOReturn IGFX::BacklightRegistersAltFixKBL::wrapHwSetBacklight(void *controller, uint32_t brightness) { // // Differences between this function and the original one: // + // - The wrapper no longer checks whether the current ig-platform-id has the bit `HasBacklight` set. // - The wrapper no longer checks whether Camellia is enabled and invokes `CamelliaBase::SetDPCDBacklight()`. - // - The wrapper no longer writes Apple's PWM frequency (0x56CE or 0x4571 depending upon whether the bit 8 - // is set in `SFUSE_STRAP` (0xC2014), i.e. whether the raw frequency is used) to `BXT_BLC_PWM_FREQ1` (0xC8254). + // - The wrapper writes the PWM frequency set by the system firmware to `BXT_BLC_PWM_FREQ1` if the given brightness is non-zero. // - The wrapper uses the PWM frequency set by the system firmware, the given new brightness value and - // Apple's frequency divider (set to 0xFFFF in getOSInformation()) to calculate the value of `BXT_BLC_PWM_DUTY1`. + // Apple's frequency divider (specified by the current ig-platform-id) to calculate the value of `BXT_BLC_PWM_DUTY1`. + // - The wrapper uses the PWM control value set by the system firmware to `BXT_BLC_PWM_CTL1` if the given brightness is non-zero. + // + // When this submodule is enabled, the following functions will invoke `AppleIntelFramebufferController::hwSetBacklight()`: // - auto self = &callbackIGFX->modBacklightRegistersAltFix; - DBGLOG("igfx", "BLT: [CFL+] Called with the controller at 0x%016llx and the brightness level 0x%x.", reinterpret_cast(controller), brightness); + // - AppleIntelFramebufferController::LightUpEDP() invokes it with the brightness level stored in the framebuffer controller. + // Note that Apple's original implementation invokes it with the brightness level bitwise ORed with the frequency divider. + // - AppleIntelFramebufferController::DisableDisplay() invokes it with a brightness level of 0 to disable the display. + // Note that Apple's original implementation writes 0 to both `BXT_BLC_PWM_FREQ1` and `BXT_BLC_PWM_CTL1`. + // - AppleIntelFramebufferController::hwSetPanelPower() invokes it with the brightness level stored in the framebuffer controller. + // Note that Apple's original implementation does not update `BXT_BLC_PWM_FREQ1` but writes 0xC0000000 to `BXT_BLC_PWM_CTL1`. + // + auto self = &callbackIGFX->modBacklightRegistersAltFixKBL; + DBGLOG("igfx", "BLT: [KBL ] Called with the controller at 0x%016llx and the brightness level 0x%x.", reinterpret_cast(controller), brightness); - // Step 1: Fetch and preserve the PWM frequency set by the system firmware + // Step 1: Fetch and preserve the PWM control value set by the system firmware + self->fetchFirmwareBacklightControlIfNecessary(controller); + + // Step 2: Fetch and preserve the PWM frequency set by the system firmware // Note that we need to restore the frequency after the system wakes up - if (self->firmwareBacklightFrequency == 0) { - // Guard: The system should be initialized with a non-zero PWM frequency - if (auto bootValue = callbackIGFX->readRegister32(controller, BXT_BLC_PWM_FREQ1); bootValue != 0) { - DBGLOG("igfx", "BLT: [CFL+] System initialized with a PWM frequency of 0x%x.", bootValue); - self->firmwareBacklightFrequency = bootValue; - } else { - SYSLOG("igfx", "BLT: [CFL+] System initialized with a PWM frequency of 0. Will use the fallback frequency."); - self->firmwareBacklightFrequency = kFallbackBacklightFrequency; + self->fetchFirmwareBacklightFrequencyIfNecessary(controller); + + // Step 3: Calculate the new duty cycle for the given brightness level + uint32_t dutyCycle = self->calcDutyCycle(controller, brightness); + DBGLOG("igfx", "BLT: [KBL ] Frequency = 0x%x, Divider = 0x%x, Duty Cycle = 0x%x.", + self->firmwareBacklightFrequency, self->getFrequencyDivider(controller), dutyCycle); + + // Step 4: Write the PWM frequency set by the system firmware to BXT_BLC_PWM_FREQ1 + self->writeFrequency(controller, brightness == 0 ? 0 : self->firmwareBacklightFrequency); + + // Step 5: Write the new duty cycle to BXT_BLC_PWM_DUTY1 + self->writeDutyCycle(controller, dutyCycle); + + // Step 6: Store the new brightness level in the controller + if (brightness != 0) + self->setBrightnessLevel(controller, brightness); + + // Step 7: Update the PWM control register + self->writePWMControl(controller, brightness == 0 ? 0 : self->firmwareBacklightControl); + + // All done + DBGLOG("igfx", "BLT: [KBL ] The new brightness level 0x%x is now effective.", brightness); + return kIOReturnSuccess; +} + +/** + * Analyze `hwSetBacklight()` to find the offset of each required member field in the framebuffer controller + * + * @param address The address of `hwSetBacklight()` to be analyzed + * @param instructions The maximum number of instructions to be analyzed + * @return A context object that stores the offset of each required member field in the framebuffer controller + */ +IGFX::BacklightRegistersAltFixCFL::ProbeContext IGFX::BacklightRegistersAltFixCFL::probeMemberOffsets(mach_vm_address_t address, size_t instructions) const { + DBGLOG("igfx", "BLT: [CFL ] Analyzing the function at 0x%016llx to probe the offset of each required member field.", address); + hde64s handle; + + // Record which register stores the implicit controller instance + // By default, %rdi stores the implicit controller instance (i.e., the 1st argument) + uint32_t registerController = 7; + + // Record which register stores the given brightness level + // By default, %esi stores the given brightness level (i.e., the 2nd argument) + uint32_t registerBrightnessLevel = 6; + + // Record the offset of the member field that stores the frequency divider + size_t offsetFrequencyDivider = 0; + + // Record the offset of the member field that will store the given brightness level + size_t offsetBrightnessLevel = 0; + + // Analyze at most the given number instructions to find the offsets + for (size_t index = 0; index < instructions; index += 1) { + // Guard: Should be able to disassemble the current instruction + address += Disassembler::hdeDisasm(address, &handle); + if (handle.flags & F_ERROR) { + SYSLOG("igfx", "BLT: [CFL ] Error: Cannot disassemble the instruction."); + break; + } + + // Guard: Step 1: Identify which register stores the controller instance + // Pattern: movq %rdi, %r?? + // Checks: MOV, 64-bit, Direct Mode, Source Register is %rdi + if (Patterns::movqArg0(handle)) { + registerController = handle.rex_b << 3 | handle.modrm_rm; + DBGLOG("igfx", "BLT: [CFL ] Found the instruction movq: Register %s now stores the controller instance.", registerName(registerController)); + continue; + } + + // Guard: Step 2: Identify which register stores the given brightness level + // Pattern: movl %esi, %r?? + // Checks: MOV, 32-bit, Direct Mode, Source Register is %esi + if (Patterns::movlArg1(handle)) { + registerBrightnessLevel = handle.rex_b << 3 | handle.modrm_rm; + DBGLOG("igfx", "BLT: [CFL ] Found the instruction movl: Register %s now stores the new brightness level.", registerName(registerBrightnessLevel)); + continue; + } + + // Guard: Step 3: Verify that the given brightness level is stored in the same register + // Pattern: imull %r??, %r?? where the source register stores the given brightness level and the destination register stores the PWM frequency + // Checks: IMUL, 32-bit, Direct Mode + if (Patterns::imull(handle)) { + if (uint32_t source = handle.rex_b << 3 | handle.modrm_rm; source != registerBrightnessLevel) { + DBGLOG("igfx", "BLT: [CFL ] Found the instruction imull: Register %s instead of %s now stores the new brightness level.", + registerName(source), registerName(registerBrightnessLevel)); + registerBrightnessLevel = source; + } + continue; + } + + // Guard: Step 4: Identify the offset of the member field in the controller that stores the frequency divider + // Pattern: divl (%r??) + // Note that even though Apple initializes this field with a hard coded value of `0xFFFF` in `AppleIntelFramebufferController::getOSInformation()`, + // we cannot assume that it is always set to `0xFFFF` in future macOS releases, so we will fetch the divider from the controller. + // Checks: DIV, 32-bit, Memory Mode, Register is identical to the one found in previous steps + if (Patterns::divlByMemory(handle, registerController)) { + offsetFrequencyDivider = handle.disp.disp32; + DBGLOG("igfx", "BLT: [CFL ] Found the instruction divl: The frequency divider is stored at offset 0x%zx.", offsetFrequencyDivider); + continue; + } + + // Guard: Step 5: Identify the offset of the member field in the controller that will store the given brightness level + // Pattern: movl %r??, (%r??) + // Checks: MOV, 32-bit, Memory Mode, + // Source register is identical to the one that stores the brightness level, + // Destination register is identical to the one that stores the controller + if (Patterns::movlToMemory(handle, registerBrightnessLevel, registerController)) { + offsetBrightnessLevel = handle.disp.disp32; + DBGLOG("igfx", "BLT: [CFL ] Found the instruction movl: The brightness level is stored at offset 0x%zx.", offsetBrightnessLevel); + break; } } - uint64_t frequency = self->firmwareBacklightFrequency; + // All done + return {offsetFrequencyDivider, offsetBrightnessLevel}; +} + +/** + * Get all functions that contain an inlined invocation of `hwSetBacklight()` and thus will be analyzed and patched + * + * @return A null-terminated array of descriptor, each of describes a function to be analyzed and patched. + */ +IGFX::BacklightRegistersAltFixCFL::FunctionDescriptor* IGFX::BacklightRegistersAltFixCFL::getFunctionDescriptors() const { + static constexpr const char* kLightUpEDPSymbol = "__ZN31AppleIntelFramebufferController10LightUpEDPEP21AppleIntelFramebufferP21AppleIntelDisplayPathPK29IODetailedTimingInformationV2"; + static constexpr const char* kHwSetPanelPowerSymbol = "__ZN31AppleIntelFramebufferController15hwSetPanelPowerEj"; - // Step 2: Fetch the value of Apple's frequency divider - uint64_t divider = getMember(controller, self->offsetFrequencyDivider); + static FunctionDescriptor kFunctionDescriptors[] = { + { + "AppleIntelFramebufferController::LightUpEDP", + kLightUpEDPSymbol, + 0, + IGFX::BacklightRegistersAltFixCFL::probeInlinedInvocation, + IGFX::BacklightRegistersAltFixCFL::revertInlinedInvocation, + true, + }, + { + "AppleIntelFramebufferController::hwSetPanelPower", + kHwSetPanelPowerSymbol, + 0, + IGFX::BacklightRegistersAltFixCFL::probeInlinedInvocation, + IGFX::BacklightRegistersAltFixCFL::revertInlinedInvocation, + true, + }, + { + nullptr, + nullptr, + 0, + nullptr, + nullptr, + false, + } + }; - // Step 3: Calculate the new value of BXT_BLC_PWM_DUTY1 - uint32_t duty = static_cast(frequency * brightness / divider); + return kFunctionDescriptors; +} + +/** + * Get the address of the wrapper function that sets the backlight compatible with the current machine + * + * @return The address of the wrapper function. + */ +mach_vm_address_t IGFX::BacklightRegistersAltFixCFL::getHwSetBacklightWrapper() const { + return reinterpret_cast(&IGFX::BacklightRegistersAltFixCFL::wrapHwSetBacklight); +} + +/** + * Find the location of an inlined invocation of `hwSetBacklight()` in the given function + * + * @param descriptor A descriptor that describes the function to be analyzed + * @param probeContext A context object that stores the offset of each required member field in the framebuffer controller + * @return A context object that stores a pair of `` offsets, relative to the given address, + * which indicates the location of an inlined invocation of `hwSetBacklight()` in this function, + * and the register that stores the implicit framebuffer controller instance. + * @note This function is specialized for `LightUpEDP()` and `hwSetPanelPower()`. + */ +IGFX::BacklightRegistersAltFixCFL::InvocationContext IGFX::BacklightRegistersAltFixCFL::probeInlinedInvocation(const FunctionDescriptor &descriptor, const ProbeContext &probeContext) { + DBGLOG("igfx", "BLT: [CFL ] Analyzing %s() at 0x%016llx to identify the position of the inlined invocation of hwSetBacklight().", descriptor.name, descriptor.address); + hde64s handle; - DBGLOG("igfx", "BLT: [CFL+] Frequency = 0x%llx, Divider = 0x%llx, Duty = 0x%x.", frequency, divider, duty); + // The address of the current instruction + auto current = descriptor.address; - // Step 4: Write the frequency to BXT_BLC_PWM_FREQ1 - callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_FREQ1, static_cast(frequency)); + // The context of the inlined invocation + InvocationContext context; - // Step 5: Write the new value to BXT_BLC_PWM_DUTY1 - if (callbackIGFX->modBacklightSmoother.enabled) { - // Need to pass the scaled value to the smoother - DBGLOG("igfx", "BLS: [CFL+] Will pass the rescaled value 0x%08x to the smoother version.", duty); - IGFX::BacklightSmoother::smoothCFLWriteRegisterPWMDuty1(controller, BXT_BLC_PWM_DUTY1, duty); - } else { - // Otherwise invoke the original function - DBGLOG("igfx", "BLT: [CFL+] Will pass the rescaled value 0x%08x to the original version.", duty); - callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_DUTY1, duty); + // Analyze at most the given number instructions to find the location of inlined invocation of `hwSetBacklight()` + for (size_t index = 0; index < kMaxNumInstructions; index += 1) { + // Guard: Should be able to disassemble the current instruction + size_t size = Disassembler::hdeDisasm(current, &handle); + if (handle.flags & F_ERROR) { + SYSLOG("igfx", "BLT: [CFL ] Error: Cannot disassemble the instruction."); + break; + } + + // Guard: Step 1: Find the start address, relative to the given address, of the inlined invocation of `hwSetBacklight()` + // Pattern: leal 0xfff37da7(%r??), %r?? where the source register stores the base address of the MMIO region + // Note that the start address found in `LightUpEDP()` is after the invocation of `CamelliaBase::SetDPCDBacklight()` + // and the retrieval of the base address of the MMIO region + if (Patterns::lealWithOffset(handle, 0xFFF37DA7)) { + context.start = current - descriptor.address; + DBGLOG("igfx", "BLT: [CFL ] Found the instruction leal: The relative start address of the inlined invocation is 0x%zx.", context.start); + current += size; + continue; + } + + // Guard: Step 2: Identify which register stores the controller instance + // Pattern: divl (%r??) + // where the offset is identical to the one found in `hwSetBacklight()` + if (Patterns::divlByMemoryWithOffset(handle, probeContext.offsetFrequencyDivider)) { + context.registerController = handle.rex_b << 3 | handle.modrm_rm; + DBGLOG("igfx", "BLT: [CFL ] Found the instruction divl: Register %s stores the controller instance.", registerName(context.registerController)); + current += size; + continue; + } + + // Guard: Step 3: Find the end address, relative to the given address, of the inlined invocation of `hwSetBacklight()` + // Pattern: addl $0xfff37dab, %r?? + if (Patterns::addlWithImm32(handle, 0xFFF37DAB)) { + context.end = current - descriptor.address; + DBGLOG("igfx", "BLT: [CFL ] Found the instruction addl: The relative end address of the inlined invocation is 0x%zx.", context.end); + break; + } + + current += size; } - // Step 6: Store the new brightness level to the controller - getMember(controller, self->offsetBrightnessLevel) = brightness; + // All done + return context; +} + +/** + * Wrapper to set the backlight compatible with the current machine + * + * @param controller The implicit framebuffer controller + * @param brightness The new brightness level + * @return `kIOReturnSuccess`. + * @note When this function returns, the given brightness level should be stored into the given framebuffer controller. + * @note Unlike the original fix implemented in BLR, BLT computes the value of the frequency register and the duty register + * compatible with the current machine and commit those values using the original version of `WriteRegister32()`. + * Apple's implementation will not be used by this submodule. + */ +IOReturn IGFX::BacklightRegistersAltFixCFL::wrapHwSetBacklight(void *controller, uint32_t brightness) { + // + // Differences between this function and the original one: + // + // - The wrapper no longer checks whether Camellia is enabled and invokes `CamelliaBase::SetDPCDBacklight()`. + // - The wrapper no longer writes Apple's PWM frequency (0x56CE or 0x4571 depending upon whether the bit 8 + // is set in `SFUSE_STRAP` (0xC2014), i.e. whether the raw frequency is used) to `BXT_BLC_PWM_FREQ1` (0xC8254). + // - The wrapper uses the PWM frequency set by the system firmware, the given new brightness value and + // Apple's frequency divider (set to 0xFFFF in getOSInformation()) to calculate the value of `BXT_BLC_PWM_DUTY1`. + // + auto self = &callbackIGFX->modBacklightRegistersAltFixCFL; + DBGLOG("igfx", "BLT: [CFL ] Called with the controller at 0x%016llx and the brightness level 0x%x.", reinterpret_cast(controller), brightness); + + // Step 1: Fetch and preserve the PWM frequency set by the system firmware + // Note that we need to restore the frequency after the system wakes up + self->fetchFirmwareBacklightFrequencyIfNecessary(controller); + + // Step 2: Calculate the new duty cycle for the given brightness level + uint32_t dutyCycle = self->calcDutyCycle(controller, brightness); + DBGLOG("igfx", "BLT: [CFL ] Frequency = 0x%x, Divider = 0x%x, Duty Cycle = 0x%x.", + self->firmwareBacklightFrequency, self->getFrequencyDivider(controller), dutyCycle); + + // Step 3: Write the PWM frequency set by the system firmware to BXT_BLC_PWM_FREQ1 + self->writeFrequency(controller, self->firmwareBacklightFrequency); + + // Step 4: Write the new duty cycle to BXT_BLC_PWM_DUTY1 + self->writeDutyCycle(controller, dutyCycle); + + // Step 5: Store the new brightness level in the controller + self->setBrightnessLevel(controller, brightness); // All done + DBGLOG("igfx", "BLT: [CFL ] The new brightness level 0x%x is now effective.", brightness); return kIOReturnSuccess; }