From 3c995cc8d83bcb684fbe1523d60bbb42b1b0d6c1 Mon Sep 17 00:00:00 2001 From: Acelogic <7362641+Acelogic@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:19:14 -0400 Subject: [PATCH 1/4] compat: convert a swap record's brightness to 16.16 nits The A16 kext now takes the panel brightness from the swap record IOMobileFramebufferSwapSetBrightness fills: an IEEE double in nits, unaligned, straight from a client. edp_bl_double_to_q16 turns its bits into 16.16 nits in integer arithmetic, rounded to nearest and capped, refuses NaN, infinities and negative values (which name no brightness), and maps zero and subnormals to 0 (the darkest request). The model test checks it bit-exact against the host's double arithmetic for chosen values and for every exponent with a spread of mantissas, and four mutations (NaN or negative accepted, truncation, no cap) must fail it. Header-only and unused by the monitor: the SNAP_EDP_BACKLIGHT monitors built with run next1b's flags, with and without the define, are byte-identical to org next's. --- compat/edp-backlight.h | 21 ++++++++++++++++ tools/test-edp-backlight.c | 49 +++++++++++++++++++++++++++++++++++++ tools/test-edp-backlight.py | 7 ++++++ 3 files changed, 77 insertions(+) diff --git a/compat/edp-backlight.h b/compat/edp-backlight.h index 89a6aec..5ec37f1 100644 --- a/compat/edp-backlight.h +++ b/compat/edp-backlight.h @@ -233,6 +233,27 @@ static inline uint32_t edp_bl_nits_to_slider(uint64_t nits_q16, uint64_t lo_q16, if (nits_q16 >= hi_q16) return EDP_BL_SLIDER_MAX; return (uint32_t)(((nits_q16 - lo_q16) * EDP_BL_SLIDER_MAX + (hi_q16 - lo_q16) / 2) / (hi_q16 - lo_q16)); } +/* A swap record's brightness (an IEEE 754 binary64 in nits, as IOMobileFramebufferSwapSetBrightness stores it) as + * 16.16 nits, rounded to nearest and capped at cap_q16, in integer arithmetic only: the bits come straight from a + * client's record. 0 with *q16 set; -1 for a NaN, an infinity or a negative value, which name no brightness. Zero and + * subnormals are 0 nits (the darkest request). */ +static inline int edp_bl_double_to_q16(uint64_t bits, uint64_t cap_q16, uint64_t *q16) +{ + /* A normal value is mantissa * 2^(exponent - 1075), so value * 2^16 = mantissa * 2^(exponent - 1059). */ + uint32_t exponent = (uint32_t)(bits >> 52) & 0x7ffu; + uint64_t mantissa = (bits & ((1ull << 52) - 1)) | 1ull << 52, v; + if (exponent == 0x7ffu || (bits >> 63 && bits << 1)) return -1; + if (exponent == 0) + v = 0; + else if (exponent >= 1059) /* 2^36 nits or more; a mantissa below 2^53 shifted by up to 10 stays below 2^63 */ + v = exponent - 1059 > 10 ? UINT64_MAX : mantissa << (exponent - 1059); + else if (1059 - exponent > 63) + v = 0; + else + v = (mantissa + (1ull << (1059 - exponent - 1))) >> (1059 - exponent); + *q16 = v > cap_q16 ? cap_q16 : v; + return 0; +} /* ---- Allow-lists: every MMIO access and every DPCD transaction the monitor makes passes one of these ----------- */ static inline int edp_bl_mmio_read_ok(uint64_t pa) diff --git a/tools/test-edp-backlight.c b/tools/test-edp-backlight.c index 45930ab..8e165b1 100644 --- a/tools/test-edp-backlight.c +++ b/tools/test-edp-backlight.c @@ -242,6 +242,54 @@ static void test_curve(void) CHECK(edp_bl_nits_to_level(edp_bl_slider_to_nits(65536, lo, Q(500)), maxn, floor, max) == max); } +/* A swap record's brightness double as 16.16 nits: bit-exact against the host's own double arithmetic, refusing what + * names no brightness, capped, and never trusting the bits to be a sensible number. */ +static uint64_t bits_of(double d) { uint64_t b; memcpy(&b, &d, sizeof b); return b; } +static void test_swap_double(void) +{ + const uint64_t cap = (uint64_t)EDP_BL_NITS_LIMIT_MAX << 16; + uint64_t q = 12345; + static const double exact[] = {0.0, 1.0, 5.0, 10.015, 140.0, 174.89, 384.615, 499.999, 500.0, 0.5, 1.0 / 65536, + 1.5 / 65536, 0.49 / 65536, 1e-300, 999.99998}; + for (unsigned i = 0; i < sizeof exact / sizeof exact[0]; i++) { + double diff; + CHECK(edp_bl_double_to_q16(bits_of(exact[i]), cap, &q) == 0); + diff = (double)q - exact[i] * 65536.0; + CHECK(diff <= 0.5 && diff >= -0.5); + } + CHECK(edp_bl_double_to_q16(bits_of(140.0), cap, &q) == 0 && q == 140ull << 16); + CHECK(edp_bl_double_to_q16(bits_of(-0.0), cap, &q) == 0 && q == 0); + CHECK(edp_bl_double_to_q16(bits_of(4.9e-324), cap, &q) == 0 && q == 0); /* subnormal */ + CHECK(edp_bl_double_to_q16(bits_of(1000.5), cap, &q) == 0 && q == cap); + CHECK(edp_bl_double_to_q16(bits_of(1e300), cap, &q) == 0 && q == cap); + CHECK(edp_bl_double_to_q16(bits_of(68719476736.0), cap, &q) == 0 && q == cap); /* 2^36: the shifted branch */ + CHECK(edp_bl_double_to_q16(bits_of(1e300), UINT64_MAX, &q) == 0 && q == UINT64_MAX); + CHECK(edp_bl_double_to_q16(bits_of(68719476736.0), UINT64_MAX, &q) == 0 && q == 1ull << 52); + q = 777; + CHECK(edp_bl_double_to_q16(0x7ff8000000000000ull, cap, &q) == -1 && q == 777); /* the NaN CoreAnimation logs */ + CHECK(edp_bl_double_to_q16(0x7ff0000000000001ull, cap, &q) == -1); /* signalling NaN */ + CHECK(edp_bl_double_to_q16(0x7ff0000000000000ull, cap, &q) == -1); /* +inf */ + CHECK(edp_bl_double_to_q16(0xfff0000000000000ull, cap, &q) == -1); /* -inf */ + CHECK(edp_bl_double_to_q16(bits_of(-1.0), cap, &q) == -1 && edp_bl_double_to_q16(bits_of(-1e-300), cap, &q) == -1); + /* Every exponent and a spread of mantissas: monotonic in the value, never above the cap, rounding within half a + * step of the host's arithmetic. */ + uint64_t seed = 0x9e3779b97f4a7c15ull, prev = 0; + for (uint32_t e = 0; e < 0x7ff; e++) { + for (unsigned k = 0; k < 8; k++) { + seed = seed * 6364136223846793005ull + 1442695040888963407ull; + uint64_t frac = k == 0 ? 0 : k == 1 ? (1ull << 52) - 1 : seed >> 12; + uint64_t b = (uint64_t)e << 52 | frac; + double d; + memcpy(&d, &b, sizeof d); + CHECK(edp_bl_double_to_q16(b, cap, &q) == 0 && q <= cap); + double want = d * 65536.0; + if (want < (double)cap) CHECK((double)q >= want - 0.5 && (double)q <= want + 0.5); + else CHECK(q == cap); + if (k == 0) { CHECK(q >= prev); prev = q; } + } + } +} + static void test_helpers(void) { uint32_t bits = 0; @@ -272,6 +320,7 @@ static void test_helpers(void) CHECK(edp_bl_encode(0x5A3, 1, b) == 2 && b[0] == 0x05 && b[1] == 0xA3 && edp_bl_decode(b, 1) == 0x5A3); CHECK(edp_bl_encode(0xC8, 0, b) == 1 && b[0] == 0xC8 && edp_bl_decode(b, 0) == 0xC8); test_curve(); + test_swap_double(); uint8_t caps[4] = {0x03, 0x07, 0x06, 0x10}; uint64_t x1 = edp_bl_reply_x1(EDP_BL_STATE_MODE, EDP_BL_DETAIL_BITS, caps, 0x12); CHECK(edp_bl_reply_abi(x1) == EDP_BL_ABI && edp_bl_reply_state(x1) == EDP_BL_STATE_MODE && diff --git a/tools/test-edp-backlight.py b/tools/test-edp-backlight.py index 6edaf3c..85d3b91 100644 --- a/tools/test-edp-backlight.py +++ b/tools/test-edp-backlight.py @@ -58,6 +58,13 @@ MUTATIONS = ( 'if (nits_q16 >= full) return max - 1;'), ('minimum below the floor', 'compat/edp-backlight.h', 'return configured > at_floor ? configured : at_floor;', 'return configured + 0 * at_floor;'), + ('swap NaN accepted', 'compat/edp-backlight.h', 'if (exponent == 0x7ffu || (bits >> 63 && bits << 1)) return -1;', + 'if (bits >> 63 && bits << 1) return -1;'), + ('swap negative accepted', 'compat/edp-backlight.h', + 'if (exponent == 0x7ffu || (bits >> 63 && bits << 1)) return -1;', 'if (exponent == 0x7ffu) return -1;'), + ('swap nits truncated', 'compat/edp-backlight.h', + 'v = (mantissa + (1ull << (1059 - exponent - 1))) >> (1059 - exponent);', 'v = mantissa >> (1059 - exponent);'), + ('swap cap ignored', 'compat/edp-backlight.h', '*q16 = v > cap_q16 ? cap_q16 : v;', '*q16 = v + 0 * cap_q16;'), ('no clamp', 'compat/edp-backlight.c', 'uint32_t level = edp_bl_clamp(request, s->floor, s->max);', 'uint32_t level = (uint32_t)request;'), ('mask bits dropped', 'compat/edp-backlight.c', ' << 1 | (status & EDP_INTR_MASK_BITS));', ' << 1);'), From 463c4036c5f01a9982bea0c6424ef5b18cd777d4 Mon Sep 17 00:00:00 2001 From: Acelogic <7362641+Acelogic@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:19:28 -0400 Subject: [PATCH 2/4] repo: point SnapAdreno at the swap brightness path SnapAdreno e1cf072 (branch backlight-path, not pushed): AdrenoDisplay overrides swap_submit and hands the brightness each swap carries to AdrenoBacklight as source "swap"; AdrenoBacklight no longer publishes force-brightness-control, whose writes WindowServer's sandbox refused in run next2c. The default kext is section for section unchanged. --- gpu/adreno-kext/kext | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gpu/adreno-kext/kext b/gpu/adreno-kext/kext index 9cc1e92..e1cf072 160000 --- a/gpu/adreno-kext/kext +++ b/gpu/adreno-kext/kext @@ -1 +1 @@ -Subproject commit 9cc1e92ba9ea440c8f422697179f2cde4099b9eb +Subproject commit e1cf0729c63488204cc56df9414ebf22948b9d20 From 774f13dfc89782999f09ea51bdf39a21015f271c Mon Sep 17 00:00:00 2001 From: Acelogic <7362641+Acelogic@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:19:43 -0400 Subject: [PATCH 3/4] tests: check the swap brightness path of AdrenoBacklight test-edp-backlight.py follows the kext's move from BrightnessControl writes to the swap record: - Wiring: AdrenoDisplay overrides swap_submit, reads the record at the expected offsets through edp_bl_double_to_q16, returns the family's answer and hands the value on as source "swap" from swap_begin_gated, all under ADRENO_BACKLIGHT; the service no longer publishes force-brightness-control. - UserspaceContract (26A428 host with ipsw, else SKIP): besides the BrightnessControl names the IOMFB bind needs, IOMobileFramebuffer exports IOMobileFramebufferSwapSetBrightness, QuartzCore has IOMFBDisplay::swap_brightness, and the framework's setter and swap submit still use the kext's record offsets and size. - Kext (--kext-reference): both backlight profiles override swap_submit, the default kext does not. check-iomfb-vtable.py's help names the new override. --- tools/check-iomfb-vtable.py | 2 +- tools/test-edp-backlight.py | 97 ++++++++++++++++++++++++++++--------- 2 files changed, 75 insertions(+), 24 deletions(-) diff --git a/tools/check-iomfb-vtable.py b/tools/check-iomfb-vtable.py index feea293..6e16588 100755 --- a/tools/check-iomfb-vtable.py +++ b/tools/check-iomfb-vtable.py @@ -24,7 +24,7 @@ Writes /iomfb-vtable-check.json (the full per-slot table) and exits 1 on an Usage: tools/check-iomfb-vtable.py [--out DIR] [--kc PATH] [--kdk PATH] [--no-display] [--display-define NAME[=V] ...] --display-define (alias --define) compiles AdrenoDisplay.cpp, and only it, with that define too: a profile's AdrenoDisplay set, or an opt-in override such as ADRENO_DISPLAY_TRACE_MODES=1 or ADRENO_BACKLIGHT=1 (whose -set_brightness_gated override must then pass the same slot check); without it the default build is checked. +set_brightness_gated and swap_submit overrides must then pass the same slot check); without it the default build is checked. """ import argparse import hashlib diff --git a/tools/test-edp-backlight.py b/tools/test-edp-backlight.py index 85d3b91..9b9de3e 100644 --- a/tools/test-edp-backlight.py +++ b/tools/test-edp-backlight.py @@ -13,12 +13,14 @@ safety property or bends the curve in a copy of the engine; the same program mus Wiring: probes/xnuboot.c defaults SNAP_EDP_BACKLIGHT to 0 and every monitor use sits under it; compat/edp-backlight.c is linked only through probes/run.py OPTIONAL; the call number and event number are unique; the kext's backlight code sits -under ADRENO_BACKLIGHT and publishes what BrightnessControl needs to write to it (force-brightness-control), mapping -every request through the one curve; installed-fresh appends the define last, adds the boot-args, and refuses until the +under ADRENO_BACKLIGHT, publishes what BrightnessControl reads to bind the panel's IOMFB (not +force-brightness-control), takes the brightness from the swap record and maps every request through the one curve; +installed-fresh appends the define last, adds the boot-args, and refuses until the AuxKC is pinned. -UserspaceContract (a 26A428 host with ipsw only, else SKIP): the dyld cache's BrightnessControl and IOMobileFramebuffer -still carry the names that path relies on (gpu/adreno-kext/kext/src/AdrenoBacklight.h). +UserspaceContract (a 26A428 host with ipsw only, else SKIP): the dyld cache's BrightnessControl, IOMobileFramebuffer and +QuartzCore still carry the names that path relies on, and IOMobileFramebufferSwapSetBrightness still writes the swap +record where the kext reads it (gpu/adreno-kext/kext/src/AdrenoBacklight.h). --build: xnuboot with each --qualification's flags, define absent, =0 and =1: absent == =0 == the qualification's monitor_sha256; =1 differs and fits the code budget (probes/run.py OUT_AT - CODE_AT), its headroom printed. @@ -182,19 +184,33 @@ class Wiring(unittest.TestCase): self.assertEqual(bad, []) def test_kext_brightness_path(self): - # The service carries what WindowServer's BrightnessControl needs to write brightness-nits to it - # (gpu/adreno-kext/kext/src/AdrenoBacklight.h, steps 2 and 3), and every request goes through the curve. - src = (ROOT / 'gpu/adreno-kext/kext/src/AdrenoDisplay/backlight.cpp').read_text() - force = re.search(r'kForceBrightnessControl\[(\d+)\] = \{([^}]*)\}', src) - self.assertIsNotNone(force, 'force-brightness-control bytes not found') - self.assertNotEqual(int(force.group(2).split(',')[0], 0), 0, 'its first byte must be nonzero') - for needle in ('setProperty("force-brightness-control", (void *)kForceBrightnessControl,', - 'setProperty("backlight-control", kOSBooleanTrue)', 'd->getObject("brightness-nits")', - 'putDict(params, "BrightnessMilliNits", nits)', 'setProperty("IODisplayParameters", params)', + # The service carries what WindowServer's BrightnessControl reads to bind the panel's IOMFB (and nothing that + # makes it write to the service: the sandbox refuses that), AdrenoDisplay takes the brightness from the swap + # record, and every request goes through the one curve (gpu/adreno-kext/kext/src/AdrenoBacklight.h). + kext = ROOT / 'gpu/adreno-kext/kext/src' + src = (kext / 'AdrenoDisplay/backlight.cpp').read_text() + for needle in ('setProperty("backlight-control", kOSBooleanTrue)', 'putDict(params, "BrightnessMilliNits", nits)', + 'setProperty("IODisplayParameters", params)', 'edp_bl_nits_to_level(nits_q16 < fMinQ16 ? fMinQ16 : nits_q16, fMaxNits, fFloor, fMax)', - 'fMinQ16 = edp_bl_min_nits_q16(fMinNits, fMaxNits, fFloor, fMax);'): + 'fMinQ16 = edp_bl_min_nits_q16(fMinNits, fMaxNits, fFloor, fMax);', + 'uint64_t AdrenoDisplay::swap_submit(IOMFBSwapRec *rec, IOUserClient *client, unsigned int *swapID)', + 'if (!edp_bl_double_to_q16(bits, (uint64_t)EDP_BL_NITS_LIMIT_MAX << 16, &q16))', + 'return IOMobileFramebufferLegacy::swap_submit(rec, client, swapID);', + 'fBacklight->requestNits(v & ~kSwapNitsPending, AdrenoBacklight::kSourceSwap);'): self.assertIn(needle, src) + self.assertNotIn('force-brightness-control', src) self.assertEqual(src.count('edp_bl_nits_to_level('), 1, 'one mapping for every source') + # The record's brightness lies where IOMobileFramebufferSwapSetBrightness writes it (UserspaceContract checks + # the same constants against the dyld cache). + consts = dict(re.findall(r'static constexpr size_t (kSwap\w+) = (0x[0-9a-f]+);', src)) + self.assertEqual({k: int(v, 16) for k, v in consts.items()}, + {'kSwapRecordSize': 0x6e0, 'kSwapBrightnessPresent': 0x354, 'kSwapBrightnessNits': 0x35e}) + header = (kext / 'AdrenoDisplay.h').read_text() + self.assertIn('virtual uint64_t swap_submit(IOMFBSwapRec *rec, IOUserClient *client, unsigned int *swapID) ' + 'APPLE_KEXT_OVERRIDE;', header) + bad = scope_violations(kext / 'AdrenoDisplay/presentation.cpp', r'backlight', 'ADRENO_BACKLIGHT') + self.assertEqual(bad, []) + self.assertIn('backlightSwapGated();', (kext / 'AdrenoDisplay/presentation.cpp').read_text()) def test_cycle(self): from cycle_lib import installed_fresh as f @@ -238,14 +254,21 @@ class Wiring(unittest.TestCase): DSC = Path('/System/Volumes/Preboot/Cryptexes/OS/System/Library/dyld/dyld_shared_cache_arm64e') IPSW = shutil.which('ipsw') or '/opt/homebrew/bin/ipsw' -# The names the guest's 26A428 userspace must still use for AdrenoBacklight.h's path, per image: BrightnessControl's -# match key, the keys it reads and writes, and the BrightnessControl entry points IOMobileFramebuffer resolves. +# What the guest's 26A428 userspace must still do for AdrenoBacklight.h's path. Strings per image: BrightnessControl's +# match key and the keys it reads; the BrightnessControl entry points IOMobileFramebuffer resolves to bind an IOMFB. USERSPACE_NAMES = { - 'BrightnessControl': ('IOPropertyMatch', 'backlight-control', 'force-brightness-control', 'brightness-nits', - 'IODisplayParameters', 'BrightnessMilliNits', 'LmaxProduct'), - 'IOMobileFramebuffer': ('BCBrtControlNewMonitorForAllDisplaysWithHandler', 'BCBrtControlGetCapabilities', - 'BCBrtControlSetNits'), + 'BrightnessControl': ('IOPropertyMatch', 'backlight-control', 'IODisplayParameters', 'BrightnessMilliNits', + 'LmaxProduct'), + 'IOMobileFramebuffer': ('BCBrtControlNewMonitorForAllDisplaysWithHandler', 'BCBrtControlGetCapabilities'), } +# Symbols per image: the swap brightness call and CoreAnimation's per-swap sender. +USERSPACE_SYMBOLS = { + 'IOMobileFramebuffer': ('_IOMobileFramebufferSwapSetBrightness',), + 'QuartzCore': ('__ZN2CA12WindowServer12IOMFBDisplay15swap_brightnessEv',), +} +# The framework keeps the swap record at this offset of its framebuffer object (kern_SwapBegin clears it there and +# kern_SwapEnd submits it from there as external method 5). +RECORD_BASE = 0x18 def host_build(): @@ -270,6 +293,32 @@ class UserspaceContract(unittest.TestCase): missing = [(image, n) for image, ns in USERSPACE_NAMES.items() for n in ns if (n, image) not in found] self.assertEqual(missing, []) + def test_symbols_present(self): + for image, symbols in USERSPACE_SYMBOLS.items(): + for symbol in symbols: + out = subprocess.run([IPSW, 'dsc', 'symaddr', str(DSC), symbol, '--image', image, '--no-color'], + capture_output=True, text=True, timeout=600) + self.assertRegex(out.stdout, r'0x[0-9a-f]+:.*\b%s\s+%s\b' % (re.escape(symbol), image), + f'{image}: {symbol}') + + def disass(self, symbol): + out = subprocess.run([IPSW, 'dsc', 'disass', str(DSC), '--symbol', symbol, '--symbol-image', + 'IOMobileFramebuffer', '--no-color'], capture_output=True, text=True, timeout=600) + self.assertEqual(out.returncode, 0, out.stderr[-1000:]) + return out.stdout + + def test_record_offsets(self): + # The kext's offsets, rebased onto the framework's object: the setter must address both, and the submit must + # hand the whole record (that size, from that base) to external method 5. + src = (ROOT / 'gpu/adreno-kext/kext/src/AdrenoDisplay/backlight.cpp').read_text() + consts = {k: int(v, 16) for k, v in re.findall(r'static constexpr size_t (kSwap\w+) = (0x[0-9a-f]+);', src)} + setter = self.disass('_kern_SwapSetBrightness') + for field in ('kSwapBrightnessPresent', 'kSwapBrightnessNits'): + self.assertRegex(setter, r'#%#x\b' % (RECORD_BASE + consts[field]), field) + submit = self.disass('_kern_SwapEnd') + for value in (RECORD_BASE, consts['kSwapRecordSize'], 5): + self.assertRegex(submit, r'#%#x\b' % value) + @unittest.skipUnless(ARGS.build, 'pass --build to compile the monitor (minutes)') class Build(unittest.TestCase): @@ -309,14 +358,16 @@ class Kext(unittest.TestCase): c = subprocess.run([sys.executable, str(ROOT / 'tools/macho-sections.py'), str(ref), str(out / 'default/AdrenoX2.kext/Contents/MacOS/AdrenoX2')], capture_output=True, text=True) self.assertRegex(c.stdout, r'(\d+) of \1 sections identical', c.stdout) - default = (out / 'default/AdrenoX2.kext/Contents/MacOS/AdrenoX2').read_bytes() - self.assertNotIn(b'force-brightness-control', default) + default = subprocess.run(['nm', '-m', str(out / 'default/AdrenoX2.kext/Contents/MacOS/AdrenoX2')], + capture_output=True, text=True).stdout + self.assertNotIn('__ZN13AdrenoDisplay11swap_submitEP12IOMFBSwapRecP12IOUserClientPj', default) for profile in profiles: binary = out / profile / 'AdrenoX2.kext/Contents/MacOS/AdrenoX2' nm = subprocess.run(['nm', '-m', str(binary)], capture_output=True, text=True).stdout self.assertIn('__ZN13AdrenoDisplay20set_brightness_gatedEi', nm, profile) + self.assertIn('__ZN13AdrenoDisplay11swap_submitEP12IOMFBSwapRecP12IOUserClientPj', nm, profile) self.assertIn('__ZN15AdrenoBacklight10gMetaClassE', nm, profile) - self.assertIn(b'force-brightness-control\0', binary.read_bytes(), profile) + self.assertNotIn(b'force-brightness-control', binary.read_bytes(), profile) if __name__ == '__main__': From 8f9eeb5ba28714ff14aaf3da1bbdea62a2a6a7d4 Mon Sep 17 00:00:00 2001 From: Acelogic <7362641+Acelogic@users.noreply.github.com> Date: Sat, 24 Oct 2026 20:19:43 -0400 Subject: [PATCH 4/4] docs: say --edp-backlight brightness arrives in the swaps RUNNING.md no longer says BrightnessControl writes brightness-nits to the service (WindowServer's sandbox refuses that): the service lets WindowServer bind the panel's framebuffer, CoreBrightness's nits arrive in the swaps as on a DCP Mac, and the kext logs "SET from swap". --- docs/RUNNING.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/RUNNING.md b/docs/RUNNING.md index c614994..a926069 100644 --- a/docs/RUNNING.md +++ b/docs/RUNNING.md @@ -202,10 +202,11 @@ DPCD `0x721`-`0x723`, clamping the level to between 2% of full scale and the panel's maximum, and stopping for the boot on any failure with the brightness left as it was (`compat/edp-backlight.h`, event `EV_EDP_BACKLIGHT`). The guest gets the AuxKC pinned as `INPUT_AUXKC_DESKTOP_BACKLIGHT`, whose AdrenoX2 has the -`AdrenoBacklight` service, and the boot-arg `adreno_backlight=1`. CoreBrightness -hands its nits to WindowServer, whose BrightnessControl writes them to the -service as `brightness-nits` because the service carries -`force-brightness-control` (the path and its evidence are in the kext's +`AdrenoBacklight` service, and the boot-arg `adreno_backlight=1`. The service +lets WindowServer bind the panel's framebuffer to a brightness control; +CoreBrightness's nits then arrive in the swaps WindowServer submits (as on a +DCP Mac), and AdrenoDisplay hands each new value to the service, which logs +`SET from swap` (the path and its evidence are in the kext's `src/AdrenoBacklight.h`). The service maps nits to a level proportionally, the top level being `adreno_backlight_max_nits` (default 500) and the darkest request `adreno_backlight_min_nits` (default 5, raised to the 2% floor's ~10