From b9c12cb1585fec9828d9b0e8ed87f754806a31ce Mon Sep 17 00:00:00 2001 From: Acelogic <7362641+Acelogic@users.noreply.github.com> Date: Sat, 24 Oct 2026 03:19:35 -0400 Subject: [PATCH] compat: let the IB validator admit opted-in CS sampler units CoreImage's multi-input kernels (the folder, document and Safari icons inside System Settings panes) load one S# per sampled input. The validator admits exactly one CS sampler unit per LOAD_STATE. ADRENO_IB_CS_SAMPLERS (default off) adds adreno_ib_set_cs_sampler_units: 1 (the default, as without the define) up to ADRENO_IB_CS_SAMPLER_UNITS_MAX (8), any other value meaning 1. An S# holds no address and the load stays bounded by its package relocation (NUM_UNIT * 16 bytes), as an FS sampler load is; DST_OFF is still refused. SnapAdreno's AdrenoX2 calls it once, from configureDevice, only with the boot-arg adreno_cs_samplers=1. tools/test-adreno-ib-command.c gains test_ib_cs_samplers: one unit admitted, two refused (E_PACKET) and DST_OFF refused in every build; with the define, after the opt-in, 2 and 8 units admitted, 9 refused (E_PACKET), 3 units over a 2-entry table refused (E_BOUNDS), and the limit back to one for 0 and 9. -DIB_REPLAY_CS_SAMPLER_UNITS=N makes --replay validate as an opted-in kext (the settings-icons suites). Evidence: self test (ASan/UBSan) PASS 47286 IB command checks without the define (46714 before), 47286 with it 0, 48982 with it 1; three validator mutants (opt-in ignored, two units without the define, no clamp) each fail it. Without the define the AdrenoX2 kexts (default, desktop-nocoherent, desktop) are byte-identical to the unchanged tree's (ZERO_AR_DATE=1, same OUT path). --- compat/adreno-ib-command.h | 9 +++++ compat/adreno-ib-command/validate.c | 17 ++++++++- tools/test-adreno-ib-command.c | 57 ++++++++++++++++++++++++++++- 3 files changed, 79 insertions(+), 4 deletions(-) diff --git a/compat/adreno-ib-command.h b/compat/adreno-ib-command.h index e6c3e5e..c2a2d85 100644 --- a/compat/adreno-ib-command.h +++ b/compat/adreno-ib-command.h @@ -123,6 +123,15 @@ int adreno_ib_relocate(struct adreno_ib_work *, * UBO/S#/T#/UAV loads, consistent local/global/group geometry, and resource-WRITE linear 2D RGBA32Float * storage descriptors with a checked row extent. See docs/ADRENO-COMPUTE-PACKETS-20261019.md. */ int adreno_ib_validate(struct adreno_ib_work *); +#if ADRENO_IB_CS_SAMPLERS +/* The S# units one CS sampler load (CP_LOAD_STATE6_FRAG, SB6_CS_TEX, ST6_SHADER) may carry in the IBs validated after + * the call: 1 (the fixed limit without ADRENO_IB_CS_SAMPLERS, and the default) up to ADRENO_IB_CS_SAMPLER_UNITS_MAX; + * any other value sets 1. CoreImage's multi-input kernels sample T#1..N with samplers 0..N-1. An S# holds no address, + * and the load stays bounded by its package relocation (NUM_UNIT * 16 bytes), as an FS sampler load is. Process-wide; + * the kext sets it once, before it publishes ADRENO_IB_REGISTRY_PROPERTY, so no validation runs concurrently. */ +#define ADRENO_IB_CS_SAMPLER_UNITS_MAX 8u +void adreno_ib_set_cs_sampler_units(uint32_t units); +#endif /* The RESOURCE relocation range rule (design 3.2; AdrenoMemoryMap::resolveRange calls it under the map lock): * a reserved, pinned, fully mapped window resource and [offset, offset + bytes) inside its logical length, with * no FILL32-style size or dword limits. 0 and *address = resource VA + offset, or ADRENO_RESOURCE_E_*. */ diff --git a/compat/adreno-ib-command/validate.c b/compat/adreno-ib-command/validate.c index 8c5d647..534c2d6 100644 --- a/compat/adreno-ib-command/validate.c +++ b/compat/adreno-ib-command/validate.c @@ -60,6 +60,18 @@ static int check_textures(struct adreno_ib_work *w, int32_t i, uint64_t count, u * live image BASE is RESOURCE/WRITE. */ #define ADRENO_IB_CS_STORAGE_MAX 8u #define ADRENO_IB_CS_TEXTURE_MAX 8u +#if ADRENO_IB_CS_SAMPLERS +_Static_assert(ADRENO_IB_CS_SAMPLER_UNITS_MAX <= ADRENO_IB_CS_TEXTURE_MAX, + "a CS sampler table is never larger than the T# table it samples"); +static uint32_t cs_sampler_units = 1u; +void adreno_ib_set_cs_sampler_units(uint32_t units) +{ + cs_sampler_units = units && units <= ADRENO_IB_CS_SAMPLER_UNITS_MAX ? units : 1u; +} +#define ADRENO_IB_CS_SAMPLER_UNITS cs_sampler_units +#else +#define ADRENO_IB_CS_SAMPLER_UNITS 1u +#endif static int check_storage_images(struct adreno_ib_work *w, int32_t i, uint64_t count, uint32_t at) { const struct adreno_ib_reloc *table = &w->relocs[i]; @@ -233,10 +245,11 @@ static int load_state(struct adreno_ib_work *w, uint32_t op, uint32_t at, uint32 (type == A8XX_E_ST6_SHADER || type == A8XX_E_ST6_CONSTANTS); if (src == A8XX_E_SS6_INDIRECT && op == A8XX_E_CP_LOAD_STATE6_FRAG && (texture_load || cs_uav)) { if (count != 3) return fail(w, ADRENO_IB_E_PACKET, at - 4); - /* CS samplers: one unit. CS T#s: 1..ADRENO_IB_CS_TEXTURE_MAX units (CoreImage's T#1 with a null T#0), each + /* CS samplers: one unit (ADRENO_IB_CS_SAMPLERS: up to adreno_ib_set_cs_sampler_units' count, bounded by the + * relocation below). CS T#s: 1..ADRENO_IB_CS_TEXTURE_MAX units (CoreImage's T#1 with a null T#0), each * checked by check_textures below. */ if (block == A8XX_E_SB6_CS_TEX && - (!units || units > (type == A8XX_E_ST6_CONSTANTS ? ADRENO_IB_CS_TEXTURE_MAX : 1u) || + (!units || units > (type == A8XX_E_ST6_CONSTANTS ? ADRENO_IB_CS_TEXTURE_MAX : ADRENO_IB_CS_SAMPLER_UNITS) || (w0 & A8XX_PKT_CP_LOAD_STATE6_0__DST_OFF__MASK))) return fail(w, ADRENO_IB_E_PACKET, at); const struct field ext = {A8XX_PKT_CP_LOAD_STATE6_1__EXT_SRC_ADDR__MASK, 0, 0xffffffffu, ADRENO_IB_HI_BITS_REG64, diff --git a/tools/test-adreno-ib-command.c b/tools/test-adreno-ib-command.c index 032d094..dc11061 100644 --- a/tools/test-adreno-ib-command.c +++ b/tools/test-adreno-ib-command.c @@ -31,7 +31,9 @@ * F10 the zero page is not at PoC before the first IB (R1); the package is not what the CP fetches. * Built with -DSNAP_ADRENO_IB_ASYNC=1 every IB goes through the kext's START/POLL loop (compat/adreno-ib-async.h) * and a CP that takes model time, so the list keeps its preparation across the POLLs (agents/async-ib - * FAILURE-MODES.md A17); SNAP_IB_PATH=sync runs the same suite through that build's synchronous ADRENO_GUEST_IB. */ + * FAILURE-MODES.md A17); SNAP_IB_PATH=sync runs the same suite through that build's synchronous ADRENO_GUEST_IB. + * Built with -DADRENO_IB_CS_SAMPLERS=1 the CS sampler checks also cover the count a kext opts into; adding + * -DIB_REPLAY_CS_SAMPLER_UNITS=N makes --replay validate as a kext booted with adreno_cs_samplers=1 (N = 8). */ #include #include #include @@ -600,6 +602,53 @@ static void test_ib_draw(void) CHECK(adreno_resource_list_release(&ib_list, &ibfx_ops) == 0); CHECK(ibfx[0].retained == 0 && ibfx[1].retained == 0 && ibfx[0].prepared == 0); } +/* One CS sampler load (CP_LOAD_STATE6_FRAG SB6_CS_TEX ST6_SHADER, indirect) of `units` S#s from a package table of + * `table` zero S#s, DST_OFF `dst`, through the whole kext path: the submission's result. */ +static int ib_cs_sampler_load(uint32_t units, uint32_t table, uint32_t dst) +{ + ib_setup(); + ib_standard_fixtures(); + struct ib_pkg k; + memset(&k, 0, sizeof k); + pk_pkt7(&k, A8XX_E_CP_SET_MARKER, 1); pk_word(&k, A8XX_PKT_A8XX_CP_SET_MARKER_0__MODE(A8XX_E_RM6_COMPUTE)); + pk_pkt7(&k, A8XX_E_CP_LOAD_STATE6_FRAG, 3); + pk_word(&k, A8XX_PKT_CP_LOAD_STATE6_0__DST_OFF(dst) | A8XX_PKT_CP_LOAD_STATE6_0__STATE_TYPE(A8XX_E_ST6_SHADER) | + A8XX_PKT_CP_LOAD_STATE6_0__STATE_SRC(A8XX_E_SS6_INDIRECT) | + A8XX_PKT_CP_LOAD_STATE6_0__STATE_BLOCK(A8XX_E_SB6_CS_TEX) | A8XX_PKT_CP_LOAD_STATE6_0__NUM_UNIT(units)); + pk_word(&k, 0); pk_word(&k, 0); + const uint32_t ext = k.n - 2; + pk_pkt7(&k, A8XX_E_CP_EVENT_WRITE7, 1); pk_word(&k, A8XX_PKT_CP_EVENT_WRITE7_0__EVENT(A8XX_E_CACHE_CLEAN)); + k.data_bytes = 192; /* room for 12 S#s, zero */ + pk_reloc(&k, ext, ext + 1, ADRENO_IB_RELOC_PACKAGE, ADRENO_IB_ACCESS_READ, 4, 32, 0, pk_data_at(&k), 16ull * table); + size_t n = pk_serialize(&k, ib_command_bytes); + CHECK(ib_standard_list(&ib_list, ib_entries) == 0); + int result = ib_command(&ib_list, ib_command_bytes, n); + CHECK(adreno_resource_list_release(&ib_list, &ibfx_ops) == 0); + return result; +} +/* CS sampler loads: one S# unit, the limit the validator has always had; with ADRENO_IB_CS_SAMPLERS, up to the count + * the kext opts into (the boot-arg adreno_cs_samplers=1: ADRENO_IB_CS_SAMPLER_UNITS_MAX), still bounded by the table's + * relocation and refused with a DST_OFF, and back to one unit for a count outside 1..MAX. */ +static void test_ib_cs_samplers(void) +{ + CHECK(ib_cs_sampler_load(1, 1, 0) == 0); + CHECK(ib_cs_sampler_load(2, 2, 0) == ADRENO_IB_E_PACKET); + CHECK(ib_cs_sampler_load(1, 1, 1) == ADRENO_IB_E_PACKET); +#if ADRENO_IB_CS_SAMPLERS + adreno_ib_set_cs_sampler_units(ADRENO_IB_CS_SAMPLER_UNITS_MAX); + CHECK(ib_cs_sampler_load(1, 1, 0) == 0); + CHECK(ib_cs_sampler_load(2, 2, 0) == 0); + CHECK(ib_cs_sampler_load(ADRENO_IB_CS_SAMPLER_UNITS_MAX, ADRENO_IB_CS_SAMPLER_UNITS_MAX, 0) == 0); + CHECK(ib_cs_sampler_load(ADRENO_IB_CS_SAMPLER_UNITS_MAX + 1, ADRENO_IB_CS_SAMPLER_UNITS_MAX + 1, 0) == ADRENO_IB_E_PACKET); + CHECK(ib_cs_sampler_load(3, 2, 0) == ADRENO_IB_E_BOUNDS); /* more S#s than the relocated table holds */ + CHECK(ib_cs_sampler_load(2, 2, 1) == ADRENO_IB_E_PACKET); + adreno_ib_set_cs_sampler_units(0); + CHECK(ib_cs_sampler_load(2, 2, 0) == ADRENO_IB_E_PACKET); + adreno_ib_set_cs_sampler_units(ADRENO_IB_CS_SAMPLER_UNITS_MAX + 1); + CHECK(ib_cs_sampler_load(2, 2, 0) == ADRENO_IB_E_PACKET); + adreno_ib_set_cs_sampler_units(1); +#endif +} /* F6: every reply the monitor can give, through the whole kext path (fake monitor replies). */ static void test_ib_replies(void) { @@ -949,6 +998,9 @@ int main(int argc, char **argv) dram = calloc(1, RAM_SIZE); cache = calloc(1, RAM_SIZE); ib_arena = aligned_alloc(0x4000, ADRENO_IB_ARENA_BYTES); if (!dram || !cache || !ib_arena || adreno_ib_tables_init(&ib_tables)) return 2; +#if ADRENO_IB_CS_SAMPLERS && defined(IB_REPLAY_CS_SAMPLER_UNITS) + adreno_ib_set_cs_sampler_units(IB_REPLAY_CS_SAMPLER_UNITS); /* as a kext booted with adreno_cs_samplers=1 */ +#endif return replay(argv[2]); } if (argc != 1) return 64; @@ -958,7 +1010,8 @@ int main(int argc, char **argv) ib_arena = aligned_alloc(0x4000, ADRENO_IB_ARENA_BYTES); if (!dram || !cache || !ib_arena) return 2; CHECK(adreno_ib_tables_init(&ib_tables) == 0); - test_ib_tables(); test_ib_probe(); test_ib_draw(); test_ib_replies(); test_ib_fail_fast(); test_fence_ring_wrap(); + test_ib_tables(); test_ib_probe(); test_ib_draw(); test_ib_cs_samplers(); test_ib_replies(); test_ib_fail_fast(); + test_fence_ring_wrap(); #if MONITOR_IB test_ib_monitor_states(); #endif