From 32fe1d00f572b4f41260e0ddcf88b15091ec2564 Mon Sep 17 00:00:00 2001 From: Siarhei Siamashka Date: Mon, 17 Jun 2013 13:32:11 +0300 Subject: [PATCH] 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 video: bcm2708_fb: Add compat_ioctl support. When using a 64 bit kernel with 32 bit userspace we need compat ioctl handling for FBIODMACOPY as one of the parameters is a pointer. Signed-off-by: Dave Stevenson --- drivers/video/fbdev/bcm2708_fb.c | 170 ++++++++++++++++++++++++++++++- drivers/video/fbdev/core/fbmem.c | 35 +++++++ include/uapi/linux/fb.h | 12 +++ 3 files changed, 213 insertions(+), 4 deletions(-) --- a/drivers/video/fbdev/bcm2708_fb.c +++ b/drivers/video/fbdev/bcm2708_fb.c @@ -32,8 +32,10 @@ #include #include #include +#include #include #include +#include #include #include @@ -184,9 +186,6 @@ static int bcm2708_fb_debugfs_init(struc fb->debugfs_subdir = debugfs_create_dir(buf, fb->debugfs_dir); - debugfs_create_regset32("stats", 0444, fb->debugfs_dir, - &fb->stats.regset); - if (!fb->debugfs_subdir) { dev_warn(fb->fb.dev, "%s: could not create debugfs entry %u\n", __func__, fb->display_settings.display_num); @@ -603,7 +602,110 @@ static int bcm2708_fb_pan_display(struct return result; } -static int bcm2708_ioctl(struct fb_info *info, unsigned int cmd, unsigned long arg) +static void dma_memcpy(struct bcm2708_fb *fb, dma_addr_t dst, dma_addr_t src, + int size) +{ + struct bcm2708_fb_dev *fbdev = fb->fbdev; + struct bcm2708_dma_cb *cb = fbdev->cb_base; + int burst_size = (fbdev->dma_chan == 0) ? 8 : 2; + + 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; + + // Not sure what to do if this gets a signal whilst waiting + if (mutex_lock_interruptible(&fbdev->dma_mutex)) + return; + + if (size < dma_busy_wait_threshold) { + bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle); + bcm_dma_wait_idle(fbdev->dma_chan_base); + } else { + void __iomem *local_dma_chan = fbdev->dma_chan_base; + + cb->info |= BCM2708_DMA_INT_EN; + bcm_dma_start(fbdev->dma_chan_base, fbdev->cb_handle); + while (bcm_dma_is_busy(local_dma_chan)) { + wait_event_interruptible(fbdev->dma_waitq, + !bcm_dma_is_busy(local_dma_chan)); + } + fbdev->dma_stats.dma_irqs++; + } + fbdev->dma_stats.dma_copies++; + + mutex_unlock(&fbdev->dma_mutex); +} + +/* address with no aliases */ +#define INTALIAS_NORMAL(x) ((x) & ~0xc0000000) +/* cache coherent but non-allocating in L1 and L2 */ +#define INTALIAS_L1L2_NONALLOCATING(x) (((x) & ~0xc0000000) | 0x80000000) + +static long vc_mem_copy(struct bcm2708_fb *fb, 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; + } + + if (!fb->gpu.base || !fb->gpu.length) { + 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(%zd)\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); + u8 *p = (u8 *)((uintptr_t)ioparam->src + offset); + u8 *q = (u8 *)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) { @@ -619,6 +721,21 @@ static int bcm2708_ioctl(struct fb_info RPI_FIRMWARE_FRAMEBUFFER_SET_VSYNC, &dummy, sizeof(dummy)); break; + + case FBIODMACOPY: + { + struct fb_dmacopy ioparam; + /* Get the parameter data. + */ + if (copy_from_user + (&ioparam, (void *)arg, sizeof(ioparam))) { + pr_err("[%s]: failed to copy-from-user\n", __func__); + ret = -EFAULT; + break; + } + ret = vc_mem_copy(fb, &ioparam); + break; + } default: dev_dbg(info->device, "Unknown ioctl 0x%x\n", cmd); return -ENOTTY; @@ -629,6 +746,48 @@ static int bcm2708_ioctl(struct fb_info return ret; } + +#ifdef CONFIG_COMPAT +struct fb_dmacopy32 { + compat_uptr_t dst; + __u32 src; + __u32 length; +}; + +#define FBIODMACOPY32 _IOW('z', 0x22, struct fb_dmacopy32) + +static int bcm2708_compat_ioctl(struct fb_info *info, unsigned int cmd, + unsigned long arg) +{ + struct bcm2708_fb *fb = to_bcm2708(info); + int ret; + + switch (cmd) { + case FBIODMACOPY32: + { + struct fb_dmacopy32 param32; + struct fb_dmacopy param; + /* Get the parameter data. + */ + if (copy_from_user(¶m32, (void *)arg, sizeof(param32))) { + pr_err("[%s]: failed to copy-from-user\n", __func__); + ret = -EFAULT; + break; + } + param.dst = compat_ptr(param32.dst); + param.src = param32.src; + param.length = param32.length; + ret = vc_mem_copy(fb, ¶m); + break; + } + default: + ret = bcm2708_ioctl(info, cmd, arg); + break; + } + return ret; +} +#endif + static void bcm2708_fb_fillrect(struct fb_info *info, const struct fb_fillrect *rect) { @@ -821,6 +980,9 @@ static struct fb_ops bcm2708_fb_ops = { .fb_imageblit = bcm2708_fb_imageblit, .fb_pan_display = bcm2708_fb_pan_display, .fb_ioctl = bcm2708_ioctl, +#ifdef CONFIG_COMPAT + .fb_compat_ioctl = bcm2708_compat_ioctl, +#endif }; static int bcm2708_fb_register(struct bcm2708_fb *fb) --- a/drivers/video/fbdev/core/fbmem.c +++ b/drivers/video/fbdev/core/fbmem.c @@ -1086,6 +1086,30 @@ 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; + lock_fb_info(info); + 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) { @@ -1094,6 +1118,7 @@ static long do_fb_ioctl(struct fb_info * struct fb_fix_screeninfo fix; struct fb_cmap cmap_from; struct fb_cmap_user cmap; + struct fb_copyarea copy; void __user *argp = (void __user *)arg; long ret = 0; @@ -1169,6 +1194,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; + } + fallthrough; default: lock_fb_info(info); fb = info->fbops; @@ -1308,6 +1342,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); fallthrough; case FBIOBLANK: --- 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 */ @@ -348,6 +354,12 @@ struct fb_copyarea { __u32 sy; }; +struct fb_dmacopy { + void *dst; + __u32 src; + __u32 length; +}; + struct fb_fillrect { __u32 dx; /* screen-relative */ __u32 dy;