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).
This commit is contained in:
Acelogic committed 2026-10-24 03:19:35 -04:00
1 parent 58bc2aee86
commit b9c12cb158
3 files changed
+79 -4

No files matched your search

+9
View File
@@ -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_*. */
+15 -2
View File
@@ -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,
+55 -2
View File
@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -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