OSDN Git Service

drm/virtio: Use vmalloc for command buffer allocations.
authorDavid Riley <davidriley@chromium.org>
Wed, 11 Sep 2019 18:14:03 +0000 (11:14 -0700)
committerGerd Hoffmann <kraxel@redhat.com>
Thu, 12 Sep 2019 07:49:24 +0000 (09:49 +0200)
Userspace requested command buffer allocations could be too large
to make as a contiguous allocation.  Use vmalloc if necessary to
satisfy those allocations.

Signed-off-by: David Riley <davidriley@chromium.org>
Link: http://patchwork.freedesktop.org/patch/msgid/20190911181403.40909-3-davidriley@chromium.org
Signed-off-by: Gerd Hoffmann <kraxel@redhat.com>
drivers/gpu/drm/virtio/virtgpu_ioctl.c
drivers/gpu/drm/virtio/virtgpu_vq.c

index f5083c5..9af1ec6 100644 (file)
@@ -143,7 +143,7 @@ static int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
                        goto out_unused_fd;
        }
 
-       buf = memdup_user(u64_to_user_ptr(exbuf->command), exbuf->size);
+       buf = vmemdup_user(u64_to_user_ptr(exbuf->command), exbuf->size);
        if (IS_ERR(buf)) {
                ret = PTR_ERR(buf);
                goto out_unresv;
@@ -172,7 +172,7 @@ static int virtio_gpu_execbuffer_ioctl(struct drm_device *dev, void *data,
        return 0;
 
 out_memdup:
-       kfree(buf);
+       kvfree(buf);
 out_unresv:
        if (buflist)
                virtio_gpu_array_unlock_resv(buflist);
index 5a64c77..9f9b782 100644 (file)
@@ -155,7 +155,7 @@ static void free_vbuf(struct virtio_gpu_device *vgdev,
 {
        if (vbuf->resp_size > MAX_INLINE_RESP_SIZE)
                kfree(vbuf->resp_buf);
-       kfree(vbuf->data_buf);
+       kvfree(vbuf->data_buf);
        kmem_cache_free(vgdev->vbufs, vbuf);
 }
 
@@ -256,13 +256,54 @@ void virtio_gpu_dequeue_cursor_func(struct work_struct *work)
        wake_up(&vgdev->cursorq.ack_queue);
 }
 
+/* Create sg_table from a vmalloc'd buffer. */
+static struct sg_table *vmalloc_to_sgt(char *data, uint32_t size, int *sg_ents)
+{
+       int ret, s, i;
+       struct sg_table *sgt;
+       struct scatterlist *sg;
+       struct page *pg;
+
+       if (WARN_ON(!PAGE_ALIGNED(data)))
+               return NULL;
+
+       sgt = kmalloc(sizeof(*sgt), GFP_KERNEL);
+       if (!sgt)
+               return NULL;
+
+       *sg_ents = DIV_ROUND_UP(size, PAGE_SIZE);
+       ret = sg_alloc_table(sgt, *sg_ents, GFP_KERNEL);
+       if (ret) {
+               kfree(sgt);
+               return NULL;
+       }
+
+       for_each_sg(sgt->sgl, sg, *sg_ents, i) {
+               pg = vmalloc_to_page(data);
+               if (!pg) {
+                       sg_free_table(sgt);
+                       kfree(sgt);
+                       return NULL;
+               }
+
+               s = min_t(int, PAGE_SIZE, size);
+               sg_set_page(sg, pg, s, 0);
+
+               size -= s;
+               data += s;
+       }
+
+       return sgt;
+}
+
 static bool virtio_gpu_queue_ctrl_buffer_locked(struct virtio_gpu_device *vgdev,
-                                               struct virtio_gpu_vbuffer *vbuf)
+                                               struct virtio_gpu_vbuffer *vbuf,
+                                               struct scatterlist *vout)
                __releases(&vgdev->ctrlq.qlock)
                __acquires(&vgdev->ctrlq.qlock)
 {
        struct virtqueue *vq = vgdev->ctrlq.vq;
-       struct scatterlist *sgs[3], vcmd, vout, vresp;
+       struct scatterlist *sgs[3], vcmd, vresp;
        int outcnt = 0, incnt = 0;
        bool notify = false;
        int ret;
@@ -274,9 +315,8 @@ static bool virtio_gpu_queue_ctrl_buffer_locked(struct virtio_gpu_device *vgdev,
        sgs[outcnt + incnt] = &vcmd;
        outcnt++;
 
-       if (vbuf->data_size) {
-               sg_init_one(&vout, vbuf->data_buf, vbuf->data_size);
-               sgs[outcnt + incnt] = &vout;
+       if (vout) {
+               sgs[outcnt + incnt] = vout;
                outcnt++;
        }
 
@@ -308,7 +348,24 @@ static void virtio_gpu_queue_fenced_ctrl_buffer(struct virtio_gpu_device *vgdev,
                                                struct virtio_gpu_fence *fence)
 {
        struct virtqueue *vq = vgdev->ctrlq.vq;
+       struct scatterlist *vout = NULL, sg;
+       struct sg_table *sgt = NULL;
        bool notify;
+       int outcnt = 0;
+
+       if (vbuf->data_size) {
+               if (is_vmalloc_addr(vbuf->data_buf)) {
+                       sgt = vmalloc_to_sgt(vbuf->data_buf, vbuf->data_size,
+                                            &outcnt);
+                       if (!sgt)
+                               return -ENOMEM;
+                       vout = sgt->sgl;
+               } else {
+                       sg_init_one(&sg, vbuf->data_buf, vbuf->data_size);
+                       vout = &sg;
+                       outcnt = 1;
+               }
+       }
 
 again:
        spin_lock(&vgdev->ctrlq.qlock);
@@ -321,7 +378,7 @@ again:
         * to wait for free space, which can result in fence ids being
         * submitted out-of-order.
         */
-       if (vq->num_free < 3) {
+       if (vq->num_free < 2 + outcnt) {
                spin_unlock(&vgdev->ctrlq.qlock);
                wait_event(vgdev->ctrlq.ack_queue, vq->num_free >= 3);
                goto again;
@@ -334,10 +391,15 @@ again:
                        virtio_gpu_array_unlock_resv(vbuf->objs);
                }
        }
-       notify = virtio_gpu_queue_ctrl_buffer_locked(vgdev, vbuf);
+       notify = virtio_gpu_queue_ctrl_buffer_locked(vgdev, vbuf, vout);
        spin_unlock(&vgdev->ctrlq.qlock);
        if (notify)
                virtqueue_notify(vgdev->ctrlq.vq);
+
+       if (sgt) {
+               sg_free_table(sgt);
+               kfree(sgt);
+       }
 }
 
 static void virtio_gpu_queue_ctrl_buffer(struct virtio_gpu_device *vgdev,