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  
8  struct 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  
fw_next(struct tegra_drm_firewall * fw,u32 * word)17  static 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  
fw_check_addr_valid(struct tegra_drm_firewall * fw,u32 offset)27  static 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  
fw_check_reg(struct tegra_drm_firewall * fw,u32 offset)41  static 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  
fw_check_regs_seq(struct tegra_drm_firewall * fw,u32 offset,u32 count,bool incr)66  static 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  
fw_check_regs_mask(struct tegra_drm_firewall * fw,u32 offset,u16 mask)82  static 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  
fw_check_regs_imm(struct tegra_drm_firewall * fw,u32 offset)96  static 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  
fw_check_class(struct tegra_drm_firewall * fw,u32 class)111  static 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  
126  enum {
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  
tegra_drm_fw_validate(struct tegra_drm_client * client,u32 * data,u32 start,u32 words,struct tegra_drm_submit_data * submit,u32 * job_class)144  int 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