diff options
Diffstat (limited to 'drivers/media/video/tegra/avp/trpc_sema.c')
-rw-r--r-- | drivers/media/video/tegra/avp/trpc_sema.c | 244 |
1 files changed, 244 insertions, 0 deletions
diff --git a/drivers/media/video/tegra/avp/trpc_sema.c b/drivers/media/video/tegra/avp/trpc_sema.c new file mode 100644 index 00000000000..cd717a1a0ca --- /dev/null +++ b/drivers/media/video/tegra/avp/trpc_sema.c | |||
@@ -0,0 +1,244 @@ | |||
1 | /* | ||
2 | * Copyright (C) 2010 Google, Inc. | ||
3 | * | ||
4 | * Author: | ||
5 | * Dima Zavin <dima@android.com> | ||
6 | * | ||
7 | * This software is licensed under the terms of the GNU General Public | ||
8 | * License version 2, as published by the Free Software Foundation, and | ||
9 | * may be copied, distributed, and modified under those terms. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | */ | ||
17 | |||
18 | #include <linux/err.h> | ||
19 | #include <linux/file.h> | ||
20 | #include <linux/fs.h> | ||
21 | #include <linux/miscdevice.h> | ||
22 | #include <linux/sched.h> | ||
23 | #include <linux/slab.h> | ||
24 | #include <linux/spinlock.h> | ||
25 | #include <linux/tegra_sema.h> | ||
26 | #include <linux/types.h> | ||
27 | #include <linux/uaccess.h> | ||
28 | #include <linux/wait.h> | ||
29 | |||
30 | #include "trpc_sema.h" | ||
31 | |||
32 | struct tegra_sema_info { | ||
33 | struct file *file; | ||
34 | wait_queue_head_t wq; | ||
35 | spinlock_t lock; | ||
36 | int count; | ||
37 | }; | ||
38 | |||
39 | static int rpc_sema_minor = -1; | ||
40 | |||
41 | static inline bool is_trpc_sema_file(struct file *file) | ||
42 | { | ||
43 | dev_t rdev = file->f_dentry->d_inode->i_rdev; | ||
44 | |||
45 | if (MAJOR(rdev) == MISC_MAJOR && MINOR(rdev) == rpc_sema_minor) | ||
46 | return true; | ||
47 | return false; | ||
48 | } | ||
49 | |||
50 | struct tegra_sema_info *trpc_sema_get_from_fd(int fd) | ||
51 | { | ||
52 | struct file *file; | ||
53 | |||
54 | file = fget(fd); | ||
55 | if (unlikely(file == NULL)) { | ||
56 | pr_err("%s: fd %d is invalid\n", __func__, fd); | ||
57 | return ERR_PTR(-EINVAL); | ||
58 | } | ||
59 | |||
60 | if (!is_trpc_sema_file(file)) { | ||
61 | pr_err("%s: fd (%d) is not a trpc_sema file\n", __func__, fd); | ||
62 | fput(file); | ||
63 | return ERR_PTR(-EINVAL); | ||
64 | } | ||
65 | |||
66 | return file->private_data; | ||
67 | } | ||
68 | |||
69 | void trpc_sema_put(struct tegra_sema_info *info) | ||
70 | { | ||
71 | if (info->file) | ||
72 | fput(info->file); | ||
73 | } | ||
74 | |||
75 | int tegra_sema_signal(struct tegra_sema_info *info) | ||
76 | { | ||
77 | unsigned long flags; | ||
78 | |||
79 | if (!info) | ||
80 | return -EINVAL; | ||
81 | |||
82 | spin_lock_irqsave(&info->lock, flags); | ||
83 | info->count++; | ||
84 | wake_up_interruptible_all(&info->wq); | ||
85 | spin_unlock_irqrestore(&info->lock, flags); | ||
86 | return 0; | ||
87 | } | ||
88 | |||
89 | int tegra_sema_wait(struct tegra_sema_info *info, long *timeout) | ||
90 | { | ||
91 | unsigned long flags; | ||
92 | int ret = 0; | ||
93 | unsigned long endtime; | ||
94 | long timeleft = *timeout; | ||
95 | |||
96 | *timeout = 0; | ||
97 | if (timeleft < 0) | ||
98 | timeleft = MAX_SCHEDULE_TIMEOUT; | ||
99 | |||
100 | timeleft = msecs_to_jiffies(timeleft); | ||
101 | endtime = jiffies + timeleft; | ||
102 | |||
103 | again: | ||
104 | if (timeleft) | ||
105 | ret = wait_event_interruptible_timeout(info->wq, | ||
106 | info->count > 0, | ||
107 | timeleft); | ||
108 | spin_lock_irqsave(&info->lock, flags); | ||
109 | if (info->count > 0) { | ||
110 | info->count--; | ||
111 | ret = 0; | ||
112 | } else if (ret == 0 || timeout == 0) { | ||
113 | ret = -ETIMEDOUT; | ||
114 | } else if (ret < 0) { | ||
115 | ret = -EINTR; | ||
116 | if (timeleft != MAX_SCHEDULE_TIMEOUT && | ||
117 | time_before(jiffies, endtime)) | ||
118 | *timeout = jiffies_to_msecs(endtime - jiffies); | ||
119 | else | ||
120 | *timeout = 0; | ||
121 | } else { | ||
122 | /* we woke up but someone else got the semaphore and we have | ||
123 | * time left, try again */ | ||
124 | timeleft = ret; | ||
125 | spin_unlock_irqrestore(&info->lock, flags); | ||
126 | goto again; | ||
127 | } | ||
128 | spin_unlock_irqrestore(&info->lock, flags); | ||
129 | return ret; | ||
130 | } | ||
131 | |||
132 | int tegra_sema_open(struct tegra_sema_info **sema) | ||
133 | { | ||
134 | struct tegra_sema_info *info; | ||
135 | info = kzalloc(sizeof(struct tegra_sema_info), GFP_KERNEL); | ||
136 | if (!info) | ||
137 | return -ENOMEM; | ||
138 | |||
139 | init_waitqueue_head(&info->wq); | ||
140 | spin_lock_init(&info->lock); | ||
141 | *sema = info; | ||
142 | return 0; | ||
143 | } | ||
144 | |||
145 | static int trpc_sema_open(struct inode *inode, struct file *file) | ||
146 | { | ||
147 | struct tegra_sema_info *info; | ||
148 | int ret; | ||
149 | |||
150 | ret = tegra_sema_open(&info); | ||
151 | if (ret < 0) | ||
152 | return ret; | ||
153 | |||
154 | info->file = file; | ||
155 | nonseekable_open(inode, file); | ||
156 | file->private_data = info; | ||
157 | return 0; | ||
158 | } | ||
159 | |||
160 | int tegra_sema_release(struct tegra_sema_info *sema) | ||
161 | { | ||
162 | kfree(sema); | ||
163 | return 0; | ||
164 | } | ||
165 | |||
166 | static int trpc_sema_release(struct inode *inode, struct file *file) | ||
167 | { | ||
168 | struct tegra_sema_info *info = file->private_data; | ||
169 | |||
170 | file->private_data = NULL; | ||
171 | tegra_sema_release(info); | ||
172 | return 0; | ||
173 | } | ||
174 | |||
175 | static long trpc_sema_ioctl(struct file *file, unsigned int cmd, | ||
176 | unsigned long arg) | ||
177 | { | ||
178 | struct tegra_sema_info *info = file->private_data; | ||
179 | int ret; | ||
180 | long timeout; | ||
181 | |||
182 | if (_IOC_TYPE(cmd) != TEGRA_SEMA_IOCTL_MAGIC || | ||
183 | _IOC_NR(cmd) < TEGRA_SEMA_IOCTL_MIN_NR || | ||
184 | _IOC_NR(cmd) > TEGRA_SEMA_IOCTL_MAX_NR) | ||
185 | return -ENOTTY; | ||
186 | else if (!info) | ||
187 | return -EINVAL; | ||
188 | |||
189 | switch (cmd) { | ||
190 | case TEGRA_SEMA_IOCTL_WAIT: | ||
191 | if (copy_from_user(&timeout, (void __user *)arg, sizeof(long))) | ||
192 | return -EFAULT; | ||
193 | ret = tegra_sema_wait(info, &timeout); | ||
194 | if (ret != -EINTR) | ||
195 | break; | ||
196 | if (copy_to_user((void __user *)arg, &timeout, sizeof(long))) | ||
197 | ret = -EFAULT; | ||
198 | break; | ||
199 | case TEGRA_SEMA_IOCTL_SIGNAL: | ||
200 | ret = tegra_sema_signal(info); | ||
201 | break; | ||
202 | default: | ||
203 | pr_err("%s: Unknown tegra_sema ioctl 0x%x\n", __func__, | ||
204 | _IOC_NR(cmd)); | ||
205 | ret = -ENOTTY; | ||
206 | break; | ||
207 | } | ||
208 | return ret; | ||
209 | } | ||
210 | |||
211 | static const struct file_operations trpc_sema_misc_fops = { | ||
212 | .owner = THIS_MODULE, | ||
213 | .open = trpc_sema_open, | ||
214 | .release = trpc_sema_release, | ||
215 | .unlocked_ioctl = trpc_sema_ioctl, | ||
216 | }; | ||
217 | |||
218 | static struct miscdevice trpc_sema_misc_device = { | ||
219 | .minor = MISC_DYNAMIC_MINOR, | ||
220 | .name = "tegra_sema", | ||
221 | .fops = &trpc_sema_misc_fops, | ||
222 | }; | ||
223 | |||
224 | int __init trpc_sema_init(void) | ||
225 | { | ||
226 | int ret; | ||
227 | |||
228 | if (rpc_sema_minor >= 0) { | ||
229 | pr_err("%s: trpc_sema already registered\n", __func__); | ||
230 | return -EBUSY; | ||
231 | } | ||
232 | |||
233 | ret = misc_register(&trpc_sema_misc_device); | ||
234 | if (ret) { | ||
235 | pr_err("%s: can't register misc device\n", __func__); | ||
236 | return ret; | ||
237 | } | ||
238 | |||
239 | rpc_sema_minor = trpc_sema_misc_device.minor; | ||
240 | pr_info("%s: registered misc dev %d:%d\n", __func__, MISC_MAJOR, | ||
241 | rpc_sema_minor); | ||
242 | |||
243 | return 0; | ||
244 | } | ||