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