1 /*
2 * Copyright 2024 Sergio Lopez
3 * SPDX-License-Identifier: MIT
4 */
5
6 #include "agx_device_virtio.h"
7
8 #include <inttypes.h>
9 #include <sys/mman.h>
10
11 #include "drm-uapi/virtgpu_drm.h"
12 #include "unstable_asahi_drm.h"
13
14 #define VIRGL_RENDERER_UNSTABLE_APIS 1
15 #include "vdrm.h"
16 #include "virglrenderer_hw.h"
17
18 #include "asahi_proto.h"
19
20 /**
21 * Helper for simple pass-thru ioctls
22 */
23 int
agx_virtio_simple_ioctl(struct agx_device * dev,unsigned cmd,void * _req)24 agx_virtio_simple_ioctl(struct agx_device *dev, unsigned cmd, void *_req)
25 {
26 struct vdrm_device *vdrm = dev->vdrm;
27 unsigned req_len = sizeof(struct asahi_ccmd_ioctl_simple_req);
28 unsigned rsp_len = sizeof(struct asahi_ccmd_ioctl_simple_rsp);
29
30 req_len += _IOC_SIZE(cmd);
31 if (cmd & IOC_OUT)
32 rsp_len += _IOC_SIZE(cmd);
33
34 uint8_t buf[req_len];
35 struct asahi_ccmd_ioctl_simple_req *req = (void *)buf;
36 struct asahi_ccmd_ioctl_simple_rsp *rsp;
37
38 req->hdr = ASAHI_CCMD(IOCTL_SIMPLE, req_len);
39 req->cmd = cmd;
40 memcpy(req->payload, _req, _IOC_SIZE(cmd));
41
42 rsp = vdrm_alloc_rsp(vdrm, &req->hdr, rsp_len);
43
44 int ret = vdrm_send_req(vdrm, &req->hdr, true);
45 if (ret) {
46 fprintf(stderr, "simple_ioctl: vdrm_send_req failed\n");
47 return ret;
48 }
49
50 if (cmd & IOC_OUT)
51 memcpy(_req, rsp->payload, _IOC_SIZE(cmd));
52
53 return rsp->ret;
54 }
55
56 static struct agx_bo *
agx_virtio_bo_alloc(struct agx_device * dev,size_t size,size_t align,enum agx_bo_flags flags)57 agx_virtio_bo_alloc(struct agx_device *dev, size_t size, size_t align,
58 enum agx_bo_flags flags)
59 {
60 struct agx_bo *bo;
61 unsigned handle = 0;
62
63 size = ALIGN_POT(size, dev->params.vm_page_size);
64
65 /* executable implies low va */
66 assert(!(flags & AGX_BO_EXEC) || (flags & AGX_BO_LOW_VA));
67
68 struct asahi_ccmd_gem_new_req req = {
69 .hdr = ASAHI_CCMD(GEM_NEW, sizeof(req)),
70 .size = size,
71 };
72
73 if (flags & AGX_BO_WRITEBACK)
74 req.flags |= ASAHI_GEM_WRITEBACK;
75
76 uint32_t blob_flags =
77 VIRTGPU_BLOB_FLAG_USE_MAPPABLE | VIRTGPU_BLOB_FLAG_USE_SHAREABLE;
78
79 req.bind_flags = ASAHI_BIND_READ;
80 if (!(flags & AGX_BO_READONLY)) {
81 req.bind_flags |= ASAHI_BIND_WRITE;
82 }
83
84 uint32_t blob_id = p_atomic_inc_return(&dev->next_blob_id);
85
86 enum agx_va_flags va_flags = flags & AGX_BO_LOW_VA ? AGX_VA_USC : 0;
87 struct agx_va *va =
88 agx_va_alloc(dev, size, dev->params.vm_page_size, va_flags, 0);
89 if (!va) {
90 fprintf(stderr, "Failed to allocate BO VMA\n");
91 return NULL;
92 }
93
94 req.addr = va->addr;
95 req.blob_id = blob_id;
96 req.vm_id = dev->vm_id;
97
98 handle = vdrm_bo_create(dev->vdrm, size, blob_flags, blob_id, &req.hdr);
99 if (!handle) {
100 fprintf(stderr, "vdrm_bo_created failed\n");
101 return NULL;
102 }
103
104 pthread_mutex_lock(&dev->bo_map_lock);
105 bo = agx_lookup_bo(dev, handle);
106 dev->max_handle = MAX2(dev->max_handle, handle);
107 pthread_mutex_unlock(&dev->bo_map_lock);
108
109 /* Fresh handle */
110 assert(!memcmp(bo, &((struct agx_bo){}), sizeof(*bo)));
111
112 bo->size = size;
113 bo->align = MAX2(dev->params.vm_page_size, align);
114 bo->flags = flags;
115 bo->handle = handle;
116 bo->prime_fd = -1;
117 bo->blob_id = blob_id;
118 bo->va = va;
119 bo->vbo_res_id = vdrm_handle_to_res_id(dev->vdrm, handle);
120
121 dev->ops.bo_mmap(dev, bo);
122 return bo;
123 }
124
125 static int
agx_virtio_bo_bind(struct agx_device * dev,struct agx_bo * bo,uint64_t addr,size_t size_B,uint64_t offset_B,uint32_t flags,bool unbind)126 agx_virtio_bo_bind(struct agx_device *dev, struct agx_bo *bo, uint64_t addr,
127 size_t size_B, uint64_t offset_B, uint32_t flags,
128 bool unbind)
129 {
130 assert(offset_B == 0 && "TODO: need to extend virtgpu");
131
132 struct asahi_ccmd_gem_bind_req req = {
133 .op = unbind ? ASAHI_BIND_OP_UNBIND : ASAHI_BIND_OP_BIND,
134 .flags = flags,
135 .vm_id = dev->vm_id,
136 .res_id = bo->vbo_res_id,
137 .size = size_B,
138 .addr = addr,
139 .hdr.cmd = ASAHI_CCMD_GEM_BIND,
140 .hdr.len = sizeof(struct asahi_ccmd_gem_bind_req),
141 };
142
143 int ret = vdrm_send_req(dev->vdrm, &req.hdr, false);
144 if (ret) {
145 fprintf(stderr, "DRM_IOCTL_ASAHI_GEM_BIND failed: %d (handle=%d)\n", ret,
146 bo->handle);
147 }
148
149 return ret;
150 }
151
152 static void
agx_virtio_bo_mmap(struct agx_device * dev,struct agx_bo * bo)153 agx_virtio_bo_mmap(struct agx_device *dev, struct agx_bo *bo)
154 {
155 if (bo->map) {
156 return;
157 }
158
159 bo->map = vdrm_bo_map(dev->vdrm, bo->handle, bo->size, NULL);
160 if (bo->map == MAP_FAILED) {
161 bo->map = NULL;
162 fprintf(stderr, "mmap failed: result=%p size=0x%llx fd=%i\n", bo->map,
163 (long long)bo->size, dev->fd);
164 }
165 }
166
167 static ssize_t
agx_virtio_get_params(struct agx_device * dev,void * buf,size_t size)168 agx_virtio_get_params(struct agx_device *dev, void *buf, size_t size)
169 {
170 struct vdrm_device *vdrm = dev->vdrm;
171 struct asahi_ccmd_get_params_req req = {
172 .params.size = size,
173 .hdr.cmd = ASAHI_CCMD_GET_PARAMS,
174 .hdr.len = sizeof(struct asahi_ccmd_get_params_req),
175 };
176 struct asahi_ccmd_get_params_rsp *rsp;
177
178 rsp =
179 vdrm_alloc_rsp(vdrm, &req.hdr, sizeof(struct asahi_ccmd_get_params_rsp));
180
181 int ret = vdrm_send_req(vdrm, &req.hdr, true);
182 if (ret)
183 goto out;
184
185 ret = rsp->ret;
186 if (!ret) {
187 memcpy(buf, &rsp->params, size);
188 return size;
189 }
190
191 out:
192 return ret;
193 }
194
195 static int
agx_virtio_submit(struct agx_device * dev,struct drm_asahi_submit * submit,uint32_t vbo_res_id)196 agx_virtio_submit(struct agx_device *dev, struct drm_asahi_submit *submit,
197 uint32_t vbo_res_id)
198 {
199 struct drm_asahi_command *commands =
200 (struct drm_asahi_command *)submit->commands;
201 struct drm_asahi_sync *in_syncs = (struct drm_asahi_sync *)submit->in_syncs;
202 struct drm_asahi_sync *out_syncs =
203 (struct drm_asahi_sync *)submit->out_syncs;
204 size_t req_len = sizeof(struct asahi_ccmd_submit_req);
205
206 for (int i = 0; i < submit->command_count; i++) {
207 switch (commands[i].cmd_type) {
208 case DRM_ASAHI_CMD_COMPUTE: {
209 req_len += sizeof(struct drm_asahi_command) +
210 sizeof(struct drm_asahi_cmd_compute);
211 break;
212 }
213
214 case DRM_ASAHI_CMD_RENDER: {
215 struct drm_asahi_cmd_render *render =
216 (struct drm_asahi_cmd_render *)commands[i].cmd_buffer;
217 req_len += sizeof(struct drm_asahi_command) +
218 sizeof(struct drm_asahi_cmd_render);
219 req_len += render->fragment_attachment_count *
220 sizeof(struct drm_asahi_attachment);
221 break;
222 }
223
224 default:
225 return EINVAL;
226 }
227 }
228
229 struct asahi_ccmd_submit_req *req =
230 (struct asahi_ccmd_submit_req *)calloc(1, req_len);
231
232 req->queue_id = submit->queue_id;
233 req->result_res_id = vbo_res_id;
234 req->command_count = submit->command_count;
235
236 char *ptr = (char *)&req->payload;
237
238 for (int i = 0; i < submit->command_count; i++) {
239 memcpy(ptr, &commands[i], sizeof(struct drm_asahi_command));
240 ptr += sizeof(struct drm_asahi_command);
241
242 memcpy(ptr, (char *)commands[i].cmd_buffer, commands[i].cmd_buffer_size);
243 ptr += commands[i].cmd_buffer_size;
244
245 if (commands[i].cmd_type == DRM_ASAHI_CMD_RENDER) {
246 struct drm_asahi_cmd_render *render =
247 (struct drm_asahi_cmd_render *)commands[i].cmd_buffer;
248 size_t fragments_size = sizeof(struct drm_asahi_attachment) *
249 render->fragment_attachment_count;
250 memcpy(ptr, (char *)render->fragment_attachments, fragments_size);
251 ptr += fragments_size;
252 }
253 }
254
255 req->hdr.cmd = ASAHI_CCMD_SUBMIT;
256 req->hdr.len = req_len;
257
258 struct drm_virtgpu_execbuffer_syncobj *vdrm_in_syncs = calloc(
259 submit->in_sync_count, sizeof(struct drm_virtgpu_execbuffer_syncobj));
260 for (int i = 0; i < submit->in_sync_count; i++) {
261 vdrm_in_syncs[i].handle = in_syncs[i].handle;
262 vdrm_in_syncs[i].point = in_syncs[i].timeline_value;
263 }
264
265 struct drm_virtgpu_execbuffer_syncobj *vdrm_out_syncs = calloc(
266 submit->out_sync_count, sizeof(struct drm_virtgpu_execbuffer_syncobj));
267 for (int i = 0; i < submit->out_sync_count; i++) {
268 vdrm_out_syncs[i].handle = out_syncs[i].handle;
269 vdrm_out_syncs[i].point = out_syncs[i].timeline_value;
270 }
271
272 struct vdrm_execbuf_params p = {
273 /* Signal the host we want to wait for the command to complete */
274 .ring_idx = 1,
275 .req = &req->hdr,
276 .num_in_syncobjs = submit->in_sync_count,
277 .in_syncobjs = vdrm_in_syncs,
278 .num_out_syncobjs = submit->out_sync_count,
279 .out_syncobjs = vdrm_out_syncs,
280 };
281
282 int ret = vdrm_execbuf(dev->vdrm, &p);
283
284 free(vdrm_out_syncs);
285 free(vdrm_in_syncs);
286 free(req);
287 return ret;
288 }
289
290 const agx_device_ops_t agx_virtio_device_ops = {
291 .bo_alloc = agx_virtio_bo_alloc,
292 .bo_bind = agx_virtio_bo_bind,
293 .bo_mmap = agx_virtio_bo_mmap,
294 .get_params = agx_virtio_get_params,
295 .submit = agx_virtio_submit,
296 };
297
298 bool
agx_virtio_open_device(struct agx_device * dev)299 agx_virtio_open_device(struct agx_device *dev)
300 {
301 struct vdrm_device *vdrm;
302
303 vdrm = vdrm_device_connect(dev->fd, 2);
304 if (!vdrm) {
305 fprintf(stderr, "could not connect vdrm\n");
306 return false;
307 }
308
309 dev->vdrm = vdrm;
310 dev->ops = agx_virtio_device_ops;
311 return true;
312 }
313