master
c 248 lines 6.68 KB
Raw
1 /*
2 * Virtio GPU Device
3 *
4 * Copyright Red Hat, Inc. 2013-2014
5 *
6 * Authors:
7 * Dave Airlie <airlied@redhat.com>
8 * Gerd Hoffmann <kraxel@redhat.com>
9 *
10 * This work is licensed under the terms of the GNU GPL, version 2 or later.
11 * See the COPYING file in the top-level directory.
12 */
13
14 #include "qemu/osdep.h"
15 #include "qemu/error-report.h"
16 #include "qemu/units.h"
17 #include "qemu/iov.h"
18 #include "ui/console.h"
19 #include "hw/virtio/virtio-gpu.h"
20 #include "hw/virtio/virtio-gpu-pixman.h"
21 #include "trace.h"
22 #include "system/ramblock.h"
23 #include "system/hostmem.h"
24 #include <sys/ioctl.h>
25 #include <linux/memfd.h>
26 #include "qemu/memfd.h"
27 #include "standard-headers/linux/udmabuf.h"
28 #include "standard-headers/drm/drm_fourcc.h"
29
30 static void virtio_gpu_create_udmabuf(struct virtio_gpu_simple_resource *res)
31 {
32 struct udmabuf_create_list *list;
33 RAMBlock *rb;
34 ram_addr_t offset;
35 int udmabuf, i;
36
37 udmabuf = udmabuf_fd();
38 if (udmabuf < 0) {
39 return;
40 }
41
42 list = g_try_malloc0(sizeof(struct udmabuf_create_list) +
43 sizeof(struct udmabuf_create_item) * res->iov_cnt);
44 if (!list) {
45 return;
46 }
47
48 for (i = 0; i < res->iov_cnt; i++) {
49 rcu_read_lock();
50 rb = qemu_ram_block_from_host(res->iov[i].iov_base, false, &offset);
51 rcu_read_unlock();
52
53 if (!rb || rb->fd < 0) {
54 g_free(list);
55 return;
56 }
57
58 list->list[i].memfd = rb->fd;
59 list->list[i].offset = offset;
60 list->list[i].size = res->iov[i].iov_len;
61 }
62
63 list->count = res->iov_cnt;
64 list->flags = UDMABUF_FLAGS_CLOEXEC;
65
66 res->dmabuf_fd = ioctl(udmabuf, UDMABUF_CREATE_LIST, list);
67 if (res->dmabuf_fd < 0) {
68 warn_report("%s: UDMABUF_CREATE_LIST: %s", __func__,
69 strerror(errno));
70 }
71 g_free(list);
72 }
73
74 static void virtio_gpu_remap_udmabuf(struct virtio_gpu_simple_resource *res)
75 {
76 res->remapped = mmap(NULL, res->blob_size, PROT_READ,
77 MAP_SHARED, res->dmabuf_fd, 0);
78 if (res->remapped == MAP_FAILED) {
79 warn_report("%s: dmabuf mmap failed: %s", __func__,
80 strerror(errno));
81 res->remapped = NULL;
82 }
83 }
84
85 static void virtio_gpu_destroy_udmabuf(struct virtio_gpu_simple_resource *res)
86 {
87 if (res->remapped) {
88 munmap(res->remapped, res->blob_size);
89 res->remapped = NULL;
90 }
91 if (res->dmabuf_fd >= 0) {
92 close(res->dmabuf_fd);
93 res->dmabuf_fd = -1;
94 }
95 }
96
97 static int find_memory_backend_type(Object *obj, void *opaque)
98 {
99 bool *memfd_backend = opaque;
100 int ret;
101
102 if (object_dynamic_cast(obj, TYPE_MEMORY_BACKEND)) {
103 HostMemoryBackend *backend = MEMORY_BACKEND(obj);
104 RAMBlock *rb = backend->mr.ram_block;
105
106 if (rb && rb->fd > 0) {
107 ret = fcntl(rb->fd, F_GET_SEALS);
108 if (ret > 0) {
109 *memfd_backend = true;
110 }
111 }
112 }
113
114 return 0;
115 }
116
117 bool virtio_gpu_have_udmabuf(void)
118 {
119 Object *memdev_root;
120 int udmabuf;
121 bool memfd_backend = false;
122
123 udmabuf = udmabuf_fd();
124 if (udmabuf < 0) {
125 return false;
126 }
127
128 memdev_root = object_resolve_path("/objects", NULL);
129 object_child_foreach(memdev_root, find_memory_backend_type, &memfd_backend);
130
131 return memfd_backend;
132 }
133
134 bool virtio_gpu_init_udmabuf(struct virtio_gpu_simple_resource *res)
135 {
136 void *pdata = NULL;
137
138 res->dmabuf_fd = -1;
139 if (res->iov_cnt == 1 &&
140 res->iov[0].iov_len < 4096) {
141 pdata = res->iov[0].iov_base;
142 } else if (res->blob_size) {
143 virtio_gpu_create_udmabuf(res);
144 if (res->dmabuf_fd < 0) {
145 return false;
146 }
147 virtio_gpu_remap_udmabuf(res);
148 if (!res->remapped) {
149 virtio_gpu_destroy_udmabuf(res);
150 return false;
151 }
152 pdata = res->remapped;
153 }
154
155 res->blob = pdata;
156
157 return true;
158 }
159
160 static void virtio_gpu_free_dmabuf(VirtIOGPU *g, VGPUDMABuf *dmabuf)
161 {
162 struct virtio_gpu_scanout *scanout;
163
164 scanout = &g->parent_obj.scanout[dmabuf->scanout_id];
165 qemu_console_gl_release_dmabuf(scanout->con, dmabuf->buf);
166 g_clear_pointer(&dmabuf->buf, qemu_dmabuf_free);
167 QTAILQ_REMOVE(&g->dmabuf.bufs, dmabuf, next);
168 g_free(dmabuf);
169 }
170
171 void virtio_gpu_fini_udmabuf(VirtIOGPU *g, struct virtio_gpu_simple_resource *res)
172 {
173 int max_outputs = g->parent_obj.conf.max_outputs;
174 int i;
175
176 for (i = 0; i < max_outputs; i++) {
177 VGPUDMABuf *dmabuf = g->dmabuf.primary[i];
178
179 if (dmabuf &&
180 qemu_dmabuf_get_num_planes(dmabuf->buf) > 0 &&
181 qemu_dmabuf_get_fds(dmabuf->buf, NULL)[0] == res->dmabuf_fd &&
182 res->dmabuf_fd != -1) {
183 qemu_dmabuf_close(dmabuf->buf);
184 res->dmabuf_fd = -1;
185 }
186 }
187
188 virtio_gpu_destroy_udmabuf(res);
189 }
190
191 static VGPUDMABuf
192 *virtio_gpu_create_dmabuf(VirtIOGPU *g,
193 uint32_t scanout_id,
194 struct virtio_gpu_simple_resource *res,
195 struct virtio_gpu_framebuffer *fb,
196 struct virtio_gpu_rect *r)
197 {
198 VGPUDMABuf *dmabuf;
199 uint32_t offset = 0;
200
201 if (res->dmabuf_fd < 0) {
202 return NULL;
203 }
204
205 dmabuf = g_new0(VGPUDMABuf, 1);
206 dmabuf->buf = qemu_dmabuf_new(r->width, r->height,
207 &offset, &fb->stride,
208 r->x, r->y, fb->width, fb->height,
209 qemu_pixman_to_drm_format(fb->format),
210 DRM_FORMAT_MOD_INVALID, &res->dmabuf_fd,
211 1, true, false);
212 dmabuf->scanout_id = scanout_id;
213 QTAILQ_INSERT_HEAD(&g->dmabuf.bufs, dmabuf, next);
214
215 return dmabuf;
216 }
217
218 int virtio_gpu_update_dmabuf(VirtIOGPU *g,
219 uint32_t scanout_id,
220 struct virtio_gpu_simple_resource *res,
221 struct virtio_gpu_framebuffer *fb,
222 struct virtio_gpu_rect *r)
223 {
224 struct virtio_gpu_scanout *scanout = &g->parent_obj.scanout[scanout_id];
225 VGPUDMABuf *new_primary, *old_primary = NULL;
226 uint32_t width, height;
227
228 new_primary = virtio_gpu_create_dmabuf(g, scanout_id, res, fb, r);
229 if (!new_primary) {
230 return -EINVAL;
231 }
232
233 if (g->dmabuf.primary[scanout_id]) {
234 old_primary = g->dmabuf.primary[scanout_id];
235 }
236
237 width = qemu_dmabuf_get_width(new_primary->buf);
238 height = qemu_dmabuf_get_height(new_primary->buf);
239 g->dmabuf.primary[scanout_id] = new_primary;
240 qemu_console_resize(scanout->con, width, height);
241 qemu_console_gl_scanout_dmabuf(scanout->con, new_primary->buf);
242
243 if (old_primary) {
244 virtio_gpu_free_dmabuf(g, old_primary);
245 }
246
247 return 0;
248 }