video: tegra: host: Fix error handling
[linux-3.10.git] / drivers / video / tegra / host / nvhost_as.c
1 /*
2  * drivers/video/tegra/host/nvhost_as.c
3  *
4  * Tegra Host Address Spaces
5  *
6  * Copyright (c) 2011-2013, NVIDIA CORPORATION.  All rights reserved.
7  *
8  * This program is free software; you can redistribute it and/or modify it
9  * under the terms and conditions of the GNU General Public License,
10  * version 2, as published by the Free Software Foundation.
11  *
12  * This program is distributed in the hope it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  * more details.
16  *
17  * You should have received a copy of the GNU General Public License along with
18  * this program; if not, write to the Free Software Foundation, Inc.,
19  * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
20  */
21
22 #include <linux/slab.h>
23 #include <linux/fs.h>
24 #include <linux/cdev.h>
25 #include <linux/uaccess.h>
26
27 #include <trace/events/nvhost.h>
28
29 #include <linux/nvhost_as_ioctl.h>
30
31 #include "dev.h"
32 #include "bus_client.h"
33 #include "nvhost_hwctx.h"
34 #include "nvhost_as.h"
35
36 int nvhost_as_dev_open(struct inode *inode, struct file *filp)
37 {
38         struct nvhost_as_share *as_share;
39         struct nvhost_channel *ch;
40         int err;
41
42         nvhost_dbg_fn("");
43
44         /* this will come from module, not channel, later */
45         ch = container_of(inode->i_cdev, struct nvhost_channel, as_cdev);
46         if (!ch->as) {
47                 nvhost_dbg_fn("no as for the channel!");
48                 return -ENOENT;
49         }
50
51         ch = nvhost_getchannel(ch);
52         if (!ch) {
53                 nvhost_dbg_fn("fail to get channel!");
54                 return -ENOMEM;
55         }
56
57         err = nvhost_as_alloc_share(ch, &as_share, true /*fd-attached path*/);
58         if (err) {
59                 nvhost_dbg_fn("failed to alloc share");
60                 goto clean_up;
61         }
62
63         filp->private_data = as_share;
64
65 clean_up:
66         return 0;
67 }
68
69 int nvhost_as_dev_release(struct inode *inode, struct file *filp)
70 {
71         struct nvhost_as_share *as_share = filp->private_data;
72         struct nvhost_channel *ch;
73         int ret;
74
75         nvhost_dbg_fn("");
76
77         ch = container_of(inode->i_cdev, struct nvhost_channel, as_cdev);
78
79         ret = nvhost_as_release_share(as_share, 0/* no hwctx to release */);
80
81         nvhost_putchannel(ch);
82
83         return ret;
84 }
85
86 long nvhost_as_dev_ctl(struct file *filp, unsigned int cmd, unsigned long arg)
87 {
88         int err = 0;
89         struct nvhost_as_share *as_share = filp->private_data;
90         struct nvhost_channel *ch = as_share->ch;
91         struct device *dev = as_share->as_dev;
92
93         u8 buf[NVHOST_AS_IOCTL_MAX_ARG_SIZE];
94
95         if ((_IOC_TYPE(cmd) != NVHOST_AS_IOCTL_MAGIC) ||
96                 (_IOC_NR(cmd) == 0) ||
97                 (_IOC_NR(cmd) > NVHOST_AS_IOCTL_LAST))
98                 return -EFAULT;
99
100         BUG_ON(_IOC_SIZE(cmd) > NVHOST_AS_IOCTL_MAX_ARG_SIZE);
101
102         if (_IOC_DIR(cmd) & _IOC_WRITE) {
103                 if (copy_from_user(buf, (void __user *)arg, _IOC_SIZE(cmd)))
104                         return -EFAULT;
105         }
106
107         nvhost_module_busy(ch->dev);
108
109         switch (cmd) {
110         case NVHOST_AS_IOCTL_BIND_CHANNEL:
111                 trace_nvhost_as_ioctl_bind_channel(dev_name(&ch->dev->dev));
112                 err = nvhost_as_ioctl_bind_channel(as_share,
113                                (struct nvhost_as_bind_channel_args *)buf);
114
115                 break;
116         case NVHOST_AS_IOCTL_ALLOC_SPACE:
117                 trace_nvhost_as_ioctl_alloc_space(dev_name(&ch->dev->dev));
118                 err = nvhost_as_ioctl_alloc_space(as_share,
119                                   (struct nvhost_as_alloc_space_args *)buf);
120                 break;
121         case NVHOST_AS_IOCTL_FREE_SPACE:
122                 trace_nvhost_as_ioctl_free_space(dev_name(&ch->dev->dev));
123                 err = nvhost_as_ioctl_free_space(as_share,
124                                        (struct nvhost_as_free_space_args *)buf);
125                 break;
126         case NVHOST_AS_IOCTL_MAP_BUFFER:
127                 trace_nvhost_as_ioctl_map_buffer(dev_name(&ch->dev->dev));
128                 err = nvhost_as_ioctl_map_buffer(as_share,
129                                        (struct nvhost_as_map_buffer_args *)buf);
130                 break;
131         case NVHOST_AS_IOCTL_UNMAP_BUFFER:
132                 trace_nvhost_as_ioctl_unmap_buffer(dev_name(&ch->dev->dev));
133                 err = nvhost_as_ioctl_unmap_buffer(as_share,
134                                (struct nvhost_as_unmap_buffer_args *)buf);
135                 break;
136         default:
137                 nvhost_err(dev, "unrecognized aspace ioctl cmd: 0x%x", cmd);
138                 err = -ENOTTY;
139                 break;
140         }
141
142         nvhost_module_idle(ch->dev);
143
144         if ((err == 0) && (_IOC_DIR(cmd) & _IOC_READ))
145                 err = copy_to_user((void __user *)arg, buf, _IOC_SIZE(cmd));
146
147         return err;
148 }
149
150
151 int nvhost_as_init_device(struct platform_device *dev)
152 {
153         struct nvhost_master *host = nvhost_get_host(dev);
154         struct nvhost_chip_support *op = nvhost_get_chip_ops();
155         struct nvhost_device_data *pdata = nvhost_get_devdata(dev);
156         struct nvhost_channel *ch = pdata->channel;
157         struct nvhost_as *as;
158         int err = 0;
159
160         if (!op->as.init)
161                 return 0;
162
163         if (!ch) {
164                 nvhost_err(&dev->dev, "no channel in nvhost_as_init for %s",
165                            dev->name);
166                 return -ENODEV;
167         }
168
169         if (!ch->as) {
170
171                 nvhost_dbg_fn("allocating as for %s", dev->name);
172                 as = kzalloc(sizeof(*as), GFP_KERNEL);
173                 if (!as) {
174                         err = -ENOMEM;
175                         goto failed;
176                 }
177                 ch->as = as;
178                 as->ch = ch;
179
180                 mutex_init(&as->share_list_lock);
181                 INIT_LIST_HEAD(&as->share_list);
182
183                 err = op->as.init(host, as); /* this sets the as.ops (or not) */
184                 if (err)
185                         goto failed;
186                 if (!as->ops) {
187                         nvhost_dbg_fn("%s doesn't claim as support"
188                                       ", removing...", dev->name);
189                         /* support not available for this module */
190                         /* it isn't an error, just deallocate the as */
191                         kfree(as);
192                         ch->as = 0;
193                 }
194         }
195
196         return 0;
197
198  failed:
199         kfree(as);
200         ch->as = 0;
201
202         return err;
203
204 }
205
206 /* dumb allocator... */
207 static int generate_as_share_id(struct nvhost_as *as)
208 {
209         nvhost_dbg_fn("");
210         return ++as->last_share_id;
211 }
212 /* still dumb */
213 static void release_as_share_id(struct nvhost_as *as, int id)
214 {
215         nvhost_dbg_fn("");
216         return;
217 }
218
219 int nvhost_as_alloc_share(struct nvhost_channel *ch,
220                           struct nvhost_as_share **_as_share,
221                           bool has_fd)
222 {
223         struct nvhost_as *as = ch->as;
224         struct nvhost_as_share *as_share;
225         int err = 0;
226
227         nvhost_dbg_fn("");
228
229         *_as_share = 0;
230         as_share = kzalloc(sizeof(*as_share), GFP_KERNEL);
231         if (!as_share)
232                 return -ENOMEM;
233
234         as_share->ch      = ch;
235         as_share->as      = as;
236         as_share->host    = nvhost_get_host(ch->dev);
237         as_share->as_dev  = ch->as_node;
238         as_share->id      = generate_as_share_id(as_share->as);
239
240         /* call module to allocate hw resources */
241         err = as->ops->alloc_share(as_share);
242         if (err)
243                 goto failed;
244
245         /* When an fd is attached we'll get a call to release the as when the
246          * process exits (or otherwise closes the fd for the share).
247          * Setting up the ref_cnt in this manner allows for us to properly
248          * handle both that case and when we've created and bound a share
249          * w/o the attached fd.
250          */
251         if (has_fd)
252                 as_share->ref_cnt.counter = 1;
253         /* else set at from kzalloc above 0 */
254
255         /* add the share to the set of all shares on the module */
256         mutex_lock(&as->share_list_lock);
257         list_add_tail(&as_share->share_list_node, &as->share_list);
258         mutex_unlock(&as->share_list_lock);
259
260         /* initialize the bound list */
261         mutex_init(&as_share->bound_list_lock);
262         INIT_LIST_HEAD(&as_share->bound_list);
263
264         *_as_share = as_share;
265         return 0;
266
267  failed:
268         kfree(as_share);
269         return err;
270 }
271
272 /*
273  * hwctxs and the device nodes call this to release.
274  * once the ref_cnt hits zero the share is deleted.
275  * hwctx == 0 when the device node is being released.
276  * otherwise it is a hwctx unbind.
277  */
278 int nvhost_as_release_share(struct nvhost_as_share *as_share,
279                              struct nvhost_hwctx *hwctx)
280 {
281         int err;
282
283         nvhost_dbg_fn("");
284
285         if (hwctx) {
286                 hwctx->as_share = 0;
287
288                 mutex_lock(&as_share->bound_list_lock);
289                 list_del(&hwctx->as_share_bound_list_node);
290                 mutex_unlock(&as_share->bound_list_lock);
291         }
292
293         if (atomic_dec_return(&as_share->ref_cnt) > 0)
294                 return 0;
295
296         err = as_share->as->ops->release_share(as_share);
297
298         mutex_lock(&as_share->as->share_list_lock);
299         list_del(&as_share->share_list_node);
300         mutex_unlock(&as_share->as->share_list_lock);
301
302         release_as_share_id(as_share->as, as_share->id);
303
304         kfree(as_share);
305
306         return err;
307 }
308
309
310 static int bind_share(struct nvhost_as_share *as_share,
311                       struct nvhost_hwctx *hwctx)
312 {
313         int err = 0;
314         nvhost_dbg_fn("");
315
316         atomic_inc(&as_share->ref_cnt);
317         err = as_share->as->ops->bind_hwctx(as_share, hwctx);
318         if (err) {
319                 atomic_dec(&as_share->ref_cnt);
320                 return err;
321         }
322         hwctx->as_share = as_share;
323
324         mutex_lock(&as_share->bound_list_lock);
325         list_add_tail(&hwctx->as_share_bound_list_node, &as_share->bound_list);
326         mutex_unlock(&as_share->bound_list_lock);
327
328         return 0;
329 }
330
331 /* when clients have not set up a share themselves this
332  * can be called to set up and bind to a new one.
333  * however since they (presumably) have no access to the
334  * address space device node for the module they must
335  * use the channel map/unmap apis (deprecated) to manipulate
336  * the share */
337 int nvhost_as_alloc_and_bind_share(struct nvhost_channel *ch,
338                                    struct nvhost_hwctx *hwctx)
339 {
340         struct nvhost_as *as = ch->as;
341         struct nvhost_as_share *as_share = 0;
342         int err = 0;
343
344         nvhost_dbg_fn("");
345
346         if (!as)
347                 return -ENOENT;
348
349         err = nvhost_as_alloc_share(ch, &as_share, false /*no-fd path*/);
350         if (err)
351                 return err;
352
353         err = bind_share(as_share, hwctx);
354         if (err) {
355                 nvhost_as_release_share(as_share, hwctx);
356                 return err;
357         }
358
359         return 0;
360 }
361
362 int nvhost_as_ioctl_bind_channel(struct nvhost_as_share *as_share,
363                                  struct nvhost_as_bind_channel_args *args)
364 {
365         int err = 0;
366         struct nvhost_hwctx *hwctx;
367
368         nvhost_dbg_fn("");
369
370         hwctx = nvhost_channel_get_file_hwctx(args->channel_fd);
371         if (!hwctx || hwctx->as_share)
372                 return -EINVAL;
373
374         err = bind_share(as_share, hwctx);
375
376         return err;
377 }
378
379 int nvhost_as_ioctl_alloc_space(struct nvhost_as_share *as_share,
380                                 struct nvhost_as_alloc_space_args *args)
381 {
382         nvhost_dbg_fn("");
383         return as_share->as->ops->alloc_space(as_share, args);
384
385 }
386
387 int nvhost_as_ioctl_free_space(struct nvhost_as_share *as_share,
388                                struct nvhost_as_free_space_args *args)
389 {
390         nvhost_dbg_fn("");
391         return as_share->as->ops->free_space(as_share, args);
392 }
393
394 int nvhost_as_ioctl_map_buffer(struct nvhost_as_share *as_share,
395                                struct nvhost_as_map_buffer_args *args)
396 {
397         int err = 0;
398         struct mem_mgr *memmgr;
399         struct mem_handle *r;
400
401         nvhost_dbg_fn("");
402
403         /* note: this bumps up the ref cnt in the nvmap client.
404          * be sure to drop it with put later... we're not
405          * holding onto the nvmap client pointer */
406         memmgr = nvhost_memmgr_get_mgr_file(args->nvmap_fd);
407
408         if (IS_ERR(memmgr)) {
409                 err = PTR_ERR(memmgr);
410                 return err;
411         }
412
413         r = nvhost_memmgr_get(memmgr, args->nvmap_handle, /*XXX:get device*/0);
414         if (IS_ERR(r)) {
415                 err = PTR_ERR(r);
416                 goto finish;
417         }
418
419         err = as_share->as->ops->map_buffer(as_share,
420                                             memmgr,
421                                             r, &args->o_a.align, args->flags);
422         /* args->o_a.offset will be set if !err */
423
424  finish:
425         return err;
426 }
427
428 int nvhost_as_ioctl_unmap_buffer(struct nvhost_as_share *as_share,
429                                  struct nvhost_as_unmap_buffer_args *args)
430 {
431         nvhost_dbg_fn("");
432
433         return as_share->as->ops->unmap_buffer(as_share,
434                         args->offset, NULL, NULL);
435 }