repo: merge the eDP panel brightness (brightness)

Merge feature/brightness (7da9adf..f189da7) onto the display-modes
merge.

tools/check-iomfb-vtable.py: both branches added an AdrenoDisplay
define option (display-modes --display-define, applied to
AdrenoDisplay.cpp only; brightness --define, applied to every .cpp).
Keep one implementation: --display-define, with --define as an alias
of the same list, compiles AdrenoDisplay.cpp alone with the defines,
which is what both branches' help text promises and what
test-adreno-display-trace.py passes.

gpu/adreno-kext/kext: SnapAdreno next 53c0054, the merge of
feature/display-modes and feature/brightness.
This commit is contained in:
Acelogic committed 2026-10-24 03:46:35 -04:00
commit 5b35148492
19 files changed
+1406 -7

No files matched your search

+5
View File
@@ -189,6 +189,11 @@
* kext takes it as ON only with its own boot-arg adreno_coherent_force=1, and says so loudly either way. */
#define ADRENO_COHERENT_DETAIL_FORCED 0x2000u
/* ---- Panel brightness: ADRENO_GUEST_BACKLIGHT (monitor SNAP_EDP_BACKLIGHT) ------------------------------------------
* QUERY and SET of the panel's DPCD backlight level over the eDP AUX channel; ABI, states and errors in
* compat/edp-backlight.h. No ADT property: discovery is QUERY, which a monitor without it refuses (ADRENO_E_PARAMS). */
#define ADRENO_GUEST_BACKLIGHT 0x0001000C
/* ---- Ring wrap (design S1; shared by FENCE, FILL32 and IB) ----------------------------------------------
* The ring is ADRENO_RING_DWORDS(CP_RB_CNTL.BUFSZ) dwords (q1n1 writes BUFSZ ADRENO_RING_BUFSZ = 8192 dwords);
* the monitor adopts it only when p->ring_dwords matches. Submissions stay synchronous: an emit starts with
+193
View File
@@ -0,0 +1,193 @@
/* SPDX-License-Identifier: MIT */
/*
* ADRENO_GUEST_BACKLIGHT's engine (compat/edp-backlight.h): one native AUX transaction at a time on the eDP
* controller the firmware left running, under the monitor's service lock. Linked only into a -DSNAP_EDP_BACKLIGHT=1
* monitor (probes/run.py OPTIONAL) and into tools/test-edp-backlight.c. Written for size: the monitor has about 3 KiB
* of code budget left with the desktop flags (probes/run.py OUT_AT - CODE_AT).
*
* Failure behaviour: every refusal leaves the panel as it was. A probe that finds the clocks, the power domain or
* the AUX controller off reads nothing else and never enables anything; a panel that does not report AUX-settable
* brightness is never written. The state becomes FAILED, and nothing more reaches the AUX channel this boot, after
* EDP_BL_MAX_FAILURES failed SETs in a row, one read-back that differs from what was written, one transaction that
* never completes (it may still be in flight: nothing may follow it into the FIFO), or one access outside the
* allow-list. The controller is never reset or re-enabled from here: that would be a write outside the backlight set.
*/
#include "edp-backlight.h"
#define AHB(off) (EDP_DP3_AHB_PA + (off))
#define AUX(off) (EDP_DP3_AUX_PA + (off))
/* MMIO through the allow-list. A refusal is sticky (s->refused): every later access in the call is skipped, the
* transaction in progress fails, and edp_backlight_call stops the engine for the boot. Reads that did not happen
* return 0. */
__attribute__((noinline)) static uint32_t rd(struct edp_backlight_state *s, uint64_t pa)
{
uint32_t v = 0;
if (!s->refused && (!edp_bl_mmio_read_ok(pa) || s->io->read32(s->io->ctx, pa, &v))) s->refused = 1;
return s->refused ? 0 : v;
}
__attribute__((noinline)) static void wr(struct edp_backlight_state *s, uint64_t pa, uint32_t v)
{
if (!s->refused && (!edp_bl_mmio_write_ok(pa) || s->io->write32(s->io->ctx, pa, v))) s->refused = 1;
}
/* Acknowledge the AUX statuses in status. INTR_STATUS also holds the HPD and PLL statuses (left alone) and every
* interrupt's mask bit: a 0 written to a mask bit would mask that interrupt, so each is written back as read. */
static void aux_ack(struct edp_backlight_state *s, uint32_t status)
{
if (status & EDP_INTR_AUX_ALL)
wr(s, AHB(EDP_AHB_INTR_STATUS), (status & EDP_INTR_AUX_ALL) << 1 | (status & EDP_INTR_MASK_BITS));
}
/* One native DPCD transaction from the allow-list (Linux dp_aux.c's register sequence): 0, or -1 with
* s->detail = EDP_BL_DETAIL_AUX_IO. A transaction that never completes sets s->timed_out. Not retried. */
__attribute__((noinline)) static int aux(struct edp_backlight_state *s, int read, uint32_t addr, uint8_t *buf,
uint32_t len)
{
uint8_t hdr[4];
uint32_t st, v;
if (!edp_bl_dpcd_ok(addr, len, read)) s->refused = 1;
edp_aux_header(addr, len, read, hdr);
for (uint32_t i = 0; i < 4 + (read ? 0 : len); i++)
wr(s, AUX(EDP_AUX_DATA), (uint32_t)(i < 4 ? hdr[i] : buf[i - 4]) << 8 | (i ? 0 : EDP_AUX_DATA_INDEX_WRITE));
wr(s, AUX(EDP_AUX_TRANS_CTRL), 0);
rd(s, AUX(EDP_AUX_PHY_INTR_STATUS));
wr(s, AUX(EDP_AUX_PHY_INTR_CLEAR), EDP_AUX_PHY_CLEAR_A);
wr(s, AUX(EDP_AUX_PHY_INTR_CLEAR), EDP_AUX_PHY_CLEAR_B);
wr(s, AUX(EDP_AUX_PHY_INTR_CLEAR), 0);
wr(s, AUX(EDP_AUX_TRANS_CTRL), EDP_AUX_TRANS_GO);
if (s->refused) goto fail;
s->transactions++;
uint64_t start = s->io->counter(s->io->ctx), budget = s->io->hz / EDP_BL_AUX_BUDGET_DIV;
do st = rd(s, AHB(EDP_AHB_INTR_STATUS));
while (!(st & EDP_INTR_AUX_ALL) && !s->refused && s->io->counter(s->io->ctx) - start < budget);
s->last_intr = st;
if (!(st & EDP_INTR_AUX_ALL)) { s->timed_out = 1; goto fail; }
aux_ack(s, st);
if ((st & EDP_INTR_AUX_ERRORS) || !(st & EDP_INTR_AUX_DONE)) goto fail;
if (read) {
/* The reply: GO cleared, the FIFO index reset for reading; its first byte is the reply command, then one byte
* per read address, each carrying its index (dp_aux.c cmd_fifo_rx). A short reply fails the transaction. */
wr(s, AUX(EDP_AUX_TRANS_CTRL), rd(s, AUX(EDP_AUX_TRANS_CTRL)) & ~EDP_AUX_TRANS_GO);
wr(s, AUX(EDP_AUX_DATA), EDP_AUX_DATA_INDEX_WRITE | EDP_AUX_DATA_READ);
rd(s, AUX(EDP_AUX_DATA));
for (uint32_t i = 0; i < len; i++) {
v = rd(s, AUX(EDP_AUX_DATA));
if (((v >> 16) & 0xff) != i) goto fail;
buf[i] = (uint8_t)(v >> 8);
}
}
if (!s->refused) return 0;
fail:
s->errors++;
s->detail = EDP_BL_DETAIL_AUX_IO;
return -1;
}
__attribute__((noinline)) static int stop(struct edp_backlight_state *s, uint32_t state, uint32_t detail)
{
s->state = state;
s->detail = detail;
return 0;
}
/* Reads only: dispcc and the AUX controller's enable bit (each must already be on: nothing is enabled from here),
* then three DPCD reads. */
static int probe(struct edp_backlight_state *s)
{
static const struct { uint32_t pa, mask, want, detail; } on[] = {
{(uint32_t)(DPU_DISPCC_PA + DPU_DISPCC_MDSS_AHB_CBCR), DPU_CBCR_CLK_OFF, 0, EDP_BL_DETAIL_CLOCK},
{(uint32_t)(DPU_DISPCC_PA + DPU_DISPCC_MDSS_CORE_GDSCR), EDP_GDSC_PWR_ON, EDP_GDSC_PWR_ON, EDP_BL_DETAIL_POWER},
{(uint32_t)(DPU_DISPCC_PA + EDP_DISPCC_DPTX3_AUX_CBCR), DPU_CBCR_CLK_OFF, 0, EDP_BL_DETAIL_AUX_CLOCK},
{(uint32_t)AUX(EDP_AUX_CTRL), EDP_AUX_CTRL_ENABLE, EDP_AUX_CTRL_ENABLE, EDP_BL_DETAIL_AUX_OFF}};
uint32_t bits;
uint8_t b[6];
if (!s->io->counter || s->io->hz < 1000) return stop(s, EDP_BL_STATE_FAILED, EDP_BL_DETAIL_MMIO);
for (uint32_t i = 0; i < sizeof on / sizeof on[0]; i++)
if ((rd(s, on[i].pa) & on[i].mask) != on[i].want || s->refused)
return stop(s, EDP_BL_STATE_FAILED, on[i].detail);
aux_ack(s, rd(s, AHB(EDP_AHB_INTR_STATUS))); /* statuses the firmware left behind */
if (aux(s, 1, DPCD_EDP_CONFIGURATION_CAP, b, 1)) return stop(s, EDP_BL_STATE_FAILED, s->detail);
s->config_cap = b[0];
if (!(b[0] & DPCD_DISPLAY_CONTROL_CAPABLE)) return stop(s, EDP_BL_STATE_UNSUPPORTED, EDP_BL_DETAIL_NO_CONTROL);
if (aux(s, 1, DPCD_EDP_DPCD_REV, s->caps, 4)) return stop(s, EDP_BL_STATE_FAILED, s->detail);
if (!(s->caps[2] & DPCD_EDP_BL_AUX_SET_CAP)) return stop(s, EDP_BL_STATE_UNSUPPORTED, EDP_BL_DETAIL_NO_AUX_SET);
if (aux(s, 1, DPCD_EDP_BL_MODE_SET, b, 6)) return stop(s, EDP_BL_STATE_FAILED, s->detail);
s->mode_reg = b[0];
s->lsb = (s->caps[2] & DPCD_EDP_BL_BYTE_COUNT) ? 1 : 0;
if (edp_bl_bits(b[3], b[4], b[5], s->lsb, &bits)) return stop(s, EDP_BL_STATE_UNSUPPORTED, EDP_BL_DETAIL_BITS);
s->bits = (uint8_t)bits;
s->max = edp_bl_max_level(bits);
s->floor = edp_bl_floor_level(s->max);
s->current = edp_bl_decode(b + 1, s->lsb);
return stop(s, (b[0] & DPCD_EDP_BL_MODE_MASK) == DPCD_EDP_BL_MODE_DPCD ? EDP_BL_STATE_READY : EDP_BL_STATE_MODE,
EDP_BL_DETAIL_NONE);
}
/* Write len bytes at addr and read them back. 0, or EDP_BL_E_IO / EDP_BL_E_VERIFY. */
static int write_verified(struct edp_backlight_state *s, uint32_t addr, uint8_t *bytes, uint32_t len)
{
uint8_t back[2] = {0, 0};
if (aux(s, 0, addr, bytes, len) || aux(s, 1, addr, back, len)) return EDP_BL_E_IO;
return back[0] == bytes[0] && (len < 2 || back[1] == bytes[1]) ? 0 : EDP_BL_E_VERIFY;
}
static int set(struct edp_backlight_state *s, uint64_t request, uint64_t flags, uint32_t *clamped)
{
uint8_t bytes[2];
if (s->state != EDP_BL_STATE_READY &&
(s->state != EDP_BL_STATE_MODE || !(flags & EDP_BL_SET_MODE))) return EDP_BL_E_STATE;
uint32_t level = edp_bl_clamp(request, s->floor, s->max);
*clamped = level != request;
s->sets++;
if (s->state == EDP_BL_STATE_READY && level == s->current) return 0;
int e = write_verified(s, DPCD_EDP_BL_BRIGHTNESS_MSB, bytes, edp_bl_encode(level, s->lsb, bytes));
if (!e && s->state == EDP_BL_STATE_MODE) {
/* Level first, then the mode: a panel still in PWM mode ignores 0x722, so the switch lands on this level.
* Only the two mode bits change. On failure the original mode goes back once (not after a timeout), and
* whatever happens nothing more is sent. */
uint8_t mode = (uint8_t)((s->mode_reg & ~DPCD_EDP_BL_MODE_MASK) | DPCD_EDP_BL_MODE_DPCD);
e = write_verified(s, DPCD_EDP_BL_MODE_SET, &mode, 1);
if (e) {
if (!s->timed_out) aux(s, 0, DPCD_EDP_BL_MODE_SET, &s->mode_reg, 1);
stop(s, EDP_BL_STATE_FAILED, EDP_BL_DETAIL_MODE);
return e;
}
s->mode_reg = mode;
s->state = EDP_BL_STATE_READY;
}
if (e == EDP_BL_E_VERIFY) stop(s, EDP_BL_STATE_FAILED, EDP_BL_DETAIL_VERIFY);
else if (e && (++s->failures >= EDP_BL_MAX_FAILURES || s->timed_out)) stop(s, EDP_BL_STATE_FAILED, s->detail);
if (e) return e;
s->failures = 0;
s->detail = EDP_BL_DETAIL_NONE;
s->current = level;
return 0;
}
int edp_backlight_call(struct edp_backlight_state *s, const struct edp_backlight_io *io, uint64_t r[4])
{
uint64_t op = r[1], arg = r[2], flags = r[3];
uint32_t clamped = 0;
int e = 0;
s->io = io;
s->calls++;
if ((op == EDP_BL_QUERY && (arg || flags)) || (op == EDP_BL_SET && (flags & ~(uint64_t)EDP_BL_SET_MODE)) ||
op > EDP_BL_SET)
e = EDP_BL_E_OP;
else {
if (s->state == EDP_BL_STATE_UNPROBED) probe(s);
if (op == EDP_BL_SET) e = set(s, arg, flags, &clamped);
}
if (s->refused) { /* an access outside the allow-list was attempted: stop for the boot */
stop(s, EDP_BL_STATE_FAILED, EDP_BL_DETAIL_MMIO);
if (op == EDP_BL_SET && !e) e = EDP_BL_E_IO;
}
s->io = 0;
r[0] = (uint64_t)(int64_t)e;
r[1] = edp_bl_reply_x1(s->state, s->detail, s->caps, s->mode_reg);
r[2] = s->floor | (uint64_t)s->max << 32;
r[3] = s->current | (uint64_t)s->bits << 32 | (uint64_t)s->lsb << 40 | (uint64_t)clamped << 41 |
(uint64_t)s->failures << 48;
return 0;
}
+278
View File
@@ -0,0 +1,278 @@
/* SPDX-License-Identifier: MIT */
/*
* The A16 panel's brightness: VESA eDP DPCD backlight registers written over the eDP controller's AUX channel. The
* EL2 monitor owns the AUX channel (-DSNAP_EDP_BACKLIGHT=1, compat/edp-backlight.c); the AdrenoX2 kext's
* AdrenoBacklight (-DADRENO_BACKLIGHT=1) asks it for a level with ADRENO_GUEST_BACKLIGHT. The guest never maps any
* of these registers. Header-only and freestanding: constants, the call ABI, and the pure helpers the monitor, the
* kext (C++) and the host test (tools/test-edp-backlight.c) share. Nothing here touches hardware.
*
* Evidence (AGENTS.md labels; the captures stay private, only facts are cited):
* - VERIFIED, Windows capture pnp-present.json: the internal panel enumerates as DISPLAY\SDC422C (Samsung, 0x422C).
* - VERIFIED, the A16 DSDT's \_SB.GPU0._ROM panel table for ManufactureID 0x834C / ProductCode 0x422C: eDP
* (InterfaceType 18), BacklightType 8, no BacklightPmic* key, a DPCD caps override whose byte 0x00D is 0x0B
* (bit 3: DPCD display control capable), BrightnessRangeLevel0 "5000 500000 1000 2" (INFERRED: millinits min,
* max and step). The DSDT has no _BCM, _BCL or _BQC anywhere.
* - VERIFIED, Linux glymur.dtsi: the eDP controller is mdss_dp3, regions "ahb" 0x0af6c000 + 0x200 and "aux"
* 0x0af6c200 + 0x200, clocked by DISP_CC_MDSS_AHB_CLK and DISP_CC_MDSS_DPTX3_AUX_CLK (dispcc-glymur.c CBCRs
* 0x80b0 and 0x80a8). glymur-crd.dts's Samsung OLED (samsung,atna33xc20) has no backlight node: panel drivers
* of that kind control it with drm_panel_dp_aux_backlight (DPCD). No glymur PMIC node has a WLED or LPG.
* - INFERRED: BacklightType 8 is that DPCD control. The monitor does not assume it: it reads the panel's own
* capability bytes first and refuses unless they say the brightness is AUX-settable.
* Register offsets and bit meanings are facts from Linux drivers/gpu/drm/msm/dp/dp_reg.h, dp_aux.c, dp_ctrl.c and
* include/drm/display/drm_dp.h (VESA eDP 1.4b section 3.3.10); no Linux code is reused.
*/
#ifndef SNAP_EDP_BACKLIGHT_H
#define SNAP_EDP_BACKLIGHT_H
#include <stdint.h>
#include "dpu-intf.h" /* DPU_DISPCC_PA, DPU_DISPCC_MDSS_AHB_CBCR, DPU_DISPCC_MDSS_CORE_GDSCR, DPU_CBCR_CLK_OFF */
/* ---- The eDP controller (mdss_dp3) ---------------------------------------------------------------------------- */
#define EDP_DP3_AHB_PA 0x0AF6C000ULL /* glymur.dtsi mdss_dp3 reg[0] "ahb", 0x200 */
#define EDP_DP3_AUX_PA 0x0AF6C200ULL /* glymur.dtsi mdss_dp3 reg[1] "aux", 0x200 */
#define EDP_DISPCC_DPTX3_AUX_CBCR 0x80A8u /* dispcc-glymur.c disp_cc_mdss_dptx3_aux_clk */
#define EDP_GDSC_PWR_ON (1u << 31)
#define EDP_AHB_INTR_STATUS 0x020u /* status at bits 0,3..27; its ack at status << 1, its mask at << 2 */
#define EDP_AUX_HPD_INT_STATUS 0x004u /* bits 31:29 HPD state (read only by the survey) */
#define EDP_AUX_CTRL 0x030u /* bit 0 enable: the monitor never writes it */
#define EDP_AUX_DATA 0x034u /* FIFO: byte at 15:8, index at 23:16, bit 31 index write, bit 0 read */
#define EDP_AUX_TRANS_CTRL 0x038u /* bit 9 GO */
#define EDP_AUX_PHY_INTR_CLEAR 0x04Cu
#define EDP_AUX_PHY_INTR_STATUS 0x0BCu
#define EDP_AUX_CTRL_ENABLE (1u << 0)
#define EDP_AUX_DATA_READ (1u << 0)
#define EDP_AUX_DATA_INDEX_WRITE (1u << 31)
#define EDP_AUX_TRANS_GO (1u << 9)
#define EDP_INTR_AUX_DONE (1u << 3)
#define EDP_INTR_AUX_ERRORS ((1u << 6) | (1u << 9) | (1u << 12) | (1u << 15) | (1u << 18) | (1u << 21) | \
(1u << 27)) /* wrong address, timeout, NACK/DEFER, data count, I2C x2, PHY */
#define EDP_INTR_AUX_ALL (EDP_INTR_AUX_DONE | EDP_INTR_AUX_ERRORS)
#define EDP_INTR_STATUS_BITS 0x09249249u /* every status position, 0..27 step 3 */
#define EDP_INTR_MASK_BITS (EDP_INTR_STATUS_BITS << 2)
/* The PHY AUX interrupt clear sequence the controller expects before every GO (dp_aux.c clear_hw_interrupts). */
#define EDP_AUX_PHY_CLEAR_A 0x1Fu
#define EDP_AUX_PHY_CLEAR_B 0x9Fu
#define EDP_AUX_MAX_BYTES 16u /* one native AUX transaction */
/* ---- DPCD (drm_dp.h; eDP display control registers, VESA eDP 1.4b 3.3.10) ------------------------------------- */
#define DPCD_EDP_CONFIGURATION_CAP 0x00Du
#define DPCD_DISPLAY_CONTROL_CAPABLE (1u << 3)
#define DPCD_EDP_DPCD_REV 0x700u /* read as four bytes: rev, general cap 1, adjustment cap, general cap 2 */
#define DPCD_EDP_BL_AUX_SET_CAP (1u << 1) /* 0x702 */
#define DPCD_EDP_BL_BYTE_COUNT (1u << 2) /* 0x702: 0x723 (LSB) is used */
#define DPCD_EDP_BL_MODE_SET 0x721u /* read as six bytes: mode, MSB, LSB, bit count, its min and max */
#define DPCD_EDP_BL_MODE_MASK 0x03u
#define DPCD_EDP_BL_MODE_DPCD 0x02u
#define DPCD_EDP_BL_BRIGHTNESS_MSB 0x722u
#define DPCD_EDP_PWMGEN_BITS_MASK 0x1Fu
/* ---- Policy ------------------------------------------------------------------------------------------------------ */
/* The lowest level the monitor writes, in thousandths of full scale (at least level 1). An OLED's DPCD level need
* not be linear in luminance: until a hardware run measures the curve, 10% keeps the panel visibly lit even if the
* TCON applies a 2.2 gamma (about 0.6% of peak). The guest cannot lower it; the top is the panel's own maximum. */
#define EDP_BL_FLOOR_PERMILLE 100u
#define EDP_BL_MAX_FAILURES 3u /* consecutive failed SETs before the monitor stops for the boot */
#define EDP_BL_AUX_BUDGET_DIV 200u /* one transaction's completion budget: hz / 200 = 5 ms */
/* The kext's nominal range for CoreBrightness, from the panel table's millinit range (INFERRED); boot-args may
* narrow it within [EDP_BL_NITS_LIMIT_MIN, EDP_BL_NITS_LIMIT_MAX]. */
#define EDP_BL_NITS_MIN_DEFAULT 5u
#define EDP_BL_NITS_MAX_DEFAULT 500u
#define EDP_BL_NITS_LIMIT_MIN 1u
#define EDP_BL_NITS_LIMIT_MAX 1000u
#define EDP_BL_SLIDER_MAX 65536u /* IODisplayParameters "brightness" scale (AppleARMBacklight's, OBSERVED) */
/* ---- ADRENO_GUEST_BACKLIGHT (HVC #ADRENO_GUEST_HVC, compat/adreno-guest.h) --------------------------------------
* Admitted only at EL1 for an HVC in the loaded AuxKC's bytes (early_stop_guest_caller), under the service lock. No
* ADT property: discovery is QUERY, which a monitor without SNAP_EDP_BACKLIGHT refuses (its fence handler:
* ADRENO_E_PARAMS), so an older monitor leaves the kext without a backlight service.
* QUERY In: x1 = EDP_BL_QUERY, x2 = x3 = 0. The first QUERY or SET probes: clocks and power (reads), AUX enable
* (read), then AUX reads of DPCD 0x00D, 0x700..0x703 and 0x721..0x726. No DPCD write.
* SET In: x1 = EDP_BL_SET, x2 = level, x3 = flags (EDP_BL_SET_MODE or 0). The level is clamped into
* [floor, max] and written to 0x722 (and 0x723 when the panel uses it) in one AUX write, then read back.
* In state MODE the call also needs EDP_BL_SET_MODE: the level is written first (a panel in another mode
* ignores it), then 0x721's two mode bits become DPCD with every other bit kept, and both are read back.
* Out (both): x0 = 0 or EDP_BL_E_*; x1 = edp_bl_reply_x1() (ABI, state, detail, DPCD 0x700..0x703, mode byte);
* x2 = floor | max << 32; x3 = the panel's level (unchanged by a failed SET) | bits << 32 | lsb << 40 |
* clamped << 41 | consecutive failures << 48.
* DPCD writes are only 0x721 (mode bits), 0x722 and 0x723; MMIO writes only AUX_DATA, AUX_TRANS_CTRL,
* PHY_AUX_INTERRUPT_CLEAR and INTR_STATUS (AUX acks, every mask bit written back as read). */
#define EDP_BL_ABI 1u
#define EDP_BL_QUERY 0u
#define EDP_BL_SET 1u
#define EDP_BL_SET_MODE 1u /* SET flag: allow the DPCD mode switch */
#define EDP_BL_STATE_UNPROBED 0u
#define EDP_BL_STATE_READY 1u /* DPCD mode: SET writes the level */
#define EDP_BL_STATE_MODE 2u /* capable but in another mode: SET needs EDP_BL_SET_MODE */
#define EDP_BL_STATE_FAILED 3u /* stopped for this boot: nothing more is sent to the panel */
#define EDP_BL_STATE_UNSUPPORTED 4u /* the hardware does not offer AUX brightness: nothing is sent */
#define EDP_BL_DETAIL_NONE 0u
#define EDP_BL_DETAIL_CLOCK 1u /* MDSS AHB clock off */
#define EDP_BL_DETAIL_POWER 2u /* MDSS core GDSC off */
#define EDP_BL_DETAIL_AUX_CLOCK 3u /* DPTX3 AUX clock off */
#define EDP_BL_DETAIL_AUX_OFF 4u /* AUX controller disabled */
#define EDP_BL_DETAIL_AUX_IO 5u /* a transaction failed or timed out */
#define EDP_BL_DETAIL_NO_CONTROL 6u /* DPCD 0x00D: not display-control capable */
#define EDP_BL_DETAIL_NO_AUX_SET 7u /* DPCD 0x702: brightness not AUX-settable */
#define EDP_BL_DETAIL_BITS 8u /* DPCD 0x724..0x726 or byte count inconsistent */
#define EDP_BL_DETAIL_VERIFY 9u /* a read-back differs from what was written */
#define EDP_BL_DETAIL_MODE 10u /* the mode switch did not take */
#define EDP_BL_DETAIL_MMIO 11u /* an access outside the allow-list was refused */
#define EDP_BL_E_OP (-101) /* unknown sub-op, or reserved bits set: nothing done */
#define EDP_BL_E_ORIGIN (-102) /* not EL1, or the HVC is not in the AuxKC */
#define EDP_BL_E_STATE (-103) /* FAILED, UNSUPPORTED, or MODE without EDP_BL_SET_MODE */
#define EDP_BL_E_IO (-104) /* an AUX transaction failed; the level is the one before */
#define EDP_BL_E_VERIFY (-105) /* a read-back differed: the monitor stopped (FAILED) */
static inline uint64_t edp_bl_reply_x1(uint32_t state, uint32_t detail, const uint8_t caps[4], uint8_t mode_reg)
{
return (uint64_t)EDP_BL_ABI | (uint64_t)(state & 0xff) << 8 | (uint64_t)(detail & 0xff) << 16 |
(uint64_t)caps[0] << 24 | (uint64_t)caps[1] << 32 | (uint64_t)caps[2] << 40 | (uint64_t)caps[3] << 48 |
(uint64_t)mode_reg << 56;
}
static inline uint32_t edp_bl_reply_abi(uint64_t x1) { return (uint32_t)(x1 & 0xff); }
static inline uint32_t edp_bl_reply_state(uint64_t x1) { return (uint32_t)((x1 >> 8) & 0xff); }
static inline uint32_t edp_bl_reply_detail(uint64_t x1) { return (uint32_t)((x1 >> 16) & 0xff); }
/* ---- Pure helpers ------------------------------------------------------------------------------------------------ */
/* The four AUX request header bytes (DP 1.4 2.7.1): command and address 19:16, address 15:8, 7:0, length - 1. */
static inline void edp_aux_header(uint32_t addr, uint32_t len, int read, uint8_t hdr[4])
{
hdr[0] = (uint8_t)(((addr >> 16) & 0x0f) | (read ? 0x10 : 0x00));
hdr[1] = (uint8_t)(addr >> 8);
hdr[2] = (uint8_t)addr;
hdr[3] = (uint8_t)(len - 1);
}
/* The effective PWMGEN bit count (eDP 1.4b 3.3.10.2: a count outside [min, max] is taken as the nearer bound).
* 0 with *bits set, or -1: no usable count (min > max, 0, above 16, or above 8 without the LSB register). */
static inline int edp_bl_bits(uint8_t count, uint8_t cap_min, uint8_t cap_max, int lsb, uint32_t *bits)
{
uint32_t n = count & DPCD_EDP_PWMGEN_BITS_MASK, lo = cap_min & DPCD_EDP_PWMGEN_BITS_MASK,
hi = cap_max & DPCD_EDP_PWMGEN_BITS_MASK;
if (lo > hi) return -1;
if (n < lo) n = lo;
if (n > hi) n = hi;
if (!n || n > 16 || (!lsb && n > 8)) return -1;
*bits = n;
return 0;
}
static inline uint32_t edp_bl_max_level(uint32_t bits) { return (1u << bits) - 1u; }
static inline uint32_t edp_bl_floor_level(uint32_t max)
{
uint32_t f = (uint32_t)(((uint64_t)max * EDP_BL_FLOOR_PERMILLE + 999u) / 1000u);
return f ? f : 1u;
}
static inline uint32_t edp_bl_clamp(uint64_t level, uint32_t floor, uint32_t max)
{
return level < floor ? floor : level > max ? max : (uint32_t)level;
}
/* 0x722 holds the MSB, 0x723 the LSB when the panel uses it (BYTE_COUNT); otherwise the level is one byte. */
static inline uint32_t edp_bl_encode(uint32_t level, int lsb, uint8_t out[2])
{
if (!lsb) { out[0] = (uint8_t)level; out[1] = 0; return 1; }
out[0] = (uint8_t)(level >> 8);
out[1] = (uint8_t)level;
return 2;
}
static inline uint32_t edp_bl_decode(const uint8_t in[2], int lsb)
{
return lsb ? (uint32_t)in[0] << 8 | in[1] : in[0];
}
/* Nits (16.16 fixed point, as CoreBrightness's "brightness-nits") to a level: linear from [min_nits, max_nits] onto
* [floor, max], clamped at both ends. The panel's luminance curve is not known (INFERRED linear); the floor holds
* whatever is asked. */
static inline uint32_t edp_bl_nits_to_level(uint64_t nits_q16, uint32_t min_nits, uint32_t max_nits,
uint32_t floor, uint32_t max)
{
uint64_t lo = (uint64_t)min_nits << 16, hi = (uint64_t)max_nits << 16;
if (hi <= lo || max <= floor) return floor;
if (nits_q16 <= lo) return floor;
if (nits_q16 >= hi) return max;
return floor + (uint32_t)(((nits_q16 - lo) * (max - floor) + (hi - lo) / 2) / (hi - lo));
}
static inline uint64_t edp_bl_level_to_nits(uint32_t level, uint32_t min_nits, uint32_t max_nits,
uint32_t floor, uint32_t max)
{
uint64_t lo = (uint64_t)min_nits << 16, hi = (uint64_t)max_nits << 16;
if (hi <= lo || max <= floor || level <= floor) return lo;
if (level >= max) return hi;
return lo + ((uint64_t)(level - floor) * (hi - lo) + (max - floor) / 2) / (max - floor);
}
/* IODisplayParameters "brightness" (0..EDP_BL_SLIDER_MAX) as nits, linear between the nominal ends. */
static inline uint64_t edp_bl_slider_to_nits(uint64_t slider, uint32_t min_nits, uint32_t max_nits)
{
uint64_t lo = (uint64_t)min_nits << 16, hi = (uint64_t)max_nits << 16;
if (slider > EDP_BL_SLIDER_MAX) slider = EDP_BL_SLIDER_MAX;
return hi <= lo ? lo : lo + ((hi - lo) * slider + EDP_BL_SLIDER_MAX / 2) / EDP_BL_SLIDER_MAX;
}
static inline uint32_t edp_bl_nits_to_slider(uint64_t nits_q16, uint32_t min_nits, uint32_t max_nits)
{
uint64_t lo = (uint64_t)min_nits << 16, hi = (uint64_t)max_nits << 16;
if (hi <= lo || nits_q16 <= lo) return 0;
if (nits_q16 >= hi) return EDP_BL_SLIDER_MAX;
return (uint32_t)(((nits_q16 - lo) * EDP_BL_SLIDER_MAX + (hi - lo) / 2) / (hi - lo));
}
/* ---- 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)
{
static const uint32_t ok[] = {
(uint32_t)(DPU_DISPCC_PA + DPU_DISPCC_MDSS_AHB_CBCR), (uint32_t)(DPU_DISPCC_PA + DPU_DISPCC_MDSS_CORE_GDSCR),
(uint32_t)(DPU_DISPCC_PA + EDP_DISPCC_DPTX3_AUX_CBCR), (uint32_t)(EDP_DP3_AHB_PA + EDP_AHB_INTR_STATUS),
(uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_CTRL), (uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_DATA),
(uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_TRANS_CTRL), (uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_PHY_INTR_STATUS)};
for (uint32_t i = 0; i < sizeof ok / sizeof ok[0]; i++)
if (pa == ok[i]) return 1;
return 0;
}
static inline int edp_bl_mmio_write_ok(uint64_t pa)
{
static const uint32_t ok[] = {
(uint32_t)(EDP_DP3_AHB_PA + EDP_AHB_INTR_STATUS), (uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_DATA),
(uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_TRANS_CTRL), (uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_PHY_INTR_CLEAR)};
for (uint32_t i = 0; i < sizeof ok / sizeof ok[0]; i++)
if (pa == ok[i]) return 1;
return 0;
}
static inline int edp_bl_dpcd_ok(uint32_t addr, uint32_t len, int read)
{
/* addr << 8 | len; the writes are the first three */
static const uint32_t ok[] = {
DPCD_EDP_BL_MODE_SET << 8 | 1, DPCD_EDP_BL_BRIGHTNESS_MSB << 8 | 1, DPCD_EDP_BL_BRIGHTNESS_MSB << 8 | 2,
DPCD_EDP_BL_MODE_SET << 8 | 6, DPCD_EDP_DPCD_REV << 8 | 4, DPCD_EDP_CONFIGURATION_CAP << 8 | 1};
for (uint32_t i = 0; i < (read ? sizeof ok / sizeof ok[0] : 3u); i++)
if (addr << 8 == (ok[i] & ~0xffu) && len == (ok[i] & 0xffu)) return 1;
return 0;
}
static inline int edp_bl_dpcd_read_ok(uint32_t addr, uint32_t len) { return edp_bl_dpcd_ok(addr, len, 1); }
static inline int edp_bl_dpcd_write_ok(uint32_t addr, uint32_t len) { return edp_bl_dpcd_ok(addr, len, 0); }
/* ---- The monitor's engine (compat/edp-backlight.c) --------------------------------------------------------------- */
struct edp_backlight_io {
void *ctx;
int (*read32)(void *ctx, uint64_t pa, uint32_t *value); /* nonzero: refused, nothing read */
int (*write32)(void *ctx, uint64_t pa, uint32_t value); /* nonzero: refused, nothing written */
uint64_t (*counter)(void *ctx);
uint64_t hz;
};
/* One zeroed monitor page (setup.c); the state is also the record a report can read. */
struct edp_backlight_state {
uint32_t state, detail;
uint8_t caps[4]; /* DPCD 0x700..0x703 */
uint8_t mode_reg, bits, lsb, config_cap;
uint32_t floor, max, current, failures;
uint32_t refused; /* an access outside the allow-list was attempted (sticky) */
uint32_t timed_out; /* a transaction never completed (sticky) */
uint64_t calls, sets, transactions, errors, last_intr;
const struct edp_backlight_io *io; /* during edp_backlight_call only */
};
_Static_assert(sizeof(struct edp_backlight_state) <= 4096, "one monitor page");
/* regs = the HVC's x0..x3 (x0 = ADRENO_GUEST_BACKLIGHT), replaced by the reply. Always returns 0. */
int edp_backlight_call(struct edp_backlight_state *s, const struct edp_backlight_io *io, uint64_t regs[4]);
#endif
+17
View File
@@ -143,6 +143,7 @@ The flags fall into these groups:
| Input | `--input`, `--input-touchpad`, `--input-ptp`, `--input-active-us` |
| Present path | `--present-dartpool`, `--present-resident`, `--present-beam`, `--present-va-reclaim`, `--present-maint`, `--present-latch`, `--present-pipeline` |
| GPU | `--ib-async`, `--gpu-recover`, `--gpu-va-wide`, `--gpu-coherent`, `--resident-mib`, `--adreno-fill-br`, `--adreno-fence-br`, `--gpu-x290-survey` |
| Panel brightness | `--edp-backlight`, `--edp-backlight-mode` |
| Clocks and bandwidth | `--thermal-guard`, `--gpu-dvfs`, `--gpu-dvfs-ceiling`, `--cpu-dvfs`, `--ddr-vote`, `--llcc`, `--bw-probe` |
| Storage | `--nvme-async`, `--nvme-converted` |
| Wi-Fi | `--qcc2072-wifi`, `--qcc2072-auxkc`, `--qcc2072-auxkc-sha256` |
@@ -186,6 +187,22 @@ The flag also runs q1n1's `a16gpu adopt --coherent`, which needs a q1n1 checkout
that has that option. `--gpu-coherent-force` is a debug build without the
self-test, for A/B rendering checks only.
`--edp-backlight` (needs `--present-latch` and its chain) lets macOS set the
panel's brightness. The monitor gets `SNAP_EDP_BACKLIGHT`: its
`ADRENO_GUEST_BACKLIGHT` call writes the panel's DPCD backlight level over the
eDP controller's AUX channel, touching only the AUX transport registers and
DPCD `0x721`-`0x723`, clamping the level to between 10% 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 AdrenoX2's `desktop-backlight` profile, whose `AdrenoBacklight` service is
what CoreBrightness drives, and the boot-arg `adreno_backlight=1`. The AuxKC
must be built (`KEXT_PROFILE=desktop-backlight tools/build-auxkc-desktop.sh
NAME`) and pinned as `INPUT_AUXKC_DESKTOP_BACKLIGHT`; until then the flag is
refused. `--edp-backlight-mode` lets the first change switch a panel left in
PWM-pin mode to DPCD control. `tools/survey-edp-aux.py` checks, read-only from
q1n1, that the clocks and the AUX controller the call needs are on.
`tools/test-edp-backlight.py` is the offline test.
## Installing a new Metal plug-in
The plug-in lives on the guest's system. To replace it, boot with `--console
+2 -1
View File
@@ -128,7 +128,8 @@ MULTI = {'pauth-address': ['probes/pauth-address.S','compat/pauth-native.S'],
'compat/adreno.c', 'compat/adreno-fill.c', 'compat/adreno-ib.c', 'compat/irq-forward.c', 'compat/ans.c', 'compat/ans-window.c', 'compat/nvme-host.c', 'compat/early-stop.c', 'compat/a16-thermal.c', 'probes/memory.S']}
# Units linked only when their opt-in define is in FLAGS, after MULTI's, so a build without the define links exactly
# the objects it always did and its ELF stays byte-identical.
OPTIONAL = {'xnuboot': (('-DSNAP_ADRENO_COHERENT=1', 'compat/adreno-coherent.c'),)}
OPTIONAL = {'xnuboot': (('-DSNAP_ADRENO_COHERENT=1', 'compat/adreno-coherent.c'),
('-DSNAP_EDP_BACKLIGHT=1', 'compat/edp-backlight.c'))}
def optional_sources(name, flags):
+9
View File
@@ -870,6 +870,15 @@ struct guest_net_state {
#define GUEST_NET_POLL2 4u /* q1n1 Q1N1_CALL_POLL2 */
#define GUEST_NET_CALL_E_OP UINT64_C(0xffffffffffffffff) /* Q1N1_CALL_E_OP: an older q1n1 */
#endif
/* SNAP_EDP_BACKLIGHT (compat/edp-backlight.h, agents/brightness): ADRENO_GUEST_BACKLIGHT sets the panel's DPCD
* brightness over the eDP controller's AUX channel for the kext's AdrenoBacklight, admitted like ADRENO_GUEST_STOP.
* Its state is one zeroed monitor page (setup.c). Off, the code is absent and compat/edp-backlight.c is not linked. */
#ifndef SNAP_EDP_BACKLIGHT
#define SNAP_EDP_BACKLIGHT 0
#endif
#if SNAP_EDP_BACKLIGHT
#include "../compat/edp-backlight.h"
#endif
#include "../compat/fast-vector.h"
_Static_assert(FAST_EXIT_CLASSES==FAST_TRAP_CLASSES-1+FAST_CROSS_CLASSES &&
FAST_EXIT_CROSS_SLOT(1)==FAST_TRAP_CLASSES-1,
+5
View File
@@ -455,6 +455,11 @@ struct context {
* monitor arena pages taken at setup (owner 0, never released). */
struct guest_net_state *guest_net;
#endif
#if SNAP_EDP_BACKLIGHT
/* compat/edp-backlight.h: ADRENO_GUEST_BACKLIGHT's state, one monitor
* arena page taken at setup (owner 0, never released). */
struct edp_backlight_state *edp_backlight;
#endif
#if SNAP_FAST_EL0_SYSREG || SNAP_SERVICE_SUBCLASS
/* Appended only under these opt-in defines, inside the page before the
* table arena (asserted below), so define-free layout readers keep every
+4
View File
@@ -576,6 +576,10 @@ static int txn_publish_exclude(void *ctx,uint64_t page)
#if SNAP_GUEST_NET
/* The guest-network state, stored the same way (compat/guest-net-link.h). */
if(page>=(uint64_t)c->guest_net && page-(uint64_t)c->guest_net<GUEST_NET_STATE_PAGES*4096)return 1;
#endif
#if SNAP_EDP_BACKLIGHT
/* ADRENO_GUEST_BACKLIGHT's state page, stored the same way (compat/edp-backlight.h). */
if(page==(uint64_t)c->edp_backlight)return 1;
#endif
for(unsigned cpu=0;cpu<GUEST_CPU_COUNT;cpu++)
for(uint64_t g=0;g<c->vcpus[cpu].guard_count;g++)
+43
View File
@@ -652,3 +652,46 @@ static void guest_net_hvc(struct context *c, uint64_t *frame, uint64_t elr, uint
}
#endif
#if SNAP_EDP_BACKLIGHT
/* compat/edp-backlight.c reaches the eDP controller and dispcc through these, physical addresses in q1n1's EL2
* identity map (Device memory, as the thermal guard's reads). The engine admits only its allow-list
* (edp_bl_mmio_read_ok/edp_bl_mmio_write_ok, proven by tools/test-edp-backlight.c); the barrier orders each access
* before the next, as the controller's FIFO protocol needs. */
static int edp_backlight_read(void *ctx, uint64_t pa, uint32_t *value)
{
(void)ctx;
*value = *(const volatile uint32_t *)pa;
__asm__ volatile("dsb sy" ::: "memory");
return 0;
}
static int edp_backlight_write(void *ctx, uint64_t pa, uint32_t value)
{
(void)ctx;
*(volatile uint32_t *)pa = value;
__asm__ volatile("dsb sy" ::: "memory");
return 0;
}
static uint64_t edp_backlight_clock(void *ctx) {(void)ctx; return service_profile_clock();}
/* ADRENO_GUEST_BACKLIGHT (compat/edp-backlight.h): admitted from EL1 for the HVC the loaded AuxKC's own bytes hold
* (early_stop_guest_caller, as ADRENO_GUEST_STOP). Under the service lock: one AUX transaction at a time, each
* bounded by hz / EDP_BL_AUX_BUDGET_DIV. */
static void edp_backlight_hvc(struct context *c, uint64_t *frame, uint64_t elr, uint64_t spsr)
{
const struct params *p = c->params;
const struct edp_backlight_io io = {c, edp_backlight_read, edp_backlight_write, edp_backlight_clock,
p->clock_host_hz};
uint64_t hvc = elr - 4, pa = guest_pa(hvc), r[4] = {frame[0], frame[1], frame[2], frame[3]};
uint32_t word = 0;
if (pa && p->auxkc.pa_end > p->auxkc.pa_base + 4 && pa >= p->auxkc.pa_base && pa <= p->auxkc.pa_end - 4)
word = *(volatile uint32_t *)pa;
if (early_stop_guest_caller(&p->auxkc, hvc, pa, word, spsr)) {
r[0] = (uint64_t)(int64_t)EDP_BL_E_ORIGIN;
r[1] = r[2] = r[3] = 0;
} else
edp_backlight_call(c->edp_backlight, &io, r);
record(c, EV_EDP_BACKLIGHT, hvc, frame[1] << 32 | (uint32_t)frame[2], r[0] << 32 | (uint32_t)r[3], r[1]);
for (unsigned n = 0; n < 4; n++) frame[n] = r[n];
}
#endif
+5
View File
@@ -217,6 +217,11 @@ struct event {
* control's PMU passes << 16, c = stale[0] << 32 | stale[1] (the controls'
* stale words, each at most 1,024); probes/run.py decodes it */
#endif
#if SNAP_EDP_BACKLIGHT
#define EV_EDP_BACKLIGHT 60 /* ADRENO_GUEST_BACKLIGHT (compat/edp-backlight.h): a = sub-op << 32 | (u32)level asked,
* b = (u32)result << 32 | the panel's level after the call, c = the reply's x1 (ABI |
* state << 8 | detail << 16 | DPCD 0x700..0x703 << 24 | mode << 56); pc = the HVC */
#endif
#define EV_HANDOFF 7 /* a field of the handoff was read: a = offset */
#define EV_WATCHDOG 6 /* the guest ran too long and was taken back */
#define EV_DISPATCH_CMD 8 /* a call that is not an endpoint call:
+8
View File
@@ -90,6 +90,14 @@ int xnuboot_setup(uint64_t work, uint64_t size, uint64_t params, uint64_t out,
for (uint64_t n = 0; n < GUEST_NET_STATE_PAGES * 4096 / 8; n++) ((volatile uint64_t *)c->guest_net)[n] = 0;
__asm__ volatile("" ::: "memory");
c->next_table += GUEST_NET_STATE_PAGES * 4096;
#endif
#if SNAP_EDP_BACKLIGHT
/* compat/edp-backlight.h: one page, zeroed (EDP_BL_STATE_UNPROBED), probed by the first guest call. */
if (c->next_table + 4096 > c->work_end) return 1;
c->edp_backlight = (struct edp_backlight_state *)c->next_table;
for (uint64_t n = 0; n < 4096 / 8; n++) ((volatile uint64_t *)c->edp_backlight)[n] = 0;
__asm__ volatile("" ::: "memory");
c->next_table += 4096;
#endif
if(c->params->early_stop.flags & EARLY_STOP_F_THERMAL) {
if(c->next_table>c->work_end || c->work_end-c->next_table<4096)return 1;
+6
View File
@@ -578,6 +578,12 @@ static int xnuboot_trap_locked(uint64_t *frame, uint64_t esr, uint64_t *elr)
return 0;
}
#endif
#if SNAP_EDP_BACKLIGHT
if (frame[0] == ADRENO_GUEST_BACKLIGHT) {
edp_backlight_hvc(c, frame, *elr, physical_spsr);
return 0;
}
#endif
#if SNAP_ADRENO_IB_ASYNC
if (frame[0] == ADRENO_GUEST_IB_POLL) {
gpu_ib_poll_hvc(c, frame, *elr);
+8 -4
View File
@@ -21,7 +21,10 @@
(kernel, IOMobileGraphicsFamily, IOAVFamily, IOSurface, IOGPUFamily).
Writes <out>/iomfb-vtable-check.json (the full per-slot table) and exits 1 on any failure.
Usage: tools/check-iomfb-vtable.py [--out DIR] [--kc PATH] [--kdk PATH] [--no-display] [--display-define NAME=V ...]
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.
"""
import argparse
import hashlib
@@ -199,9 +202,10 @@ def main():
ap.add_argument('--kdk', type=Path, default=gen.DEFAULT_KDK)
ap.add_argument('--kc', type=Path, default=gen.DEFAULT_KC)
ap.add_argument('--no-display', action='store_true', help='check only the stub subclass')
ap.add_argument('--display-define', action='append', default=[], metavar='NAME[=VALUE]',
help='a -D for AdrenoDisplay.cpp only, repeatable (e.g. a profile\'s AdrenoDisplay set, or '
'ADRENO_DISPLAY_TRACE_MODES=1, whose overrides add slots to check)')
ap.add_argument('--display-define', '--define', dest='display_define', action='append', default=[],
metavar='NAME[=VALUE]',
help='a -D for AdrenoDisplay.cpp only, repeatable (e.g. a profile\'s AdrenoDisplay set, '
'ADRENO_DISPLAY_TRACE_MODES=1 or ADRENO_BACKLIGHT=1, whose overrides add slots to check)')
args = ap.parse_args()
out = args.out
out.mkdir(parents=True, exist_ok=True)
+11
View File
@@ -272,6 +272,17 @@ def parse(argv=None):
'submission is in the kext. SnapI2CHID polls every 4 ms while touched, timed from the previous '
'poll\'s start. The release hold is off by default since b12v11. Kext only; give it to every '
'mode of the attempt')
p.add_argument('--edp-backlight', action='store_true',
help='opt-in panel brightness (agents/brightness; needs --present-latch and its chain): monitor define '
'SNAP_EDP_BACKLIGHT (ADRENO_GUEST_BACKLIGHT: the panel\'s DPCD backlight level over the eDP '
'AUX channel, clamped to [10%% of full scale, the panel\'s maximum], fail closed), the AuxKC '
'INPUT_AUXKC_DESKTOP_BACKLIGHT (AdrenoBacklight, the backlight service CoreBrightness drives) and '
'guest boot-arg adreno_backlight=1; give it to every mode of the attempt')
p.add_argument('--edp-backlight-mode', action='store_true',
help='with --edp-backlight: guest boot-arg adreno_backlight_mode=1, letting the first brightness '
'change switch a panel the firmware left in PWM-pin mode to DPCD control (level written first, '
'only the two mode bits changed, read back; on failure the old mode is restored and the '
'monitor stops)')
p.add_argument('--present-latch-us', type=int, default=None,
help='with --present-latch: guest boot-arg adreno_latch_us, the slack kept before row 0 (default '
'3000; 0 = the beam kext\'s aim before the vblank)')
+31 -1
View File
@@ -231,6 +231,12 @@ INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH_SHA = 'ccf0c8f4da4f63fbe25c
# b12v19r: + resident re-pin kext b2686dfb (agents/resident-evict). Previous: relatch-sparse 1cd18fa5...
# b12v19: relatch + cache-sparse kext 2cd7ef66 (agents/b12v19-kext). Rollback b12v18: relatch-nosparse 65243492...
# b12v18: relatch kext 128667ac (agents/present-pacing). Rollback: latch2-pipe2-recover-nosparse, 2cb7b6cc...
# --edp-backlight (opt-in, agents/brightness): the --present-latch collection with SnapAdreno PROFILE=desktop-backlight's
# AdrenoX2 (the desktop profile plus -DADRENO_BACKLIGHT=1 in AdrenoDisplay.o: the AdrenoBacklight service CoreBrightness
# drives) and SnapI2CHID's desktop profile. None until built (KEXT_PROFILE=desktop-backlight
# tools/build-auxkc-desktop.sh NAME prints the two lines) and pinned: --edp-backlight refuses meanwhile.
INPUT_AUXKC_DESKTOP_BACKLIGHT = None
INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA = None
# --guest-net: INPUT_AUXKC's objects with SnapintoshVirtualEthernet's datapath (docs/GUEST-NETWORK-20261019.md).
# None until that collection is built and pinned; --guest-net refuses meanwhile.
INPUT_AUXKC_NET = None
@@ -648,6 +654,13 @@ GPU_COHERENT_DEFINE = '-DSNAP_ADRENO_COHERENT=1'
# anything. The monitor reports ON with ADRENO_COHERENT_DETAIL_FORCED and runs no job; the kext accepts that only with
# its own boot-arg adreno_coherent_force=1. Appended after GPU_COHERENT_DEFINE.
GPU_COHERENT_FORCE_DEFINE = '-DSNAP_ADRENO_COHERENT_FORCE=1'
# --edp-backlight (agents/brightness): compat/edp-backlight.h SNAP_EDP_BACKLIGHT, ADRENO_GUEST_BACKLIGHT QUERY/SET of the
# panel's DPCD backlight level over the eDP controller's AUX channel. Only the AUX transport registers and DPCD
# 0x721-0x723 are written, the level is clamped to [10% of full scale, the panel's maximum], and any failure leaves the
# brightness as it was. The kext half is INPUT_AUXKC_DESKTOP_BACKLIGHT and the guest boot-arg adreno_backlight=1 the
# flag adds. Monitor define only (no loader change). Appended after every earlier flag; off, the monitor is
# byte-identical.
EDP_BACKLIGHT_DEFINE = '-DSNAP_EDP_BACKLIGHT=1'
# agents/dvfs (FAILURE-MODES.md): opt-in governors on vCPU 0's locked tick, state at the end of root_ext (so
# --root-cache-wide), report DVFSREP1 after the thermal record, restored at the stop; both need --thermal-guard (they
# never write without the guard's readings). compat/cpu-dvfs.h SNAP_CPU_DVFS: SCMI PERF LEVEL_SET fastchannels of the
@@ -742,6 +755,9 @@ def route_selected(args):
need(not latch or va_wide,
'--present-latch needs --gpu-va-wide and its chain (its AdrenoX2 is that collection\'s with ADRENO_LATCH, its '
'SnapI2CHID stage B with SNAP_I2C_HID_LATENCY)')
edp_backlight = getattr(args, 'edp_backlight', False)
need(not edp_backlight or latch,
'--edp-backlight needs --present-latch and its chain (its AdrenoX2 is that collection\'s with ADRENO_BACKLIGHT)')
need(not (vsync60 and (damage or cached or gpumap)),
'--present-vsync60 already includes --present-gpumap\'s (and so --present-cached-read\'s) kext: give one of them')
need(not (damage and cached), '--present-cached-read already includes --present-damage\'s present: give one of them')
@@ -767,6 +783,12 @@ def route_selected(args):
if vsync60:
return ((INPUT_TOUCHPAD_PROFILE if touchpad else INPUT_PROFILE), INPUT_AUXKC_VSYNC60,
INPUT_AUXKC_VSYNC60_SHA, INPUT_BUNDLES)
if dartpool and ptp and resident and beam and va_reclaim and va_wide and latch and edp_backlight:
need(INPUT_AUXKC_DESKTOP_BACKLIGHT is not None and INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA is not None,
'--edp-backlight: its AuxKC is not pinned yet (INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA; '
'KEXT_PROFILE=desktop-backlight tools/build-auxkc-desktop.sh NAME links it)')
return (INPUT_TOUCHPAD_PROFILE, INPUT_AUXKC_DESKTOP_BACKLIGHT, INPUT_AUXKC_DESKTOP_BACKLIGHT_SHA,
INPUT_BUNDLES)
if dartpool and ptp and resident and beam and va_reclaim and va_wide and latch:
need(INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH is not None and
INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH_SHA is not None,
@@ -1060,6 +1082,8 @@ def variant_flags(sets, args, variant=None):
need(getattr(args, 'gpu_coherent', False), '--gpu-coherent-force needs --gpu-coherent: it forces that '
'self-test\'s outcome')
flags.append(GPU_COHERENT_FORCE_DEFINE)
if getattr(args, 'edp_backlight', False):
flags.append(EDP_BACKLIGHT_DEFINE)
return flags
@@ -1131,6 +1155,12 @@ def boot_addition(args):
extra.append('adreno_coherent=1') # the kext still skips only after the monitor's self-test said ON
if getattr(args, 'gpu_coherent_force', False):
extra.append('adreno_coherent_force=1') # and, debug only, after a forced ON (no self-test)
need(not getattr(args, 'edp_backlight_mode', False) or getattr(args, 'edp_backlight', False),
'--edp-backlight-mode needs --edp-backlight')
if getattr(args, 'edp_backlight', False):
extra.append('adreno_backlight=1') # the kext publishes its service only after the monitor's QUERY
if getattr(args, 'edp_backlight_mode', False):
extra.append('adreno_backlight_mode=1') # and may switch a panel left in PWM mode to DPCD control
resident_mib = getattr(args, 'resident_mib', None)
if resident_mib is not None:
need(0 <= resident_mib <= 4096, '--resident-mib must be 0..4096')
@@ -1707,7 +1737,7 @@ def check(ws, args, dest):
need(record['auxkc_pin']['sha256'] == selected(args)[2],
'the AuxKC differs from the preparation (give --present-damage/--present-cached-read/--present-gpumap/'
'--present-vsync60/--present-dartpool/--present-resident/--present-beam/--present-va-reclaim/--gpu-va-wide/'
'--present-latch/'
'--present-latch/--edp-backlight/'
'--input-ptp/--guest-net to every mode)')
need(record['flags'] == variant_flags(st.flag_sets(ws.root)[0], args, record['variant']), 'variant flags differ from the recipe')
need(record['gpu_steps'] == [list(s) for s in gpu_steps(ws.root, args)], 'GPU steps differ from the pinned launcher')
+132
View File
@@ -0,0 +1,132 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""Read-only survey of the eDP controller's AUX block (mdss_dp3) from q1n1, before an --edp-backlight boot.
compat/edp-backlight.h: the monitor's ADRENO_GUEST_BACKLIGHT needs the MDSS AHB and DPTX3 AUX clocks on, the MDSS
core power domain on and the AUX controller enabled, as the firmware left them; it never turns any of them on. This
reads, in order, stopping at the first failed check (an unclocked MDSS read is an SError, a cold reboot):
1. DISP_CC_MDSS_AHB_CBCR and DISP_CC_MDSS_DPTX3_AUX_CBCR: CLK_OFF (bit 31) clear; MDSS_CORE_GDSCR: PWR_ON (bit 31)
set;
2. the controller's HW version, interrupt status (with its mask bits) and the AUX block's HPD, enable, timeout,
limit and status registers.
Nothing is written, no AUX transaction is started, and neither the AUX FIFO nor the PHY interrupt status (possibly
read-to-clear) is read: the panel's DPCD capabilities are first read by the monitor's own probe at boot.
python3 tools/survey-edp-aux.py --plan # the read list, offline
python3 tools/survey-edp-aux.py --check SURVEY.json # decode a saved survey, offline
python3 tools/survey-edp-aux.py --survey --device udp://enN --output NEW.json
"""
from pathlib import Path
import argparse
import json
import re
import sys
import time
ROOT = Path(__file__).resolve().parents[1]
def constants():
out = {}
for header in ('compat/dpu-intf.h', 'compat/edp-backlight.h'):
text = (ROOT / header).read_text()
for name, value in re.findall(r'#define ((?:DPU|EDP)_\w+)\s+(0x[0-9A-Fa-f]+|\d+)(?:ULL|u)?\b', text):
out[name] = int(value, 0)
for name, shift in re.findall(r'#define ((?:DPU|EDP)_\w+)\s+\(1u << (\d+)\)', text):
out[name] = 1 << int(shift)
return out
C = constants()
AHB, AUX, CC = C['EDP_DP3_AHB_PA'], C['EDP_DP3_AUX_PA'], C['DPU_DISPCC_PA']
CLOCKS = (('DISP_CC_MDSS_AHB_CBCR', CC + C['DPU_DISPCC_MDSS_AHB_CBCR'], 'clock'),
('DISP_CC_MDSS_CORE_GDSCR', CC + C['DPU_DISPCC_MDSS_CORE_GDSCR'], 'gdsc'),
('DISP_CC_MDSS_DPTX3_AUX_CBCR', CC + C['EDP_DISPCC_DPTX3_AUX_CBCR'], 'clock'))
# Offsets from Linux drivers/gpu/drm/msm/dp/dp_reg.h (facts only): HW_VERSION 0x00, INTR_STATUS 0x20, INTR_STATUS2
# 0x24 in "ahb"; HPD_CTRL 0x00, HPD_INT_STATUS 0x04, HPD_INT_MASK 0x0c, AUX_CTRL 0x30, TIMEOUT_COUNT 0x3c,
# AUX_LIMITS 0x40, AUX_STATUS 0x44 in "aux".
REGS = (('DP_HW_VERSION', AHB + 0x00), ('DP_INTR_STATUS', AHB + C['EDP_AHB_INTR_STATUS']),
('DP_INTR_STATUS2', AHB + 0x24), ('AUX_HPD_CTRL', AUX + 0x00),
('AUX_HPD_INT_STATUS', AUX + C['EDP_AUX_HPD_INT_STATUS']), ('AUX_HPD_INT_MASK', AUX + 0x0c),
('AUX_CTRL', AUX + C['EDP_AUX_CTRL']), ('AUX_TIMEOUT_COUNT', AUX + 0x3c), ('AUX_LIMITS', AUX + 0x40),
('AUX_STATUS', AUX + 0x44))
NEVER = (AUX + C['EDP_AUX_DATA'], AUX + C['EDP_AUX_PHY_INTR_STATUS'])
def plan():
reads = [(name, pa, kind) for name, pa, kind in CLOCKS] + [(name, pa, 'dp') for name, pa in REGS]
for _, pa, kind in reads:
assert pa not in NEVER and (kind != 'dp' or AHB <= pa < AHB + 0x400)
return reads
INTR_NAMES = ((0, 'HPD'), (3, 'AUX_DONE'), (6, 'WRONG_ADDR'), (9, 'TIMEOUT'), (12, 'NACK_DEFER'),
(15, 'WRONG_DATA_CNT'), (18, 'I2C_NACK'), (21, 'I2C_DEFER'), (24, 'PLL_UNLOCKED'), (27, 'AUX_ERROR'))
def check(record):
regs = {k: int(v, 0) if isinstance(v, str) else v for k, v in record['registers'].items()}
out = {}
for name, _, kind in CLOCKS:
v = regs.get(name)
out[name] = None if v is None else ('on' if (kind == 'clock' and not v >> 31) or (kind == 'gdsc' and v >> 31)
else 'OFF')
if 'AUX_CTRL' not in regs:
out['verdict'] = 'stopped before the DP block: ' + record.get('stopped', 'no DP registers')
return out
intr, hpd = regs['DP_INTR_STATUS'], regs['AUX_HPD_INT_STATUS']
out['aux_enabled'] = bool(regs['AUX_CTRL'] & C['EDP_AUX_CTRL_ENABLE'])
out['hpd_state'] = {0x40000000: 'connected', 0x20000000: 'pending', 0: 'disconnected'}.get(
hpd & 0xE0000000, f'{hpd & 0xE0000000:#x}')
out['pending_statuses'] = [n for bit, n in INTR_NAMES if intr >> bit & 1]
out['unmasked_interrupts'] = [n for bit, n in INTR_NAMES if intr >> (bit + 2) & 1]
ready = all(out[n] == 'on' for n, _, _ in CLOCKS) and out['aux_enabled']
out['verdict'] = ('ready: the monitor probe will read DPCD 0x00D, 0x700-0x703 and 0x721-0x726' if ready else
'NOT ready: ADRENO_GUEST_BACKLIGHT will answer FAILED (nothing is enabled from EL2)')
return out
def survey(device, output):
sys.path.insert(0, str(ROOT.parent / 'q1n1' / 'tools'))
import q1n1proxy
proxy = q1n1proxy.connect(device=device)
registers, record = {}, dict(schema='snapintosh-edp-aux-survey-v1', device=device, started=time.time())
for name, pa, kind in CLOCKS: # 1: clocks and power before any DP read
v = registers[name] = proxy.read32(pa)
if (kind == 'clock' and v >> 31) or (kind == 'gdsc' and not v >> 31):
record.update(registers={k: hex(x) for k, x in registers.items()},
stopped=f'{name} = {v:#x}: not clocked or not powered; no DP read')
record['check'] = check(record)
Path(output).write_text(json.dumps(record, indent=1) + '\n')
raise SystemExit(record['stopped'])
for name, pa, _ in plan()[len(CLOCKS):]: # 2
registers[name] = proxy.read32(pa)
record.update(registers={k: hex(v) for k, v in registers.items()}, finished=time.time())
record['check'] = check(record)
Path(output).write_text(json.dumps(record, indent=1) + '\n')
print(json.dumps(record['check'], indent=1))
def main():
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
ap.add_argument('--plan', action='store_true')
ap.add_argument('--check')
ap.add_argument('--survey', action='store_true')
ap.add_argument('--device')
ap.add_argument('--output')
a = ap.parse_args()
if a.plan:
for name, pa, kind in plan():
print(f'{pa:#012x} {kind:5} {name}')
elif a.check:
print(json.dumps(check(json.loads(Path(a.check).read_text())), indent=1))
elif a.survey:
if not a.device or not a.output or Path(a.output).exists():
raise SystemExit('--survey needs --device and a new --output')
survey(a.device, a.output)
else:
ap.print_help()
if __name__ == '__main__':
main()
+412
View File
@@ -0,0 +1,412 @@
/* SPDX-License-Identifier: MIT
* compat/edp-backlight.{h,c} against a model of the eDP controller's AUX FIFO and a DPCD panel; no hardware.
*
* clang -std=gnu11 -O1 -g -Wall -Wextra -Werror -fsanitize=address,undefined -fno-sanitize-recover=all \
* -Icompat tools/test-edp-backlight.c compat/edp-backlight.c -o build/test-edp-backlight
*
* The model is written from the same facts as the engine (Linux dp_aux.c's FIFO sequence, drm_dp.h's DPCD layout)
* but checks them independently: every MMIO access against its own list of the controller registers the backlight
* may touch, every DPCD write against {0x721, 0x722, 0x723}, every INTR_STATUS write for kept mask bits and
* AUX-only acks, and the PHY clear sequence before every GO. tools/test-edp-backlight.py also compiles weakened
* copies of the engine that this program must reject. */
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include "edp-backlight.h"
#define CHECK(x) do { if (!(x)) { fprintf(stderr, "line %d: %s\n", __LINE__, #x); exit(1); } } while (0)
#define HZ UINT64_C(19200000)
/* The model's own register list (not the header's allow-list). */
#define M_DISPCC 0x0AF00000u
#define M_AHB 0x0AF6C000u
#define M_AUX 0x0AF6C200u
enum { R_AHB_CBCR, R_GDSCR, R_AUX_CBCR, R_INTR, R_AUX_CTRL, R_AUX_DATA, R_TRANS, R_PHY_STATUS, R_PHY_CLEAR, R_N };
static const uint32_t reg_pa[R_N] = {M_DISPCC + 0x80B0, M_DISPCC + 0x9000, M_DISPCC + 0x80A8, M_AHB + 0x20,
M_AUX + 0x30, M_AUX + 0x34, M_AUX + 0x38, M_AUX + 0xBC, M_AUX + 0x4C};
static const int reg_readable[R_N] = {1, 1, 1, 1, 1, 1, 1, 1, 0};
static const int reg_writable[R_N] = {0, 0, 0, 1, 0, 1, 1, 0, 1};
#define ST_DONE (1u << 3)
#define ST_NACK (1u << 12)
#define AUX_STATUSES 0x08249248u /* bits 3, 6, 9, 12, 15, 18, 21, 27 */
struct model {
uint32_t reg[R_N];
uint32_t status, mask; /* INTR_STATUS: statuses and mask bits, kept apart */
uint8_t fifo[32];
uint32_t widx, ridx, reads_left;
int read_mode;
uint8_t reply[20];
uint32_t reply_len;
unsigned phy_step; /* 0 -> 0x1f -> 0x9f -> 0 seen since the last GO */
int trans_zeroed;
uint8_t dpcd[0x800];
int silent; /* the sink never answers: no status at all */
int nack; /* answer the next n transactions with NACK */
int ignore_level; /* the panel drops writes to 0x722/0x723 */
int ignore_mode; /* ... and to 0x721 */
uint64_t now;
unsigned violations, gos, dpcd_writes, mmio_reads, mmio_writes, dp_reads;
uint32_t last_write_addr, last_write_len;
uint8_t last_write[16];
};
static struct model M;
static struct edp_backlight_state S;
static int reg_of(uint64_t pa)
{
for (int i = 0; i < R_N; i++) if (pa == reg_pa[i]) return i;
return -1;
}
static void violation(const char *what, uint64_t pa)
{
fprintf(stderr, "violation: %s %#llx\n", what, (unsigned long long)pa);
M.violations++;
}
static void transaction(void)
{
M.gos++;
if (!M.trans_zeroed || M.phy_step != 3) violation("GO without TRANS_CTRL=0 and the PHY clear sequence", 0);
if (M.status & AUX_STATUSES) violation("GO with an unacknowledged AUX status", M.status);
M.trans_zeroed = 0;
M.phy_step = 0;
if (M.widx < 4) { violation("GO with a short header", M.widx); return; }
uint32_t addr = (uint32_t)(M.fifo[0] & 0x0f) << 16 | (uint32_t)M.fifo[1] << 8 | M.fifo[2], len = M.fifo[3] + 1u;
int read = M.fifo[0] & 0x10;
if (M.silent) return;
if (M.nack) { M.nack--; M.status |= ST_NACK | ST_DONE; return; }
if (addr + len > sizeof M.dpcd) { violation("DPCD beyond the model", addr); return; }
if (read) {
if (M.widx != 4) violation("read with data bytes", M.widx);
M.reply[0] = 0; /* ACK */
memcpy(M.reply + 1, M.dpcd + addr, len);
M.reply_len = len + 1;
} else {
if (M.widx != 4 + len) violation("write length differs from the header", M.widx);
M.dpcd_writes++;
M.last_write_addr = addr;
M.last_write_len = len;
memcpy(M.last_write, M.fifo + 4, len);
for (uint32_t i = 0; i < len; i++) {
uint32_t a = addr + i;
if (a != 0x721 && a != 0x722 && a != 0x723) violation("DPCD write outside 0x721..0x723", a);
if ((a == 0x722 || a == 0x723) && M.ignore_level) continue;
if (a == 0x721 && M.ignore_mode) continue;
M.dpcd[a] = M.fifo[4 + i];
}
}
M.status |= ST_DONE;
}
static int read32(void *ctx, uint64_t pa, uint32_t *v)
{
(void)ctx;
int r = reg_of(pa);
M.mmio_reads++;
if (r < 0 || !reg_readable[r]) { violation("read outside the backlight registers", pa); return -1; }
if (r > R_AUX_CBCR) M.dp_reads++;
switch (r) {
case R_INTR: *v = M.status | M.mask; break;
case R_AUX_DATA:
if (!M.read_mode || M.ridx >= M.reply_len) { violation("FIFO read past the reply", pa); *v = 0; break; }
/* the reply command first, then data byte i with index i (dp_aux.c cmd_fifo_rx) */
*v = (uint32_t)M.reply[M.ridx] << 8 | (M.ridx ? (M.ridx - 1) << 16 : 0);
M.ridx++;
break;
default: *v = M.reg[r];
}
return 0;
}
static int write32(void *ctx, uint64_t pa, uint32_t v)
{
(void)ctx;
int r = reg_of(pa);
M.mmio_writes++;
if (r < 0 || !reg_writable[r]) { violation("write outside the backlight registers", pa); return -1; }
switch (r) {
case R_INTR: {
uint32_t ack = (v >> 1) & EDP_INTR_STATUS_BITS, mask = v & (EDP_INTR_STATUS_BITS << 2);
if (ack & ~AUX_STATUSES) violation("ack of a non-AUX status", v);
if (mask != M.mask) violation("INTR_STATUS mask bits changed", v);
if (v & ~(EDP_INTR_STATUS_BITS << 1 | EDP_INTR_STATUS_BITS << 2)) violation("status bits written", v);
M.status &= ~ack;
break;
}
case R_AUX_DATA:
if (v & EDP_AUX_DATA_INDEX_WRITE) {
M.read_mode = v & EDP_AUX_DATA_READ;
M.widx = M.ridx = 0;
if (M.read_mode) break;
} else if (M.read_mode) { violation("FIFO byte written in read mode", v); break; }
if (M.widx >= sizeof M.fifo) { violation("FIFO overflow", v); break; }
if (v & ~0x0000ff00u & ~EDP_AUX_DATA_INDEX_WRITE) violation("FIFO word with stray bits", v);
M.fifo[M.widx++] = (uint8_t)(v >> 8);
break;
case R_TRANS:
M.reg[R_TRANS] = v;
if (v == 0) { M.trans_zeroed = 1; M.phy_step = 0; }
else if (v == EDP_AUX_TRANS_GO) transaction();
else if (v & EDP_AUX_TRANS_GO) violation("GO with I2C or other bits", v);
break;
case R_PHY_CLEAR:
if ((M.phy_step == 0 && v == 0x1f) || (M.phy_step == 1 && v == 0x9f) || (M.phy_step == 2 && v == 0)) M.phy_step++;
else violation("PHY clear out of sequence", v);
break;
}
return 0;
}
static uint64_t counter(void *ctx) { (void)ctx; return M.now += 1000; }
static const struct edp_backlight_io IO = {0, read32, write32, counter, HZ};
/* A healthy A16-like panel: display control capable, AUX-settable 11-bit brightness, DPCD mode, level 768. */
static void reset(void)
{
memset(&M, 0, sizeof M);
memset(&S, 0, sizeof S);
M.reg[R_AHB_CBCR] = 0x1; /* CLK_OFF clear */
M.reg[R_GDSCR] = 0x80000000u; /* PWR_ON */
M.reg[R_AUX_CBCR] = 0x1;
M.reg[R_AUX_CTRL] = 0x1; /* enabled by the firmware */
M.mask = 0x00000004u | 0x00000020u; /* HPD and AUX-done interrupts unmasked by the firmware */
M.dpcd[0x00D] = 0x0B;
M.dpcd[0x700] = 0x03; M.dpcd[0x701] = 0x07; M.dpcd[0x702] = 0x06; M.dpcd[0x703] = 0x00;
M.dpcd[0x721] = 0x12; /* DPCD mode, dynamic backlight bit kept */
M.dpcd[0x722] = 0x03; M.dpcd[0x723] = 0x00;
M.dpcd[0x724] = 11; M.dpcd[0x725] = 11; M.dpcd[0x726] = 11;
}
static int64_t call(uint64_t op, uint64_t arg, uint64_t flags, uint64_t out[4])
{
uint64_t r[4] = {0x0001000C, op, arg, flags};
CHECK(edp_backlight_call(&S, &IO, r) == 0);
memcpy(out, r, sizeof r);
return (int64_t)r[0];
}
static uint32_t level_of(const uint64_t r[4]) { return (uint32_t)r[3]; }
static uint32_t state_of(const uint64_t r[4]) { return edp_bl_reply_state(r[1]); }
static uint32_t panel_level(void) { return (uint32_t)M.dpcd[0x722] << 8 | M.dpcd[0x723]; }
static void test_helpers(void)
{
uint32_t bits = 0;
uint8_t h[4], b[2];
edp_aux_header(0x722, 2, 0, h);
CHECK(h[0] == 0x00 && h[1] == 0x07 && h[2] == 0x22 && h[3] == 0x01);
edp_aux_header(0xF0123, 16, 1, h);
CHECK(h[0] == 0x1F && h[1] == 0x01 && h[2] == 0x23 && h[3] == 0x0F);
CHECK(!edp_bl_bits(11, 11, 11, 1, &bits) && bits == 11);
CHECK(!edp_bl_bits(4, 8, 16, 1, &bits) && bits == 8); /* below min: the min */
CHECK(!edp_bl_bits(20, 1, 12, 1, &bits) && bits == 12); /* above max: the max */
CHECK(edp_bl_bits(8, 9, 8, 1, &bits)); /* min > max */
CHECK(edp_bl_bits(0, 0, 0, 1, &bits)); /* no count */
CHECK(edp_bl_bits(17, 17, 17, 1, &bits)); /* above 16 */
CHECK(edp_bl_bits(10, 10, 10, 0, &bits)); /* above 8 without the LSB register */
CHECK(!edp_bl_bits(8, 8, 8, 0, &bits) && bits == 8);
CHECK(edp_bl_max_level(11) == 2047 && edp_bl_max_level(8) == 255 && edp_bl_max_level(16) == 65535);
CHECK(edp_bl_floor_level(2047) == 205 && edp_bl_floor_level(255) == 26 && edp_bl_floor_level(1) == 1);
for (uint32_t n = 1; n <= 16; n++) {
uint32_t max = edp_bl_max_level(n), floor = edp_bl_floor_level(max);
CHECK(floor >= 1 && floor <= max);
CHECK((uint64_t)floor * 1000 >= (uint64_t)max * EDP_BL_FLOOR_PERMILLE);
}
CHECK(edp_bl_clamp(0, 205, 2047) == 205 && edp_bl_clamp(5000, 205, 2047) == 2047 &&
edp_bl_clamp(1000, 205, 2047) == 1000 && edp_bl_clamp(UINT64_MAX, 205, 2047) == 2047);
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);
/* nits -> level: ends map onto floor and max, monotonic, round trip within one level */
uint32_t prev = 0;
for (uint64_t q = 0; q <= (600ull << 16); q += 1u << 12) {
uint32_t l = edp_bl_nits_to_level(q, 5, 500, 205, 2047);
CHECK(l >= 205 && l <= 2047 && l >= prev);
prev = l;
}
CHECK(edp_bl_nits_to_level(5ull << 16, 5, 500, 205, 2047) == 205);
CHECK(edp_bl_nits_to_level(500ull << 16, 5, 500, 205, 2047) == 2047);
CHECK(edp_bl_nits_to_level(0, 5, 500, 205, 2047) == 205);
CHECK(edp_bl_nits_to_level(UINT64_MAX >> 20, 5, 500, 205, 2047) == 2047);
CHECK(edp_bl_nits_to_level(100ull << 16, 500, 5, 205, 2047) == 205); /* inverted range: the floor */
for (uint32_t l = 205; l <= 2047; l += 7)
CHECK(edp_bl_nits_to_level(edp_bl_level_to_nits(l, 5, 500, 205, 2047), 5, 500, 205, 2047) == l);
CHECK(edp_bl_slider_to_nits(0, 5, 500) == 5ull << 16 && edp_bl_slider_to_nits(65536, 5, 500) == 500ull << 16 &&
edp_bl_slider_to_nits(1u << 20, 5, 500) == 500ull << 16);
CHECK(edp_bl_nits_to_slider(5ull << 16, 5, 500) == 0 && edp_bl_nits_to_slider(500ull << 16, 5, 500) == 65536);
for (uint32_t v = 0; v <= 65536; v += 512) {
uint32_t back = edp_bl_nits_to_slider(edp_bl_slider_to_nits(v, 5, 500), 5, 500);
CHECK(back + 1 >= v && back <= v + 1);
}
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 &&
edp_bl_reply_detail(x1) == EDP_BL_DETAIL_BITS && (x1 >> 40 & 0xff) == 0x06 && x1 >> 56 == 0x12);
/* the header's allow-lists */
CHECK(edp_bl_dpcd_write_ok(0x722, 2) && edp_bl_dpcd_write_ok(0x722, 1) && edp_bl_dpcd_write_ok(0x721, 1));
CHECK(!edp_bl_dpcd_write_ok(0x720, 1) && !edp_bl_dpcd_write_ok(0x721, 2) && !edp_bl_dpcd_write_ok(0x723, 1) &&
!edp_bl_dpcd_write_ok(0x600, 1) && !edp_bl_dpcd_write_ok(0x734, 3) && !edp_bl_dpcd_write_ok(0x722, 3) &&
!edp_bl_dpcd_write_ok(0x700, 4) && !edp_bl_dpcd_write_ok(0x00D, 1) && !edp_bl_dpcd_write_ok(0x721, 6));
CHECK(edp_bl_dpcd_read_ok(0x00D, 1) && edp_bl_dpcd_read_ok(0x700, 4) && edp_bl_dpcd_read_ok(0x721, 6) &&
!edp_bl_dpcd_read_ok(0x000, 16) && !edp_bl_dpcd_read_ok(0x700, 5));
for (int i = 0; i < R_N; i++) {
CHECK(edp_bl_mmio_read_ok(reg_pa[i]) == reg_readable[i]);
CHECK(edp_bl_mmio_write_ok(reg_pa[i]) == reg_writable[i]);
}
CHECK(!edp_bl_mmio_write_ok(M_AUX + 0x30) && !edp_bl_mmio_write_ok(M_AUX + 0x00) && !edp_bl_mmio_read_ok(0));
}
static void test_probe_and_set(void)
{
uint64_t r[4];
reset();
M.status = ST_DONE; /* a status the firmware left behind */
CHECK(call(EDP_BL_QUERY, 0, 0, r) == 0);
CHECK(edp_bl_reply_abi(r[1]) == EDP_BL_ABI && state_of(r) == EDP_BL_STATE_READY);
CHECK((uint32_t)r[2] == 205 && r[2] >> 32 == 2047 && level_of(r) == 768 && (r[3] >> 32 & 0xff) == 11 &&
(r[3] >> 40 & 1) == 1);
CHECK(M.dpcd_writes == 0 && M.gos == 3 && M.violations == 0);
CHECK(call(EDP_BL_SET, 1500, 0, r) == 0 && level_of(r) == 1500 && panel_level() == 1500 && !(r[3] >> 41 & 1));
CHECK(M.last_write_addr == 0x722 && M.last_write_len == 2 && M.last_write[0] == 0x05 && M.last_write[1] == 0xDC);
unsigned gos = M.gos;
CHECK(call(EDP_BL_SET, 1500, 0, r) == 0 && M.gos == gos); /* unchanged: no traffic */
CHECK(call(EDP_BL_SET, 0, 0, r) == 0 && panel_level() == 205 && (r[3] >> 41 & 1)); /* never black */
CHECK(call(EDP_BL_SET, 70000, 0, r) == 0 && panel_level() == 2047 && (r[3] >> 41 & 1)); /* never past max */
CHECK(M.dpcd[0x721] == 0x12 && M.violations == 0);
}
static void test_byte_panel(void)
{
uint64_t r[4];
reset();
M.dpcd[0x702] = 0x02; /* no BYTE_COUNT: 0x722 only */
M.dpcd[0x722] = 0x80; M.dpcd[0x723] = 0x55;
M.dpcd[0x724] = 8; M.dpcd[0x725] = 8; M.dpcd[0x726] = 8;
CHECK(call(EDP_BL_QUERY, 0, 0, r) == 0 && state_of(r) == EDP_BL_STATE_READY && level_of(r) == 0x80 &&
r[2] >> 32 == 255 && (uint32_t)r[2] == 26);
CHECK(call(EDP_BL_SET, 200, 0, r) == 0 && M.dpcd[0x722] == 200 && M.dpcd[0x723] == 0x55 && M.last_write_len == 1);
CHECK(M.violations == 0);
}
static void test_mode_switch(void)
{
uint64_t r[4];
reset();
M.dpcd[0x721] = 0x10; /* PWM pin mode, dynamic backlight bit set */
CHECK(call(EDP_BL_QUERY, 0, 0, r) == 0 && state_of(r) == EDP_BL_STATE_MODE && r[1] >> 56 == 0x10);
unsigned gos = M.gos;
CHECK(call(EDP_BL_SET, 900, 0, r) == EDP_BL_E_STATE && M.gos == gos && M.dpcd_writes == 0);
CHECK(call(EDP_BL_SET, 900, EDP_BL_SET_MODE, r) == 0 && state_of(r) == EDP_BL_STATE_READY);
CHECK(panel_level() == 900 && M.dpcd[0x721] == 0x12 && M.dpcd_writes == 2 && M.violations == 0);
/* the level was written before the mode */
CHECK(M.last_write_addr == 0x721);
reset();
M.dpcd[0x721] = 0x10;
M.ignore_mode = 1; /* the panel refuses the switch */
CHECK(call(EDP_BL_SET, 900, EDP_BL_SET_MODE, r) == EDP_BL_E_VERIFY && state_of(r) == EDP_BL_STATE_FAILED &&
edp_bl_reply_detail(r[1]) == EDP_BL_DETAIL_MODE);
CHECK(M.last_write_addr == 0x721 && M.last_write[0] == 0x10); /* the original mode written back */
gos = M.gos;
CHECK(call(EDP_BL_SET, 900, EDP_BL_SET_MODE, r) == EDP_BL_E_STATE && M.gos == gos);
CHECK(M.violations == 0);
}
static void test_refusals(void)
{
uint64_t r[4];
struct { int reg; uint32_t value; uint32_t detail; } off[] = {
{R_AHB_CBCR, 0x80000001u, EDP_BL_DETAIL_CLOCK}, {R_GDSCR, 0, EDP_BL_DETAIL_POWER},
{R_AUX_CBCR, 0x80000001u, EDP_BL_DETAIL_AUX_CLOCK}, {R_AUX_CTRL, 0, EDP_BL_DETAIL_AUX_OFF}};
for (unsigned i = 0; i < sizeof off / sizeof off[0]; i++) {
reset();
M.reg[off[i].reg] = off[i].value;
CHECK(call(EDP_BL_SET, 1000, 0, r) == EDP_BL_E_STATE && state_of(r) == EDP_BL_STATE_FAILED &&
edp_bl_reply_detail(r[1]) == off[i].detail);
CHECK(M.mmio_writes == 0 && M.gos == 0 && M.violations == 0);
if (off[i].reg <= R_AUX_CBCR) CHECK(M.dp_reads == 0); /* nothing in the DP block read unclocked */
}
reset();
M.dpcd[0x00D] = 0x03; /* not display-control capable */
CHECK(call(EDP_BL_QUERY, 0, 0, r) == 0 && state_of(r) == EDP_BL_STATE_UNSUPPORTED &&
edp_bl_reply_detail(r[1]) == EDP_BL_DETAIL_NO_CONTROL && M.gos == 1 && M.dpcd_writes == 0);
reset();
M.dpcd[0x702] = 0x01; /* PWM pin only */
CHECK(call(EDP_BL_SET, 1000, 0, r) == EDP_BL_E_STATE && edp_bl_reply_detail(r[1]) == EDP_BL_DETAIL_NO_AUX_SET &&
M.dpcd_writes == 0);
reset();
M.dpcd[0x725] = 12; M.dpcd[0x726] = 8; /* min > max */
CHECK(call(EDP_BL_SET, 1000, 0, r) == EDP_BL_E_STATE && edp_bl_reply_detail(r[1]) == EDP_BL_DETAIL_BITS &&
M.dpcd_writes == 0);
reset();
unsigned gos = M.gos;
CHECK(call(7, 0, 0, r) == EDP_BL_E_OP && M.gos == gos && M.mmio_reads == 0);
CHECK(call(EDP_BL_QUERY, 1, 0, r) == EDP_BL_E_OP && M.mmio_reads == 0);
CHECK(call(EDP_BL_SET, 1000, 2, r) == EDP_BL_E_OP && M.mmio_reads == 0);
CHECK(M.violations == 0);
}
static void test_io_failures(void)
{
uint64_t r[4];
reset();
CHECK(call(EDP_BL_QUERY, 0, 0, r) == 0);
M.nack = 1; /* one NACK: refused, level unchanged, then recovers */
CHECK(call(EDP_BL_SET, 1200, 0, r) == EDP_BL_E_IO && level_of(r) == 768 && (r[3] >> 48) == 1 &&
state_of(r) == EDP_BL_STATE_READY);
CHECK(call(EDP_BL_SET, 1200, 0, r) == 0 && level_of(r) == 1200 && (r[3] >> 48) == 0);
M.nack = 100; /* three failures in a row: stopped */
for (unsigned i = 0; i < EDP_BL_MAX_FAILURES; i++) CHECK(call(EDP_BL_SET, 300 + i, 0, r) == EDP_BL_E_IO);
CHECK(state_of(r) == EDP_BL_STATE_FAILED && level_of(r) == 1200);
unsigned gos = M.gos;
CHECK(call(EDP_BL_SET, 700, 0, r) == EDP_BL_E_STATE && M.gos == gos);
CHECK(M.violations == 0);
reset();
CHECK(call(EDP_BL_QUERY, 0, 0, r) == 0);
M.silent = 1; /* no completion: stopped at once, nothing follows into the FIFO */
uint64_t before = M.now;
CHECK(call(EDP_BL_SET, 1200, 0, r) == EDP_BL_E_IO && state_of(r) == EDP_BL_STATE_FAILED);
CHECK(M.now - before <= HZ / EDP_BL_AUX_BUDGET_DIV + 4000); /* one budget, not unbounded */
gos = M.gos;
M.silent = 0;
CHECK(call(EDP_BL_SET, 1300, 0, r) == EDP_BL_E_STATE && M.gos == gos);
reset();
M.silent = 1; /* silent at the probe */
CHECK(call(EDP_BL_QUERY, 0, 0, r) == 0 && state_of(r) == EDP_BL_STATE_FAILED &&
edp_bl_reply_detail(r[1]) == EDP_BL_DETAIL_AUX_IO && M.gos == 1);
reset();
CHECK(call(EDP_BL_QUERY, 0, 0, r) == 0);
M.ignore_level = 1; /* the read-back differs: stopped */
CHECK(call(EDP_BL_SET, 1600, 0, r) == EDP_BL_E_VERIFY && state_of(r) == EDP_BL_STATE_FAILED &&
edp_bl_reply_detail(r[1]) == EDP_BL_DETAIL_VERIFY && level_of(r) == 768);
CHECK(M.violations == 0);
}
/* The engine's own allow-list: a monitor callback refusing an access stops it for the boot. */
static int refuse_dp(void *ctx, uint64_t pa, uint32_t *v)
{
if (pa == reg_pa[R_AUX_CTRL]) return -1;
return read32(ctx, pa, v);
}
static void test_callback_refusal(void)
{
uint64_t r[4] = {0x0001000C, EDP_BL_QUERY, 0, 0};
const struct edp_backlight_io io = {0, refuse_dp, write32, counter, HZ};
reset();
CHECK(edp_backlight_call(&S, &io, r) == 0 && edp_bl_reply_state(r[1]) == EDP_BL_STATE_FAILED &&
edp_bl_reply_detail(r[1]) == EDP_BL_DETAIL_MMIO && M.mmio_writes == 0);
const struct edp_backlight_io slow = {0, read32, write32, counter, 10}; /* a clock that cannot time anything */
reset();
uint64_t q[4] = {0x0001000C, EDP_BL_QUERY, 0, 0};
CHECK(edp_backlight_call(&S, &slow, q) == 0 && edp_bl_reply_state(q[1]) == EDP_BL_STATE_FAILED &&
M.mmio_reads == 0);
}
int main(void)
{
test_helpers();
test_probe_and_set();
test_byte_panel();
test_mode_switch();
test_refusals();
test_io_failures();
test_callback_refusal();
printf("edp-backlight: PASS\n");
return 0;
}
+236
View File
@@ -0,0 +1,236 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
"""--edp-backlight, offline: the monitor engine against a controller model, weakened engines it must reject, the
opt-in wiring, and (on request) the default-build identity of the monitor and the kext. No hardware.
python3 tools/test-edp-backlight.py # model, mutations, wiring (seconds)
python3 tools/test-edp-backlight.py --build --qualification QUAL.json # + monitors: define absent, =0, =1
python3 tools/test-edp-backlight.py --kext-reference DIR # + kext: default sections == DIR's
Model: tools/test-edp-backlight.c runs compat/edp-backlight.{h,c} against a model of the eDP controller's AUX FIFO and a
DPCD panel and must PASS (ASan/UBSan). Each MUTATIONS entry weakens one safety property in a copy of the engine; the
same program must FAIL on every one.
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; installed-fresh appends the define last, adds the boot-args, and refuses until the AuxKC is
pinned.
--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.
--kext-reference: the default AdrenoX2 built from this tree has every section of DIR/AdrenoX2.kext's (a default build
of the base commit, tools/macho-sections.py), and PROFILE=desktop-backlight builds.
"""
from pathlib import Path
import argparse
import hashlib
import json
import os
import re
import shutil
import subprocess
import sys
import tempfile
import types
import unittest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / 'tools'))
_ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
_ap.add_argument('--build', action='store_true')
_ap.add_argument('--qualification', action='append', default=[])
_ap.add_argument('--kext-reference')
_ap.add_argument('--out')
ARGS, REST = _ap.parse_known_args() if __name__ == '__main__' else (_ap.parse_args([]), [])
ENGINE = ('compat/edp-backlight.h', 'compat/edp-backlight.c', 'compat/dpu-intf.h')
MUTATIONS = (
('no floor', 'compat/edp-backlight.h', '#define EDP_BL_FLOOR_PERMILLE 100u', '#define EDP_BL_FLOOR_PERMILLE 0u'),
('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);'),
('stale status kept', 'compat/edp-backlight.c', ' aux_ack(s, rd(s, AHB(EDP_AHB_INTR_STATUS)));', ' (void)0;'),
('reads writable', 'compat/edp-backlight.h', '(read ? sizeof ok / sizeof ok[0] : 3u)',
'(read || 1 ? sizeof ok / sizeof ok[0] : 3u)'),
('AUX_CTRL writable', 'compat/edp-backlight.h', '(uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_PHY_INTR_CLEAR)};',
'(uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_PHY_INTR_CLEAR), (uint32_t)(EDP_DP3_AUX_PA + EDP_AUX_CTRL)};'),
('timeout retried later', 'compat/edp-backlight.c', '{ s->timed_out = 1; goto fail; }', 'goto fail;'),
('no read-back', 'compat/edp-backlight.c', 'return back[0] == bytes[0] && (len < 2 || back[1] == bytes[1]) ? 0 : EDP_BL_E_VERIFY;',
'return 0;'),
('no clock check', 'compat/edp-backlight.c',
' {(uint32_t)(DPU_DISPCC_PA + DPU_DISPCC_MDSS_AHB_CBCR), DPU_CBCR_CLK_OFF, 0, EDP_BL_DETAIL_CLOCK},\n', ''),
('mode bits clobbered', 'compat/edp-backlight.c',
'(uint8_t)((s->mode_reg & ~DPCD_EDP_BL_MODE_MASK) | DPCD_EDP_BL_MODE_DPCD)', '(uint8_t)DPCD_EDP_BL_MODE_DPCD'),
('no failure limit', 'compat/edp-backlight.c', '++s->failures >= EDP_BL_MAX_FAILURES', '++s->failures >= 1000u'),
('AUX caps ignored', 'compat/edp-backlight.c', 'if (!(s->caps[2] & DPCD_EDP_BL_AUX_SET_CAP))', 'if (0)'),
)
CFLAGS = ['-std=gnu11', '-O1', '-g', '-Wall', '-Wextra', '-Werror', '-fsanitize=address,undefined',
'-fno-sanitize-recover=all']
def build_and_run(work, compat_dir):
exe = work / 'test-edp-backlight'
b = subprocess.run(['clang', *CFLAGS, f'-I{compat_dir}', str(ROOT / 'tools/test-edp-backlight.c'),
str(compat_dir / 'edp-backlight.c'), '-o', str(exe)], capture_output=True, text=True)
if b.returncode:
return 'build', b.stdout + b.stderr
r = subprocess.run([str(exe)], capture_output=True, text=True, timeout=120)
return r.returncode, r.stdout + r.stderr
def scope_violations(path, pattern, define):
"""Lines matching pattern outside every '#if...define' block (or in its #else)."""
stack, bad = [], []
for n, line in enumerate(path.read_text().split('\n'), 1):
s = line.strip()
if re.match(r'#\s*if', s):
stack.append([define in s and not s.startswith('#ifndef'), s])
continue
if re.match(r'#\s*(else|elif)', s) and stack:
stack[-1][0] = False
continue
if re.match(r'#\s*endif', s) and stack:
stack.pop()
continue
code = re.sub(r'//.*|/\*.*?(\*/|$)', '', s)
if s.startswith('*') or s.startswith('/*'):
continue
if re.search(pattern, code) and not any(on for on, _ in stack) and not re.match(r'#\s*define', s):
bad.append(f'{path.relative_to(ROOT)}:{n}: {s}')
return bad
class Model(unittest.TestCase):
def test_engine_passes(self):
with tempfile.TemporaryDirectory() as d:
code, out = build_and_run(Path(d), ROOT / 'compat')
self.assertEqual(code, 0, out)
self.assertIn('edp-backlight: PASS', out)
def test_mutations_fail(self):
for name, rel, old, new in MUTATIONS:
with self.subTest(name), tempfile.TemporaryDirectory() as d:
work = Path(d)
compat = work / 'compat'
compat.mkdir()
for f in ENGINE:
shutil.copy(ROOT / f, compat / Path(f).name)
target = compat / Path(rel).name
text = target.read_text()
self.assertEqual(text.count(old), 1, f'{name}: anchor not found once')
target.write_text(text.replace(old, new))
code, out = build_and_run(work, compat)
self.assertNotEqual(code, 'build', f'{name}: mutation does not compile: {out[-400:]}')
self.assertNotEqual(code, 0, f'{name}: the model accepted a weakened engine')
class Wiring(unittest.TestCase):
def test_monitor_default_off_and_guarded(self):
src = (ROOT / 'probes/xnuboot.c').read_text()
self.assertIn('#ifndef SNAP_EDP_BACKLIGHT\n#define SNAP_EDP_BACKLIGHT 0\n#endif', src)
bad = []
for path in [ROOT / 'probes/xnuboot.c', *sorted((ROOT / 'probes/xnuboot').glob('*.c'))]:
bad += scope_violations(path, r'edp_backlight|EDP_BL_|EV_EDP_BACKLIGHT|ADRENO_GUEST_BACKLIGHT',
'SNAP_EDP_BACKLIGHT')
self.assertEqual(bad, [])
def test_optional_unit(self):
sys.path.insert(0, str(ROOT / 'tools'))
import monitor_build_offline as mbo
self.assertNotIn('compat/edp-backlight.c', mbo.multi_sources(ROOT, []))
self.assertIn('compat/edp-backlight.c', mbo.multi_sources(ROOT, ['-DSNAP_EDP_BACKLIGHT=1']))
def test_numbers_unique(self):
calls = {}
for h in sorted((ROOT / 'compat').glob('*.h')):
for name, value in re.findall(r'#define (ADRENO_GUEST_\w+) (0x[0-9A-Fa-f]+)\b', h.read_text()):
calls.setdefault(int(value, 16), set()).add(name)
self.assertEqual({v: n for v, n in calls.items() if len(n) > 1}, {})
self.assertEqual(calls[0x0001000C], {'ADRENO_GUEST_BACKLIGHT'})
events = {}
for name, value in re.findall(r'#define (EV_\w+) +(\d+)', (ROOT / 'probes/xnuboot/report_layout.c').read_text()):
events.setdefault(int(value), set()).add(name)
self.assertEqual({v: n for v, n in events.items() if len(n) > 1}, {})
def test_kext_guarded(self):
kext = ROOT / 'gpu/adreno-kext/kext/src'
h = (kext / 'AdrenoDisplay.h').read_text()
self.assertIn('#ifndef ADRENO_BACKLIGHT\n#define ADRENO_BACKLIGHT 0\n#endif', h)
bad = []
for path in (kext / 'AdrenoDisplay.h', kext / 'AdrenoDisplay.cpp'):
bad += scope_violations(path, r'AdrenoBacklight|backlight|edp-backlight|fBacklight|set_brightness_gated',
'ADRENO_BACKLIGHT')
self.assertEqual(bad, [])
def test_cycle(self):
from cycle_lib import installed_fresh as f
chain = dict(variant='fast', input=True, input_touchpad=True, input_ptp=True, present_dartpool=True,
present_resident=True, present_beam=True, present_va_reclaim=True, gpu_va_wide=True,
present_latch=True)
self.assertEqual(f.selected(types.SimpleNamespace(**chain))[1], f.INPUT_AUXKC_PTP_DARTPOOL_RESIDENT_BEAM_VA_WIDE_LATCH)
if f.INPUT_AUXKC_DESKTOP_BACKLIGHT is None:
with self.assertRaises(BaseException):
f.selected(types.SimpleNamespace(**chain, edp_backlight=True))
else:
self.assertEqual(f.selected(types.SimpleNamespace(**chain, edp_backlight=True))[1],
f.INPUT_AUXKC_DESKTOP_BACKLIGHT)
with self.assertRaises(BaseException):
f.selected(types.SimpleNamespace(variant='fast', input=True, edp_backlight=True))
self.assertNotIn('adreno_backlight', f.boot_addition(types.SimpleNamespace()))
self.assertTrue(f.boot_addition(types.SimpleNamespace(edp_backlight=True)).endswith(' adreno_backlight=1'))
self.assertTrue(f.boot_addition(types.SimpleNamespace(edp_backlight=True, edp_backlight_mode=True)).endswith(
' adreno_backlight=1 adreno_backlight_mode=1'))
with self.assertRaises(BaseException):
f.boot_addition(types.SimpleNamespace(edp_backlight_mode=True))
src = (ROOT / 'tools/cycle_lib/installed_fresh.py').read_text()
body = src[src.index('def variant_flags('):]
body = body[:body.index('\n return flags\n')]
self.assertTrue(body.rstrip().endswith('flags.append(EDP_BACKLIGHT_DEFINE)'), 'the define is not appended last')
@unittest.skipUnless(ARGS.build, 'pass --build to compile the monitor (minutes)')
class Build(unittest.TestCase):
def test_monitor(self):
import monitor_build_offline as mbo
out = Path(ARGS.out) / 'monitors' if ARGS.out else Path(tempfile.mkdtemp(prefix='edp-backlight-build-'))
run = (ROOT / 'probes/run.py').read_text()
budget = int(re.search(r'^OUT_AT = (0x[0-9a-fA-F]+)', run, re.M).group(1), 16) - \
int(re.search(r'^CODE_AT = (0x[0-9a-fA-F]+)', run, re.M).group(1), 16)
results = {}
for path in ARGS.qualification:
record = json.loads(Path(path).read_text())
name, blobs = Path(path).stem, {}
for label, extra in (('default', []), ('off', ['-DSNAP_EDP_BACKLIGHT=0']), ('on', ['-DSNAP_EDP_BACKLIGHT=1'])):
r = mbo.build(ROOT, out / f'{name}-{label}', list(record['flags']) + extra)
blobs[label] = r['sha256']
results[f'{name}-{label}'] = dict(bytes=r['bytes'], headroom=budget - r['bytes'], sha256=r['sha256'])
self.assertGreater(budget - r['bytes'], 0, f'{name}-{label} exceeds the code budget')
self.assertEqual(blobs['default'], record['monitor_sha256'], name)
self.assertEqual(blobs['off'], blobs['default'], name)
self.assertNotEqual(blobs['on'], blobs['default'], name)
print(json.dumps(results, indent=1), file=sys.stderr)
@unittest.skipUnless(ARGS.kext_reference, 'pass --kext-reference DIR to build the kext')
class Kext(unittest.TestCase):
def test_default_sections(self):
out = Path(ARGS.out) / 'kext' if ARGS.out else Path(tempfile.mkdtemp(prefix='edp-backlight-kext-'))
env = dict(os.environ, ZERO_AR_DATE='1')
kext = ROOT / 'gpu/adreno-kext/kext/build.sh'
for profile, dest in (('', out / 'default'), ('desktop-backlight', out / 'desktop-backlight')):
e = dict(env, OUT=str(dest), **({'PROFILE': profile} if profile else {}))
b = subprocess.run(['sh', str(kext)], env=e, capture_output=True, text=True)
self.assertEqual(b.returncode, 0, b.stdout[-2000:] + b.stderr[-2000:])
ref = Path(ARGS.kext_reference) / 'AdrenoX2.kext/Contents/MacOS/AdrenoX2'
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)
nm = subprocess.run(['nm', '-m', str(out / 'desktop-backlight/AdrenoX2.kext/Contents/MacOS/AdrenoX2')],
capture_output=True, text=True).stdout
self.assertIn('__ZN13AdrenoDisplay20set_brightness_gatedEi', nm)
self.assertIn('__ZN15AdrenoBacklight10gMetaClassE', nm)
if __name__ == '__main__':
os.chdir(ROOT)
unittest.main(argv=[sys.argv[0], *REST], verbosity=2)