blob: c2592ec9efb6d22f46c32dccc503aefc58dd0f30 [file] [log] [blame]
Bryan Huntsman3f2bc4d2011-08-16 17:27:22 -07001/* arch/arm/mach-msm/hw3d.c
2 *
3 * Register/Interrupt access for userspace 3D library.
4 *
5 * Copyright (C) 2007 Google, Inc.
6 * Author: Brian Swetland <swetland@google.com>
7 *
8 * This software is licensed under the terms of the GNU General Public
9 * License version 2, as published by the Free Software Foundation, and
10 * may be copied, distributed, and modified under those terms.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 */
18
19#include <linux/module.h>
20#include <linux/fs.h>
21#include <linux/miscdevice.h>
22#include <linux/uaccess.h>
23#include <linux/poll.h>
24#include <linux/time.h>
25#include <linux/irq.h>
26#include <linux/interrupt.h>
27#include <linux/wait.h>
28#include <linux/mm.h>
29#include <linux/clk.h>
30#include <linux/android_pmem.h>
31#include <mach/board.h>
32
33static DEFINE_SPINLOCK(hw3d_lock);
34static DECLARE_WAIT_QUEUE_HEAD(hw3d_queue);
35static int hw3d_pending;
36static int hw3d_disabled;
37
38static struct clk *grp_clk;
39static struct clk *imem_clk;
40DECLARE_MUTEX(hw3d_sem);
41static unsigned int hw3d_granted;
42static struct file *hw3d_granted_file;
43
44static irqreturn_t hw3d_irq_handler(int irq, void *data)
45{
46 unsigned long flags;
47
48 spin_lock_irqsave(&hw3d_lock, flags);
49 if (!hw3d_disabled) {
50 disable_irq(INT_GRAPHICS);
51 hw3d_disabled = 1;
52 }
53 hw3d_pending = 1;
54 spin_unlock_irqrestore(&hw3d_lock, flags);
55
56 wake_up(&hw3d_queue);
57
58 return IRQ_HANDLED;
59}
60
61static void hw3d_disable_interrupt(void)
62{
63 unsigned long flags;
64 spin_lock_irqsave(&hw3d_lock, flags);
65 if (!hw3d_disabled) {
66 disable_irq(INT_GRAPHICS);
67 hw3d_disabled = 1;
68 }
69 spin_unlock_irqrestore(&hw3d_lock, flags);
70}
71
72static long hw3d_wait_for_interrupt(void)
73{
74 unsigned long flags;
75 int ret;
76
77 for (;;) {
78 spin_lock_irqsave(&hw3d_lock, flags);
79 if (hw3d_pending) {
80 hw3d_pending = 0;
81 spin_unlock_irqrestore(&hw3d_lock, flags);
82 return 0;
83 }
84 if (hw3d_disabled) {
85 hw3d_disabled = 0;
86 enable_irq(INT_GRAPHICS);
87 }
88 spin_unlock_irqrestore(&hw3d_lock, flags);
89
90 ret = wait_event_interruptible(hw3d_queue, hw3d_pending);
91 if (ret < 0) {
92 hw3d_disable_interrupt();
93 return ret;
94 }
95 }
96
97 return 0;
98}
99
100#define HW3D_REGS_LEN 0x100000
101static long hw3d_wait_for_revoke(struct hw3d_info *info, struct file *filp)
102{
103 struct hw3d_data *data = filp->private_data;
104 int ret;
105
106 if (is_master(info, filp)) {
107 pr_err("%s: cannot revoke on master node\n", __func__);
108 return -EPERM;
109 }
110
111 ret = wait_event_interruptible(info->revoke_wq,
112 info->revoking ||
113 data->closing);
114 if (ret == 0 && data->closing)
115 ret = -EPIPE;
116 if (ret < 0)
117 return ret;
118 return 0;
119}
120
121static void locked_hw3d_client_done(struct hw3d_info *info, int had_timer)
122{
123 if (info->enabled) {
124 pr_debug("hw3d: was enabled\n");
125 info->enabled = 0;
126 clk_disable(info->grp_clk);
127 clk_disable(info->imem_clk);
128 }
129 info->revoking = 0;
130
131 /* double check that the irqs are disabled */
132 locked_hw3d_irq_disable(info);
133
134 if (had_timer)
135 wake_unlock(&info->wake_lock);
136 wake_up(&info->revoke_done_wq);
137}
138
139static void do_force_revoke(struct hw3d_info *info)
140{
141 unsigned long flags;
142
143 /* at this point, the task had a chance to relinquish the gpu, but
144 * it hasn't. So, we kill it */
145 spin_lock_irqsave(&info->lock, flags);
146 pr_debug("hw3d: forcing revoke\n");
147 locked_hw3d_irq_disable(info);
148 if (info->client_task) {
149 pr_info("hw3d: force revoke from pid=%d\n",
150 info->client_task->pid);
151 force_sig(SIGKILL, info->client_task);
152 put_task_struct(info->client_task);
153 info->client_task = NULL;
154 }
155 locked_hw3d_client_done(info, 1);
156 pr_debug("hw3d: done forcing revoke\n");
157 spin_unlock_irqrestore(&info->lock, flags);
158}
159
160#define REVOKE_TIMEOUT (2 * HZ)
161static void locked_hw3d_revoke(struct hw3d_info *info)
162{
163 /* force us to wait to suspend until the revoke is done. If the
164 * user doesn't release the gpu, the timer will turn off the gpu,
165 * and force kill the process. */
166 wake_lock(&info->wake_lock);
167 info->revoking = 1;
168 wake_up(&info->revoke_wq);
169 mod_timer(&info->revoke_timer, jiffies + REVOKE_TIMEOUT);
170}
171
172bool is_msm_hw3d_file(struct file *file)
173{
174 struct hw3d_info *info = hw3d_info;
175 if (MAJOR(file->f_dentry->d_inode->i_rdev) == MAJOR(info->devno) &&
176 (is_master(info, file) || is_client(info, file)))
177 return 1;
178 return 0;
179}
180
181void put_msm_hw3d_file(struct file *file)
182{
183 if (!is_msm_hw3d_file(file))
184 return;
185 fput(file);
186}
187
188static long hw3d_revoke_gpu(struct file *file)
189{
190 int ret = 0;
191 unsigned long user_start, user_len;
192 struct pmem_region region = {.offset = 0x0, .len = HW3D_REGS_LEN};
193
194 down(&hw3d_sem);
195 if (!hw3d_granted)
196 goto end;
197 /* revoke the pmem region completely */
198 if ((ret = pmem_remap(&region, file, PMEM_UNMAP)))
199 goto end;
200 get_pmem_user_addr(file, &user_start, &user_len);
201 /* reset the gpu */
202 clk_disable(grp_clk);
203 clk_disable(imem_clk);
204 hw3d_granted = 0;
205end:
206 up(&hw3d_sem);
207 return ret;
208}
209
210static long hw3d_grant_gpu(struct file *file)
211{
212 int ret = 0;
213 struct pmem_region region = {.offset = 0x0, .len = HW3D_REGS_LEN};
214
215 down(&hw3d_sem);
216 if (hw3d_granted) {
217 ret = -1;
218 goto end;
219 }
220 /* map the registers */
221 if ((ret = pmem_remap(&region, file, PMEM_MAP)))
222 goto end;
223 clk_enable(grp_clk);
224 clk_enable(imem_clk);
225 hw3d_granted = 1;
226 hw3d_granted_file = file;
227end:
228 up(&hw3d_sem);
229 return ret;
230}
231
232static int hw3d_release(struct inode *inode, struct file *file)
233{
234 down(&hw3d_sem);
235 /* if the gpu is in use, and its inuse by the file that was released */
236 if (hw3d_granted && (file == hw3d_granted_file)) {
237 clk_disable(grp_clk);
238 clk_disable(imem_clk);
239 hw3d_granted = 0;
240 hw3d_granted_file = NULL;
241 }
242 up(&hw3d_sem);
243 return 0;
244}
245
246static void hw3d_vma_open(struct vm_area_struct *vma)
247{
248 /* XXX: should the master be allowed to fork and keep the mappings? */
249
250 /* TODO: remap garbage page into here.
251 *
252 * For now, just pull the mapping. The user shouldn't be forking
253 * and using it anyway. */
254 zap_page_range(vma, vma->vm_start, vma->vm_end - vma->vm_start, NULL);
255}
256
257static void hw3d_vma_close(struct vm_area_struct *vma)
258{
259 struct file *file = vma->vm_file;
260 struct hw3d_data *data = file->private_data;
261 int i;
262
263 pr_debug("hw3d: current %u ppid %u file %p count %ld\n",
264 current->pid, current->parent->pid, file, file_count(file));
265
266 BUG_ON(!data);
267
268 mutex_lock(&data->mutex);
269 for (i = 0; i < HW3D_NUM_REGIONS; ++i) {
270 if (data->vmas[i] == vma) {
271 data->vmas[i] = NULL;
272 goto done;
273 }
274 }
275 pr_warning("%s: vma %p not of ours during vma_close\n", __func__, vma);
276done:
277 mutex_unlock(&data->mutex);
278}
279
280static int hw3d_mmap(struct file *file, struct vm_area_struct *vma)
281{
282 struct hw3d_info *info = hw3d_info;
283 struct hw3d_data *data = file->private_data;
284 unsigned long vma_size = vma->vm_end - vma->vm_start;
285 int ret = 0;
286 int region = REGION_PAGE_ID(vma->vm_pgoff);
287
288 if (region >= HW3D_NUM_REGIONS) {
289 pr_err("%s: Trying to mmap unknown region %d\n", __func__,
290 region);
291 return -EINVAL;
292 } else if (vma_size > info->regions[region].size) {
293 pr_err("%s: VMA size %ld exceeds region %d size %ld\n",
294 __func__, vma_size, region,
295 info->regions[region].size);
296 return -EINVAL;
297 } else if (REGION_PAGE_OFFS(vma->vm_pgoff) != 0 ||
298 (vma_size & ~PAGE_MASK)) {
299 pr_err("%s: Can't remap part of the region %d\n", __func__,
300 region);
301 return -EINVAL;
302 } else if (!is_master(info, file) &&
303 current->group_leader != info->client_task) {
304 pr_err("%s: current(%d) != client_task(%d)\n", __func__,
305 current->group_leader->pid, info->client_task->pid);
306 return -EPERM;
307 } else if (!is_master(info, file) &&
308 (info->revoking || info->suspending)) {
309 pr_err("%s: cannot mmap while revoking(%d) or suspending(%d)\n",
310 __func__, info->revoking, info->suspending);
311 return -EPERM;
312 }
313
314 mutex_lock(&data->mutex);
315 if (data->vmas[region] != NULL) {
316 pr_err("%s: Region %d already mapped (pid=%d tid=%d)\n",
317 __func__, region, current->group_leader->pid,
318 current->pid);
319 ret = -EBUSY;
320 goto done;
321 }
322
323 /* our mappings are always noncached */
324#ifdef pgprot_noncached
325 vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
326#endif
327
328 ret = io_remap_pfn_range(vma, vma->vm_start,
329 info->regions[region].pbase >> PAGE_SHIFT,
330 vma_size, vma->vm_page_prot);
331 if (ret) {
332 pr_err("%s: Cannot remap page range for region %d!\n", __func__,
333 region);
334 ret = -EAGAIN;
335 goto done;
336 }
337
338 /* Prevent a malicious client from stealing another client's data
339 * by forcing a revoke on it and then mmapping the GPU buffers.
340 */
341 if (region != HW3D_REGS)
342 memset(info->regions[region].vbase, 0,
343 info->regions[region].size);
344
345 vma->vm_ops = &hw3d_vm_ops;
346
347 /* mark this region as mapped */
348 data->vmas[region] = vma;
349
350done:
351 mutex_unlock(&data->mutex);
352 return ret;
353}
354
355static long hw3d_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
356{
357 switch (cmd) {
358 case HW3D_REVOKE_GPU:
359 return hw3d_revoke_gpu(file);
360 break;
361 case HW3D_GRANT_GPU:
362 return hw3d_grant_gpu(file);
363 break;
364 case HW3D_WAIT_FOR_INTERRUPT:
365 return hw3d_wait_for_interrupt();
366 break;
367 default:
368 return -EINVAL;
369 }
370 return 0;
371}
372
373static struct android_pmem_platform_data pmem_data = {
374 .name = "hw3d",
375 .start = 0xA0000000,
376 .size = 0x100000,
377 .allocator_type = PMEM_ALLOCATORTYPE_ALLORNOTHING,
378 .cached = 0,
379};
380
381static int __init hw3d_init(void)
382{
383 int ret;
384
385 grp_clk = clk_get(NULL, "grp_clk");
386 if (IS_ERR(grp_clk))
387 return PTR_ERR(grp_clk);
388
389 imem_clk = clk_get(NULL, "imem_clk");
390 if (IS_ERR(imem_clk)) {
391 clk_put(grp_clk);
392 return PTR_ERR(imem_clk);
393 }
394 ret = request_irq(INT_GRAPHICS, hw3d_irq_handler,
395 IRQF_TRIGGER_HIGH, "hw3d", 0);
396 if (ret) {
397 clk_put(grp_clk);
398 clk_put(imem_clk);
399 return ret;
400 }
401 hw3d_disable_interrupt();
402 hw3d_granted = 0;
403
404 return pmem_setup(&pmem_data, hw3d_ioctl, hw3d_release);
405}
406
407device_initcall(hw3d_init);