| // 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; |
| |
| 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 -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; |
| } |