diff options
author | Eric Anholt <[email protected]> | 2014-07-21 11:27:35 -0700 |
---|---|---|
committer | Eric Anholt <[email protected]> | 2014-08-11 14:45:28 -0700 |
commit | a8f2bf0f51222a96a49dfb3d6f9b36d3e54d08cd (patch) | |
tree | 5f7ab98c9ba5a69d761b253560908ac9c2358a81 /src/gallium/drivers/vc4/vc4_simulator_validate_shaders.c | |
parent | 6a5ece12aac0f079dff3aaf00b2ec5c420b9b1f8 (diff) |
vc4: Rewrite the kernel ABI to support texture uniform relocation.
This required building a shader parser that would walk the program to find
where the texturing-related uniforms are in the uniforms stream.
Note that as of this commit, a new kernel is required for rendering on
actual VC4 hardware (currently that commit is named "drm/vc4: Introduce
shader validation and better command stream validation.", but is likely to
be squashed as part of an eventual merge of the kernel driver).
Diffstat (limited to 'src/gallium/drivers/vc4/vc4_simulator_validate_shaders.c')
-rw-r--r-- | src/gallium/drivers/vc4/vc4_simulator_validate_shaders.c | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/src/gallium/drivers/vc4/vc4_simulator_validate_shaders.c b/src/gallium/drivers/vc4/vc4_simulator_validate_shaders.c new file mode 100644 index 00000000000..c02deb406c7 --- /dev/null +++ b/src/gallium/drivers/vc4/vc4_simulator_validate_shaders.c @@ -0,0 +1,334 @@ +/* + * Copyright © 2014 Broadcom + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice (including the next + * paragraph) shall be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +/** + * DOC: Shader validator for VC4. + * + * The VC4 has no IOMMU between it and system memory. So, a user with access + * to execute shaders could escalate privilege by overwriting system memory + * (using the VPM write address register in the general-purpose DMA mode) or + * reading system memory it shouldn't (reading it as a texture, or uniform + * data, or vertex data). + * + * This walks over a shader starting from some offset within a BO, ensuring + * that its accesses are appropriately bounded, and recording how many texture + * accesses are made and where so that we can do relocations for them in the + * uniform stream. + * + * The kernel API has shaders stored in user-mapped BOs. The BOs will be + * forcibly unmapped from the process before validation, and any cache of + * validated state will be flushed if the mapping is faulted back in. + * + * Storing the shaders in BOs means that the validation process will be slow + * due to uncached reads, but since shaders are long-lived and shader BOs are + * never actually modified, this shouldn't be a problem. + */ + +#include "vc4_simulator_validate.h" +#include "vc4_qpu.h" +#include "vc4_qpu_defines.h" + +struct vc4_shader_validation_state { + struct vc4_texture_sample_info tmu_setup[2]; + int tmu_write_count[2]; +}; + +static bool +is_tmu_write(uint32_t waddr) +{ + return (waddr >= QPU_W_TMU0_S && + waddr <= QPU_W_TMU1_B); +} + +static bool +check_register_write(uint32_t waddr, bool is_b) +{ + 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; + + case QPU_W_TLB_COLOR_MS: + case QPU_W_TLB_COLOR_ALL: + case QPU_W_TLB_Z: + /* XXX: We need to track which buffers get written by the + * shader, to make sure that we have those buffers set up by + * the config packets. But we need to pass them for now to + * get things up and running. + */ + return true; + + case QPU_W_TMU0_S: + case QPU_W_TMU0_T: + case QPU_W_TMU0_R: + case QPU_W_TMU0_B: + case QPU_W_TMU1_S: + case QPU_W_TMU1_T: + case QPU_W_TMU1_R: + case QPU_W_TMU1_B: + /* XXX: We need to track where the uniforms get loaded for + * texturing so that we can do relocations, and to validate + * those uniform contents. + */ + return true; + + case QPU_W_HOST_INT: + case QPU_W_TMU_NOSWAP: + case QPU_W_TLB_STENCIL_SETUP: + case QPU_W_TLB_ALPHA_MASK: + case QPU_W_MUTEX_RELEASE: + /* XXX: I haven't thought about these, so don't support them + * for now. + */ + DRM_ERROR("Unsupported waddr %d\n", waddr); + return false; + + case QPU_W_VPM_ADDR: + DRM_ERROR("General VPM DMA unsupported\n"); + return false; + + case QPU_W_VPM: + case QPU_W_VPMVCD_SETUP: + /* We allow VPM setup in general, even including VPM DMA + * configuration setup, because the (unsafe) DMA can only be + * triggered by QPU_W_VPM_ADDR writes. + */ + return true; + } + + return true; +} + +static bool +record_validated_texture_sample(struct vc4_validated_shader_info *validated_shader, + struct vc4_shader_validation_state *validation_state, + int tmu) +{ + uint32_t s = validated_shader->num_texture_samples; + int i; + struct vc4_texture_sample_info *temp_samples; + + temp_samples = krealloc(validated_shader->texture_samples, + (s + 1) * sizeof(*temp_samples), + GFP_KERNEL); + if (!temp_samples) + return false; + + memcpy(temp_samples[s].p_offset, + validation_state->tmu_setup[tmu].p_offset, + validation_state->tmu_write_count[tmu] * sizeof(uint32_t)); + for (i = validation_state->tmu_write_count[tmu]; i < 4; i++) + temp_samples[s].p_offset[i] = ~0; + + validated_shader->num_texture_samples = s + 1; + validated_shader->texture_samples = temp_samples; + + return true; +} + +static bool +check_tmu_writes(uint64_t inst, + struct vc4_validated_shader_info *validated_shader, + struct vc4_shader_validation_state *validation_state, + uint32_t waddr) +{ + int tmu = waddr > QPU_W_TMU0_B; + + if (!is_tmu_write(waddr)) + return true; + + if (validation_state->tmu_write_count[tmu] >= 4) { + DRM_ERROR("TMU%d got too many parameters before dispatch\n", + tmu); + return false; + } + validation_state->tmu_setup[tmu].p_offset[validation_state->tmu_write_count[tmu]] = + validated_shader->uniforms_size; + validation_state->tmu_write_count[tmu]++; + validated_shader->uniforms_size += 4; + + if (waddr == QPU_W_TMU0_S || waddr == QPU_W_TMU1_S) { + if (!record_validated_texture_sample(validated_shader, + validation_state, tmu)) { + return false; + } + + validation_state->tmu_write_count[tmu] = 0; + } + + return true; +} + +static bool +check_instruction_writes(uint64_t inst, + struct vc4_validated_shader_info *validated_shader, + struct vc4_shader_validation_state *validation_state) +{ + uint32_t waddr_add = QPU_GET_FIELD(inst, QPU_WADDR_ADD); + uint32_t waddr_mul = QPU_GET_FIELD(inst, QPU_WADDR_MUL); + bool ws = inst & QPU_WS; + + if (is_tmu_write(waddr_add) && is_tmu_write(waddr_mul)) { + DRM_ERROR("ADD and MUL both set up textures\n"); + return false; + } + + if (!check_tmu_writes(inst, validated_shader, validation_state, + waddr_add)) { + return false; + } + + if (!check_tmu_writes(inst, validated_shader, validation_state, + waddr_mul)) { + return false; + } + + return (check_register_write(waddr_add, ws) && + check_register_write(waddr_mul, !ws)); +} + +static bool +check_instruction_reads(uint64_t inst, + struct vc4_validated_shader_info *validated_shader) +{ + uint32_t waddr_add = QPU_GET_FIELD(inst, QPU_WADDR_ADD); + uint32_t waddr_mul = QPU_GET_FIELD(inst, QPU_WADDR_MUL); + uint32_t raddr_a = QPU_GET_FIELD(inst, QPU_RADDR_A); + uint32_t raddr_b = QPU_GET_FIELD(inst, QPU_RADDR_B); + + if (raddr_a == QPU_R_UNIF || + raddr_b == QPU_R_UNIF) { + if (is_tmu_write(waddr_add) || is_tmu_write(waddr_mul)) { + DRM_ERROR("uniform read in the same instruction as " + "texture setup"); + return false; + } + + /* This can't overflow the uint32_t, because we're reading 8 + * bytes of instruction to increment by 4 here, so we'd + * already be OOM. + */ + validated_shader->uniforms_size += 4; + } + + return true; +} + +struct vc4_validated_shader_info * +vc4_validate_shader(struct drm_gem_cma_object *shader_obj, + uint32_t start_offset) +{ + bool found_shader_end = false; + int shader_end_ip = 0; + uint32_t ip, max_ip; + uint64_t *shader; + struct vc4_validated_shader_info *validated_shader; + struct vc4_shader_validation_state validation_state; + + memset(&validation_state, 0, sizeof(validation_state)); + + if (start_offset + sizeof(uint64_t) > shader_obj->base.size) { + DRM_ERROR("shader starting at %d outside of BO sized %d\n", + start_offset, + shader_obj->base.size); + return NULL; + } + shader = shader_obj->vaddr + start_offset; + max_ip = (shader_obj->base.size - start_offset) / sizeof(uint64_t); + + validated_shader = kcalloc(sizeof(*validated_shader), 1, GFP_KERNEL); + if (!validated_shader) + return NULL; + + for (ip = 0; ip < max_ip; ip++) { + uint64_t inst = shader[ip]; + uint32_t sig = QPU_GET_FIELD(inst, QPU_SIG); + + switch (sig) { + case QPU_SIG_NONE: + case QPU_SIG_WAIT_FOR_SCOREBOARD: + case QPU_SIG_SCOREBOARD_UNLOCK: + case QPU_SIG_LOAD_TMU0: + case QPU_SIG_LOAD_TMU1: + if (!check_instruction_writes(inst, validated_shader, + &validation_state)) { + DRM_ERROR("Bad write at ip %d\n", ip); + goto fail; + } + + if (!check_instruction_reads(inst, validated_shader)) + goto fail; + + break; + + case QPU_SIG_LOAD_IMM: + if (!check_instruction_writes(inst, validated_shader, + &validation_state)) { + DRM_ERROR("Bad LOAD_IMM write at ip %d\n", ip); + goto fail; + } + break; + + case QPU_SIG_PROG_END: + found_shader_end = true; + shader_end_ip = ip; + break; + + default: + DRM_ERROR("Unsupported QPU signal %d at " + "instruction %d\n", sig, ip); + goto fail; + } + + /* There are two delay slots after program end is signaled + * that are still executed, then we're finished. + */ + if (found_shader_end && ip == shader_end_ip + 2) + break; + } + + if (ip == max_ip) { + DRM_ERROR("shader starting at %d failed to terminate before " + "shader BO end at %d\n", + start_offset, + shader_obj->base.size); + goto fail; + } + + /* Again, no chance of integer overflow here because the worst case + * scenario is 8 bytes of uniforms plus handles per 8-byte + * instruction. + */ + validated_shader->uniforms_src_size = + (validated_shader->uniforms_size + + 4 * validated_shader->num_texture_samples); + + return validated_shader; + +fail: + kfree(validated_shader); + return NULL; +} |