firewall.c (5724B)
1// SPDX-License-Identifier: GPL-2.0-only 2/* Copyright (c) 2010-2020 NVIDIA Corporation */ 3 4#include "drm.h" 5#include "submit.h" 6#include "uapi.h" 7 8struct tegra_drm_firewall { 9 struct tegra_drm_submit_data *submit; 10 struct tegra_drm_client *client; 11 u32 *data; 12 u32 pos; 13 u32 end; 14 u32 class; 15}; 16 17static int fw_next(struct tegra_drm_firewall *fw, u32 *word) 18{ 19 if (fw->pos == fw->end) 20 return -EINVAL; 21 22 *word = fw->data[fw->pos++]; 23 24 return 0; 25} 26 27static bool fw_check_addr_valid(struct tegra_drm_firewall *fw, u32 offset) 28{ 29 u32 i; 30 31 for (i = 0; i < fw->submit->num_used_mappings; i++) { 32 struct tegra_drm_mapping *m = fw->submit->used_mappings[i].mapping; 33 34 if (offset >= m->iova && offset <= m->iova_end) 35 return true; 36 } 37 38 return false; 39} 40 41static int fw_check_reg(struct tegra_drm_firewall *fw, u32 offset) 42{ 43 bool is_addr; 44 u32 word; 45 int err; 46 47 err = fw_next(fw, &word); 48 if (err) 49 return err; 50 51 if (!fw->client->ops->is_addr_reg) 52 return 0; 53 54 is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class, 55 offset); 56 57 if (!is_addr) 58 return 0; 59 60 if (!fw_check_addr_valid(fw, word)) 61 return -EINVAL; 62 63 return 0; 64} 65 66static int fw_check_regs_seq(struct tegra_drm_firewall *fw, u32 offset, 67 u32 count, bool incr) 68{ 69 u32 i; 70 71 for (i = 0; i < count; i++) { 72 if (fw_check_reg(fw, offset)) 73 return -EINVAL; 74 75 if (incr) 76 offset++; 77 } 78 79 return 0; 80} 81 82static int fw_check_regs_mask(struct tegra_drm_firewall *fw, u32 offset, 83 u16 mask) 84{ 85 unsigned long bmask = mask; 86 unsigned int bit; 87 88 for_each_set_bit(bit, &bmask, 16) { 89 if (fw_check_reg(fw, offset+bit)) 90 return -EINVAL; 91 } 92 93 return 0; 94} 95 96static int fw_check_regs_imm(struct tegra_drm_firewall *fw, u32 offset) 97{ 98 bool is_addr; 99 100 is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class, 101 offset); 102 if (is_addr) 103 return -EINVAL; 104 105 return 0; 106} 107 108static int fw_check_class(struct tegra_drm_firewall *fw, u32 class) 109{ 110 if (!fw->client->ops->is_valid_class) { 111 if (class == fw->client->base.class) 112 return 0; 113 else 114 return -EINVAL; 115 } 116 117 if (!fw->client->ops->is_valid_class(class)) 118 return -EINVAL; 119 120 return 0; 121} 122 123enum { 124 HOST1X_OPCODE_SETCLASS = 0x00, 125 HOST1X_OPCODE_INCR = 0x01, 126 HOST1X_OPCODE_NONINCR = 0x02, 127 HOST1X_OPCODE_MASK = 0x03, 128 HOST1X_OPCODE_IMM = 0x04, 129 HOST1X_OPCODE_RESTART = 0x05, 130 HOST1X_OPCODE_GATHER = 0x06, 131 HOST1X_OPCODE_SETSTRMID = 0x07, 132 HOST1X_OPCODE_SETAPPID = 0x08, 133 HOST1X_OPCODE_SETPYLD = 0x09, 134 HOST1X_OPCODE_INCR_W = 0x0a, 135 HOST1X_OPCODE_NONINCR_W = 0x0b, 136 HOST1X_OPCODE_GATHER_W = 0x0c, 137 HOST1X_OPCODE_RESTART_W = 0x0d, 138 HOST1X_OPCODE_EXTEND = 0x0e, 139}; 140 141int tegra_drm_fw_validate(struct tegra_drm_client *client, u32 *data, u32 start, 142 u32 words, struct tegra_drm_submit_data *submit, 143 u32 *job_class) 144{ 145 struct tegra_drm_firewall fw = { 146 .submit = submit, 147 .client = client, 148 .data = data, 149 .pos = start, 150 .end = start+words, 151 .class = *job_class, 152 }; 153 bool payload_valid = false; 154 u32 payload; 155 int err; 156 157 while (fw.pos != fw.end) { 158 u32 word, opcode, offset, count, mask, class; 159 160 err = fw_next(&fw, &word); 161 if (err) 162 return err; 163 164 opcode = (word & 0xf0000000) >> 28; 165 166 switch (opcode) { 167 case HOST1X_OPCODE_SETCLASS: 168 offset = word >> 16 & 0xfff; 169 mask = word & 0x3f; 170 class = (word >> 6) & 0x3ff; 171 err = fw_check_class(&fw, class); 172 fw.class = class; 173 *job_class = class; 174 if (!err) 175 err = fw_check_regs_mask(&fw, offset, mask); 176 if (err) 177 dev_warn(client->base.dev, 178 "illegal SETCLASS(offset=0x%x, mask=0x%x, class=0x%x) at word %u", 179 offset, mask, class, fw.pos-1); 180 break; 181 case HOST1X_OPCODE_INCR: 182 offset = (word >> 16) & 0xfff; 183 count = word & 0xffff; 184 err = fw_check_regs_seq(&fw, offset, count, true); 185 if (err) 186 dev_warn(client->base.dev, 187 "illegal INCR(offset=0x%x, count=%u) in class 0x%x at word %u", 188 offset, count, fw.class, fw.pos-1); 189 break; 190 case HOST1X_OPCODE_NONINCR: 191 offset = (word >> 16) & 0xfff; 192 count = word & 0xffff; 193 err = fw_check_regs_seq(&fw, offset, count, false); 194 if (err) 195 dev_warn(client->base.dev, 196 "illegal NONINCR(offset=0x%x, count=%u) in class 0x%x at word %u", 197 offset, count, fw.class, fw.pos-1); 198 break; 199 case HOST1X_OPCODE_MASK: 200 offset = (word >> 16) & 0xfff; 201 mask = word & 0xffff; 202 err = fw_check_regs_mask(&fw, offset, mask); 203 if (err) 204 dev_warn(client->base.dev, 205 "illegal MASK(offset=0x%x, mask=0x%x) in class 0x%x at word %u", 206 offset, mask, fw.class, fw.pos-1); 207 break; 208 case HOST1X_OPCODE_IMM: 209 /* IMM cannot reasonably be used to write a pointer */ 210 offset = (word >> 16) & 0xfff; 211 err = fw_check_regs_imm(&fw, offset); 212 if (err) 213 dev_warn(client->base.dev, 214 "illegal IMM(offset=0x%x) in class 0x%x at word %u", 215 offset, fw.class, fw.pos-1); 216 break; 217 case HOST1X_OPCODE_SETPYLD: 218 payload = word & 0xffff; 219 payload_valid = true; 220 break; 221 case HOST1X_OPCODE_INCR_W: 222 if (!payload_valid) 223 return -EINVAL; 224 225 offset = word & 0x3fffff; 226 err = fw_check_regs_seq(&fw, offset, payload, true); 227 if (err) 228 dev_warn(client->base.dev, 229 "illegal INCR_W(offset=0x%x) in class 0x%x at word %u", 230 offset, fw.class, fw.pos-1); 231 break; 232 case HOST1X_OPCODE_NONINCR_W: 233 if (!payload_valid) 234 return -EINVAL; 235 236 offset = word & 0x3fffff; 237 err = fw_check_regs_seq(&fw, offset, payload, false); 238 if (err) 239 dev_warn(client->base.dev, 240 "illegal NONINCR(offset=0x%x) in class 0x%x at word %u", 241 offset, fw.class, fw.pos-1); 242 break; 243 default: 244 dev_warn(client->base.dev, "illegal opcode at word %u", 245 fw.pos-1); 246 return -EINVAL; 247 } 248 249 if (err) 250 return err; 251 } 252 253 return 0; 254}