162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-only 262306a36Sopenharmony_ci/* Copyright (c) 2010-2020 NVIDIA Corporation */ 362306a36Sopenharmony_ci 462306a36Sopenharmony_ci#include "drm.h" 562306a36Sopenharmony_ci#include "submit.h" 662306a36Sopenharmony_ci#include "uapi.h" 762306a36Sopenharmony_ci 862306a36Sopenharmony_cistruct tegra_drm_firewall { 962306a36Sopenharmony_ci struct tegra_drm_submit_data *submit; 1062306a36Sopenharmony_ci struct tegra_drm_client *client; 1162306a36Sopenharmony_ci u32 *data; 1262306a36Sopenharmony_ci u32 pos; 1362306a36Sopenharmony_ci u32 end; 1462306a36Sopenharmony_ci u32 class; 1562306a36Sopenharmony_ci}; 1662306a36Sopenharmony_ci 1762306a36Sopenharmony_cistatic int fw_next(struct tegra_drm_firewall *fw, u32 *word) 1862306a36Sopenharmony_ci{ 1962306a36Sopenharmony_ci if (fw->pos == fw->end) 2062306a36Sopenharmony_ci return -EINVAL; 2162306a36Sopenharmony_ci 2262306a36Sopenharmony_ci *word = fw->data[fw->pos++]; 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_ci return 0; 2562306a36Sopenharmony_ci} 2662306a36Sopenharmony_ci 2762306a36Sopenharmony_cistatic bool fw_check_addr_valid(struct tegra_drm_firewall *fw, u32 offset) 2862306a36Sopenharmony_ci{ 2962306a36Sopenharmony_ci u32 i; 3062306a36Sopenharmony_ci 3162306a36Sopenharmony_ci for (i = 0; i < fw->submit->num_used_mappings; i++) { 3262306a36Sopenharmony_ci struct tegra_drm_mapping *m = fw->submit->used_mappings[i].mapping; 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_ci if (offset >= m->iova && offset <= m->iova_end) 3562306a36Sopenharmony_ci return true; 3662306a36Sopenharmony_ci } 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci return false; 3962306a36Sopenharmony_ci} 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_cistatic int fw_check_reg(struct tegra_drm_firewall *fw, u32 offset) 4262306a36Sopenharmony_ci{ 4362306a36Sopenharmony_ci bool is_addr; 4462306a36Sopenharmony_ci u32 word; 4562306a36Sopenharmony_ci int err; 4662306a36Sopenharmony_ci 4762306a36Sopenharmony_ci err = fw_next(fw, &word); 4862306a36Sopenharmony_ci if (err) 4962306a36Sopenharmony_ci return err; 5062306a36Sopenharmony_ci 5162306a36Sopenharmony_ci if (!fw->client->ops->is_addr_reg) 5262306a36Sopenharmony_ci return 0; 5362306a36Sopenharmony_ci 5462306a36Sopenharmony_ci is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class, 5562306a36Sopenharmony_ci offset); 5662306a36Sopenharmony_ci 5762306a36Sopenharmony_ci if (!is_addr) 5862306a36Sopenharmony_ci return 0; 5962306a36Sopenharmony_ci 6062306a36Sopenharmony_ci if (!fw_check_addr_valid(fw, word)) 6162306a36Sopenharmony_ci return -EINVAL; 6262306a36Sopenharmony_ci 6362306a36Sopenharmony_ci return 0; 6462306a36Sopenharmony_ci} 6562306a36Sopenharmony_ci 6662306a36Sopenharmony_cistatic int fw_check_regs_seq(struct tegra_drm_firewall *fw, u32 offset, 6762306a36Sopenharmony_ci u32 count, bool incr) 6862306a36Sopenharmony_ci{ 6962306a36Sopenharmony_ci u32 i; 7062306a36Sopenharmony_ci 7162306a36Sopenharmony_ci for (i = 0; i < count; i++) { 7262306a36Sopenharmony_ci if (fw_check_reg(fw, offset)) 7362306a36Sopenharmony_ci return -EINVAL; 7462306a36Sopenharmony_ci 7562306a36Sopenharmony_ci if (incr) 7662306a36Sopenharmony_ci offset++; 7762306a36Sopenharmony_ci } 7862306a36Sopenharmony_ci 7962306a36Sopenharmony_ci return 0; 8062306a36Sopenharmony_ci} 8162306a36Sopenharmony_ci 8262306a36Sopenharmony_cistatic int fw_check_regs_mask(struct tegra_drm_firewall *fw, u32 offset, 8362306a36Sopenharmony_ci u16 mask) 8462306a36Sopenharmony_ci{ 8562306a36Sopenharmony_ci unsigned long bmask = mask; 8662306a36Sopenharmony_ci unsigned int bit; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci for_each_set_bit(bit, &bmask, 16) { 8962306a36Sopenharmony_ci if (fw_check_reg(fw, offset+bit)) 9062306a36Sopenharmony_ci return -EINVAL; 9162306a36Sopenharmony_ci } 9262306a36Sopenharmony_ci 9362306a36Sopenharmony_ci return 0; 9462306a36Sopenharmony_ci} 9562306a36Sopenharmony_ci 9662306a36Sopenharmony_cistatic int fw_check_regs_imm(struct tegra_drm_firewall *fw, u32 offset) 9762306a36Sopenharmony_ci{ 9862306a36Sopenharmony_ci bool is_addr; 9962306a36Sopenharmony_ci 10062306a36Sopenharmony_ci if (!fw->client->ops->is_addr_reg) 10162306a36Sopenharmony_ci return 0; 10262306a36Sopenharmony_ci 10362306a36Sopenharmony_ci is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class, 10462306a36Sopenharmony_ci offset); 10562306a36Sopenharmony_ci if (is_addr) 10662306a36Sopenharmony_ci return -EINVAL; 10762306a36Sopenharmony_ci 10862306a36Sopenharmony_ci return 0; 10962306a36Sopenharmony_ci} 11062306a36Sopenharmony_ci 11162306a36Sopenharmony_cistatic int fw_check_class(struct tegra_drm_firewall *fw, u32 class) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci if (!fw->client->ops->is_valid_class) { 11462306a36Sopenharmony_ci if (class == fw->client->base.class) 11562306a36Sopenharmony_ci return 0; 11662306a36Sopenharmony_ci else 11762306a36Sopenharmony_ci return -EINVAL; 11862306a36Sopenharmony_ci } 11962306a36Sopenharmony_ci 12062306a36Sopenharmony_ci if (!fw->client->ops->is_valid_class(class)) 12162306a36Sopenharmony_ci return -EINVAL; 12262306a36Sopenharmony_ci 12362306a36Sopenharmony_ci return 0; 12462306a36Sopenharmony_ci} 12562306a36Sopenharmony_ci 12662306a36Sopenharmony_cienum { 12762306a36Sopenharmony_ci HOST1X_OPCODE_SETCLASS = 0x00, 12862306a36Sopenharmony_ci HOST1X_OPCODE_INCR = 0x01, 12962306a36Sopenharmony_ci HOST1X_OPCODE_NONINCR = 0x02, 13062306a36Sopenharmony_ci HOST1X_OPCODE_MASK = 0x03, 13162306a36Sopenharmony_ci HOST1X_OPCODE_IMM = 0x04, 13262306a36Sopenharmony_ci HOST1X_OPCODE_RESTART = 0x05, 13362306a36Sopenharmony_ci HOST1X_OPCODE_GATHER = 0x06, 13462306a36Sopenharmony_ci HOST1X_OPCODE_SETSTRMID = 0x07, 13562306a36Sopenharmony_ci HOST1X_OPCODE_SETAPPID = 0x08, 13662306a36Sopenharmony_ci HOST1X_OPCODE_SETPYLD = 0x09, 13762306a36Sopenharmony_ci HOST1X_OPCODE_INCR_W = 0x0a, 13862306a36Sopenharmony_ci HOST1X_OPCODE_NONINCR_W = 0x0b, 13962306a36Sopenharmony_ci HOST1X_OPCODE_GATHER_W = 0x0c, 14062306a36Sopenharmony_ci HOST1X_OPCODE_RESTART_W = 0x0d, 14162306a36Sopenharmony_ci HOST1X_OPCODE_EXTEND = 0x0e, 14262306a36Sopenharmony_ci}; 14362306a36Sopenharmony_ci 14462306a36Sopenharmony_ciint tegra_drm_fw_validate(struct tegra_drm_client *client, u32 *data, u32 start, 14562306a36Sopenharmony_ci u32 words, struct tegra_drm_submit_data *submit, 14662306a36Sopenharmony_ci u32 *job_class) 14762306a36Sopenharmony_ci{ 14862306a36Sopenharmony_ci struct tegra_drm_firewall fw = { 14962306a36Sopenharmony_ci .submit = submit, 15062306a36Sopenharmony_ci .client = client, 15162306a36Sopenharmony_ci .data = data, 15262306a36Sopenharmony_ci .pos = start, 15362306a36Sopenharmony_ci .end = start+words, 15462306a36Sopenharmony_ci .class = *job_class, 15562306a36Sopenharmony_ci }; 15662306a36Sopenharmony_ci bool payload_valid = false; 15762306a36Sopenharmony_ci u32 payload; 15862306a36Sopenharmony_ci int err; 15962306a36Sopenharmony_ci 16062306a36Sopenharmony_ci while (fw.pos != fw.end) { 16162306a36Sopenharmony_ci u32 word, opcode, offset, count, mask, class; 16262306a36Sopenharmony_ci 16362306a36Sopenharmony_ci err = fw_next(&fw, &word); 16462306a36Sopenharmony_ci if (err) 16562306a36Sopenharmony_ci return err; 16662306a36Sopenharmony_ci 16762306a36Sopenharmony_ci opcode = (word & 0xf0000000) >> 28; 16862306a36Sopenharmony_ci 16962306a36Sopenharmony_ci switch (opcode) { 17062306a36Sopenharmony_ci case HOST1X_OPCODE_SETCLASS: 17162306a36Sopenharmony_ci offset = word >> 16 & 0xfff; 17262306a36Sopenharmony_ci mask = word & 0x3f; 17362306a36Sopenharmony_ci class = (word >> 6) & 0x3ff; 17462306a36Sopenharmony_ci err = fw_check_class(&fw, class); 17562306a36Sopenharmony_ci fw.class = class; 17662306a36Sopenharmony_ci *job_class = class; 17762306a36Sopenharmony_ci if (!err) 17862306a36Sopenharmony_ci err = fw_check_regs_mask(&fw, offset, mask); 17962306a36Sopenharmony_ci if (err) 18062306a36Sopenharmony_ci dev_warn(client->base.dev, 18162306a36Sopenharmony_ci "illegal SETCLASS(offset=0x%x, mask=0x%x, class=0x%x) at word %u", 18262306a36Sopenharmony_ci offset, mask, class, fw.pos-1); 18362306a36Sopenharmony_ci break; 18462306a36Sopenharmony_ci case HOST1X_OPCODE_INCR: 18562306a36Sopenharmony_ci offset = (word >> 16) & 0xfff; 18662306a36Sopenharmony_ci count = word & 0xffff; 18762306a36Sopenharmony_ci err = fw_check_regs_seq(&fw, offset, count, true); 18862306a36Sopenharmony_ci if (err) 18962306a36Sopenharmony_ci dev_warn(client->base.dev, 19062306a36Sopenharmony_ci "illegal INCR(offset=0x%x, count=%u) in class 0x%x at word %u", 19162306a36Sopenharmony_ci offset, count, fw.class, fw.pos-1); 19262306a36Sopenharmony_ci break; 19362306a36Sopenharmony_ci case HOST1X_OPCODE_NONINCR: 19462306a36Sopenharmony_ci offset = (word >> 16) & 0xfff; 19562306a36Sopenharmony_ci count = word & 0xffff; 19662306a36Sopenharmony_ci err = fw_check_regs_seq(&fw, offset, count, false); 19762306a36Sopenharmony_ci if (err) 19862306a36Sopenharmony_ci dev_warn(client->base.dev, 19962306a36Sopenharmony_ci "illegal NONINCR(offset=0x%x, count=%u) in class 0x%x at word %u", 20062306a36Sopenharmony_ci offset, count, fw.class, fw.pos-1); 20162306a36Sopenharmony_ci break; 20262306a36Sopenharmony_ci case HOST1X_OPCODE_MASK: 20362306a36Sopenharmony_ci offset = (word >> 16) & 0xfff; 20462306a36Sopenharmony_ci mask = word & 0xffff; 20562306a36Sopenharmony_ci err = fw_check_regs_mask(&fw, offset, mask); 20662306a36Sopenharmony_ci if (err) 20762306a36Sopenharmony_ci dev_warn(client->base.dev, 20862306a36Sopenharmony_ci "illegal MASK(offset=0x%x, mask=0x%x) in class 0x%x at word %u", 20962306a36Sopenharmony_ci offset, mask, fw.class, fw.pos-1); 21062306a36Sopenharmony_ci break; 21162306a36Sopenharmony_ci case HOST1X_OPCODE_IMM: 21262306a36Sopenharmony_ci /* IMM cannot reasonably be used to write a pointer */ 21362306a36Sopenharmony_ci offset = (word >> 16) & 0xfff; 21462306a36Sopenharmony_ci err = fw_check_regs_imm(&fw, offset); 21562306a36Sopenharmony_ci if (err) 21662306a36Sopenharmony_ci dev_warn(client->base.dev, 21762306a36Sopenharmony_ci "illegal IMM(offset=0x%x) in class 0x%x at word %u", 21862306a36Sopenharmony_ci offset, fw.class, fw.pos-1); 21962306a36Sopenharmony_ci break; 22062306a36Sopenharmony_ci case HOST1X_OPCODE_SETPYLD: 22162306a36Sopenharmony_ci payload = word & 0xffff; 22262306a36Sopenharmony_ci payload_valid = true; 22362306a36Sopenharmony_ci break; 22462306a36Sopenharmony_ci case HOST1X_OPCODE_INCR_W: 22562306a36Sopenharmony_ci if (!payload_valid) 22662306a36Sopenharmony_ci return -EINVAL; 22762306a36Sopenharmony_ci 22862306a36Sopenharmony_ci offset = word & 0x3fffff; 22962306a36Sopenharmony_ci err = fw_check_regs_seq(&fw, offset, payload, true); 23062306a36Sopenharmony_ci if (err) 23162306a36Sopenharmony_ci dev_warn(client->base.dev, 23262306a36Sopenharmony_ci "illegal INCR_W(offset=0x%x) in class 0x%x at word %u", 23362306a36Sopenharmony_ci offset, fw.class, fw.pos-1); 23462306a36Sopenharmony_ci break; 23562306a36Sopenharmony_ci case HOST1X_OPCODE_NONINCR_W: 23662306a36Sopenharmony_ci if (!payload_valid) 23762306a36Sopenharmony_ci return -EINVAL; 23862306a36Sopenharmony_ci 23962306a36Sopenharmony_ci offset = word & 0x3fffff; 24062306a36Sopenharmony_ci err = fw_check_regs_seq(&fw, offset, payload, false); 24162306a36Sopenharmony_ci if (err) 24262306a36Sopenharmony_ci dev_warn(client->base.dev, 24362306a36Sopenharmony_ci "illegal NONINCR(offset=0x%x) in class 0x%x at word %u", 24462306a36Sopenharmony_ci offset, fw.class, fw.pos-1); 24562306a36Sopenharmony_ci break; 24662306a36Sopenharmony_ci default: 24762306a36Sopenharmony_ci dev_warn(client->base.dev, "illegal opcode at word %u", 24862306a36Sopenharmony_ci fw.pos-1); 24962306a36Sopenharmony_ci return -EINVAL; 25062306a36Sopenharmony_ci } 25162306a36Sopenharmony_ci 25262306a36Sopenharmony_ci if (err) 25362306a36Sopenharmony_ci return err; 25462306a36Sopenharmony_ci } 25562306a36Sopenharmony_ci 25662306a36Sopenharmony_ci return 0; 25762306a36Sopenharmony_ci} 258