// SPDX-License-Identifier: GPL-2.0-only /* Copyright (c) 2010-2020 NVIDIA Corporation */ #include "drm.h" #include "submit.h" #include "uapi.h" struct tegra_drm_firewall { struct tegra_drm_submit_data *submit; struct tegra_drm_client *client; u32 *data; u32 pos; u32 end; u32 class; }; static int fw_next(struct tegra_drm_firewall *fw, u32 *word) { if (fw->pos == fw->end) return -EINVAL; *word = fw->data[fw->pos++]; return 0; } static bool fw_check_addr_valid(struct tegra_drm_firewall *fw, u32 offset) { u32 i; for (i = 0; i < fw->submit->num_used_mappings; i++) { struct tegra_drm_mapping *m = fw->submit->used_mappings[i].mapping; if (offset >= m->iova && offset <= m->iova_end) return true; } return false; } static int fw_check_reg(struct tegra_drm_firewall *fw, u32 offset) { bool is_addr; u32 word; int err; err = fw_next(fw, &word); if (err) return err; if (!fw->client->ops->is_addr_reg) return 0; is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class, offset); if (!is_addr) return 0; if (!fw_check_addr_valid(fw, word)) return -EINVAL; return 0; } static int fw_check_regs_seq(struct tegra_drm_firewall *fw, u32 offset, u32 count, bool incr) { u32 i; for (i = 0; i < count; i++) { if (fw_check_reg(fw, offset)) return -EINVAL; if (incr) offset++; } return 0; } static int fw_check_regs_mask(struct tegra_drm_firewall *fw, u32 offset, u16 mask) { unsigned long bmask = mask; unsigned int bit; for_each_set_bit(bit, &bmask, 16) { if (fw_check_reg(fw, offset+bit)) return -EINVAL; } return 0; } static int fw_check_regs_imm(struct tegra_drm_firewall *fw, u32 offset) { bool is_addr; is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class, offset); if (is_addr) return -EINVAL; return 0; } static int fw_check_class(struct tegra_drm_firewall *fw, u32 class) { if (!fw->client->ops->is_valid_class) { if (class == fw->client->base.class) return 0; else return -EINVAL; } if (!fw->client->ops->is_valid_class(class)) return -EINVAL; return 0; } enum { HOST1X_OPCODE_SETCLASS = 0x00, HOST1X_OPCODE_INCR = 0x01, HOST1X_OPCODE_NONINCR = 0x02, HOST1X_OPCODE_MASK = 0x03, HOST1X_OPCODE_IMM = 0x04, HOST1X_OPCODE_RESTART = 0x05, HOST1X_OPCODE_GATHER = 0x06, HOST1X_OPCODE_SETSTRMID = 0x07, HOST1X_OPCODE_SETAPPID = 0x08, HOST1X_OPCODE_SETPYLD = 0x09, HOST1X_OPCODE_INCR_W = 0x0a, HOST1X_OPCODE_NONINCR_W = 0x0b, HOST1X_OPCODE_GATHER_W = 0x0c, HOST1X_OPCODE_RESTART_W = 0x0d, HOST1X_OPCODE_EXTEND = 0x0e, }; int tegra_drm_fw_validate(struct tegra_drm_client *client, u32 *data, u32 start, u32 words, struct tegra_drm_submit_data *submit, u32 *job_class) { struct tegra_drm_firewall fw = { .submit = submit, .client = client, .data = data, .pos = start, .end = start+words, .class = *job_class, }; bool payload_valid = false; u32 payload; int err; while (fw.pos != fw.end) { u32 word, opcode, offset, count, mask, class; err = fw_next(&fw, &word); if (err) return err; opcode = (word & 0xf0000000) >> 28; switch (opcode) { case HOST1X_OPCODE_SETCLASS: offset = word >> 16 & 0xfff; mask = word & 0x3f; class = (word >> 6) & 0x3ff; err = fw_check_class(&fw, class); fw.class = class; *job_class = class; if (!err) err = fw_check_regs_mask(&fw, offset, mask); if (err) dev_warn(client->base.dev, "illegal SETCLASS(offset=0x%x, mask=0x%x, class=0x%x) at word %u", offset, mask, class, fw.pos-1); break; case HOST1X_OPCODE_INCR: offset = (word >> 16) & 0xfff; count = word & 0xffff; err = fw_check_regs_seq(&fw, offset, count, true); if (err) dev_warn(client->base.dev, "illegal INCR(offset=0x%x, count=%u) in class 0x%x at word %u", offset, count, fw.class, fw.pos-1); break; case HOST1X_OPCODE_NONINCR: offset = (word >> 16) & 0xfff; count = word & 0xffff; err = fw_check_regs_seq(&fw, offset, count, false); if (err) dev_warn(client->base.dev, "illegal NONINCR(offset=0x%x, count=%u) in class 0x%x at word %u", offset, count, fw.class, fw.pos-1); break; case HOST1X_OPCODE_MASK: offset = (word >> 16) & 0xfff; mask = word & 0xffff; err = fw_check_regs_mask(&fw, offset, mask); if (err) dev_warn(client->base.dev, "illegal MASK(offset=0x%x, mask=0x%x) in class 0x%x at word %u", offset, mask, fw.class, fw.pos-1); break; case HOST1X_OPCODE_IMM: /* IMM cannot reasonably be used to write a pointer */ offset = (word >> 16) & 0xfff; err = fw_check_regs_imm(&fw, offset); if (err) dev_warn(client->base.dev, "illegal IMM(offset=0x%x) in class 0x%x at word %u", offset, fw.class, fw.pos-1); break; case HOST1X_OPCODE_SETPYLD: payload = word & 0xffff; payload_valid = true; break; case HOST1X_OPCODE_INCR_W: if (!payload_valid) return -EINVAL; offset = word & 0x3fffff; err = fw_check_regs_seq(&fw, offset, payload, true); if (err) dev_warn(client->base.dev, "illegal INCR_W(offset=0x%x) in class 0x%x at word %u", offset, fw.class, fw.pos-1); break; case HOST1X_OPCODE_NONINCR_W: if (!payload_valid) return -EINVAL; offset = word & 0x3fffff; err = fw_check_regs_seq(&fw, offset, payload, false); if (err) dev_warn(client->base.dev, "illegal NONINCR(offset=0x%x) in class 0x%x at word %u", offset, fw.class, fw.pos-1); break; default: dev_warn(client->base.dev, "illegal opcode at word %u", fw.pos-1); return -EINVAL; } if (err) return err; } return 0; }