From d56622a9c76ab913da66bb22c3158b10f9fb0543 Mon Sep 17 00:00:00 2001 From: Siarhei Siamashka Date: Mon, 17 Jun 2013 13:32:11 +0300 Subject: [PATCH 056/454] fbdev: add FBIOCOPYAREA ioctl Based on the patch authored by Ali Gholami Rudi at https://lkml.org/lkml/2009/7/13/153 Provide an ioctl for userspace applications, but only if this operation is hardware accelerated (otherwide it does not make any sense). Signed-off-by: Siarhei Siamashka bcm2708_fb: Add ioctl for reading gpu memory through dma --- drivers/video/fbdev/bcm2708_fb.c | 111 +++++++++++++++++++++++++++++++ drivers/video/fbdev/core/fbmem.c | 36 ++++++++++ include/uapi/linux/fb.h | 12 ++++ 3 files changed, 159 insertions(+) --- a/drivers/video/fbdev/bcm2708_fb.c +++ b/drivers/video/fbdev/bcm2708_fb.c @@ -31,8 +31,10 @@ #include #include #include +#include #include #include +#include #include //#define BCM2708_FB_DEBUG @@ -94,6 +96,7 @@ struct bcm2708_fb { wait_queue_head_t dma_waitq; struct bcm2708_fb_stats stats; unsigned long fb_bus_address; + struct { u32 base, length; } gpu; }; #define to_bcm2708(info) container_of(info, struct bcm2708_fb, fb) @@ -426,6 +429,106 @@ static int bcm2708_fb_pan_display(struct return result; } +static void dma_memcpy(struct bcm2708_fb *fb, dma_addr_t dst, dma_addr_t src, int size) +{ + int burst_size = (fb->dma_chan == 0) ? 8 : 2; + struct bcm2708_dma_cb *cb = fb->cb_base; + + cb->info = BCM2708_DMA_BURST(burst_size) | BCM2708_DMA_S_WIDTH | + BCM2708_DMA_S_INC | BCM2708_DMA_D_WIDTH | + BCM2708_DMA_D_INC; + cb->dst = dst; + cb->src = src; + cb->length = size; + cb->stride = 0; + cb->pad[0] = 0; + cb->pad[1] = 0; + cb->next = 0; + + if (size < dma_busy_wait_threshold) { + bcm_dma_start(fb->dma_chan_base, fb->cb_handle); + bcm_dma_wait_idle(fb->dma_chan_base); + } else { + void __iomem *dma_chan = fb->dma_chan_base; + cb->info |= BCM2708_DMA_INT_EN; + bcm_dma_start(fb->dma_chan_base, fb->cb_handle); + while (bcm_dma_is_busy(dma_chan)) { + wait_event_interruptible( + fb->dma_waitq, + !bcm_dma_is_busy(dma_chan)); + } + fb->stats.dma_irqs++; + } + fb->stats.dma_copies++; +} + +#define INTALIAS_NORMAL(x) ((x)&~0xc0000000) // address with no aliases +#define INTALIAS_L1L2_NONALLOCATING(x) (((x)&~0xc0000000)|0x80000000) // cache coherent but non-allocating in L1 and L2 + +static long vc_mem_copy(struct bcm2708_fb *fb, unsigned long arg) +{ + struct fb_dmacopy ioparam; + size_t size = PAGE_SIZE; + u32 *buf = NULL; + dma_addr_t bus_addr; + long rc = 0; + size_t offset; + + /* restrict this to root user */ + if (!uid_eq(current_euid(), GLOBAL_ROOT_UID)) + { + rc = -EFAULT; + goto out; + } + + /* Get the parameter data. + */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam)) != 0) { + pr_err("[%s]: failed to copy-from-user\n", + __func__); + rc = -EFAULT; + goto out; + } + + if (fb->gpu.base == 0 || fb->gpu.length == 0) { + pr_err("[%s]: Unable to determine gpu memory (%x,%x)\n", __func__, fb->gpu.base, fb->gpu.length); + return -EFAULT; + } + + if (INTALIAS_NORMAL(ioparam.src) < fb->gpu.base || INTALIAS_NORMAL(ioparam.src) >= fb->gpu.base + fb->gpu.length) { + pr_err("[%s]: Invalid memory access %x (%x-%x)", __func__, INTALIAS_NORMAL(ioparam.src), fb->gpu.base, fb->gpu.base + fb->gpu.length); + return -EFAULT; + } + + buf = dma_alloc_coherent(fb->fb.device, PAGE_ALIGN(size), &bus_addr, + GFP_ATOMIC); + if (!buf) { + pr_err("[%s]: failed to dma_alloc_coherent(%d)\n", + __func__, size); + rc = -ENOMEM; + goto out; + } + + for (offset = 0; offset < ioparam.length; offset += size) { + size_t remaining = ioparam.length - offset; + size_t s = min(size, remaining); + unsigned char *p = (unsigned char *)ioparam.src + offset; + unsigned char *q = (unsigned char *)ioparam.dst + offset; + dma_memcpy(fb, bus_addr, INTALIAS_L1L2_NONALLOCATING((dma_addr_t)p), size); + if (copy_to_user(q, buf, s) != 0) { + pr_err("[%s]: failed to copy-to-user\n", + __func__); + rc = -EFAULT; + goto out; + } + } +out: + if (buf) + dma_free_coherent(fb->fb.device, PAGE_ALIGN(size), buf, bus_addr); + return rc; +} + static int bcm2708_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { struct bcm2708_fb *fb = to_bcm2708(info); @@ -438,6 +541,9 @@ static int bcm2708_ioctl(struct fb_info RPI_FIRMWARE_FRAMEBUFFER_SET_VSYNC, &dummy, sizeof(dummy)); break; + case FBIODMACOPY: + ret = vc_mem_copy(fb, arg); + break; default: dev_dbg(info->device, "Unknown ioctl 0x%x\n", cmd); return -ENOTTY; @@ -760,6 +866,11 @@ static int bcm2708_fb_probe(struct platf fb->dev = dev; fb->fb.device = &dev->dev; + // failure here isn't fatal, but we'll fail in vc_mem_copy if fb->gpu is not valid + rpi_firmware_property(fb->fw, + RPI_FIRMWARE_GET_VC_MEMORY, + &fb->gpu, sizeof(fb->gpu)); + ret = bcm2708_fb_register(fb); if (ret == 0) { platform_set_drvdata(dev, fb); --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -1086,6 +1086,31 @@ fb_blank(struct fb_info *info, int blank } EXPORT_SYMBOL(fb_blank); +static int fb_copyarea_user(struct fb_info *info, + struct fb_copyarea *copy) +{ + int ret = 0; + if (!lock_fb_info(info)) + return -ENODEV; + if (copy->dx >= info->var.xres || + copy->sx >= info->var.xres || + copy->width > info->var.xres || + copy->dy >= info->var.yres || + copy->sy >= info->var.yres || + copy->height > info->var.yres || + copy->dx + copy->width > info->var.xres || + copy->sx + copy->width > info->var.xres || + copy->dy + copy->height > info->var.yres || + copy->sy + copy->height > info->var.yres) { + ret = -EINVAL; + goto out; + } + info->fbops->fb_copyarea(info, copy); +out: + unlock_fb_info(info); + return ret; +} + static long do_fb_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) { @@ -1096,6 +1121,7 @@ static long do_fb_ioctl(struct fb_info * struct fb_cmap cmap_from; struct fb_cmap_user cmap; struct fb_event event; + struct fb_copyarea copy; void __user *argp = (void __user *)arg; long ret = 0; @@ -1213,6 +1239,15 @@ static long do_fb_ioctl(struct fb_info * unlock_fb_info(info); console_unlock(); break; + case FBIOCOPYAREA: + if (info->flags & FBINFO_HWACCEL_COPYAREA) { + /* only provide this ioctl if it is accelerated */ + if (copy_from_user(©, argp, sizeof(copy))) + return -EFAULT; + ret = fb_copyarea_user(info, ©); + break; + } + /* fall through */ default: if (!lock_fb_info(info)) return -ENODEV; @@ -1358,6 +1393,7 @@ static long fb_compat_ioctl(struct file case FBIOPAN_DISPLAY: case FBIOGET_CON2FBMAP: case FBIOPUT_CON2FBMAP: + case FBIOCOPYAREA: arg = (unsigned long) compat_ptr(arg); case FBIOBLANK: ret = do_fb_ioctl(info, cmd, arg); --- a/include/uapi/linux/fb.h +++ b/include/uapi/linux/fb.h @@ -35,6 +35,12 @@ #define FBIOPUT_MODEINFO 0x4617 #define FBIOGET_DISPINFO 0x4618 #define FBIO_WAITFORVSYNC _IOW('F', 0x20, __u32) +/* + * HACK: use 'z' in order not to clash with any other ioctl numbers which might + * be concurrently added to the mainline kernel + */ +#define FBIOCOPYAREA _IOW('z', 0x21, struct fb_copyarea) +#define FBIODMACOPY _IOW('z', 0x22, struct fb_dmacopy) #define FB_TYPE_PACKED_PIXELS 0 /* Packed Pixels */ #define FB_TYPE_PLANES 1 /* Non interleaved planes */ @@ -347,6 +353,12 @@ struct fb_copyarea { __u32 sy; }; +struct fb_dmacopy { + void *dst; + __u32 src; + __u32 length; +}; + struct fb_fillrect { __u32 dx; /* screen-relative */ __u32 dy;