aboutsummaryrefslogtreecommitdiffstats
path: root/target/linux/brcm2708/patches-4.9/0056-fbdev-add-FBIOCOPYAREA-ioctl.patch
blob: 34346903da6f3ad70deec35bc6a88f77cd83ae2e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
From 65422160b21224e6d26641a4c1fa3082c0281488 Mon Sep 17 00:00:00 2001
From: Siarhei Siamashka <siarhei.siamashka@gmail.com>
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 <siarhei.siamashka@gmail.com>

bcm2708_fb: Add ioctl for reading gpu memory through dma
---
 drivers/video/fbdev/bcm2708_fb.c | 109 +++++++++++++++++++++++++++++++++++++++
 drivers/video/fbdev/core/fbmem.c |  36 +++++++++++++
 include/uapi/linux/fb.h          |  12 +++++
 3 files changed, 157 insertions(+)

--- a/drivers/video/fbdev/bcm2708_fb.c
+++ b/drivers/video/fbdev/bcm2708_fb.c
@@ -31,8 +31,10 @@
 #include <linux/console.h>
 #include <linux/debugfs.h>
 #include <asm/sizes.h>
+#include <asm/uaccess.h>
 #include <linux/io.h>
 #include <linux/dma-mapping.h>
+#include <linux/cred.h>
 #include <soc/bcm2835/raspberrypi-firmware.h>
 
 //#define BCM2708_FB_DEBUG
@@ -426,6 +428,110 @@ 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;
+	struct { u32 base, length; } gpu = {};
+
+	/* 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;
+	}
+
+	rc = rpi_firmware_property(fb->fw,
+				    RPI_FIRMWARE_GET_VC_MEMORY,
+				    &gpu, sizeof(gpu));
+	if (rc != 0 || gpu.base == 0 || gpu.length == 0) {
+		pr_err("[%s]: Unable to determine gpu memory %ld,%x,%x)\n", __func__, rc, gpu.base, gpu.length);
+		return -EFAULT;
+	}
+
+	if (INTALIAS_NORMAL(ioparam.src) < gpu.base || INTALIAS_NORMAL(ioparam.src) >= gpu.base + gpu.length) {
+		pr_err("[%s]: Invalid memory access %x (%x-%x)", __func__, INTALIAS_NORMAL(ioparam.src), gpu.base, gpu.base + 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 +544,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;
--- a/drivers/video/fbdev/core/fbmem.c
+++ b/drivers/video/fbdev/core/fbmem.c
@@ -1084,6 +1084,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)
 {
@@ -1094,6 +1119,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;
 
@@ -1211,6 +1237,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(&copy, argp, sizeof(copy)))
+				return -EFAULT;
+			ret = fb_copyarea_user(info, &copy);
+			break;
+		}
+		/* fall through */
 	default:
 		if (!lock_fb_info(info))
 			return -ENODEV;
@@ -1365,6 +1400,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
@@ -34,6 +34,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 */
@@ -346,6 +352,12 @@ struct fb_copyarea {
 	__u32 sy;
 };
 
+struct fb_dmacopy {
+	void *dst;
+	__u32 src;
+	__u32 length;
+};
+
 struct fb_fillrect {
 	__u32 dx;	/* screen-relative */
 	__u32 dy;