1 /*
2 * Copyright 2021 The ChromiumOS Authors
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
5 */
6
7 #define _GNU_SOURCE
8 #include <assert.h>
9 #include <errno.h>
10 #include <stdbool.h>
11 #include <stdio.h>
12 #include <stdlib.h>
13 #include <string.h>
14 #include <unistd.h>
15
16 #include <rutabaga_gfx/rutabaga_gfx_ffi.h>
17
18 #include "virtgpu_cross_domain_protocol.h"
19
20 #define CHECK_RESULT(result) \
21 do { \
22 if (result) { \
23 printf("CHECK_RESULT failed in %s() %s:%d\n", __func__, __FILE__, __LINE__); \
24 return result; \
25 } \
26 } while (0)
27
28 #define CHECK(cond) \
29 do { \
30 if (!(cond)) { \
31 printf("CHECK failed in %s() %s:%d\n", __func__, __FILE__, __LINE__); \
32 return -EINVAL; \
33 } \
34 } while (0)
35
36 #define DEFAULT_BUFFER_SIZE 4096
37 #define WIDTH 512
38 #define HEIGHT 512
39 #define NUM_ITERATIONS 4
40
41 #define GBM_BO_USE_LINEAR (1 << 4)
42 #define GBM_BO_USE_SCANOUT (1 << 5)
43 #define fourcc_code(a, b, c, d) \
44 ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | ((uint32_t)(d) << 24))
45 #define DRM_FORMAT_XRGB8888 fourcc_code('X', 'R', '2', '4');
46
47 #define PIPE_TEXTURE_2D 2
48 #define PIPE_BIND_RENDER_TARGET 2
49 #define VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM 1
50
51 static int s_resource_id = 1;
52 static int s_fence_id = 1;
53
54 #if defined(__linux__)
55 static char *s_wayland_path = "/run/user/1000/wayland-0";
56 #elif defined(__Fuchsia__)
57 #endif
58
59 struct rutabaga_test {
60 struct rutabaga *rutabaga;
61 uint32_t ctx_id;
62 uint64_t value;
63 uint32_t guest_blob_resource_id;
64 struct iovec *guest_iovecs;
65 };
66
rutabaga_test_write_fence(uint64_t user_data,struct rutabaga_fence fence_data)67 static void rutabaga_test_write_fence(uint64_t user_data, struct rutabaga_fence fence_data)
68 {
69 struct rutabaga_test *test = (void *)(uintptr_t)user_data;
70 test->value = fence_data.fence_id;
71 }
72
test_rutabaga_init(struct rutabaga_test * test,uint64_t capset_mask)73 static int test_rutabaga_init(struct rutabaga_test *test, uint64_t capset_mask)
74 {
75 int result;
76 struct rutabaga_builder builder = { 0 };
77 struct rutabaga_channels channels = { 0 };
78
79 builder.fence_cb = rutabaga_test_write_fence;
80 builder.capset_mask = capset_mask;
81 if (capset_mask & (1 << RUTABAGA_CAPSET_CROSS_DOMAIN)) {
82 builder.user_data = (uint64_t)(uintptr_t *)(void *)test;
83 channels.channels = (struct rutabaga_channel *)calloc(1, sizeof(struct rutabaga_channel));
84 channels.num_channels = 1;
85
86 channels.channels[0].channel_name = s_wayland_path;
87 channels.channels[0].channel_type = RUTABAGA_CHANNEL_TYPE_WAYLAND;
88
89 builder.channels = &channels;
90 }
91
92 result = rutabaga_init(&builder, &test->rutabaga);
93
94 if (capset_mask & (1 << RUTABAGA_CAPSET_CROSS_DOMAIN))
95 free(channels.channels);
96
97 CHECK_RESULT(result);
98 return 0;
99 }
100
test_create_context(struct rutabaga_test * test,const char * context_name)101 static int test_create_context(struct rutabaga_test *test, const char *context_name)
102 {
103 int result;
104 uint32_t num_capsets;
105 uint32_t capset_id, capset_version, capset_size;
106 bool found_cross_domain = false;
107 struct CrossDomainCapabilities *capset;
108
109 result = rutabaga_get_num_capsets(test->rutabaga, &num_capsets);
110 CHECK_RESULT(result);
111 CHECK(num_capsets == 1);
112
113 for (uint32_t i = 0; i < num_capsets; i++) {
114 result =
115 rutabaga_get_capset_info(test->rutabaga, i, &capset_id, &capset_version, &capset_size);
116 CHECK_RESULT(result);
117 if (capset_id == RUTABAGA_CAPSET_CROSS_DOMAIN) {
118 found_cross_domain = true;
119 CHECK(capset_size == (uint32_t)sizeof(struct CrossDomainCapabilities));
120 }
121 }
122
123 CHECK(found_cross_domain);
124 CHECK_RESULT(result);
125
126 capset = (struct CrossDomainCapabilities *)calloc(1, capset_size);
127 result = rutabaga_get_capset(test->rutabaga, RUTABAGA_CAPSET_CROSS_DOMAIN, 0, (uint8_t *)capset,
128 capset_size);
129 CHECK_RESULT(result);
130
131 CHECK(capset->version == 1);
132 free(capset);
133
134 size_t context_name_len = 0;
135 if (context_name)
136 context_name_len = strlen(context_name);
137
138 result = rutabaga_context_create(test->rutabaga, test->ctx_id, RUTABAGA_CAPSET_CROSS_DOMAIN,
139 context_name, context_name_len);
140 CHECK_RESULT(result);
141
142 return 0;
143 }
144
test_init_context(struct rutabaga_test * test)145 static int test_init_context(struct rutabaga_test *test)
146 {
147 int result;
148 struct rutabaga_create_blob rc_blob = { 0 };
149 struct rutabaga_iovecs vecs = { 0 };
150 struct CrossDomainInit cmd_init = { { 0 } };
151
152 struct iovec *iovecs = (struct iovec *)calloc(1, sizeof(struct iovec));
153 iovecs[0].iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
154 iovecs[0].iov_len = DEFAULT_BUFFER_SIZE;
155
156 test->guest_iovecs = iovecs;
157 rc_blob.blob_mem = RUTABAGA_BLOB_MEM_GUEST;
158 rc_blob.blob_flags = RUTABAGA_BLOB_FLAG_USE_MAPPABLE;
159 rc_blob.size = DEFAULT_BUFFER_SIZE;
160
161 vecs.iovecs = iovecs;
162 vecs.num_iovecs = 1;
163
164 result = rutabaga_resource_create_blob(test->rutabaga, 0, test->guest_blob_resource_id,
165 &rc_blob, &vecs, NULL);
166 CHECK_RESULT(result);
167
168 result = rutabaga_context_attach_resource(test->rutabaga, test->ctx_id,
169 test->guest_blob_resource_id);
170 CHECK_RESULT(result);
171
172 cmd_init.hdr.cmd = CROSS_DOMAIN_CMD_INIT;
173 cmd_init.hdr.cmd_size = sizeof(struct CrossDomainInit);
174 cmd_init.ring_id = test->guest_blob_resource_id;
175 cmd_init.channel_type = CROSS_DOMAIN_CHANNEL_TYPE_WAYLAND;
176
177 result = rutabaga_submit_command(test->rutabaga, test->ctx_id, (uint8_t *)&cmd_init,
178 cmd_init.hdr.cmd_size);
179 CHECK_RESULT(result);
180 return 0;
181 }
182
test_command_submission(struct rutabaga_test * test)183 static int test_command_submission(struct rutabaga_test *test)
184 {
185 int result;
186 struct CrossDomainGetImageRequirements cmd_get_reqs = { 0 };
187 struct CrossDomainImageRequirements *image_reqs = (void *)test->guest_iovecs[0].iov_base;
188 struct rutabaga_create_blob rc_blob = { 0 };
189 struct rutabaga_fence fence;
190 struct rutabaga_handle handle = { 0 };
191 uint32_t map_info;
192
193 fence.flags = RUTABAGA_FLAG_FENCE | RUTABAGA_FLAG_INFO_RING_IDX;
194 fence.ctx_id = test->ctx_id;
195 fence.ring_idx = 0;
196
197 cmd_get_reqs.hdr.cmd = CROSS_DOMAIN_CMD_GET_IMAGE_REQUIREMENTS;
198 cmd_get_reqs.hdr.cmd_size = sizeof(struct CrossDomainGetImageRequirements);
199
200 for (uint32_t i = 0; i < NUM_ITERATIONS; i++) {
201 for (uint32_t j = 0; j < NUM_ITERATIONS; j++) {
202 fence.fence_id = s_fence_id;
203 map_info = 0;
204
205 cmd_get_reqs.width = WIDTH * i;
206 cmd_get_reqs.height = HEIGHT * j;
207 cmd_get_reqs.drm_format = DRM_FORMAT_XRGB8888;
208
209 cmd_get_reqs.flags = GBM_BO_USE_LINEAR | GBM_BO_USE_SCANOUT;
210
211 result = rutabaga_submit_command(test->rutabaga, test->ctx_id, (uint8_t *)&cmd_get_reqs,
212 cmd_get_reqs.hdr.cmd_size);
213
214 CHECK(test->value < fence.fence_id);
215 result = rutabaga_create_fence(test->rutabaga, &fence);
216
217 CHECK_RESULT(result);
218 for (;;) {
219 if (fence.fence_id == test->value)
220 break;
221 }
222
223 CHECK(image_reqs->strides[0] >= cmd_get_reqs.width * 4);
224 CHECK(image_reqs->size >= (cmd_get_reqs.width * 4) * cmd_get_reqs.height);
225
226 rc_blob.blob_mem = RUTABAGA_BLOB_MEM_HOST3D;
227 rc_blob.blob_flags = RUTABAGA_BLOB_FLAG_USE_MAPPABLE | RUTABAGA_BLOB_FLAG_USE_SHAREABLE;
228 rc_blob.blob_id = image_reqs->blob_id;
229 rc_blob.size = image_reqs->size;
230
231 result = rutabaga_resource_create_blob(test->rutabaga, test->ctx_id, s_resource_id,
232 &rc_blob, NULL, NULL);
233 CHECK_RESULT(result);
234
235 result = rutabaga_context_attach_resource(test->rutabaga, test->ctx_id, s_resource_id);
236 CHECK_RESULT(result);
237
238 result = rutabaga_resource_map_info(test->rutabaga, s_resource_id, &map_info);
239 CHECK_RESULT(result);
240 CHECK(map_info > 0);
241
242 result = rutabaga_resource_export_blob(test->rutabaga, s_resource_id, &handle);
243 CHECK_RESULT(result);
244 CHECK(handle.os_handle >= 0);
245
246 result = close(handle.os_handle);
247 CHECK_RESULT(result);
248
249 result = rutabaga_context_detach_resource(test->rutabaga, test->ctx_id, s_resource_id);
250 CHECK_RESULT(result);
251
252 result = rutabaga_resource_unref(test->rutabaga, s_resource_id);
253 CHECK_RESULT(result);
254
255 s_resource_id++;
256 s_fence_id++;
257 }
258 }
259
260 return 0;
261 }
262
test_context_finish(struct rutabaga_test * test)263 static int test_context_finish(struct rutabaga_test *test)
264 {
265 int result;
266
267 result = rutabaga_context_detach_resource(test->rutabaga, test->ctx_id,
268 test->guest_blob_resource_id);
269 CHECK_RESULT(result);
270
271 result = rutabaga_resource_unref(test->rutabaga, test->guest_blob_resource_id);
272 CHECK_RESULT(result);
273
274 free(test->guest_iovecs[0].iov_base);
275
276 result = rutabaga_context_destroy(test->rutabaga, test->ctx_id);
277 CHECK_RESULT(result);
278
279 return 0;
280 }
281
test_rutabaga_2d(struct rutabaga_test * test)282 static int test_rutabaga_2d(struct rutabaga_test *test)
283 {
284 struct rutabaga_create_3d rc_3d = { 0 };
285 struct rutabaga_transfer transfer = { 0 };
286 int result;
287 uint32_t resource_id = s_resource_id++;
288
289 struct rutabaga_iovecs vecs = { 0 };
290 struct iovec *iovecs = (struct iovec *)calloc(1, sizeof(struct iovec));
291 uint8_t *test_data;
292 struct iovec result_iovec;
293
294 iovecs[0].iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
295 iovecs[0].iov_len = DEFAULT_BUFFER_SIZE;
296 result_iovec.iov_base = calloc(1, DEFAULT_BUFFER_SIZE);
297 result_iovec.iov_len = DEFAULT_BUFFER_SIZE;
298 test_data = (uint8_t *)result_iovec.iov_base;
299
300 vecs.iovecs = iovecs;
301 vecs.num_iovecs = 1;
302
303 rc_3d.target = PIPE_TEXTURE_2D;
304 rc_3d.bind = PIPE_BIND_RENDER_TARGET;
305 rc_3d.format = VIRTIO_GPU_FORMAT_B8G8R8A8_UNORM;
306 rc_3d.width = DEFAULT_BUFFER_SIZE / 16;
307 rc_3d.height = 4;
308
309 transfer.w = DEFAULT_BUFFER_SIZE / 16;
310 transfer.h = 4;
311 transfer.d = 1;
312
313 result = rutabaga_resource_create_3d(test->rutabaga, resource_id, &rc_3d);
314 CHECK_RESULT(result);
315
316 result = rutabaga_resource_attach_backing(test->rutabaga, resource_id, &vecs);
317 CHECK_RESULT(result);
318
319 memset(iovecs[0].iov_base, 8, DEFAULT_BUFFER_SIZE);
320
321 result =
322 rutabaga_resource_transfer_read(test->rutabaga, 0, resource_id, &transfer, &result_iovec);
323 CHECK_RESULT(result);
324
325 CHECK(test_data[0] == 0);
326
327 result = rutabaga_resource_transfer_write(test->rutabaga, 0, resource_id, &transfer);
328 CHECK_RESULT(result);
329
330 result =
331 rutabaga_resource_transfer_read(test->rutabaga, 0, resource_id, &transfer, &result_iovec);
332 CHECK_RESULT(result);
333
334 CHECK(test_data[0] == 8);
335
336 result = rutabaga_resource_detach_backing(test->rutabaga, resource_id);
337 CHECK_RESULT(result);
338
339 result = rutabaga_resource_unref(test->rutabaga, resource_id);
340 CHECK_RESULT(result);
341
342 free(iovecs[0].iov_base);
343 free(iovecs);
344 free(test_data);
345 return 0;
346 }
347
test_rutabaga_finish(struct rutabaga_test * test)348 static int test_rutabaga_finish(struct rutabaga_test *test)
349 {
350 int result;
351
352 result = rutabaga_finish(&test->rutabaga);
353 CHECK_RESULT(result);
354 CHECK(test->rutabaga == NULL);
355 return 0;
356 }
357
main(int argc,char * argv[])358 int main(int argc, char *argv[])
359 {
360 struct rutabaga_test test = { 0 };
361 test.ctx_id = 1;
362 test.guest_blob_resource_id = s_resource_id++;
363
364 int result;
365
366 const char *context_names[] = {
367 NULL,
368 "test_context",
369 };
370 const uint32_t num_context_names = 2;
371
372 for (uint32_t i = 0; i < num_context_names; i++) {
373 const char *context_name = context_names[i];
374 for (uint32_t j = 0; j < NUM_ITERATIONS; j++) {
375 result = test_rutabaga_init(&test, 1 << RUTABAGA_CAPSET_CROSS_DOMAIN);
376 CHECK_RESULT(result);
377
378 result |= test_create_context(&test, context_name);
379 CHECK_RESULT(result);
380
381 result |= test_init_context(&test);
382 CHECK_RESULT(result);
383
384 result |= test_command_submission(&test);
385 CHECK_RESULT(result);
386
387 result |= test_context_finish(&test);
388 CHECK_RESULT(result);
389
390 result |= test_rutabaga_finish(&test);
391 CHECK_RESULT(result);
392 }
393 }
394
395 for (uint32_t i = 0; i < NUM_ITERATIONS; i++) {
396 result = test_rutabaga_init(&test, 0);
397 CHECK_RESULT(result);
398
399 result |= test_rutabaga_2d(&test);
400 CHECK_RESULT(result);
401
402 result |= test_rutabaga_finish(&test);
403 CHECK_RESULT(result);
404 }
405
406 printf("[ PASSED ] rutabaga_test success\n");
407 return 0;
408 }
409