summaryrefslogtreecommitdiff
path: root/target/linux/brcm2708/patches-4.4/0479-drm-vc4-Add-support-for-branching-in-shader-validati.patch
diff options
context:
space:
mode:
Diffstat (limited to 'target/linux/brcm2708/patches-4.4/0479-drm-vc4-Add-support-for-branching-in-shader-validati.patch')
-rw-r--r--target/linux/brcm2708/patches-4.4/0479-drm-vc4-Add-support-for-branching-in-shader-validati.patch475
1 files changed, 475 insertions, 0 deletions
diff --git a/target/linux/brcm2708/patches-4.4/0479-drm-vc4-Add-support-for-branching-in-shader-validati.patch b/target/linux/brcm2708/patches-4.4/0479-drm-vc4-Add-support-for-branching-in-shader-validati.patch
new file mode 100644
index 0000000..eb78dba
--- /dev/null
+++ b/target/linux/brcm2708/patches-4.4/0479-drm-vc4-Add-support-for-branching-in-shader-validati.patch
@@ -0,0 +1,475 @@
+From f9cd25fae900b1251e14ec894849846e0a2a7cb3 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.