1 /*
2  * Copyright (C) 2015 - Tobias Jakobi
3  *
4  * This is free software: you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published
6  * by the Free Software Foundation, either version 2 of the License,
7  * or (at your option) any later version.
8  *
9  * It is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  * You should have received a copy of the GNU General Public License
14  * along with it. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include <unistd.h>
18 #include <poll.h>
19 
20 #include <stdlib.h>
21 #include <stdio.h>
22 #include <time.h>
23 #include <getopt.h>
24 
25 #include <pthread.h>
26 
27 #include <xf86drm.h>
28 
29 #include "exynos_drm.h"
30 #include "exynos_drmif.h"
31 #include "exynos_fimg2d.h"
32 
33 struct g2d_job {
34 	unsigned int id;
35 	unsigned int busy;
36 };
37 
38 struct exynos_evhandler {
39 	struct pollfd fds;
40 	struct exynos_event_context evctx;
41 };
42 
43 struct threaddata {
44 	unsigned int stop;
45 	struct exynos_device *dev;
46 	struct exynos_evhandler evhandler;
47 };
48 
g2d_event_handler(int fd,unsigned int cmdlist_no,unsigned int tv_sec,unsigned int tv_usec,void * user_data)49 static void g2d_event_handler(int fd, unsigned int cmdlist_no, unsigned int tv_sec,
50 							unsigned int tv_usec, void *user_data)
51 {
52 	struct g2d_job *job = user_data;
53 
54 	fprintf(stderr, "info: g2d job (id = %u, cmdlist number = %u) finished!\n",
55 			job->id, cmdlist_no);
56 
57 	job->busy = 0;
58 }
59 
setup_g2d_event_handler(struct exynos_evhandler * evhandler,int fd)60 static void setup_g2d_event_handler(struct exynos_evhandler *evhandler, int fd)
61 {
62 	evhandler->fds.fd = fd;
63 	evhandler->fds.events = POLLIN;
64 	evhandler->evctx.base.version = 2;
65 	evhandler->evctx.version = 1;
66 	evhandler->evctx.g2d_event_handler = g2d_event_handler;
67 }
68 
threadfunc(void * arg)69 static void* threadfunc(void *arg) {
70 	const int timeout = 0;
71 	struct threaddata *data;
72 
73 	data = arg;
74 
75 	while (1) {
76 		if (data->stop) break;
77 
78 		usleep(500);
79 
80 		data->evhandler.fds.revents = 0;
81 
82 		if (poll(&data->evhandler.fds, 1, timeout) < 0)
83 			continue;
84 
85 		if (data->evhandler.fds.revents & (POLLHUP | POLLERR))
86 			continue;
87 
88 		if (data->evhandler.fds.revents & POLLIN)
89 			exynos_handle_event(data->dev, &data->evhandler.evctx);
90 	}
91 
92 	pthread_exit(0);
93 }
94 
95 /*
96  * We need to wait until all G2D jobs are finished, otherwise we
97  * potentially remove a BO which the engine still operates on.
98  * This results in the following kernel message:
99  * [drm:exynos_drm_gem_put_dma_addr] *ERROR* failed to lookup gem object.
100  * Also any subsequent BO allocations fail then with:
101  * [drm:exynos_drm_alloc_buf] *ERROR* failed to allocate buffer.
102  */
wait_all_jobs(struct g2d_job * jobs,unsigned num_jobs)103 static void wait_all_jobs(struct g2d_job* jobs, unsigned num_jobs)
104 {
105 	unsigned i;
106 
107 	for (i = 0; i < num_jobs; ++i) {
108 		while (jobs[i].busy)
109 			usleep(500);
110 	}
111 
112 }
113 
free_job(struct g2d_job * jobs,unsigned num_jobs)114 static struct g2d_job* free_job(struct g2d_job* jobs, unsigned num_jobs)
115 {
116 	unsigned i;
117 
118 	for (i = 0; i < num_jobs; ++i) {
119 		if (jobs[i].busy == 0)
120 			return &jobs[i];
121 	}
122 
123 	return NULL;
124 }
125 
g2d_work(struct g2d_context * ctx,struct g2d_image * img,unsigned num_jobs,unsigned iterations)126 static int g2d_work(struct g2d_context *ctx, struct g2d_image *img,
127 					unsigned num_jobs, unsigned iterations)
128 {
129 	struct g2d_job *jobs = calloc(num_jobs, sizeof(struct g2d_job));
130 	int ret;
131 	unsigned i;
132 
133 	/* setup job ids */
134 	for (i = 0; i < num_jobs; ++i)
135 		jobs[i].id = i;
136 
137 	for (i = 0; i < iterations; ++i) {
138 		unsigned x, y, w, h;
139 
140 		struct g2d_job *j = NULL;
141 
142 		while (1) {
143 			j = free_job(jobs, num_jobs);
144 
145 			if (j)
146 				break;
147 			else
148 				usleep(500);
149 		}
150 
151 		x = rand() % img->width;
152 		y = rand() % img->height;
153 
154 		if (x == (img->width - 1))
155 			x -= 1;
156 		if (y == (img->height - 1))
157 			y -= 1;
158 
159 		w = rand() % (img->width - x);
160 		h = rand() % (img->height - y);
161 
162 		if (w == 0) w = 1;
163 		if (h == 0) h = 1;
164 
165 		img->color = rand();
166 
167 		j->busy = 1;
168 		g2d_config_event(ctx, j);
169 
170 		ret = g2d_solid_fill(ctx, img, x, y, w, h);
171 
172 		if (ret == 0)
173 			g2d_exec(ctx);
174 
175 		if (ret != 0) {
176 			fprintf(stderr, "error: iteration %u (x = %u, x = %u, x = %u, x = %u) failed\n",
177 					i, x, y, w, h);
178 			break;
179 		}
180 	}
181 
182 	wait_all_jobs(jobs, num_jobs);
183 	free(jobs);
184 
185 	return 0;
186 }
187 
usage(const char * name)188 static void usage(const char *name)
189 {
190 	fprintf(stderr, "usage: %s [-ijwh]\n\n", name);
191 
192 	fprintf(stderr, "\t-i <number of iterations>\n");
193 	fprintf(stderr, "\t-j <number of G2D jobs> (default = 4)\n\n");
194 
195 	fprintf(stderr, "\t-w <buffer width> (default = 4096)\n");
196 	fprintf(stderr, "\t-h <buffer height> (default = 4096)\n");
197 
198 	exit(0);
199 }
200 
main(int argc,char ** argv)201 int main(int argc, char **argv)
202 {
203 	int fd, ret, c, parsefail;
204 
205 	pthread_t event_thread;
206 	struct threaddata event_data = {0};
207 
208 	struct exynos_device *dev;
209 	struct g2d_context *ctx;
210 	struct exynos_bo *bo;
211 
212 	struct g2d_image img = {0};
213 
214 	unsigned int iters = 0, njobs = 4;
215 	unsigned int bufw = 4096, bufh = 4096;
216 
217 	ret = 0;
218 	parsefail = 0;
219 
220 	while ((c = getopt(argc, argv, "i:j:w:h:")) != -1) {
221 		switch (c) {
222 		case 'i':
223 			if (sscanf(optarg, "%u", &iters) != 1)
224 				parsefail = 1;
225 			break;
226 		case 'j':
227 			if (sscanf(optarg, "%u", &njobs) != 1)
228 				parsefail = 1;
229 			break;
230 		case 'w':
231 			if (sscanf(optarg, "%u", &bufw) != 1)
232 				parsefail = 1;
233 			break;
234 		case 'h':
235 			if (sscanf(optarg, "%u", &bufh) != 1)
236 				parsefail = 1;
237 			break;
238 		default:
239 			parsefail = 1;
240 			break;
241 		}
242 	}
243 
244 	if (parsefail || (argc == 1) || (iters == 0))
245 		usage(argv[0]);
246 
247 	if (bufw > 4096 || bufh > 4096) {
248 		fprintf(stderr, "error: buffer width/height should be less than 4096.\n");
249 		ret = -1;
250 
251 		goto out;
252 	}
253 
254 	if (bufw == 0 || bufh == 0) {
255 		fprintf(stderr, "error: buffer width/height should be non-zero.\n");
256 		ret = -1;
257 
258 		goto out;
259 	}
260 
261 	fd = drmOpen("exynos", NULL);
262 	if (fd < 0) {
263 		fprintf(stderr, "error: failed to open drm\n");
264 		ret = -1;
265 
266 		goto out;
267 	}
268 
269 	dev = exynos_device_create(fd);
270 	if (dev == NULL) {
271 		fprintf(stderr, "error: failed to create device\n");
272 		ret = -2;
273 
274 		goto fail;
275 	}
276 
277 	ctx = g2d_init(fd);
278 	if (ctx == NULL) {
279 		fprintf(stderr, "error: failed to init G2D\n");
280 		ret = -3;
281 
282 		goto g2d_fail;
283 	}
284 
285 	bo = exynos_bo_create(dev, bufw * bufh * 4, 0);
286 	if (bo == NULL) {
287 		fprintf(stderr, "error: failed to create bo\n");
288 		ret = -4;
289 
290 		goto bo_fail;
291 	}
292 
293 	/* setup g2d image object */
294 	img.width = bufw;
295 	img.height = bufh;
296 	img.stride = bufw * 4;
297 	img.color_mode = G2D_COLOR_FMT_ARGB8888 | G2D_ORDER_AXRGB;
298 	img.buf_type = G2D_IMGBUF_GEM;
299 	img.bo[0] = bo->handle;
300 
301 	event_data.dev = dev;
302 	setup_g2d_event_handler(&event_data.evhandler, fd);
303 
304 	pthread_create(&event_thread, NULL, threadfunc, &event_data);
305 
306 	ret = g2d_work(ctx, &img, njobs, iters);
307 	if (ret != 0)
308 		fprintf(stderr, "error: g2d_work failed\n");
309 
310 	event_data.stop = 1;
311 	pthread_join(event_thread, NULL);
312 
313 	exynos_bo_destroy(bo);
314 
315 bo_fail:
316 	g2d_fini(ctx);
317 
318 g2d_fail:
319 	exynos_device_destroy(dev);
320 
321 fail:
322 	drmClose(fd);
323 
324 out:
325 	return ret;
326 }
327