summaryrefslogtreecommitdiffstats
path: root/drivers/gpu/drm/gud/gud_drv.c
diff options
context:
space:
mode:
authorNoralf Trønnes <noralf@tronnes.org>2021-07-01 19:07:48 +0200
committerNoralf Trønnes <noralf@tronnes.org>2021-07-08 14:32:32 +0200
commit2eecd93b743b5611cd3654698794b4d0cefdc9ee (patch)
tree0ccbdfa9163126586581fbc44be72e9f39ab35b4 /drivers/gpu/drm/gud/gud_drv.c
parentdrm/gud: Free buffers on device removal (diff)
downloadlinux-2eecd93b743b5611cd3654698794b4d0cefdc9ee.tar.xz
linux-2eecd93b743b5611cd3654698794b4d0cefdc9ee.zip
drm/gud: Use scatter-gather USB bulk transfer
There'a limit to how big a kmalloc buffer can be, and as memory gets fragmented it becomes more difficult to get big buffers. The downside of smaller buffers is that the driver has to split the transfer up which hampers performance. Compression might also take a hit because of the splitting. Solve this by allocating the transfer buffer using vmalloc and create a SG table to be passed on to the USB subsystem. vmalloc_32() is used to avoid DMA bounce buffers on USB controllers that can only access 32-bit addresses. This also solves the problem that split transfers can give host side tearing since flushing is decoupled from rendering. usb_sg_wait() doesn't have timeout handling builtin, so it is wrapped in a timer like 4 out of 6 users in the kernel have done. v2: - Use DIV_ROUND_UP (Linus) - Add timeout note to the commit log (Linus) - Expand note about upper buffer limit (Linus) - Change var name s/timer/ctx/ in gud_usb_bulk_timeout() Reviewed-by: Linus Walleij <linus.walleij@linaro.org> Signed-off-by: Noralf Trønnes <noralf@tronnes.org> Link: https://patchwork.freedesktop.org/patch/msgid/20210701170748.58009-2-noralf@tronnes.org
Diffstat (limited to 'drivers/gpu/drm/gud/gud_drv.c')
-rw-r--r--drivers/gpu/drm/gud/gud_drv.c50
1 files changed, 35 insertions, 15 deletions
diff --git a/drivers/gpu/drm/gud/gud_drv.c b/drivers/gpu/drm/gud/gud_drv.c
index 8518cde03f8f..d02f8e968aad 100644
--- a/drivers/gpu/drm/gud/gud_drv.c
+++ b/drivers/gpu/drm/gud/gud_drv.c
@@ -393,13 +393,40 @@ static const struct drm_driver gud_drm_driver = {
.minor = 0,
};
+static int gud_alloc_bulk_buffer(struct gud_device *gdrm)
+{
+ unsigned int i, num_pages;
+ struct page **pages;
+ void *ptr;
+ int ret;
+
+ gdrm->bulk_buf = vmalloc_32(gdrm->bulk_len);
+ if (!gdrm->bulk_buf)
+ return -ENOMEM;
+
+ num_pages = DIV_ROUND_UP(gdrm->bulk_len, PAGE_SIZE);
+ pages = kmalloc_array(num_pages, sizeof(struct page *), GFP_KERNEL);
+ if (!pages)
+ return -ENOMEM;
+
+ for (i = 0, ptr = gdrm->bulk_buf; i < num_pages; i++, ptr += PAGE_SIZE)
+ pages[i] = vmalloc_to_page(ptr);
+
+ ret = sg_alloc_table_from_pages(&gdrm->bulk_sgt, pages, num_pages,
+ 0, gdrm->bulk_len, GFP_KERNEL);
+ kfree(pages);
+
+ return ret;
+}
+
static void gud_free_buffers_and_mutex(void *data)
{
struct gud_device *gdrm = data;
vfree(gdrm->compress_buf);
gdrm->compress_buf = NULL;
- kfree(gdrm->bulk_buf);
+ sg_free_table(&gdrm->bulk_sgt);
+ vfree(gdrm->bulk_buf);
gdrm->bulk_buf = NULL;
mutex_destroy(&gdrm->ctrl_lock);
}
@@ -536,24 +563,17 @@ static int gud_probe(struct usb_interface *intf, const struct usb_device_id *id)
if (desc.max_buffer_size)
max_buffer_size = le32_to_cpu(desc.max_buffer_size);
-retry:
- /*
- * Use plain kmalloc here since devm_kmalloc() places struct devres at the beginning
- * of the buffer it allocates. This wastes a lot of memory when allocating big buffers.
- * Asking for 2M would actually allocate 4M. This would also prevent getting the biggest
- * possible buffer potentially leading to split transfers.
- */
- gdrm->bulk_buf = kmalloc(max_buffer_size, GFP_KERNEL | __GFP_NOWARN);
- if (!gdrm->bulk_buf) {
- max_buffer_size = roundup_pow_of_two(max_buffer_size) / 2;
- if (max_buffer_size < SZ_512K)
- return -ENOMEM;
- goto retry;
- }
+ /* Prevent a misbehaving device from allocating loads of RAM. 4096x4096@XRGB8888 = 64 MB */
+ if (max_buffer_size > SZ_64M)
+ max_buffer_size = SZ_64M;
gdrm->bulk_pipe = usb_sndbulkpipe(interface_to_usbdev(intf), usb_endpoint_num(bulk_out));
gdrm->bulk_len = max_buffer_size;
+ ret = gud_alloc_bulk_buffer(gdrm);
+ if (ret)
+ return ret;
+
if (gdrm->compression & GUD_COMPRESSION_LZ4) {
gdrm->lz4_comp_mem = devm_kmalloc(dev, LZ4_MEM_COMPRESS, GFP_KERNEL);
if (!gdrm->lz4_comp_mem)