123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475 |
- From f2ba2314600620134530571d3b8b22de2ad5745b Mon Sep 17 00:00:00 2001
- From: Eric Anholt <eric@anholt.net>
- Date: Sat, 2 Jul 2016 12:17:10 -0700
- Subject: [PATCH] drm/vc4: Add support for branching in shader validation.
- We're already checking that branch instructions are between the start
- of the shader and the proper PROG_END sequence. The other thing we
- need to make branching safe is to verify that the shader doesn't read
- past the end of the uniforms stream.
- To do that, we require that at any basic block reading uniforms have
- the following instructions:
- load_imm temp, <next offset within uniform stream>
- add unif_addr, temp, unif
- The instructions are generated by userspace, and the kernel verifies
- that the load_imm is of the expected offset, and that the add adds it
- to a uniform. We track which uniform in the stream that is, and at
- draw call time fix up the uniform stream to have the address of the
- start of the shader's uniforms at that location.
- Signed-off-by: Eric Anholt <eric@anholt.net>
- (cherry picked from commit 6d45c81d229d71da54d374143e7d6abad4c0cf31)
- ---
- drivers/gpu/drm/vc4/vc4_drv.h | 3 +
- drivers/gpu/drm/vc4/vc4_qpu_defines.h | 3 +
- drivers/gpu/drm/vc4/vc4_validate.c | 13 +-
- drivers/gpu/drm/vc4/vc4_validate_shaders.c | 281 +++++++++++++++++++++++++++--
- 4 files changed, 283 insertions(+), 17 deletions(-)
- --- a/drivers/gpu/drm/vc4/vc4_drv.h
- +++ b/drivers/gpu/drm/vc4/vc4_drv.h
- @@ -363,6 +363,9 @@ struct vc4_validated_shader_info {
- uint32_t uniforms_src_size;
- uint32_t num_texture_samples;
- struct vc4_texture_sample_info *texture_samples;
- +
- + uint32_t num_uniform_addr_offsets;
- + uint32_t *uniform_addr_offsets;
- };
-
- /**
- --- a/drivers/gpu/drm/vc4/vc4_qpu_defines.h
- +++ b/drivers/gpu/drm/vc4/vc4_qpu_defines.h
- @@ -270,6 +270,9 @@ enum qpu_unpack_r4 {
- #define QPU_OP_ADD_SHIFT 24
- #define QPU_OP_ADD_MASK QPU_MASK(28, 24)
-
- +#define QPU_LOAD_IMM_SHIFT 0
- +#define QPU_LOAD_IMM_MASK QPU_MASK(31, 0)
- +
- #define QPU_BRANCH_TARGET_SHIFT 0
- #define QPU_BRANCH_TARGET_MASK QPU_MASK(31, 0)
-
- --- a/drivers/gpu/drm/vc4/vc4_validate.c
- +++ b/drivers/gpu/drm/vc4/vc4_validate.c
- @@ -802,7 +802,7 @@ validate_gl_shader_rec(struct drm_device
- uint32_t src_offset = *(uint32_t *)(pkt_u + o);
- uint32_t *texture_handles_u;
- void *uniform_data_u;
- - uint32_t tex;
- + uint32_t tex, uni;
-
- *(uint32_t *)(pkt_v + o) = bo[i]->paddr + src_offset;
-
- @@ -840,6 +840,17 @@ validate_gl_shader_rec(struct drm_device
- }
- }
-
- + /* Fill in the uniform slots that need this shader's
- + * start-of-uniforms address (used for resetting the uniform
- + * stream in the presence of control flow).
- + */
- + for (uni = 0;
- + uni < validated_shader->num_uniform_addr_offsets;
- + uni++) {
- + uint32_t o = validated_shader->uniform_addr_offsets[uni];
- + ((uint32_t *)exec->uniforms_v)[o] = exec->uniforms_p;
- + }
- +
- *(uint32_t *)(pkt_v + o + 4) = exec->uniforms_p;
-
- exec->uniforms_u += validated_shader->uniforms_src_size;
- --- a/drivers/gpu/drm/vc4/vc4_validate_shaders.c
- +++ b/drivers/gpu/drm/vc4/vc4_validate_shaders.c
- @@ -39,6 +39,8 @@
- #include "vc4_drv.h"
- #include "vc4_qpu_defines.h"
-
- +#define LIVE_REG_COUNT (32 + 32 + 4)
- +
- struct vc4_shader_validation_state {
- /* Current IP being validated. */
- uint32_t ip;
- @@ -57,8 +59,9 @@ struct vc4_shader_validation_state {
- *
- * This is used for the validation of direct address memory reads.
- */
- - uint32_t live_min_clamp_offsets[32 + 32 + 4];
- - bool live_max_clamp_regs[32 + 32 + 4];
- + uint32_t live_min_clamp_offsets[LIVE_REG_COUNT];
- + bool live_max_clamp_regs[LIVE_REG_COUNT];
- + uint32_t live_immediates[LIVE_REG_COUNT];
-
- /* Bitfield of which IPs are used as branch targets.
- *
- @@ -66,6 +69,20 @@ struct vc4_shader_validation_state {
- * points and clearing the texturing/clamping state.
- */
- unsigned long *branch_targets;
- +
- + /* Set when entering a basic block, and cleared when the uniform
- + * address update is found. This is used to make sure that we don't
- + * read uniforms when the address is undefined.
- + */
- + bool needs_uniform_address_update;
- +
- + /* Set when we find a backwards branch. If the branch is backwards,
- + * the taraget is probably doing an address reset to read uniforms,
- + * and so we need to be sure that a uniforms address is present in the
- + * stream, even if the shader didn't need to read uniforms in later
- + * basic blocks.
- + */
- + bool needs_uniform_address_for_loop;
- };
-
- static uint32_t
- @@ -227,8 +244,14 @@ check_tmu_write(struct vc4_validated_sha
- /* Since direct uses a RADDR uniform reference, it will get counted in
- * check_instruction_reads()
- */
- - if (!is_direct)
- + if (!is_direct) {
- + if (validation_state->needs_uniform_address_update) {
- + DRM_ERROR("Texturing with undefined uniform address\n");
- + return false;
- + }
- +
- validated_shader->uniforms_size += 4;
- + }
-
- if (submit) {
- if (!record_texture_sample(validated_shader,
- @@ -242,6 +265,98 @@ check_tmu_write(struct vc4_validated_sha
- return true;
- }
-
- +static bool require_uniform_address_uniform(struct vc4_validated_shader_info *validated_shader)
- +{
- + uint32_t o = validated_shader->num_uniform_addr_offsets;
- + uint32_t num_uniforms = validated_shader->uniforms_size / 4;
- +
- + validated_shader->uniform_addr_offsets =
- + krealloc(validated_shader->uniform_addr_offsets,
- + (o + 1) *
- + sizeof(*validated_shader->uniform_addr_offsets),
- + GFP_KERNEL);
- + if (!validated_shader->uniform_addr_offsets)
- + return false;
- +
- + validated_shader->uniform_addr_offsets[o] = num_uniforms;
- + validated_shader->num_uniform_addr_offsets++;
- +
- + return true;
- +}
- +
- +static bool
- +validate_uniform_address_write(struct vc4_validated_shader_info *validated_shader,
- + struct vc4_shader_validation_state *validation_state,
- + bool is_mul)
- +{
- + uint64_t inst = validation_state->shader[validation_state->ip];
- + u32 add_b = QPU_GET_FIELD(inst, QPU_ADD_B);
- + u32 raddr_a = QPU_GET_FIELD(inst, QPU_RADDR_A);
- + u32 raddr_b = QPU_GET_FIELD(inst, QPU_RADDR_B);
- + u32 add_lri = raddr_add_a_to_live_reg_index(inst);
- + /* We want our reset to be pointing at whatever uniform follows the
- + * uniforms base address.
- + */
- + u32 expected_offset = validated_shader->uniforms_size + 4;
- +
- + /* We only support absolute uniform address changes, and we
- + * require that they be in the current basic block before any
- + * of its uniform reads.
- + *
- + * One could potentially emit more efficient QPU code, by
- + * noticing that (say) an if statement does uniform control
- + * flow for all threads and that the if reads the same number
- + * of uniforms on each side. However, this scheme is easy to
- + * validate so it's all we allow for now.
- + */
- +
- + if (QPU_GET_FIELD(inst, QPU_SIG) != QPU_SIG_NONE) {
- + DRM_ERROR("uniforms address change must be "
- + "normal math\n");
- + return false;
- + }
- +
- + if (is_mul || QPU_GET_FIELD(inst, QPU_OP_ADD) != QPU_A_ADD) {
- + DRM_ERROR("Uniform address reset must be an ADD.\n");
- + return false;
- + }
- +
- + if (QPU_GET_FIELD(inst, QPU_COND_ADD) != QPU_COND_ALWAYS) {
- + DRM_ERROR("Uniform address reset must be unconditional.\n");
- + return false;
- + }
- +
- + if (QPU_GET_FIELD(inst, QPU_PACK) != QPU_PACK_A_NOP &&
- + !(inst & QPU_PM)) {
- + DRM_ERROR("No packing allowed on uniforms reset\n");
- + return false;
- + }
- +
- + if (add_lri == -1) {
- + DRM_ERROR("First argument of uniform address write must be "
- + "an immediate value.\n");
- + return false;
- + }
- +
- + if (validation_state->live_immediates[add_lri] != expected_offset) {
- + DRM_ERROR("Resetting uniforms with offset %db instead of %db\n",
- + validation_state->live_immediates[add_lri],
- + expected_offset);
- + return false;
- + }
- +
- + if (!(add_b == QPU_MUX_A && raddr_a == QPU_R_UNIF) &&
- + !(add_b == QPU_MUX_B && raddr_b == QPU_R_UNIF)) {
- + DRM_ERROR("Second argument of uniform address write must be "
- + "a uniform.\n");
- + return false;
- + }
- +
- + validation_state->needs_uniform_address_update = false;
- + validation_state->needs_uniform_address_for_loop = false;
- + return require_uniform_address_uniform(validated_shader);
- +}
- +
- static bool
- check_reg_write(struct vc4_validated_shader_info *validated_shader,
- struct vc4_shader_validation_state *validation_state,
- @@ -251,14 +366,37 @@ check_reg_write(struct vc4_validated_sha
- uint32_t waddr = (is_mul ?
- QPU_GET_FIELD(inst, QPU_WADDR_MUL) :
- QPU_GET_FIELD(inst, QPU_WADDR_ADD));
- + uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
- + bool ws = inst & QPU_WS;
- + bool is_b = is_mul ^ ws;
- + u32 lri = waddr_to_live_reg_index(waddr, is_b);
- +
- + if (lri != -1) {
- + uint32_t cond_add = QPU_GET_FIELD(inst, QPU_COND_ADD);
- + uint32_t cond_mul = QPU_GET_FIELD(inst, QPU_COND_MUL);
- +
- + if (sig == QPU_SIG_LOAD_IMM &&
- + QPU_GET_FIELD(inst, QPU_PACK) == QPU_PACK_A_NOP &&
- + ((is_mul && cond_mul == QPU_COND_ALWAYS) ||
- + (!is_mul && cond_add == QPU_COND_ALWAYS))) {
- + validation_state->live_immediates[lri] =
- + QPU_GET_FIELD(inst, QPU_LOAD_IMM);
- + } else {
- + validation_state->live_immediates[lri] = ~0;
- + }
- + }
-
- switch (waddr) {
- case QPU_W_UNIFORMS_ADDRESS:
- - /* XXX: We'll probably need to support this for reladdr, but
- - * it's definitely a security-related one.
- - */
- - DRM_ERROR("uniforms address load unsupported\n");
- - return false;
- + if (is_b) {
- + DRM_ERROR("relative uniforms address change "
- + "unsupported\n");
- + return false;
- + }
- +
- + return validate_uniform_address_write(validated_shader,
- + validation_state,
- + is_mul);
-
- case QPU_W_TLB_COLOR_MS:
- case QPU_W_TLB_COLOR_ALL:
- @@ -406,9 +544,35 @@ check_instruction_writes(struct vc4_vali
- }
-
- static bool
- -check_instruction_reads(uint64_t inst,
- - struct vc4_validated_shader_info *validated_shader)
- +check_branch(uint64_t inst,
- + struct vc4_validated_shader_info *validated_shader,
- + struct vc4_shader_validation_state *validation_state,
- + int ip)
- {
- + int32_t branch_imm = QPU_GET_FIELD(inst, QPU_BRANCH_TARGET);
- + uint32_t waddr_add = QPU_GET_FIELD(inst, QPU_WADDR_ADD);
- + uint32_t waddr_mul = QPU_GET_FIELD(inst, QPU_WADDR_MUL);
- +
- + if ((int)branch_imm < 0)
- + validation_state->needs_uniform_address_for_loop = true;
- +
- + /* We don't want to have to worry about validation of this, and
- + * there's no need for it.
- + */
- + if (waddr_add != QPU_W_NOP || waddr_mul != QPU_W_NOP) {
- + DRM_ERROR("branch instruction at %d wrote a register.\n",
- + validation_state->ip);
- + return false;
- + }
- +
- + return true;
- +}
- +
- +static bool
- +check_instruction_reads(struct vc4_validated_shader_info *validated_shader,
- + struct vc4_shader_validation_state *validation_state)
- +{
- + uint64_t inst = validation_state->shader[validation_state->ip];
- uint32_t raddr_a = QPU_GET_FIELD(inst, QPU_RADDR_A);
- uint32_t raddr_b = QPU_GET_FIELD(inst, QPU_RADDR_B);
- uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG);
- @@ -420,6 +584,12 @@ check_instruction_reads(uint64_t inst,
- * already be OOM.
- */
- validated_shader->uniforms_size += 4;
- +
- + if (validation_state->needs_uniform_address_update) {
- + DRM_ERROR("Uniform read with undefined uniform "
- + "address\n");
- + return false;
- + }
- }
-
- return true;
- @@ -516,6 +686,65 @@ vc4_validate_branches(struct vc4_shader_
- return true;
- }
-
- +/* Resets any known state for the shader, used when we may be branched to from
- + * multiple locations in the program (or at shader start).
- + */
- +static void
- +reset_validation_state(struct vc4_shader_validation_state *validation_state)
- +{
- + int i;
- +
- + for (i = 0; i < 8; i++)
- + validation_state->tmu_setup[i / 4].p_offset[i % 4] = ~0;
- +
- + for (i = 0; i < LIVE_REG_COUNT; i++) {
- + validation_state->live_min_clamp_offsets[i] = ~0;
- + validation_state->live_max_clamp_regs[i] = false;
- + validation_state->live_immediates[i] = ~0;
- + }
- +}
- +
- +static bool
- +texturing_in_progress(struct vc4_shader_validation_state *validation_state)
- +{
- + return (validation_state->tmu_write_count[0] != 0 ||
- + validation_state->tmu_write_count[1] != 0);
- +}
- +
- +static bool
- +vc4_handle_branch_target(struct vc4_shader_validation_state *validation_state)
- +{
- + uint32_t ip = validation_state->ip;
- +
- + if (!test_bit(ip, validation_state->branch_targets))
- + return true;
- +
- + if (texturing_in_progress(validation_state)) {
- + DRM_ERROR("Branch target landed during TMU setup\n");
- + return false;
- + }
- +
- + /* Reset our live values tracking, since this instruction may have
- + * multiple predecessors.
- + *
- + * One could potentially do analysis to determine that, for
- + * example, all predecessors have a live max clamp in the same
- + * register, but we don't bother with that.
- + */
- + reset_validation_state(validation_state);
- +
- + /* Since we've entered a basic block from potentially multiple
- + * predecessors, we need the uniforms address to be updated before any
- + * unforms are read. We require that after any branch point, the next
- + * uniform to be loaded is a uniform address offset. That uniform's
- + * offset will be marked by the uniform address register write
- + * validation, or a one-off the end-of-program check.
- + */
- + validation_state->needs_uniform_address_update = true;
- +
- + return true;
- +}
- +
- struct vc4_validated_shader_info *
- vc4_validate_shader(struct drm_gem_cma_object *shader_obj)
- {
- @@ -524,16 +753,12 @@ vc4_validate_shader(struct drm_gem_cma_o
- uint32_t ip;
- struct vc4_validated_shader_info *validated_shader = NULL;
- struct vc4_shader_validation_state validation_state;
- - int i;
-
- memset(&validation_state, 0, sizeof(validation_state));
- validation_state.shader = shader_obj->vaddr;
- validation_state.max_ip = shader_obj->base.size / sizeof(uint64_t);
-
- - for (i = 0; i < 8; i++)
- - validation_state.tmu_setup[i / 4].p_offset[i % 4] = ~0;
- - for (i = 0; i < ARRAY_SIZE(validation_state.live_min_clamp_offsets); i++)
- - validation_state.live_min_clamp_offsets[i] = ~0;
- + reset_validation_state(&validation_state);
-
- validation_state.branch_targets =
- kcalloc(BITS_TO_LONGS(validation_state.max_ip),
- @@ -554,6 +779,9 @@ vc4_validate_shader(struct drm_gem_cma_o
-
- validation_state.ip = ip;
-
- + if (!vc4_handle_branch_target(&validation_state))
- + goto fail;
- +
- switch (sig) {
- case QPU_SIG_NONE:
- case QPU_SIG_WAIT_FOR_SCOREBOARD:
- @@ -569,7 +797,8 @@ vc4_validate_shader(struct drm_gem_cma_o
- goto fail;
- }
-
- - if (!check_instruction_reads(inst, validated_shader))
- + if (!check_instruction_reads(validated_shader,
- + &validation_state))
- goto fail;
-
- if (sig == QPU_SIG_PROG_END) {
- @@ -587,6 +816,11 @@ vc4_validate_shader(struct drm_gem_cma_o
- }
- break;
-
- + case QPU_SIG_BRANCH:
- + if (!check_branch(inst, validated_shader,
- + &validation_state, ip))
- + goto fail;
- + break;
- default:
- DRM_ERROR("Unsupported QPU signal %d at "
- "instruction %d\n", sig, ip);
- @@ -607,6 +841,21 @@ vc4_validate_shader(struct drm_gem_cma_o
- goto fail;
- }
-
- + /* If we did a backwards branch and we haven't emitted a uniforms
- + * reset since then, we still need the uniforms stream to have the
- + * uniforms address available so that the backwards branch can do its
- + * uniforms reset.
- + *
- + * We could potentially prove that the backwards branch doesn't
- + * contain any uses of uniforms until program exit, but that doesn't
- + * seem to be worth the trouble.
- + */
- + if (validation_state.needs_uniform_address_for_loop) {
- + if (!require_uniform_address_uniform(validated_shader))
- + goto fail;
- + validated_shader->uniforms_size += 4;
- + }
- +
- /* Again, no chance of integer overflow here because the worst case
- * scenario is 8 bytes of uniforms plus handles per 8-byte
- * instruction.
|