diff --git a/Changelog.md b/Changelog.md index 79da1c22..87119f56 100644 --- a/Changelog.md +++ b/Changelog.md @@ -2,6 +2,7 @@ WhateverGreen Changelog ======================= #### 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) #### v1.6.4 - Fixed Radeon RX 5500 XT identification regression diff --git a/Manual/FAQ.IntelHD.cn.md b/Manual/FAQ.IntelHD.cn.md index a3c277ba..785a1ab5 100644 --- a/Manual/FAQ.IntelHD.cn.md +++ b/Manual/FAQ.IntelHD.cn.md @@ -2021,6 +2021,14 @@ igfx: @ (DBG) BLS: [COMM] Processing the request: Current = 0x00014ead; Target = +## 修复在 Coffee Lake 平台上运行 macOS 13.4 或以上版本的笔记本开机持续3分钟暗屏问题 + +如果你之前使用“亮度寄存器修复”(也就是 `-igfxblr` 这个启动参数)来解决笔记本开机持续3分钟左右暗屏的问题,在升级到 macOS 13.4 或之后的版本后你会发现该补丁失效了。这是因为苹果简化了显卡驱动中读写寄存器相关的函数,导致编译器优化了函数调用的汇编代码,进而导致“亮度寄存器修复”以及“亮度丝滑器”注册的钩子失效。从 v1.6.5 开始,*WEG* 提供了新的补丁来撤销编译器对亮度调节相关函数的优化以及为 Coffee Lake 平台的笔记本重写了调节亮度的函数,从而解决开机持续3分钟暗屏以及“亮度丝滑器”失效的问题。 + +请注意这个新补丁仅适用于使用 macOS 13.4 以及以上的 Coffee Lake 核显驱动的笔记本用户。你可以为核显添加 `enable-backlight-registers-alternative-fix` 属性或者直接使用 `-igfxblt` 启动参数来启用这个新的补丁。与此同时,你可以删除原“亮度寄存器修复”的 `enable-backlight-registers-fix` 设备属性或者 `-igfxbls` 启动参数。如果你想在 macOS 13.4 或以上系统中使用“亮度丝滑器”,你需要添加 `-igfxblt` 以及 `-igfxbls` 这两个启动参数。 + +Ice Lake 平台的笔记本用户不受此问题影响,然而 Kaby Lake 平台的笔记本用户可能在 macOS 13.4 或以上系统中遇到类似的3分钟暗屏问题。由于没有足够空间来覆盖读取亮度相关寄存器的汇编指令,此新补丁暂不支持 Kaby Lake 平台。 + ## 修复 Ice Lake 平台上笔记本开机持续花屏7到15秒的问题 为核显添加 `enable-dbuf-early-optimizer` 属性或者直接使用 `-igfxdbeo` 启动参数以修复 Ice Lake 笔记本开机后内屏短暂花屏的问题。 diff --git a/Manual/FAQ.IntelHD.en.md b/Manual/FAQ.IntelHD.en.md index 5e9da78d..5a36e9da 100644 --- a/Manual/FAQ.IntelHD.en.md +++ b/Manual/FAQ.IntelHD.en.md @@ -2671,6 +2671,14 @@ 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 + +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. + +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 `-igfxbls` 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 `-igfxbrs` 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. + ## Fix the issue that the builtin display remains garbled after the system boots on ICL platforms Add the `enable-dbuf-early-optimizer` property to `IGPU` or use the `-igfxdbeo` boot argument instead to fix the Display Data Buffer (DBUF) allocation issue on ICL platforms, diff --git a/README.md b/README.md index 592aafa3..0cab665e 100644 --- a/README.md +++ b/README.md @@ -94,6 +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) | | `-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) | @@ -148,7 +149,7 @@ Read [FAQs](./Manual/) and avoid asking any questions. No support is provided fo - [AMD](https://www.amd.com) for ATOM VBIOS parsing code - [The PCI ID Repository](http://pci-ids.ucw.cz) for multiple GPU model names - [Andrey1970AppleLife](https://github.com/Andrey1970AppleLife) for [FAQs](./Manual/) -- [FireWolf](https://github.com/0xFireWolf/) for the DPCD maximum link rate fix, infinite loop fix for Intel HDMI connections, LSPCON driver support, Core Display Clock frequency fix for ICL platforms, DVMT pre-allocated memory calculation fix for ICL platforms, Backlight Smoother for IVB+ platforms, and Display Data Buffer fix for ICL platforms. +- [FireWolf](https://github.com/0xFireWolf/) for the DPCD maximum link rate fix, infinite loop fix for Intel HDMI connections, LSPCON driver support, Core Display Clock frequency fix for ICL platforms, DVMT pre-allocated memory calculation fix for ICL platforms, Backlight Smoother for IVB+ platforms, Display Data Buffer fix for ICL platforms, and Backlight Registers Alternative Fix. - [Floris497](https://github.com/Floris497) for the CoreDisplay [patches](https://github.com/Floris497/mac-pixel-clock-patch-v2) - [Fraxul](https://github.com/Fraxul) for original CFL backlight patch - [headkaze](https://github.com/headkaze) for Intel framebuffer patching code and CFL backlight patch improvements diff --git a/WhateverGreen/kern_igfx.hpp b/WhateverGreen/kern_igfx.hpp index 9585f7bc..bd9fdf14 100644 --- a/WhateverGreen/kern_igfx.hpp +++ b/WhateverGreen/kern_igfx.hpp @@ -1526,7 +1526,7 @@ class IGFX { /** * Fallback user-requested backlight frequency in case 0 was initially written to the register. */ - static constexpr uint32_t FallbackTargetBacklightFrequency = 120000; + static constexpr uint32_t kFallbackTargetBacklightFrequency = 120000; /** * [COMM] User-requested backlight frequency obtained from BXT_BLC_PWM_FREQ1 at system start. @@ -1600,6 +1600,134 @@ class IGFX { void processFramebufferKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) override; } modBacklightRegistersFix; + /** + * A submodule to revert invocations of backlight-related functions and patch backlight register values on macOS 13.4. + * + * @note Supported Platforms: CFL. + */ + class BacklightRegistersAltFix: public PatchSubmodule { + /** + * 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 + * so that this patch submodule can revert the inlined invocation in the function of interest + */ + struct InvocationContext { + /** + * The start address, relative to the address of the function, of the inlined invocation of `hwSetBacklight()` + */ + size_t start {0}; + + /** + * The end address, relative to the address of the function, of the inlined invocation of `hwSetBacklight()` + * + * @note This is the address of the first instruction after `call hwSetBacklight` returns. + */ + size_t end {0}; + + /** + * Record which register stores the implicit framebuffer controller instance + */ + uint32_t registerController {0}; + + /** + * Validate this invocation context + */ + bool isValid() const { + return this->start != 0 && this->end != 0 && this->registerController != 0; + } + + /** + * Calculate the number of bytes that can be safely replaced + */ + size_t freeSpace() const { + return this->end - this->start; + } + }; + + /** + * Record the PWM frequency initialized by the system firmware + */ + uint32_t firmwareBacklightFrequency {0}; + + /** + * 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}; + + /** + * Find the offset of the member fields that store the PWM frequency divider and the current brightness level + * + * @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. + */ + ppair probeMemberOffsets(mach_vm_address_t address, size_t instructions) const; + + /** + * Find the location of the inlined invocation of `hwSetBacklight()` in the function that starts at the given address + * + * @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. + */ + InvocationContext probeInlinedInvocation(mach_vm_address_t address, size_t instructions) const; + + /** + * Revert the inlined invocation of `hwSetBacklight()` in the function that starts at the given address + * + * @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. + */ + bool revertInlinedInvocation(KernelPatcher &patcher, mach_vm_address_t address, mach_vm_address_t orgHwSetBacklight, const InvocationContext &context) const; + + /** + * 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, 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. + */ + static IOReturn wrapHwSetBacklight(void *controller, uint32_t brightness); + + /** + * Get the name of the register at the given index + * + * @param index The register index + * @return The register name, `INVALID` if the given index is invalid. + */ + static inline const char* registerName(size_t index) { + static constexpr const char* kNames[] = { + "%rax", "%rcx", "%rdx", "%rbx", "%rsp", "%rbp", "%rsi", "%rdi", + "%r8" , "%r9" , "%r10", "%r11", "%r12", "%r13", "%r14", "%r15", + }; + return index < arrsize(kNames) ? kNames[index] : "INVALID"; + } + + 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; + } modBacklightRegistersAltFix; + /** * Brightness request event source needs access to the original WriteRegister32 function */ @@ -1617,6 +1745,11 @@ class IGFX { * Backlight registers fix submodule needs access to the smoother version of `WriteRegister32()` */ friend class BacklightRegistersFix; + + /** + * Backlight registers alternative fix submodule needs access to the smoother version of `WriteRegister32()` + */ + friend class BacklightRegistersAltFix; /** * Brightness request event source needs access to the queue and config parameters @@ -1835,7 +1968,7 @@ class IGFX { /** * A collection of submodules */ - PatchSubmodule *submodules[20] = { + PatchSubmodule *submodules[21] = { &modDVMTCalcFix, &modDPCDMaxLinkRateFix, &modCoreDisplayClockFix, @@ -1852,6 +1985,7 @@ class IGFX { &modPAVPDisabler, &modReadDescriptorPatch, &modBacklightRegistersFix, + &modBacklightRegistersAltFix, &modBacklightSmoother, &modFramebufferDebugSupport, &modMaxPixelClockOverride, diff --git a/WhateverGreen/kern_igfx_backlight.cpp b/WhateverGreen/kern_igfx_backlight.cpp index 534b6de2..b1f5c101 100644 --- a/WhateverGreen/kern_igfx_backlight.cpp +++ b/WhateverGreen/kern_igfx_backlight.cpp @@ -10,12 +10,15 @@ #include "kern_igfx_kexts.hpp" #include "kern_igfx.hpp" #include +#include /// /// This file contains the following backlight-related fixes and enhancements /// /// 1. Backlight registers fix that solves the 3-minute black screen on CFL+. -/// 2. Backlight smoother that makes brightness transitions smoother on IVB+. +/// 2. Backlight registers alternative fix that revertes the inlined invocation of `hwSetBacklight()` in `LightUpEDP()` and `hwSetPanelPower()`, +/// providing an alternative solution to the 3-minute black screen on CFL. +/// 3. Backlight smoother that makes brightness transitions smoother on IVB+. /// // @@ -83,26 +86,27 @@ void IGFX::BacklightRegistersFix::processFramebufferKext(KernelPatcher &patcher, callbackIGFX->modMMIORegistersWriteSupport.replacerList.add(&dCFLPWMFreq1); callbackIGFX->modMMIORegistersWriteSupport.replacerList.add(&dCFLPWMDuty1); } else { - SYSLOG("igfx", "BLS: [ERR!] Found an unsupported platform. Will not perform any injections."); + SYSLOG("igfx", "BLR: [ERR!] Found an unsupported platform. Will not perform any injections."); } } void IGFX::BacklightRegistersFix::wrapKBLWriteRegisterPWMFreq1(void *controller, uint32_t reg, uint32_t value) { DBGLOG("igfx", "BLR: [KBL ] WriteRegister32: Called with register 0x%x and value 0x%x.", reg, value); PANIC_COND(reg != BXT_BLC_PWM_FREQ1, "igfx", "Fatal Error: Register should be BXT_BLC_PWM_FREQ1."); + auto self = &callbackIGFX->modBacklightRegistersFix; - if (callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency == 0) { - // Populate the hardware PWM frequency as initially set up by the system firmware. - callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency = callbackIGFX->readRegister32(controller, BXT_BLC_PWM_FREQ1); - DBGLOG("igfx", "BLR: [KBL ] WriteRegister32: System initialized with BXT_BLC_PWM_FREQ1 = 0x%x.", - callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency); - DBGLOG("igfx", "BLR: [KBL ] WriteRegister32: System initialized with BXT_BLC_PWM_CTL1 = 0x%x.", - callbackIGFX->readRegister32(controller, BXT_BLC_PWM_CTL1)); - - if (callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency == 0) { - // This should not happen with correctly written bootloader code, but in case it does, let's use a failsafe default value. - callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency = FallbackTargetBacklightFrequency; - SYSLOG("igfx", "BLR: [KBL ] WriteRegister32: System initialized with BXT_BLC_PWM_FREQ1 = ZERO."); + // Preserve the hardware PWM frequency set by the system firmware on boot + // We'll need this to restore later after system sleep. + if (self->targetBacklightFrequency == 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", "BLR: [KBL ] WriteRegister32: System initialized with BXT_BLC_PWM_FREQ1 = 0x%x.", bootValue); + DBGLOG("igfx", "BLR: [KBL ] WriteRegister32: System initialized with BXT_BLC_PWM_CTL1 = 0x%x.", + callbackIGFX->readRegister32(controller, BXT_BLC_PWM_CTL1)); + self->targetBacklightFrequency = bootValue; + } else { + SYSLOG("igfx", "BLR: [KBL ] WriteRegister32: System initialized with BXT_BLC_PWM_FREQ1 = ZERO. Will use the fallback frequency."); + self->targetBacklightFrequency = kFallbackTargetBacklightFrequency; } } @@ -112,13 +116,13 @@ void IGFX::BacklightRegistersFix::wrapKBLWriteRegisterPWMFreq1(void *controller, uint16_t frequency = (value & 0xffff0000U) >> 16U; uint16_t dutyCycle = value & 0xffffU; - uint32_t rescaledValue = frequency == 0 ? 0 : static_cast((dutyCycle * static_cast(callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency)) / static_cast(frequency)); + uint32_t rescaledValue = frequency == 0 ? 0 : static_cast((dutyCycle * static_cast(self->targetBacklightFrequency)) / static_cast(frequency)); DBGLOG("igfx", "BLR: [KBL ] WriteRegister32: Write PWM_DUTY1 0x%x/0x%x, rescaled to 0x%x/0x%x.", - dutyCycle, frequency, rescaledValue, callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency); + dutyCycle, frequency, rescaledValue, self->targetBacklightFrequency); // Reset the hardware PWM frequency. Write the original system value if the driver-requested value is nonzero. If the driver requests // zero, we allow that, since it's trying to turn off the backlight PWM for sleep. - callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_FREQ1, frequency ? callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency : 0); + 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); @@ -127,10 +131,11 @@ void IGFX::BacklightRegistersFix::wrapKBLWriteRegisterPWMFreq1(void *controller, void IGFX::BacklightRegistersFix::wrapKBLWriteRegisterPWMCtrl1(void *controller, uint32_t reg, uint32_t value) { DBGLOG("igfx", "BLR: [KBL ] WriteRegister32: Called with register 0x%x and value 0x%x.", reg, value); PANIC_COND(reg != BXT_BLC_PWM_CTL1, "igfx", "Fatal Error: Register should be BXT_BLC_PWM_CTL1."); + auto self = &callbackIGFX->modBacklightRegistersFix; - if (callbackIGFX->modBacklightRegistersFix.targetPwmControl == 0) { + if (self->targetPwmControl == 0) { // Save the original hardware PWM control value - callbackIGFX->modBacklightRegistersFix.targetPwmControl = callbackIGFX->readRegister32(controller, BXT_BLC_PWM_CTL1); + self->targetPwmControl = callbackIGFX->readRegister32(controller, BXT_BLC_PWM_CTL1); } DBGLOG("igfx", "BLR: [KBL ] WriteRegister32: Write BXT_BLC_PWM_CTL1 0x%x, previous was 0x%x.", @@ -138,10 +143,10 @@ void IGFX::BacklightRegistersFix::wrapKBLWriteRegisterPWMCtrl1(void *controller, if (value) { // Set the PWM frequency before turning it on to avoid the 3 minute blackout bug - callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_FREQ1, callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency); + callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_FREQ1, self->targetBacklightFrequency); // Use the original hardware PWM control value. - value = callbackIGFX->modBacklightRegistersFix.targetPwmControl; + value = self->targetPwmControl; } // Finish by writing the new value @@ -151,29 +156,30 @@ void IGFX::BacklightRegistersFix::wrapKBLWriteRegisterPWMCtrl1(void *controller, void IGFX::BacklightRegistersFix::wrapCFLWriteRegisterPWMFreq1(void *controller, uint32_t reg, uint32_t value) { DBGLOG("igfx", "BLR: [CFL+] WriteRegister32: Called with register 0x%x and value 0x%x.", reg, value); PANIC_COND(reg != BXT_BLC_PWM_FREQ1, "igfx", "Fatal Error: Register should be BXT_BLC_PWM_FREQ1."); + auto self = &callbackIGFX->modBacklightRegistersFix; - if (value && value != callbackIGFX->modBacklightRegistersFix.driverBacklightFrequency) { + if (value && value != self->driverBacklightFrequency) { DBGLOG("igfx", "BLR: [CFL+] WriteRegister32: Driver requested BXT_BLC_PWM_FREQ1 = 0x%x.", value); - callbackIGFX->modBacklightRegistersFix.driverBacklightFrequency = value; + self->driverBacklightFrequency = value; } - if (callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency == 0) { - // Save the hardware PWM frequency as initially set up by the system firmware. - // We'll need this to restore later after system sleep. - callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency = callbackIGFX->readRegister32(controller, BXT_BLC_PWM_FREQ1); - DBGLOG("igfx", "BLR: [CFL+] WriteRegister32: System initialized with BXT_BLC_PWM_FREQ1 = 0x%x.", callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency); - - if (callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency == 0) { - // This should not happen with correctly written bootloader code, but in case it does, let's use a failsafe default value. - callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency = FallbackTargetBacklightFrequency; - SYSLOG("igfx", "BLR: [CFL+] WriteRegister32: System initialized with BXT_BLC_PWM_FREQ1 = ZERO."); + // Preserve the hardware PWM frequency set by the system firmware on boot + // We'll need this to restore later after system sleep. + if (self->targetBacklightFrequency == 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", "BLR: [CFL+] WriteRegister32: System initialized with BXT_BLC_PWM_FREQ1 = 0x%x.", bootValue); + self->targetBacklightFrequency = bootValue; + } else { + SYSLOG("igfx", "BLR: [CFL+] WriteRegister32: System initialized with BXT_BLC_PWM_FREQ1 = ZERO. Will use the fallback frequency."); + self->targetBacklightFrequency = kFallbackTargetBacklightFrequency; } } if (value) { // Nonzero writes to this register need to use the original system value. // Yet the driver can safely write zero to this register as part of system sleep. - value = callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency; + value = self->targetBacklightFrequency; } // Finish by writing the new value @@ -183,8 +189,9 @@ void IGFX::BacklightRegistersFix::wrapCFLWriteRegisterPWMFreq1(void *controller, void IGFX::BacklightRegistersFix::wrapCFLWriteRegisterPWMDuty1(void *controller, uint32_t reg, uint32_t value) { DBGLOG("igfx", "BLR: [CFL+] WriteRegister32: Called with register 0x%x and value 0x%x.", reg, value); PANIC_COND(reg != BXT_BLC_PWM_DUTY1, "igfx", "Fatal Error: Register should be BXT_BLC_PWM_DUTY1."); + auto self = &callbackIGFX->modBacklightRegistersFix; - if (value && callbackIGFX->modBacklightRegistersFix.driverBacklightFrequency == 0) { + if (value && self->driverBacklightFrequency == 0) { // CFL+ backlight additional fix. DBGLOG("igfx", "BLR: [CFL+] WriteRegister32: Backlight additional fix was entered."); uint32_t registerValue = callbackIGFX->readRegister32(controller, SFUSE_STRAP); @@ -192,16 +199,16 @@ void IGFX::BacklightRegistersFix::wrapCFLWriteRegisterPWMDuty1(void *controller, wrapCFLWriteRegisterPWMFreq1(controller, BXT_BLC_PWM_FREQ1, selectedFreq); } - if (callbackIGFX->modBacklightRegistersFix.driverBacklightFrequency && callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency) { + if (self->driverBacklightFrequency && self->targetBacklightFrequency) { // Translate the PWM duty cycle between the driver scale value and the HW scale value - uint32_t rescaledValue = static_cast((value * static_cast(callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency)) / static_cast(callbackIGFX->modBacklightRegistersFix.driverBacklightFrequency)); - DBGLOG("igfx", "BLR: [CFL+] WriteRegister32: Write PWM_DUTY1 0x%x/0x%x, rescaled to 0x%x/0x%x.", value, - callbackIGFX->modBacklightRegistersFix.driverBacklightFrequency, rescaledValue, callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency); + uint32_t rescaledValue = static_cast((value * static_cast(self->targetBacklightFrequency)) / static_cast(self->driverBacklightFrequency)); + DBGLOG("igfx", "BLR: [CFL+] WriteRegister32: Write PWM_DUTY1 0x%x/0x%x, rescaled to 0x%x/0x%x.", + value, self->driverBacklightFrequency, rescaledValue, self->targetBacklightFrequency); value = rescaledValue; } else { // This should never happen, but in case it does we should log it at the very least. SYSLOG("igfx", "BLR: [CFL+] WriteRegister32: Write PWM_DUTY1 has zero frequency driver (%d) target (%d).", - callbackIGFX->modBacklightRegistersFix.driverBacklightFrequency, callbackIGFX->modBacklightRegistersFix.targetBacklightFrequency); + self->driverBacklightFrequency, self->targetBacklightFrequency); } if (callbackIGFX->modBacklightSmoother.enabled) { @@ -215,6 +222,517 @@ void IGFX::BacklightRegistersFix::wrapCFLWriteRegisterPWMDuty1(void *controller, } } +// +// MARK: - Backlight Registers Alternative Fix +// + +void IGFX::BacklightRegistersAltFix::init() { + // We only need to patch the framebuffer driver + requiresPatchingFramebuffer = true; + + // We need R/W access to MMIO registers + requiresMMIORegistersReadAccess = true; + requiresMMIORegistersWriteAccess = true; +} + +void IGFX::BacklightRegistersAltFix::processKernel(KernelPatcher &patcher, DeviceInfo *info) { + enabled = checkKernelArgument("-igfxblt"); + if (!enabled) + enabled = info->videoBuiltin->getProperty("enable-backlight-registers-alternative-fix") != nullptr; +} + +void IGFX::BacklightRegistersAltFix::processFramebufferKext(KernelPatcher &patcher, size_t index, mach_vm_address_t address, size_t size) { + // + // Apple has "accidentally" 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, `hwSetBacklight()` no longer invokes + // `WriteRegister32` to update backlight registers; instead it modifies the register value via mapped memory directly, making itself an inline helper. + // `LightUpEDP()` and `hwSetPanelPower()` that invoke `hwSetBacklight()` now have the definition of `hwSetBacklight()` embedded in themselves. + // As such, the `WriteRegister32` hooks registered by the Backlight Registers Fix (BLR) and the Backlight Smoother (BLS) submodules no longer work. + // This patch submodule (BLT) reverts the optimizations done by the compiler in aforementioned three functions and overwrites Apple's implementation of `hwSetBacklight()`, + // thus providing an alternative to BLR and making BLS work properly on macOS 13.4 or later. + // + // - FireWolf + // - 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."); + return; + } else if (getKernelVersion() == KernelVersion::Ventura && getKernelMinorVersion() < 5) { + SYSLOG("igfx", "BLT: 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.", + 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."); + 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."); + 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); + 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); + 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; + } + } + + // 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; + } + + // All done + return context; +} + +bool IGFX::BacklightRegistersAltFix::revertInlinedInvocation(KernelPatcher &patcher, mach_vm_address_t address, mach_vm_address_t orgHwSetBacklight, const InvocationContext &context) const { + // + // Sample Patch 1: Revert inlined hwSetBacklight() invocation + // + // Function: AppleIntelFramebufferController::LightUpEDP() + // Offset: 488 + // Version: macOS 13.4 + // Platform: CFL + // + // Find: movq 0x2e60(%r15), %rdi // Beginning of the inlined function call + // testq %rdi, %rdi // %r15 stores the implicit controller instance + // je loc_146f78b6 + // movl 0x2e78(%r15), %esi + // + // Replace: movl 0x2e78(%r15), %esi // Fetch the target backlight level which is the 2nd argument + // movq %r15, %rdi // The implicit controller instance is the 1st argument + // call 0x146ee4ae // Call AppleIntelFramebufferController::hwSetBacklight(level) + // jmp 0x146f7920 // Jump to the end of inlined function call + // + // ---------------------------------------------------------------------------------------------------- + // + // Sample Patch 2: Revert inlined hwSetBacklight() invocation + // + // Function: AppleIntelFramebufferController::hwSetPanelPower() + // Offset: 1505 + // Version: macOS 13.4 + // Platform: CFL + // + // Find: leal 0xfff37da7(%rax), %edx // Beginning of the inlined function call + // cmpl $0xfff37daa, %edx // %r12 stores the implicit controller instance + // ja loc_146ea9e5 // %rcx stores the base address of the MMIO region + // movl 0x2e84(%r12), %eax + // + // Replace: pushq %rcx // Preserve the base address of the MMIO region + // movl 0x2e78(%r12), %esi // Fetch the target backlight level which is the 2nd argument + // movq %r12, %rdi // The implicit controller instance is the 1st argument + // call 0x146ee4ae // Call AppleIntelFramebufferController::hwSetBacklight(level) + // popq %rcx // Restore the base address of the MMIO region + // jmp 0x146eaa1c // Jump to the end of inlined function call + // + // Step 1: [Template] Preserve all caller-saved registers to avoid analyzing register liveness + static constexpr uint8_t kPreserve[] = { + 0x50, // pushq %rax + 0x57, // pushq %rdi + 0x56, // pushq %rsi + 0x52, // pushq %rdx + 0x51, // pushq %rcx + 0x41, 0x50, // pushq %r8 + 0x41, 0x51, // pushq %r9 + 0x41, 0x52, // pushq %r10 + 0x41, 0x53 // pushq %r11 + }; + + // Step 2: [Template] Fetch the new brightness level which will be the 2nd argument + static constexpr uint8_t kSetArg1[] = { + // + // Template: movl 0x???(%r8), %esi + // + // Notes: + // - Start from 0x8B if the source register is < %r8 + // - Start from 0x41 if the source register is >= %r8 and is NOT %r12 + // - Increment 0xB0 by `index` where `index` = register % 8 + // - Using %rsp or %rbp as the source register should never happen + // + 0x41, 0x8B, 0xB0, 0x00, 0x00, 0x00, 0x00 + }; + static constexpr uint8_t kSetArg1_r12[] = { + // + // Template: movl 0x???(%r12), %esi + // + // Notes: + // - 0x24 is inserted after 0xB0 if the source register is %r12 + // + 0x41, 0x8B, 0xB4, 0x24, 0x00, 0x00, 0x00, 0x00 + }; + + // Step 3: [Template] Set the controller instance which will be the 1st argument + static constexpr uint8_t kSetArg0[] = { + // + // Template: movq %rax, %rdi + // + // Notes: + // - Use 0x4C instead of 0x48 if the source register is >= %r8 + // - Increment 0xC7 by `index` * 8, where `index` = register % 8 + // + 0x48, 0x89, 0xC7 + }; + + // Step 4: [Template] Invoke `AppleIntelFramebufferController::hwSetBacklight(level)` + static constexpr uint8_t kCall[] = { + // + // Template: call + // + // Notes: + // - The offset is relative to the address of the next instruction + // + 0xE8, 0x00, 0x00, 0x00, 0x00 + }; + + // Step 5: [Template] Restore all caller-saved registers + static constexpr uint8_t kRestore[] = { + 0x41, 0x5B, // popq %r11 + 0x41, 0x5A, // popq %r10 + 0x41, 0x59, // popq %r9 + 0x41, 0x58, // popq %r8 + 0x59, // popq %rcx + 0x5A, // popq %rdx + 0x5E, // popq %rsi + 0x5F, // popq %rdi + 0x58 // popq %rax + }; + + // Step 6: [Template] Jump to the end of the inlined invocation of `hwSetBacklight()` + static constexpr uint8_t kJump[] = { + // + // Template: jmp 0x?? (8-bit offset) + // + // Notes: + // - The offset is relative to the address of the next instruction + // - The offset should not exceed 0xFF since `hwSetBacklight` is small enough to be inlined + // + 0xEB, 0x00 + }; + + // The maximum number of bytes in the final patch + 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); + 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)); + + // 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); + } 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(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) { + // %r8, %r9, %r10, %r11, %r12, %r13, %r14, %r15 + current[0] += 4; + } + current += sizeof(kSetArg0); + + // Step 4: Invoke `AppleIntelFramebufferController::hwSetBacklight(level)` + // Step 4.1: Copy the instruction + 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); + // Step 4.3: Calculate and set the offset for the call instruction + *reinterpret_cast(current - sizeof(uint32_t)) = static_cast(orgHwSetBacklight - next); + + // Step 5: Restore all caller-saved registers + lilu_os_memcpy(current, kRestore, sizeof(kRestore)); + current += sizeof(kRestore); + + // Step 6: Jump to the first instruction after the inlined invocation of `hwSetBacklight()` returns + // Step 6.1: Copy the instruction + 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."); + *(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); + for (size_t index = 0; index < patchSize; index += 1) { + DBGLOG("igfx", "BLT: [%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); + patcher.clearError(); + return false; + } + + // All done + DBGLOG("igfx", "BLT: Patched the function at 0x%016llx successfully.", address); + return true; +} + +IOReturn IGFX::BacklightRegistersAltFix::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->modBacklightRegistersAltFix; + 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 + 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; + } + } + + uint64_t frequency = self->firmwareBacklightFrequency; + + // Step 2: Fetch the value of Apple's frequency divider + uint64_t divider = getMember(controller, self->offsetFrequencyDivider); + + // Step 3: Calculate the new value of BXT_BLC_PWM_DUTY1 + uint32_t duty = static_cast(frequency * brightness / divider); + + DBGLOG("igfx", "BLT: [CFL+] Frequency = 0x%llx, Divider = 0x%llx, Duty = 0x%x.", frequency, divider, duty); + + // Step 4: Write the frequency to BXT_BLC_PWM_FREQ1 + callbackIGFX->writeRegister32(controller, BXT_BLC_PWM_FREQ1, static_cast(frequency)); + + // 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); + } + + // Step 6: Store the new brightness level to the controller + getMember(controller, self->offsetBrightnessLevel) = brightness; + + // All done + return kIOReturnSuccess; +} + // // MARK: - Brightness Request Event Source //