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:
commit
5b35148492
19 files changed
+1406
-7
No files matched your search
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
+1
-1
Submodule gpu/adreno-kext/kext updated: c1b4ad57a9...53c00544d8.
+2
-1
@@ -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):
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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++)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)')
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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()
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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)
|
||||
Reference in new issue
Block a user