• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright © 2022 Google, Inc.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice (including the next
12  * paragraph) shall be included in all copies or substantial portions of the
13  * Software.
14  *
15  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
18  * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21  * SOFTWARE.
22  */
23 
24 #include "util/libsync.h"
25 
26 #include "virtio_priv.h"
27 
28 static int
bo_allocate(struct virtio_bo * virtio_bo)29 bo_allocate(struct virtio_bo *virtio_bo)
30 {
31    struct fd_bo *bo = &virtio_bo->base;
32    if (!virtio_bo->offset) {
33       struct drm_virtgpu_map req = {
34          .handle = bo->handle,
35       };
36       int ret;
37 
38       ret = drmIoctl(bo->dev->fd, DRM_IOCTL_VIRTGPU_MAP, &req);
39       if (ret) {
40          ERROR_MSG("alloc failed: %s", strerror(errno));
41          return ret;
42       }
43 
44       virtio_bo->offset = req.offset;
45    }
46 
47    return 0;
48 }
49 
50 static int
virtio_bo_offset(struct fd_bo * bo,uint64_t * offset)51 virtio_bo_offset(struct fd_bo *bo, uint64_t *offset)
52 {
53    struct virtio_bo *virtio_bo = to_virtio_bo(bo);
54    int ret = bo_allocate(virtio_bo);
55    if (ret)
56       return ret;
57    *offset = virtio_bo->offset;
58    return 0;
59 }
60 
61 static int
virtio_bo_cpu_prep_guest(struct fd_bo * bo)62 virtio_bo_cpu_prep_guest(struct fd_bo *bo)
63 {
64    struct drm_virtgpu_3d_wait args = {
65          .handle = bo->handle,
66    };
67    int ret;
68 
69    /* Side note, this ioctl is defined as IO_WR but should be IO_W: */
70    ret = drmIoctl(bo->dev->fd, DRM_IOCTL_VIRTGPU_WAIT, &args);
71    if (ret && errno == EBUSY)
72       return -EBUSY;
73 
74    return 0;
75 }
76 
77 static int
virtio_bo_cpu_prep(struct fd_bo * bo,struct fd_pipe * pipe,uint32_t op)78 virtio_bo_cpu_prep(struct fd_bo *bo, struct fd_pipe *pipe, uint32_t op)
79 {
80    int ret;
81 
82    /*
83     * Wait first in the guest, to avoid a blocking call in host.
84     * If implicit sync it used, we still need to *also* wait in
85     * host, if it is a shared buffer, because the guest doesn't
86     * know about usage of the bo in the host (or other guests).
87     */
88 
89    ret = virtio_bo_cpu_prep_guest(bo);
90    if (ret)
91       goto out;
92 
93    /* If buffer is not shared, then it is not shared with host,
94     * so we don't need to worry about implicit sync in host:
95     */
96    if (!bo->shared)
97       goto out;
98 
99    /* If buffer is shared, but we are using explicit sync, no
100     * need to fallback to implicit sync in host:
101     */
102    if (pipe && to_virtio_pipe(pipe)->no_implicit_sync)
103       goto out;
104 
105    struct msm_ccmd_gem_cpu_prep_req req = {
106          .hdr = MSM_CCMD(GEM_CPU_PREP, sizeof(req)),
107          .res_id = to_virtio_bo(bo)->res_id,
108          .op = op,
109    };
110    struct msm_ccmd_gem_cpu_prep_rsp *rsp;
111 
112    /* We can't do a blocking wait in the host, so we have to poll: */
113    do {
114       rsp = virtio_alloc_rsp(bo->dev, &req.hdr, sizeof(*rsp));
115 
116       ret = virtio_execbuf(bo->dev, &req.hdr, true);
117       if (ret)
118          goto out;
119 
120       ret = rsp->ret;
121    } while (ret == -EBUSY);
122 
123 out:
124    return ret;
125 }
126 
127 static void
virtio_bo_cpu_fini(struct fd_bo * bo)128 virtio_bo_cpu_fini(struct fd_bo *bo)
129 {
130    /* no-op */
131 }
132 
133 static int
virtio_bo_madvise(struct fd_bo * bo,int willneed)134 virtio_bo_madvise(struct fd_bo *bo, int willneed)
135 {
136    /* TODO:
137     * Currently unsupported, synchronous WILLNEED calls would introduce too
138     * much latency.. ideally we'd keep state in the guest and only flush
139     * down to host when host is under memory pressure.  (Perhaps virtio-balloon
140     * could signal this?)
141     */
142    return willneed;
143 }
144 
145 static uint64_t
virtio_bo_iova(struct fd_bo * bo)146 virtio_bo_iova(struct fd_bo *bo)
147 {
148    /* The shmem bo is allowed to have no iova, as it is only used for
149     * guest<->host communications:
150     */
151    assert(bo->iova || (to_virtio_bo(bo)->blob_id == 0));
152    return bo->iova;
153 }
154 
155 static void
virtio_bo_set_name(struct fd_bo * bo,const char * fmt,va_list ap)156 virtio_bo_set_name(struct fd_bo *bo, const char *fmt, va_list ap)
157 {
158    char name[32];
159    int sz;
160 
161    /* Note, we cannot set name on the host for the shmem bo, as
162     * that isn't a real gem obj on the host side.. not having
163     * an iova is a convenient way to detect this case:
164     */
165    if (!bo->iova)
166       return;
167 
168    sz = vsnprintf(name, sizeof(name), fmt, ap);
169    sz = MIN2(sz, sizeof(name));
170 
171    unsigned req_len = sizeof(struct msm_ccmd_gem_set_name_req) + align(sz, 4);
172 
173    uint8_t buf[req_len];
174    struct msm_ccmd_gem_set_name_req *req = (void *)buf;
175 
176    req->hdr = MSM_CCMD(GEM_SET_NAME, req_len);
177    req->res_id = to_virtio_bo(bo)->res_id;
178    req->len = sz;
179 
180    memcpy(req->payload, name, sz);
181 
182    virtio_execbuf(bo->dev, &req->hdr, false);
183 }
184 
185 static void
bo_upload(struct fd_bo * bo,unsigned off,void * src,unsigned len)186 bo_upload(struct fd_bo *bo, unsigned off, void *src, unsigned len)
187 {
188    unsigned req_len = sizeof(struct msm_ccmd_gem_upload_req) + align(len, 4);
189 
190    uint8_t buf[req_len];
191    struct msm_ccmd_gem_upload_req *req = (void *)buf;
192 
193    req->hdr = MSM_CCMD(GEM_UPLOAD, req_len);
194    req->res_id = to_virtio_bo(bo)->res_id;
195    req->pad = 0;
196    req->off = off;
197    req->len = len;
198 
199    memcpy(req->payload, src, len);
200 
201    virtio_execbuf(bo->dev, &req->hdr, false);
202 }
203 
204 static void
virtio_bo_upload(struct fd_bo * bo,void * src,unsigned len)205 virtio_bo_upload(struct fd_bo *bo, void *src, unsigned len)
206 {
207    unsigned off = 0;
208    while (len > 0) {
209       unsigned sz = MIN2(len, 0x1000);
210       bo_upload(bo, off, src, sz);
211       off += sz;
212       src += sz;
213       len -= sz;
214    }
215 }
216 
217 static void
set_iova(struct fd_bo * bo,uint64_t iova)218 set_iova(struct fd_bo *bo, uint64_t iova)
219 {
220    struct msm_ccmd_gem_set_iova_req req = {
221          .hdr = MSM_CCMD(GEM_SET_IOVA, sizeof(req)),
222          .res_id = to_virtio_bo(bo)->res_id,
223          .iova = iova,
224    };
225 
226    virtio_execbuf(bo->dev, &req.hdr, false);
227 }
228 
229 static void
virtio_bo_destroy(struct fd_bo * bo)230 virtio_bo_destroy(struct fd_bo *bo)
231 {
232    struct virtio_bo *virtio_bo = to_virtio_bo(bo);
233 
234    /* Release iova by setting to zero: */
235    if (bo->iova) {
236       set_iova(bo, 0);
237 
238       virtio_dev_free_iova(bo->dev, bo->iova, bo->size);
239 
240       /* Need to flush batched ccmds to ensure the host sees the iova
241        * release before the GEM handle is closed (ie. detach_resource()
242        * on the host side)
243        */
244       virtio_execbuf_flush(bo->dev);
245    }
246 
247    free(virtio_bo);
248 }
249 
250 static const struct fd_bo_funcs funcs = {
251    .offset = virtio_bo_offset,
252    .cpu_prep = virtio_bo_cpu_prep,
253    .cpu_fini = virtio_bo_cpu_fini,
254    .madvise = virtio_bo_madvise,
255    .iova = virtio_bo_iova,
256    .set_name = virtio_bo_set_name,
257    .upload = virtio_bo_upload,
258    .destroy = virtio_bo_destroy,
259 };
260 
261 static struct fd_bo *
bo_from_handle(struct fd_device * dev,uint32_t size,uint32_t handle)262 bo_from_handle(struct fd_device *dev, uint32_t size, uint32_t handle)
263 {
264    struct virtio_bo *virtio_bo;
265    struct fd_bo *bo;
266 
267    virtio_bo = calloc(1, sizeof(*virtio_bo));
268    if (!virtio_bo)
269       return NULL;
270 
271    bo = &virtio_bo->base;
272 
273    /* Note we need to set these because allocation_wait_execute() could
274     * run before bo_init_commont():
275     */
276    bo->dev = dev;
277    p_atomic_set(&bo->refcnt, 1);
278 
279    bo->size = size;
280    bo->funcs = &funcs;
281    bo->handle = handle;
282 
283    /* Don't assume we can mmap an imported bo: */
284    bo->alloc_flags = FD_BO_NOMAP;
285 
286    struct drm_virtgpu_resource_info args = {
287          .bo_handle = handle,
288    };
289    int ret;
290 
291    ret = drmCommandWriteRead(dev->fd, DRM_VIRTGPU_RESOURCE_INFO, &args, sizeof(args));
292    if (ret) {
293       INFO_MSG("failed to get resource info: %s", strerror(errno));
294       free(virtio_bo);
295       return NULL;
296    }
297 
298    virtio_bo->res_id = args.res_handle;
299 
300    fd_bo_init_common(bo, dev);
301 
302    return bo;
303 }
304 
305 /* allocate a new buffer object from existing handle */
306 struct fd_bo *
virtio_bo_from_handle(struct fd_device * dev,uint32_t size,uint32_t handle)307 virtio_bo_from_handle(struct fd_device *dev, uint32_t size, uint32_t handle)
308 {
309    struct fd_bo *bo = bo_from_handle(dev, size, handle);
310 
311    if (!bo)
312       return NULL;
313 
314    bo->iova = virtio_dev_alloc_iova(dev, size);
315    if (!bo->iova)
316       goto fail;
317 
318    set_iova(bo, bo->iova);
319 
320    return bo;
321 
322 fail:
323    virtio_bo_destroy(bo);
324    return NULL;
325 }
326 
327 /* allocate a buffer object: */
328 struct fd_bo *
virtio_bo_new(struct fd_device * dev,uint32_t size,uint32_t flags)329 virtio_bo_new(struct fd_device *dev, uint32_t size, uint32_t flags)
330 {
331    struct virtio_device *virtio_dev = to_virtio_device(dev);
332    struct drm_virtgpu_resource_create_blob args = {
333          .blob_mem   = VIRTGPU_BLOB_MEM_HOST3D,
334          .size       = size,
335    };
336    struct msm_ccmd_gem_new_req req = {
337          .hdr = MSM_CCMD(GEM_NEW, sizeof(req)),
338          .size = size,
339    };
340    int ret;
341 
342    if (flags & FD_BO_SCANOUT)
343       req.flags |= MSM_BO_SCANOUT;
344 
345    if (flags & FD_BO_GPUREADONLY)
346       req.flags |= MSM_BO_GPU_READONLY;
347 
348    if (flags & FD_BO_CACHED_COHERENT) {
349       req.flags |= MSM_BO_CACHED_COHERENT;
350    } else {
351       req.flags |= MSM_BO_WC;
352    }
353 
354    if (flags & _FD_BO_VIRTIO_SHM) {
355       args.blob_id = 0;
356       args.blob_flags = VIRTGPU_BLOB_FLAG_USE_MAPPABLE;
357    } else {
358       if (flags & (FD_BO_SHARED | FD_BO_SCANOUT)) {
359          args.blob_flags = VIRTGPU_BLOB_FLAG_USE_CROSS_DEVICE |
360                VIRTGPU_BLOB_FLAG_USE_SHAREABLE;
361       }
362 
363       if (!(flags & FD_BO_NOMAP)) {
364          args.blob_flags |= VIRTGPU_BLOB_FLAG_USE_MAPPABLE;
365       }
366 
367       args.blob_id = p_atomic_inc_return(&virtio_dev->next_blob_id);
368       args.cmd = VOID2U64(&req);
369       args.cmd_size = sizeof(req);
370 
371       /* tunneled cmds are processed separately on host side,
372        * before the renderer->get_blob() callback.. the blob_id
373        * is used to like the created bo to the get_blob() call
374        */
375       req.blob_id = args.blob_id;
376       req.iova = virtio_dev_alloc_iova(dev, size);
377       if (!req.iova) {
378          ret = -ENOMEM;
379          goto fail;
380       }
381    }
382 
383    simple_mtx_lock(&virtio_dev->eb_lock);
384    if (args.cmd)
385       req.hdr.seqno = ++virtio_dev->next_seqno;
386    ret = drmIoctl(dev->fd, DRM_IOCTL_VIRTGPU_RESOURCE_CREATE_BLOB, &args);
387    simple_mtx_unlock(&virtio_dev->eb_lock);
388    if (ret)
389       goto fail;
390 
391    struct fd_bo *bo = bo_from_handle(dev, size, args.bo_handle);
392    struct virtio_bo *virtio_bo = to_virtio_bo(bo);
393 
394    virtio_bo->blob_id = args.blob_id;
395    bo->iova = req.iova;
396 
397    return bo;
398 
399 fail:
400    if (req.iova) {
401       virtio_dev_free_iova(dev, req.iova, size);
402    }
403    return NULL;
404 }
405