1 /*
2  * Copyright © 2016 Intel Corporation
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
20  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
21  * IN THE SOFTWARE.
22  *
23  * Authors:
24  *    Tiago Vignatti <tiago.vignatti at intel.com>
25  */
26 
27 /*
28  * Testcase: show case dma-buf new API and processes restrictions. Most likely
29  * you want to run like ./prime_mmap_kms --interactive-debug=paint, to see the
30  * actual rectangle painted on the screen.
31  */
32 
33 #include "igt.h"
34 
35 IGT_TEST_DESCRIPTION(
36    "Efficiently sharing CPU and GPU buffers");
37 
38 /*
39  * render_process_t:
40  *
41  * Render is basically a user-space regular client. It's the unprivileged
42  * process with limited system accesses.
43  *
44  * Worth note the vendor-independent characteristic, meaning that the
45  * client doesn't need to perform any vendor specific calls for buffer
46  * handling. Mesa GBM library is a counter-example because, even though its API
47  * is vendor-independent, under-the-hood the library actually calls vendor
48  * specific ioctls, which is not really sandboxable and not the goal here.
49  */
50 typedef struct {
51 	int prime_fd;
52 	size_t size;
53 	int width;
54 	int height;
55 } render_process_t;
56 
57 typedef struct {
58 	int x;
59 	int y;
60 	int w;
61 	int h;
62 } rect_t;
63 
64 /* set ptr in a linear view */
set_pixel(void * _ptr,int index,uint32_t color,int bpp)65 static void set_pixel(void *_ptr, int index, uint32_t color, int bpp)
66 {
67 	if (bpp == 16) {
68 		uint16_t *ptr = _ptr;
69 		ptr[index] = color;
70 	} else if (bpp == 32) {
71 		uint32_t *ptr = _ptr;
72 		ptr[index] = color;
73 	} else {
74 		igt_assert_f(false, "bpp: %d\n", bpp);
75 	}
76 }
77 
paint(render_process_t * render)78 static void paint(render_process_t *render)
79 {
80 	void *frame;
81 	rect_t rect = {
82 		.x = 200,
83 		.y = 200,
84 		.w = render->width / 4,
85 		.h = render->height / 4,
86 	};
87 	uint32_t color = 0xFF;
88 	int stride, bpp;
89 	int x, y, line_begin;
90 
91 	frame = mmap(NULL, render->size, PROT_READ | PROT_WRITE, MAP_SHARED,
92 		     render->prime_fd, 0);
93 	igt_assert(frame != MAP_FAILED);
94 
95 	/* TODO: what's the mmap'ed buffer semantics on tiling, format etc. How
96 	 * does the client know whether that the BO was created X-tiled,
97 	 * Y-tiled and how it will map back? This is something we need to
98 	 * address in this API still. */
99 	stride = render->width * 4;
100 	bpp = 32;
101 
102 	/* ioctls to keep up the GPU <-> CPU coherency */
103 	prime_sync_start(render->prime_fd, true);
104 
105 	/* the actual painting phase happens here */
106 	for (y = rect.y; y < rect.y + rect.h; y++) {
107 		line_begin = y * stride / (bpp / 8);
108 		for (x = rect.x; x < rect.x + rect.w; x++)
109 			set_pixel(frame, line_begin + x, color, bpp);
110 	}
111 
112 	prime_sync_end(render->prime_fd, true);
113 	munmap(frame, render->size);
114 }
115 
init_renderer(int prime_fd,int fb_size,int width,int height)116 static void init_renderer(int prime_fd, int fb_size, int width, int height)
117 {
118 	render_process_t render;
119 
120 	render.prime_fd = prime_fd;
121 	render.size = fb_size;
122 	render.width = width;
123 	render.height = height;
124 	paint(&render);
125 }
126 
127 /*
128  * gpu_process_t:
129  *
130  * GPU process is the privileged process and has access to the system graphics
131  * routines, like DRM, display management and driver accesses.
132  */
133 typedef struct {
134 	int drm_fd;
135 	igt_display_t display;
136 	struct igt_fb fb;
137 	igt_output_t *output;
138 	igt_plane_t *primary;
139 	enum pipe pipe;
140 } gpu_process_t;
141 
cleanup_crtc(gpu_process_t * gpu)142 static void cleanup_crtc(gpu_process_t *gpu)
143 {
144 	igt_display_t *display = &gpu->display;
145 	igt_output_t *output = gpu->output;
146 
147 	igt_plane_set_fb(gpu->primary, NULL);
148 
149 	igt_output_set_pipe(output, PIPE_ANY);
150 	igt_display_commit(display);
151 
152 	igt_remove_fb(gpu->drm_fd, &gpu->fb);
153 }
154 
prepare_crtc(gpu_process_t * gpu)155 static void prepare_crtc(gpu_process_t *gpu)
156 {
157 	igt_display_t *display = &gpu->display;
158 	igt_output_t *output = gpu->output;
159 	drmModeModeInfo *mode;
160 
161 	/* select the pipe we want to use */
162 	igt_output_set_pipe(output, gpu->pipe);
163 
164 	mode = igt_output_get_mode(output);
165 
166 	/* create a white fb and flip to it */
167 	igt_create_color_fb(gpu->drm_fd, mode->hdisplay, mode->vdisplay,
168 			DRM_FORMAT_XRGB8888, LOCAL_DRM_FORMAT_MOD_NONE,
169 			1.0, 1.0, 1.0, &gpu->fb);
170 
171 	gpu->primary = igt_output_get_plane_type(output, DRM_PLANE_TYPE_PRIMARY);
172 
173 	igt_plane_set_fb(gpu->primary, &gpu->fb);
174 	igt_display_commit(display);
175 }
176 
177 /*
178  * The idea is to create a BO (in this case the framebuffer's) in one process,
179  * export and pass its prime fd to another process, which in turn uses the fd
180  * to map and write. This is Chrome-like architectures, where the Web content
181  * (a "tab" or the "unprivileged process") maps and CPU-paints a buffer, which
182  * was previously allocated in the GPU process ("privileged process").
183  */
run_test(gpu_process_t * gpu)184 static void run_test(gpu_process_t *gpu)
185 {
186 	igt_display_t *display = &gpu->display;
187 	igt_output_t *output;
188 	enum pipe pipe;
189 	int prime_fd;
190 
191 	for_each_pipe_with_valid_output(display, pipe, output) {
192 		gpu->output = output;
193 		gpu->pipe = pipe;
194 
195 		prepare_crtc(gpu);
196 
197 		prime_fd = prime_handle_to_fd_for_mmap(gpu->drm_fd,
198 							gpu->fb.gem_handle);
199 		igt_skip_on(prime_fd == -1 && errno == EINVAL);
200 
201 		/* Note that it only shares the dma-buf fd and some
202 		  * other basic info */
203 		igt_fork(renderer_no, 1) {
204 			init_renderer(prime_fd, gpu->fb.size, gpu->fb.width,
205 				      gpu->fb.height);
206 		}
207 		igt_waitchildren();
208 
209 		igt_debug_wait_for_keypress("paint");
210 		cleanup_crtc(gpu);
211 
212 		/* once is enough */
213 		return;
214 	}
215 
216 	igt_skip("no valid crtc/connector combinations found\n");
217 }
218 
219 static int
check_for_dma_buf_mmap(int fd)220 check_for_dma_buf_mmap(int fd)
221 {
222 	int dma_buf_fd;
223 	char *ptr;
224 	uint32_t handle;
225 	int ret = 1;
226 
227 	handle = gem_create(fd, 4096);
228 	dma_buf_fd = prime_handle_to_fd(fd, handle);
229 	ptr = mmap(NULL, 4096, PROT_READ, MAP_SHARED, dma_buf_fd, 0);
230 	if (ptr != MAP_FAILED)
231 		ret = 0;
232 	munmap(ptr, 4096);
233 	gem_close(fd, handle);
234 	close(dma_buf_fd);
235 	return ret;
236 }
237 
238 igt_main
239 {
240 	gpu_process_t gpu;
241 
242 	igt_skip_on_simulation();
243 
244 	igt_fixture {
245 		gpu.drm_fd = drm_open_driver_master(DRIVER_INTEL);
246 		igt_skip_on((check_for_dma_buf_mmap(gpu.drm_fd) != 0));
247 		kmstest_set_vt_graphics_mode();
248 
249 		igt_require_pipe_crc(gpu.drm_fd);
250 
251 		igt_display_require(&gpu.display, gpu.drm_fd);
252 	}
253 
254 	igt_subtest("buffer-sharing")
255 		run_test(&gpu);
256 
257 	igt_fixture {
258 		igt_display_fini(&gpu.display);
259 		close(gpu.drm_fd);
260 	}
261 }
262