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