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	if (!fw->client->ops->is_addr_reg)
101		return 0;
102
103	is_addr = fw->client->ops->is_addr_reg(fw->client->base.dev, fw->class,
104					       offset);
105	if (is_addr)
106		return -EINVAL;
107
108	return 0;
109}
110
111static int fw_check_class(struct tegra_drm_firewall *fw, u32 class)
112{
113	if (!fw->client->ops->is_valid_class) {
114		if (class == fw->client->base.class)
115			return 0;
116		else
117			return -EINVAL;
118	}
119
120	if (!fw->client->ops->is_valid_class(class))
121		return -EINVAL;
122
123	return 0;
124}
125
126enum {
127	HOST1X_OPCODE_SETCLASS  = 0x00,
128	HOST1X_OPCODE_INCR      = 0x01,
129	HOST1X_OPCODE_NONINCR   = 0x02,
130	HOST1X_OPCODE_MASK      = 0x03,
131	HOST1X_OPCODE_IMM       = 0x04,
132	HOST1X_OPCODE_RESTART   = 0x05,
133	HOST1X_OPCODE_GATHER    = 0x06,
134	HOST1X_OPCODE_SETSTRMID = 0x07,
135	HOST1X_OPCODE_SETAPPID  = 0x08,
136	HOST1X_OPCODE_SETPYLD   = 0x09,
137	HOST1X_OPCODE_INCR_W    = 0x0a,
138	HOST1X_OPCODE_NONINCR_W = 0x0b,
139	HOST1X_OPCODE_GATHER_W  = 0x0c,
140	HOST1X_OPCODE_RESTART_W = 0x0d,
141	HOST1X_OPCODE_EXTEND    = 0x0e,
142};
143
144int tegra_drm_fw_validate(struct tegra_drm_client *client, u32 *data, u32 start,
145			  u32 words, struct tegra_drm_submit_data *submit,
146			  u32 *job_class)
147{
148	struct tegra_drm_firewall fw = {
149		.submit = submit,
150		.client = client,
151		.data = data,
152		.pos = start,
153		.end = start+words,
154		.class = *job_class,
155	};
156	bool payload_valid = false;
157	u32 payload;
158	int err;
159
160	while (fw.pos != fw.end) {
161		u32 word, opcode, offset, count, mask, class;
162
163		err = fw_next(&fw, &word);
164		if (err)
165			return err;
166
167		opcode = (word & 0xf0000000) >> 28;
168
169		switch (opcode) {
170		case HOST1X_OPCODE_SETCLASS:
171			offset = word >> 16 & 0xfff;
172			mask = word & 0x3f;
173			class = (word >> 6) & 0x3ff;
174			err = fw_check_class(&fw, class);
175			fw.class = class;
176			*job_class = class;
177			if (!err)
178				err = fw_check_regs_mask(&fw, offset, mask);
179			if (err)
180				dev_warn(client->base.dev,
181					 "illegal SETCLASS(offset=0x%x, mask=0x%x, class=0x%x) at word %u",
182					 offset, mask, class, fw.pos-1);
183			break;
184		case HOST1X_OPCODE_INCR:
185			offset = (word >> 16) & 0xfff;
186			count = word & 0xffff;
187			err = fw_check_regs_seq(&fw, offset, count, true);
188			if (err)
189				dev_warn(client->base.dev,
190					 "illegal INCR(offset=0x%x, count=%u) in class 0x%x at word %u",
191					 offset, count, fw.class, fw.pos-1);
192			break;
193		case HOST1X_OPCODE_NONINCR:
194			offset = (word >> 16) & 0xfff;
195			count = word & 0xffff;
196			err = fw_check_regs_seq(&fw, offset, count, false);
197			if (err)
198				dev_warn(client->base.dev,
199					 "illegal NONINCR(offset=0x%x, count=%u) in class 0x%x at word %u",
200					 offset, count, fw.class, fw.pos-1);
201			break;
202		case HOST1X_OPCODE_MASK:
203			offset = (word >> 16) & 0xfff;
204			mask = word & 0xffff;
205			err = fw_check_regs_mask(&fw, offset, mask);
206			if (err)
207				dev_warn(client->base.dev,
208					 "illegal MASK(offset=0x%x, mask=0x%x) in class 0x%x at word %u",
209					 offset, mask, fw.class, fw.pos-1);
210			break;
211		case HOST1X_OPCODE_IMM:
212			/* IMM cannot reasonably be used to write a pointer */
213			offset = (word >> 16) & 0xfff;
214			err = fw_check_regs_imm(&fw, offset);
215			if (err)
216				dev_warn(client->base.dev,
217					 "illegal IMM(offset=0x%x) in class 0x%x at word %u",
218					 offset, fw.class, fw.pos-1);
219			break;
220		case HOST1X_OPCODE_SETPYLD:
221			payload = word & 0xffff;
222			payload_valid = true;
223			break;
224		case HOST1X_OPCODE_INCR_W:
225			if (!payload_valid)
226				return -EINVAL;
227
228			offset = word & 0x3fffff;
229			err = fw_check_regs_seq(&fw, offset, payload, true);
230			if (err)
231				dev_warn(client->base.dev,
232					 "illegal INCR_W(offset=0x%x) in class 0x%x at word %u",
233					 offset, fw.class, fw.pos-1);
234			break;
235		case HOST1X_OPCODE_NONINCR_W:
236			if (!payload_valid)
237				return -EINVAL;
238
239			offset = word & 0x3fffff;
240			err = fw_check_regs_seq(&fw, offset, payload, false);
241			if (err)
242				dev_warn(client->base.dev,
243					 "illegal NONINCR(offset=0x%x) in class 0x%x at word %u",
244					 offset, fw.class, fw.pos-1);
245			break;
246		default:
247			dev_warn(client->base.dev, "illegal opcode at word %u",
248				 fw.pos-1);
249			return -EINVAL;
250		}
251
252		if (err)
253			return err;
254	}
255
256	return 0;
257}
258