/* * WmiEcBacklight.cpp * Firmware (EC) panel backlight behind the WMI interface NVIDIA specified * for display-mux laptops * Copyright (c) 2026 Daniel Hammer */ #include "WmiEcBacklight.hpp" #include "AcpiWmi.hpp" #include "AcpiPower.hpp" #include #include #include #include using namespace Kt; namespace Hal { namespace WmiEcBacklight { // ============================================================================ // Interface // ============================================================================ static constexpr const char* Guid = "603E9613-EF25-4338-A3D0-C46177516DB7"; // WMxx method IDs. static constexpr uint32_t MethodLevel = 1; static constexpr uint32_t MethodSource = 2; // `mode` values in the argument block. static constexpr uint32_t ModeGet = 0; static constexpr uint32_t ModeSet = 1; static constexpr uint32_t ModeGetMaxLevel = 2; static constexpr uint64_t SourceEc = 2; // 1 = GPU, 3 = eDP AUX // Lowest level driven, so the panel never goes fully dark. static constexpr int MinPercent = 1; // ============================================================================ // State // ============================================================================ static kcp::Mutex g_lock; static bool g_probed = false; static bool g_available = false; static AcpiWmi::Block g_block{}; static uint32_t g_maxLevel = 0; // Argument block: { u32 mode; u32 val; u32 ret; u32 reserved[3]; }. // The method answers with an Integer. Linux reads it back as the // `ret` field of the same block, which is where ACPICA's copy-out of // an Integer puts the value; a firmware returning the block itself as // a Buffer is read at that same offset. static bool Call(uint32_t method, uint32_t mode, uint32_t value, uint32_t& out) { uint8_t args[24] = {}; args[0] = (uint8_t)mode; args[1] = (uint8_t)(mode >> 8); args[2] = (uint8_t)(mode >> 16); args[3] = (uint8_t)(mode >> 24); args[4] = (uint8_t)value; args[5] = (uint8_t)(value >> 8); args[6] = (uint8_t)(value >> 16); args[7] = (uint8_t)(value >> 24); AML::Object result{}; if (!AcpiWmi::EvaluateMethod(g_block, 0, method, args, sizeof(args), result)) return false; if (result.Type == AML::ObjectType::Integer) { out = (uint32_t)result.Integer; return true; } if (result.Type == AML::ObjectType::Buffer && result.Buffer.Length >= 12) { const uint8_t* r = result.Buffer.Data + 8; out = r[0] | (uint32_t)r[1] << 8 | (uint32_t)r[2] << 16 | (uint32_t)r[3] << 24; return true; } // A set may legitimately return nothing. out = 0; return mode == ModeSet; } static void Probe() { g_probed = true; uint8_t guid[16]; if (!AcpiWmi::ParseGuid(Guid, guid) || !AcpiWmi::Find(guid, g_block)) return; if (!(g_block.Flags & AcpiWmi::FlagMethod) || g_block.InstanceCount == 0) return; char path[128]; AML::GetInterpreter().GetNamespace().GetNodePath(g_block.Device, path, sizeof(path)); // The interface also exists on machines where the GPU drives the // backlight; there it must be left alone. uint32_t source = 0; if (!Call(MethodSource, ModeGet, 0, source)) { KernelLogStream(INFO, "Backlight") << "WMI EC backlight at " << path << ": source query failed"; return; } if (source != SourceEc) { KernelLogStream(INFO, "Backlight") << "WMI EC backlight at " << path << " reports source " << base::dec << (uint64_t)source << ", not EC"; return; } uint32_t maximum = 0; if (!Call(MethodLevel, ModeGetMaxLevel, 0, maximum) || maximum == 0) { KernelLogStream(WARNING, "Backlight") << "WMI EC backlight at " << path << ": no maximum level"; return; } g_maxLevel = maximum; g_available = true; char method[5] = {'W', 'M', g_block.ObjectId[0], g_block.ObjectId[1], '\0'}; KernelLogStream(OK, "Backlight") << "Panel brightness via EC (WMI " << path << "." << method << ", " << base::dec << (uint64_t)maximum << " levels)"; } static int ToPercent(uint32_t level) { if (level > g_maxLevel) level = g_maxLevel; return (int)(((uint64_t)level * 100 + g_maxLevel / 2) / g_maxLevel); } // ============================================================================ // Public API // ============================================================================ int Brightness(int percent) { g_lock.Acquire(); if (!g_probed) Probe(); int result = -1; if (g_available) { uint32_t level = 0; if (percent >= 0) { if (percent > 100) percent = 100; if (percent < MinPercent) percent = MinPercent; uint32_t target = (uint32_t)(((uint64_t)g_maxLevel * (uint32_t)percent + 50) / 100); if (target == 0) target = 1; uint32_t ignored = 0; if (!Call(MethodLevel, ModeSet, target, ignored)) { result = -2; } else { result = Call(MethodLevel, ModeGet, 0, level) ? ToPercent(level) : ToPercent(target); } } else { result = Call(MethodLevel, ModeGet, 0, level) ? ToPercent(level) : -2; } } g_lock.Release(); return result; } }; };