blob: d9b24c7e432f2ced26102e8f6fffddb7f3185771 [file] [log] [blame]
rjw1f884582022-01-06 17:20:42 +08001/*
2 * Copyright (c) 2014-2015 Travis Geiselbrecht
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining
5 * a copy of this software and associated documentation files
6 * (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
11 *
12 * The above copyright notice and this permission notice shall be
13 * included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22 */
23#include <dev/virtio/gpu.h>
24
25#include <debug.h>
26#include <assert.h>
27#include <trace.h>
28#include <compiler.h>
29#include <list.h>
30#include <err.h>
31#include <string.h>
32#include <kernel/thread.h>
33#include <kernel/event.h>
34#include <kernel/mutex.h>
35#include <kernel/vm.h>
36#include <lib/gfx.h>
37#include <dev/display.h>
38
39#include "virtio_gpu.h"
40
41#define LOCAL_TRACE 0
42
43static enum handler_return virtio_gpu_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e);
44static enum handler_return virtio_gpu_config_change_callback(struct virtio_device *dev);
45static int virtio_gpu_flush_thread(void *arg);
46
47struct virtio_gpu_dev {
48 struct virtio_device *dev;
49
50 mutex_t lock;
51 event_t io_event;
52
53 void *gpu_request;
54 paddr_t gpu_request_phys;
55
56 /* a saved copy of the display */
57 struct virtio_gpu_display_one pmode;
58 int pmode_id;
59
60 /* resource id that is set as scanout */
61 uint32_t display_resource_id;
62
63 /* next resource id */
64 uint32_t next_resource_id;
65
66 event_t flush_event;
67
68 /* framebuffer */
69 void *fb;
70};
71
72static struct virtio_gpu_dev *the_gdev;
73
74static status_t send_command_response(struct virtio_gpu_dev *gdev, const void *cmd, size_t cmd_len, void **_res, size_t res_len)
75{
76 DEBUG_ASSERT(gdev);
77 DEBUG_ASSERT(cmd);
78 DEBUG_ASSERT(_res);
79 DEBUG_ASSERT(cmd_len + res_len < PAGE_SIZE);
80
81 LTRACEF("gdev %p, cmd %p, cmd_len %zu, res %p, res_len %zu\n", gdev, cmd, cmd_len, _res, res_len);
82
83 uint16_t i;
84 struct vring_desc *desc = virtio_alloc_desc_chain(gdev->dev, 0, 2, &i);
85 DEBUG_ASSERT(desc);
86
87 memcpy(gdev->gpu_request, cmd, cmd_len);
88
89 desc->addr = gdev->gpu_request_phys;
90 desc->len = cmd_len;
91 desc->flags |= VRING_DESC_F_NEXT;
92
93 /* set the second descriptor to the response with the write bit set */
94 desc = virtio_desc_index_to_desc(gdev->dev, 0, desc->next);
95 DEBUG_ASSERT(desc);
96
97 void *res = (void *)((uint8_t *)gdev->gpu_request + cmd_len);
98 *_res = res;
99 paddr_t res_phys = gdev->gpu_request_phys + cmd_len;
100 memset(res, 0, res_len);
101
102 desc->addr = res_phys;
103 desc->len = res_len;
104 desc->flags = VRING_DESC_F_WRITE;
105
106 /* submit the transfer */
107 virtio_submit_chain(gdev->dev, 0, i);
108
109 /* kick it off */
110 virtio_kick(gdev->dev, 0);
111
112 /* wait for result */
113 event_wait(&gdev->io_event);
114
115 return NO_ERROR;
116}
117
118static status_t get_display_info(struct virtio_gpu_dev *gdev)
119{
120 status_t err;
121
122 LTRACEF("gdev %p\n", gdev);
123
124 DEBUG_ASSERT(gdev);
125
126 /* grab a lock to keep this single message at a time */
127 mutex_acquire(&gdev->lock);
128
129 /* construct the get display info message */
130 struct virtio_gpu_ctrl_hdr req;
131 memset(&req, 0, sizeof(req));
132 req.type = VIRTIO_GPU_CMD_GET_DISPLAY_INFO;
133
134 /* send the message and get a response */
135 struct virtio_gpu_resp_display_info *info;
136 err = send_command_response(gdev, &req, sizeof(req), (void **)&info, sizeof(*info));
137 DEBUG_ASSERT(err == NO_ERROR);
138 if (err < NO_ERROR) {
139 mutex_release(&gdev->lock);
140 return ERR_NOT_FOUND;
141 }
142
143 /* we got response */
144 if (info->hdr.type != VIRTIO_GPU_RESP_OK_DISPLAY_INFO) {
145 mutex_release(&gdev->lock);
146 return ERR_NOT_FOUND;
147 }
148
149 LTRACEF("response:\n");
150 for (uint i = 0; i < VIRTIO_GPU_MAX_SCANOUTS; i++) {
151 if (info->pmodes[i].enabled) {
152 LTRACEF("%u: x %u y %u w %u h %u flags 0x%x\n", i,
153 info->pmodes[i].r.x, info->pmodes[i].r.y, info->pmodes[i].r.width, info->pmodes[i].r.height,
154 info->pmodes[i].flags);
155 if (gdev->pmode_id < 0) {
156 /* save the first valid pmode we see */
157 memcpy(&gdev->pmode, &info->pmodes[i], sizeof(gdev->pmode));
158 gdev->pmode_id = i;
159 }
160 }
161 }
162
163 /* release the lock */
164 mutex_release(&gdev->lock);
165
166 return NO_ERROR;
167}
168
169static status_t allocate_2d_resource(struct virtio_gpu_dev *gdev, uint32_t *resource_id, uint32_t width, uint32_t height)
170{
171 status_t err;
172
173 LTRACEF("gdev %p\n", gdev);
174
175 DEBUG_ASSERT(gdev);
176 DEBUG_ASSERT(resource_id);
177
178 /* grab a lock to keep this single message at a time */
179 mutex_acquire(&gdev->lock);
180
181 /* construct the request */
182 struct virtio_gpu_resource_create_2d req;
183 memset(&req, 0, sizeof(req));
184
185 req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_CREATE_2D;
186 req.resource_id = gdev->next_resource_id++;
187 *resource_id = req.resource_id;
188 req.format = VIRTIO_GPU_FORMAT_B8G8R8X8_UNORM;
189 req.width = width;
190 req.height = height;
191
192 /* send the command and get a response */
193 struct virtio_gpu_ctrl_hdr *res;
194 err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
195 DEBUG_ASSERT(err == NO_ERROR);
196
197 /* see if we got a valid response */
198 LTRACEF("response type 0x%x\n", res->type);
199 err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
200
201 /* release the lock */
202 mutex_release(&gdev->lock);
203
204 return err;
205}
206
207static status_t attach_backing(struct virtio_gpu_dev *gdev, uint32_t resource_id, void *ptr, size_t buf_len)
208{
209 status_t err;
210
211 LTRACEF("gdev %p, resource_id %u, ptr %p, buf_len %zu\n", gdev, resource_id, ptr, buf_len);
212
213 DEBUG_ASSERT(gdev);
214 DEBUG_ASSERT(ptr);
215
216 /* grab a lock to keep this single message at a time */
217 mutex_acquire(&gdev->lock);
218
219 /* construct the request */
220 struct {
221 struct virtio_gpu_resource_attach_backing req;
222 struct virtio_gpu_mem_entry mem;
223 } req;
224 memset(&req, 0, sizeof(req));
225
226 req.req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_ATTACH_BACKING;
227 req.req.resource_id = resource_id;
228 req.req.nr_entries = 1;
229
230 paddr_t pa;
231 arch_mmu_query((vaddr_t)ptr, &pa, NULL);
232 req.mem.addr = pa;
233 req.mem.length = buf_len;
234
235 /* send the command and get a response */
236 struct virtio_gpu_ctrl_hdr *res;
237 err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
238 DEBUG_ASSERT(err == NO_ERROR);
239
240 /* see if we got a valid response */
241 LTRACEF("response type 0x%x\n", res->type);
242 err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
243
244 /* release the lock */
245 mutex_release(&gdev->lock);
246
247 return err;
248}
249
250static status_t set_scanout(struct virtio_gpu_dev *gdev, uint32_t scanout_id, uint32_t resource_id, uint32_t width, uint32_t height)
251{
252 status_t err;
253
254 LTRACEF("gdev %p, scanout_id %u, resource_id %u, width %u, height %u\n", gdev, scanout_id, resource_id, width, height);
255
256 /* grab a lock to keep this single message at a time */
257 mutex_acquire(&gdev->lock);
258
259 /* construct the request */
260 struct virtio_gpu_set_scanout req;
261 memset(&req, 0, sizeof(req));
262
263 req.hdr.type = VIRTIO_GPU_CMD_SET_SCANOUT;
264 req.r.x = req.r.y = 0;
265 req.r.width = width;
266 req.r.height = height;
267 req.scanout_id = scanout_id;
268 req.resource_id = resource_id;
269
270 /* send the command and get a response */
271 struct virtio_gpu_ctrl_hdr *res;
272 err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
273 DEBUG_ASSERT(err == NO_ERROR);
274
275 /* see if we got a valid response */
276 LTRACEF("response type 0x%x\n", res->type);
277 err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
278
279 /* release the lock */
280 mutex_release(&gdev->lock);
281
282 return err;
283}
284
285static status_t flush_resource(struct virtio_gpu_dev *gdev, uint32_t resource_id, uint32_t width, uint32_t height)
286{
287 status_t err;
288
289 LTRACEF("gdev %p, resource_id %u, width %u, height %u\n", gdev, resource_id, width, height);
290
291 /* grab a lock to keep this single message at a time */
292 mutex_acquire(&gdev->lock);
293
294 /* construct the request */
295 struct virtio_gpu_resource_flush req;
296 memset(&req, 0, sizeof(req));
297
298 req.hdr.type = VIRTIO_GPU_CMD_RESOURCE_FLUSH;
299 req.r.x = req.r.y = 0;
300 req.r.width = width;
301 req.r.height = height;
302 req.resource_id = resource_id;
303
304 /* send the command and get a response */
305 struct virtio_gpu_ctrl_hdr *res;
306 err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
307 DEBUG_ASSERT(err == NO_ERROR);
308
309 /* see if we got a valid response */
310 LTRACEF("response type 0x%x\n", res->type);
311 err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
312
313 /* release the lock */
314 mutex_release(&gdev->lock);
315
316 return err;
317}
318
319static status_t transfer_to_host_2d(struct virtio_gpu_dev *gdev, uint32_t resource_id, uint32_t width, uint32_t height)
320{
321 status_t err;
322
323 LTRACEF("gdev %p, resource_id %u, width %u, height %u\n", gdev, resource_id, width, height);
324
325 /* grab a lock to keep this single message at a time */
326 mutex_acquire(&gdev->lock);
327
328 /* construct the request */
329 struct virtio_gpu_transfer_to_host_2d req;
330 memset(&req, 0, sizeof(req));
331
332 req.hdr.type = VIRTIO_GPU_CMD_TRANSFER_TO_HOST_2D;
333 req.r.x = req.r.y = 0;
334 req.r.width = width;
335 req.r.height = height;
336 req.offset = 0;
337 req.resource_id = resource_id;
338
339 /* send the command and get a response */
340 struct virtio_gpu_ctrl_hdr *res;
341 err = send_command_response(gdev, &req, sizeof(req), (void **)&res, sizeof(*res));
342 DEBUG_ASSERT(err == NO_ERROR);
343
344 /* see if we got a valid response */
345 LTRACEF("response type 0x%x\n", res->type);
346 err = (res->type == VIRTIO_GPU_RESP_OK_NODATA) ? NO_ERROR : ERR_NO_MEMORY;
347
348 /* release the lock */
349 mutex_release(&gdev->lock);
350
351 return err;
352}
353
354status_t virtio_gpu_start(struct virtio_device *dev)
355{
356 status_t err;
357
358 LTRACEF("dev %p\n", dev);
359
360 struct virtio_gpu_dev *gdev = (struct virtio_gpu_dev *)dev->priv;
361
362 /* get the display info and see if we find a valid pmode */
363 err = get_display_info(gdev);
364 if (err < 0) {
365 LTRACEF("failed to get display info\n");
366 return err;
367 }
368
369 if (gdev->pmode_id < 0) {
370 LTRACEF("we failed to find a pmode, exiting\n");
371 return ERR_NOT_FOUND;
372 }
373
374 /* allocate a resource */
375 err = allocate_2d_resource(gdev, &gdev->display_resource_id, gdev->pmode.r.width, gdev->pmode.r.height);
376 if (err < 0) {
377 LTRACEF("failed to allocate 2d resource\n");
378 return err;
379 }
380
381 /* attach a backing store to the resource */
382 size_t len = gdev->pmode.r.width * gdev->pmode.r.height * 4;
383 gdev->fb = pmm_alloc_kpages(ROUNDUP(len, PAGE_SIZE) / PAGE_SIZE, NULL);
384 if (!gdev->fb) {
385 TRACEF("failed to allocate framebuffer, wanted 0x%zx bytes\n", len);
386 return ERR_NO_MEMORY;
387 }
388
389 printf("virtio-gpu: framebuffer at %p, 0x%zx bytes\n", gdev->fb, len);
390
391 err = attach_backing(gdev, gdev->display_resource_id, gdev->fb, len);
392 if (err < 0) {
393 LTRACEF("failed to attach backing store\n");
394 return err;
395 }
396
397 /* attach this resource as a scanout */
398 err = set_scanout(gdev, gdev->pmode_id, gdev->display_resource_id, gdev->pmode.r.width, gdev->pmode.r.height);
399 if (err < 0) {
400 LTRACEF("failed to set scanout\n");
401 return err;
402 }
403
404 /* create the flush thread */
405 thread_t *t;
406 t = thread_create("virtio gpu flusher", &virtio_gpu_flush_thread, (void *)gdev, HIGH_PRIORITY, DEFAULT_STACK_SIZE);
407 thread_detach_and_resume(t);
408
409 /* kick it once */
410 event_signal(&gdev->flush_event, true);
411
412 LTRACE_EXIT;
413
414 return NO_ERROR;
415}
416
417
418static void dump_gpu_config(const volatile struct virtio_gpu_config *config)
419{
420 LTRACEF("events_read 0x%x\n", config->events_read);
421 LTRACEF("events_clear 0x%x\n", config->events_clear);
422 LTRACEF("num_scanouts 0x%x\n", config->num_scanouts);
423 LTRACEF("reserved 0x%x\n", config->reserved);
424}
425
426status_t virtio_gpu_init(struct virtio_device *dev, uint32_t host_features)
427{
428 LTRACEF("dev %p, host_features 0x%x\n", dev, host_features);
429
430 /* allocate a new gpu device */
431 struct virtio_gpu_dev *gdev = malloc(sizeof(struct virtio_gpu_dev));
432 if (!gdev)
433 return ERR_NO_MEMORY;
434
435 mutex_init(&gdev->lock);
436 event_init(&gdev->io_event, false, EVENT_FLAG_AUTOUNSIGNAL);
437 event_init(&gdev->flush_event, false, EVENT_FLAG_AUTOUNSIGNAL);
438
439 gdev->dev = dev;
440 dev->priv = gdev;
441
442 gdev->pmode_id = -1;
443 gdev->next_resource_id = 1;
444
445 /* allocate memory for a gpu request */
446#if WITH_KERNEL_VM
447 gdev->gpu_request = pmm_alloc_kpage();
448 gdev->gpu_request_phys = kvaddr_to_paddr(gdev->gpu_request);
449#else
450 gdev->gpu_request = malloc(sizeof(struct virtio_gpu_resp_display_info)); // XXX get size better
451 gdev->gpu_request_phys = (paddr_t)gdev->gpu_request;
452#endif
453
454 /* make sure the device is reset */
455 virtio_reset_device(dev);
456
457 volatile struct virtio_gpu_config *config = (struct virtio_gpu_config *)dev->config_ptr;
458 dump_gpu_config(config);
459
460 /* ack and set the driver status bit */
461 virtio_status_acknowledge_driver(dev);
462
463 // XXX check features bits and ack/nak them
464
465 /* allocate a virtio ring */
466 virtio_alloc_ring(dev, 0, 16);
467
468 /* set our irq handler */
469 dev->irq_driver_callback = &virtio_gpu_irq_driver_callback;
470 dev->config_change_callback = &virtio_gpu_config_change_callback;
471
472 /* set DRIVER_OK */
473 virtio_status_driver_ok(dev);
474
475 /* save the main device we've found */
476 the_gdev = gdev;
477
478 printf("found virtio gpu device\n");
479
480 return NO_ERROR;
481}
482
483static enum handler_return virtio_gpu_irq_driver_callback(struct virtio_device *dev, uint ring, const struct vring_used_elem *e)
484{
485 struct virtio_gpu_dev *gdev = (struct virtio_gpu_dev *)dev->priv;
486
487 LTRACEF("dev %p, ring %u, e %p, id %u, len %u\n", dev, ring, e, e->id, e->len);
488
489 /* parse our descriptor chain, add back to the free queue */
490 uint16_t i = e->id;
491 for (;;) {
492 int next;
493 struct vring_desc *desc = virtio_desc_index_to_desc(dev, ring, i);
494
495 //virtio_dump_desc(desc);
496
497 if (desc->flags & VRING_DESC_F_NEXT) {
498 next = desc->next;
499 } else {
500 /* end of chain */
501 next = -1;
502 }
503
504 virtio_free_desc(dev, ring, i);
505
506 if (next < 0)
507 break;
508 i = next;
509 }
510
511 /* signal our event */
512 event_signal(&gdev->io_event, false);
513
514 return INT_RESCHEDULE;
515}
516
517static enum handler_return virtio_gpu_config_change_callback(struct virtio_device *dev)
518{
519 struct virtio_gpu_dev *gdev = (struct virtio_gpu_dev *)dev->priv;
520
521 LTRACEF("gdev %p\n", gdev);
522
523 volatile struct virtio_gpu_config *config = (struct virtio_gpu_config *)dev->config_ptr;
524 dump_gpu_config(config);
525
526 return INT_RESCHEDULE;
527}
528
529static int virtio_gpu_flush_thread(void *arg)
530{
531 struct virtio_gpu_dev *gdev = (struct virtio_gpu_dev *)arg;
532 status_t err;
533
534 for (;;) {
535 event_wait(&gdev->flush_event);
536
537 /* transfer to host 2d */
538 err = transfer_to_host_2d(gdev, gdev->display_resource_id, gdev->pmode.r.width, gdev->pmode.r.height);
539 if (err < 0) {
540 LTRACEF("failed to flush resource\n");
541 continue;
542 }
543
544 /* resource flush */
545 err = flush_resource(gdev, gdev->display_resource_id, gdev->pmode.r.width, gdev->pmode.r.height);
546 if (err < 0) {
547 LTRACEF("failed to flush resource\n");
548 continue;
549 }
550 }
551
552 return 0;
553}
554
555void virtio_gpu_gfx_flush(uint starty, uint endy)
556{
557 event_signal(&the_gdev->flush_event, !arch_ints_disabled());
558}
559
560status_t display_get_info(struct display_info *info)
561{
562 memset(info, 0, sizeof(*info));
563
564 if (!the_gdev)
565 return ERR_NOT_FOUND;
566
567 info->framebuffer = the_gdev->fb;
568 info->format = GFX_FORMAT_RGB_x888;
569 info->width = the_gdev->pmode.r.width;
570 info->height = the_gdev->pmode.r.height;
571 info->stride = info->width;
572 info->flush = virtio_gpu_gfx_flush;
573
574 return NO_ERROR;
575}
576
577