diff options
Diffstat (limited to 'drivers/pps/pps.c')
-rw-r--r-- | drivers/pps/pps.c | 156 |
1 files changed, 120 insertions, 36 deletions
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index ca5183bdad8..2baadd21b7a 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c | |||
@@ -19,6 +19,7 @@ | |||
19 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | 19 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. |
20 | */ | 20 | */ |
21 | 21 | ||
22 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
22 | 23 | ||
23 | #include <linux/kernel.h> | 24 | #include <linux/kernel.h> |
24 | #include <linux/module.h> | 25 | #include <linux/module.h> |
@@ -26,9 +27,13 @@ | |||
26 | #include <linux/sched.h> | 27 | #include <linux/sched.h> |
27 | #include <linux/uaccess.h> | 28 | #include <linux/uaccess.h> |
28 | #include <linux/idr.h> | 29 | #include <linux/idr.h> |
30 | #include <linux/mutex.h> | ||
29 | #include <linux/cdev.h> | 31 | #include <linux/cdev.h> |
30 | #include <linux/poll.h> | 32 | #include <linux/poll.h> |
31 | #include <linux/pps_kernel.h> | 33 | #include <linux/pps_kernel.h> |
34 | #include <linux/slab.h> | ||
35 | |||
36 | #include "kc.h" | ||
32 | 37 | ||
33 | /* | 38 | /* |
34 | * Local variables | 39 | * Local variables |
@@ -37,6 +42,9 @@ | |||
37 | static dev_t pps_devt; | 42 | static dev_t pps_devt; |
38 | static struct class *pps_class; | 43 | static struct class *pps_class; |
39 | 44 | ||
45 | static DEFINE_MUTEX(pps_idr_lock); | ||
46 | static DEFINE_IDR(pps_idr); | ||
47 | |||
40 | /* | 48 | /* |
41 | * Char device methods | 49 | * Char device methods |
42 | */ | 50 | */ |
@@ -61,15 +69,13 @@ static long pps_cdev_ioctl(struct file *file, | |||
61 | { | 69 | { |
62 | struct pps_device *pps = file->private_data; | 70 | struct pps_device *pps = file->private_data; |
63 | struct pps_kparams params; | 71 | struct pps_kparams params; |
64 | struct pps_fdata fdata; | ||
65 | unsigned long ticks; | ||
66 | void __user *uarg = (void __user *) arg; | 72 | void __user *uarg = (void __user *) arg; |
67 | int __user *iuarg = (int __user *) arg; | 73 | int __user *iuarg = (int __user *) arg; |
68 | int err; | 74 | int err; |
69 | 75 | ||
70 | switch (cmd) { | 76 | switch (cmd) { |
71 | case PPS_GETPARAMS: | 77 | case PPS_GETPARAMS: |
72 | pr_debug("PPS_GETPARAMS: source %d\n", pps->id); | 78 | dev_dbg(pps->dev, "PPS_GETPARAMS\n"); |
73 | 79 | ||
74 | spin_lock_irq(&pps->lock); | 80 | spin_lock_irq(&pps->lock); |
75 | 81 | ||
@@ -85,7 +91,7 @@ static long pps_cdev_ioctl(struct file *file, | |||
85 | break; | 91 | break; |
86 | 92 | ||
87 | case PPS_SETPARAMS: | 93 | case PPS_SETPARAMS: |
88 | pr_debug("PPS_SETPARAMS: source %d\n", pps->id); | 94 | dev_dbg(pps->dev, "PPS_SETPARAMS\n"); |
89 | 95 | ||
90 | /* Check the capabilities */ | 96 | /* Check the capabilities */ |
91 | if (!capable(CAP_SYS_TIME)) | 97 | if (!capable(CAP_SYS_TIME)) |
@@ -95,14 +101,14 @@ static long pps_cdev_ioctl(struct file *file, | |||
95 | if (err) | 101 | if (err) |
96 | return -EFAULT; | 102 | return -EFAULT; |
97 | if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) { | 103 | if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) { |
98 | pr_debug("capture mode unspecified (%x)\n", | 104 | dev_dbg(pps->dev, "capture mode unspecified (%x)\n", |
99 | params.mode); | 105 | params.mode); |
100 | return -EINVAL; | 106 | return -EINVAL; |
101 | } | 107 | } |
102 | 108 | ||
103 | /* Check for supported capabilities */ | 109 | /* Check for supported capabilities */ |
104 | if ((params.mode & ~pps->info.mode) != 0) { | 110 | if ((params.mode & ~pps->info.mode) != 0) { |
105 | pr_debug("unsupported capabilities (%x)\n", | 111 | dev_dbg(pps->dev, "unsupported capabilities (%x)\n", |
106 | params.mode); | 112 | params.mode); |
107 | return -EINVAL; | 113 | return -EINVAL; |
108 | } | 114 | } |
@@ -115,7 +121,7 @@ static long pps_cdev_ioctl(struct file *file, | |||
115 | /* Restore the read only parameters */ | 121 | /* Restore the read only parameters */ |
116 | if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { | 122 | if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { |
117 | /* section 3.3 of RFC 2783 interpreted */ | 123 | /* section 3.3 of RFC 2783 interpreted */ |
118 | pr_debug("time format unspecified (%x)\n", | 124 | dev_dbg(pps->dev, "time format unspecified (%x)\n", |
119 | params.mode); | 125 | params.mode); |
120 | pps->params.mode |= PPS_TSFMT_TSPEC; | 126 | pps->params.mode |= PPS_TSFMT_TSPEC; |
121 | } | 127 | } |
@@ -128,7 +134,7 @@ static long pps_cdev_ioctl(struct file *file, | |||
128 | break; | 134 | break; |
129 | 135 | ||
130 | case PPS_GETCAP: | 136 | case PPS_GETCAP: |
131 | pr_debug("PPS_GETCAP: source %d\n", pps->id); | 137 | dev_dbg(pps->dev, "PPS_GETCAP\n"); |
132 | 138 | ||
133 | err = put_user(pps->info.mode, iuarg); | 139 | err = put_user(pps->info.mode, iuarg); |
134 | if (err) | 140 | if (err) |
@@ -136,20 +142,26 @@ static long pps_cdev_ioctl(struct file *file, | |||
136 | 142 | ||
137 | break; | 143 | break; |
138 | 144 | ||
139 | case PPS_FETCH: | 145 | case PPS_FETCH: { |
140 | pr_debug("PPS_FETCH: source %d\n", pps->id); | 146 | struct pps_fdata fdata; |
147 | unsigned int ev; | ||
148 | |||
149 | dev_dbg(pps->dev, "PPS_FETCH\n"); | ||
141 | 150 | ||
142 | err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata)); | 151 | err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata)); |
143 | if (err) | 152 | if (err) |
144 | return -EFAULT; | 153 | return -EFAULT; |
145 | 154 | ||
146 | pps->go = 0; | 155 | ev = pps->last_ev; |
147 | 156 | ||
148 | /* Manage the timeout */ | 157 | /* Manage the timeout */ |
149 | if (fdata.timeout.flags & PPS_TIME_INVALID) | 158 | if (fdata.timeout.flags & PPS_TIME_INVALID) |
150 | err = wait_event_interruptible(pps->queue, pps->go); | 159 | err = wait_event_interruptible(pps->queue, |
160 | ev != pps->last_ev); | ||
151 | else { | 161 | else { |
152 | pr_debug("timeout %lld.%09d\n", | 162 | unsigned long ticks; |
163 | |||
164 | dev_dbg(pps->dev, "timeout %lld.%09d\n", | ||
153 | (long long) fdata.timeout.sec, | 165 | (long long) fdata.timeout.sec, |
154 | fdata.timeout.nsec); | 166 | fdata.timeout.nsec); |
155 | ticks = fdata.timeout.sec * HZ; | 167 | ticks = fdata.timeout.sec * HZ; |
@@ -157,7 +169,9 @@ static long pps_cdev_ioctl(struct file *file, | |||
157 | 169 | ||
158 | if (ticks != 0) { | 170 | if (ticks != 0) { |
159 | err = wait_event_interruptible_timeout( | 171 | err = wait_event_interruptible_timeout( |
160 | pps->queue, pps->go, ticks); | 172 | pps->queue, |
173 | ev != pps->last_ev, | ||
174 | ticks); | ||
161 | if (err == 0) | 175 | if (err == 0) |
162 | return -ETIMEDOUT; | 176 | return -ETIMEDOUT; |
163 | } | 177 | } |
@@ -165,7 +179,7 @@ static long pps_cdev_ioctl(struct file *file, | |||
165 | 179 | ||
166 | /* Check for pending signals */ | 180 | /* Check for pending signals */ |
167 | if (err == -ERESTARTSYS) { | 181 | if (err == -ERESTARTSYS) { |
168 | pr_debug("pending signal caught\n"); | 182 | dev_dbg(pps->dev, "pending signal caught\n"); |
169 | return -EINTR; | 183 | return -EINTR; |
170 | } | 184 | } |
171 | 185 | ||
@@ -185,10 +199,44 @@ static long pps_cdev_ioctl(struct file *file, | |||
185 | return -EFAULT; | 199 | return -EFAULT; |
186 | 200 | ||
187 | break; | 201 | break; |
202 | } | ||
203 | case PPS_KC_BIND: { | ||
204 | struct pps_bind_args bind_args; | ||
205 | |||
206 | dev_dbg(pps->dev, "PPS_KC_BIND\n"); | ||
207 | |||
208 | /* Check the capabilities */ | ||
209 | if (!capable(CAP_SYS_TIME)) | ||
210 | return -EPERM; | ||
211 | |||
212 | if (copy_from_user(&bind_args, uarg, | ||
213 | sizeof(struct pps_bind_args))) | ||
214 | return -EFAULT; | ||
188 | 215 | ||
216 | /* Check for supported capabilities */ | ||
217 | if ((bind_args.edge & ~pps->info.mode) != 0) { | ||
218 | dev_err(pps->dev, "unsupported capabilities (%x)\n", | ||
219 | bind_args.edge); | ||
220 | return -EINVAL; | ||
221 | } | ||
222 | |||
223 | /* Validate parameters roughly */ | ||
224 | if (bind_args.tsformat != PPS_TSFMT_TSPEC || | ||
225 | (bind_args.edge & ~PPS_CAPTUREBOTH) != 0 || | ||
226 | bind_args.consumer != PPS_KC_HARDPPS) { | ||
227 | dev_err(pps->dev, "invalid kernel consumer bind" | ||
228 | " parameters (%x)\n", bind_args.edge); | ||
229 | return -EINVAL; | ||
230 | } | ||
231 | |||
232 | err = pps_kc_bind(pps, &bind_args); | ||
233 | if (err < 0) | ||
234 | return err; | ||
235 | |||
236 | break; | ||
237 | } | ||
189 | default: | 238 | default: |
190 | return -ENOTTY; | 239 | return -ENOTTY; |
191 | break; | ||
192 | } | 240 | } |
193 | 241 | ||
194 | return 0; | 242 | return 0; |
@@ -198,12 +246,6 @@ static int pps_cdev_open(struct inode *inode, struct file *file) | |||
198 | { | 246 | { |
199 | struct pps_device *pps = container_of(inode->i_cdev, | 247 | struct pps_device *pps = container_of(inode->i_cdev, |
200 | struct pps_device, cdev); | 248 | struct pps_device, cdev); |
201 | int found; | ||
202 | |||
203 | found = pps_get_source(pps->id) != 0; | ||
204 | if (!found) | ||
205 | return -ENODEV; | ||
206 | |||
207 | file->private_data = pps; | 249 | file->private_data = pps; |
208 | 250 | ||
209 | return 0; | 251 | return 0; |
@@ -211,11 +253,6 @@ static int pps_cdev_open(struct inode *inode, struct file *file) | |||
211 | 253 | ||
212 | static int pps_cdev_release(struct inode *inode, struct file *file) | 254 | static int pps_cdev_release(struct inode *inode, struct file *file) |
213 | { | 255 | { |
214 | struct pps_device *pps = file->private_data; | ||
215 | |||
216 | /* Free the PPS source and wake up (possible) deregistration */ | ||
217 | pps_put_source(pps); | ||
218 | |||
219 | return 0; | 256 | return 0; |
220 | } | 257 | } |
221 | 258 | ||
@@ -233,25 +270,67 @@ static const struct file_operations pps_cdev_fops = { | |||
233 | .release = pps_cdev_release, | 270 | .release = pps_cdev_release, |
234 | }; | 271 | }; |
235 | 272 | ||
273 | static void pps_device_destruct(struct device *dev) | ||
274 | { | ||
275 | struct pps_device *pps = dev_get_drvdata(dev); | ||
276 | |||
277 | /* release id here to protect others from using it while it's | ||
278 | * still in use */ | ||
279 | mutex_lock(&pps_idr_lock); | ||
280 | idr_remove(&pps_idr, pps->id); | ||
281 | mutex_unlock(&pps_idr_lock); | ||
282 | |||
283 | kfree(dev); | ||
284 | kfree(pps); | ||
285 | } | ||
286 | |||
236 | int pps_register_cdev(struct pps_device *pps) | 287 | int pps_register_cdev(struct pps_device *pps) |
237 | { | 288 | { |
238 | int err; | 289 | int err; |
290 | dev_t devt; | ||
291 | |||
292 | mutex_lock(&pps_idr_lock); | ||
293 | /* Get new ID for the new PPS source */ | ||
294 | if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) { | ||
295 | mutex_unlock(&pps_idr_lock); | ||
296 | return -ENOMEM; | ||
297 | } | ||
298 | |||
299 | /* Now really allocate the PPS source. | ||
300 | * After idr_get_new() calling the new source will be freely available | ||
301 | * into the kernel. | ||
302 | */ | ||
303 | err = idr_get_new(&pps_idr, pps, &pps->id); | ||
304 | mutex_unlock(&pps_idr_lock); | ||
305 | |||
306 | if (err < 0) | ||
307 | return err; | ||
308 | |||
309 | pps->id &= MAX_ID_MASK; | ||
310 | if (pps->id >= PPS_MAX_SOURCES) { | ||
311 | pr_err("%s: too many PPS sources in the system\n", | ||
312 | pps->info.name); | ||
313 | err = -EBUSY; | ||
314 | goto free_idr; | ||
315 | } | ||
316 | |||
317 | devt = MKDEV(MAJOR(pps_devt), pps->id); | ||
239 | 318 | ||
240 | pps->devno = MKDEV(MAJOR(pps_devt), pps->id); | ||
241 | cdev_init(&pps->cdev, &pps_cdev_fops); | 319 | cdev_init(&pps->cdev, &pps_cdev_fops); |
242 | pps->cdev.owner = pps->info.owner; | 320 | pps->cdev.owner = pps->info.owner; |
243 | 321 | ||
244 | err = cdev_add(&pps->cdev, pps->devno, 1); | 322 | err = cdev_add(&pps->cdev, devt, 1); |
245 | if (err) { | 323 | if (err) { |
246 | printk(KERN_ERR "pps: %s: failed to add char device %d:%d\n", | 324 | pr_err("%s: failed to add char device %d:%d\n", |
247 | pps->info.name, MAJOR(pps_devt), pps->id); | 325 | pps->info.name, MAJOR(pps_devt), pps->id); |
248 | return err; | 326 | goto free_idr; |
249 | } | 327 | } |
250 | pps->dev = device_create(pps_class, pps->info.dev, pps->devno, NULL, | 328 | pps->dev = device_create(pps_class, pps->info.dev, devt, pps, |
251 | "pps%d", pps->id); | 329 | "pps%d", pps->id); |
252 | if (IS_ERR(pps->dev)) | 330 | if (IS_ERR(pps->dev)) |
253 | goto del_cdev; | 331 | goto del_cdev; |
254 | dev_set_drvdata(pps->dev, pps); | 332 | |
333 | pps->dev->release = pps_device_destruct; | ||
255 | 334 | ||
256 | pr_debug("source %s got cdev (%d:%d)\n", pps->info.name, | 335 | pr_debug("source %s got cdev (%d:%d)\n", pps->info.name, |
257 | MAJOR(pps_devt), pps->id); | 336 | MAJOR(pps_devt), pps->id); |
@@ -261,12 +340,17 @@ int pps_register_cdev(struct pps_device *pps) | |||
261 | del_cdev: | 340 | del_cdev: |
262 | cdev_del(&pps->cdev); | 341 | cdev_del(&pps->cdev); |
263 | 342 | ||
343 | free_idr: | ||
344 | mutex_lock(&pps_idr_lock); | ||
345 | idr_remove(&pps_idr, pps->id); | ||
346 | mutex_unlock(&pps_idr_lock); | ||
347 | |||
264 | return err; | 348 | return err; |
265 | } | 349 | } |
266 | 350 | ||
267 | void pps_unregister_cdev(struct pps_device *pps) | 351 | void pps_unregister_cdev(struct pps_device *pps) |
268 | { | 352 | { |
269 | device_destroy(pps_class, pps->devno); | 353 | device_destroy(pps_class, pps->dev->devt); |
270 | cdev_del(&pps->cdev); | 354 | cdev_del(&pps->cdev); |
271 | } | 355 | } |
272 | 356 | ||
@@ -286,14 +370,14 @@ static int __init pps_init(void) | |||
286 | 370 | ||
287 | pps_class = class_create(THIS_MODULE, "pps"); | 371 | pps_class = class_create(THIS_MODULE, "pps"); |
288 | if (!pps_class) { | 372 | if (!pps_class) { |
289 | printk(KERN_ERR "pps: failed to allocate class\n"); | 373 | pr_err("failed to allocate class\n"); |
290 | return -ENOMEM; | 374 | return -ENOMEM; |
291 | } | 375 | } |
292 | pps_class->dev_attrs = pps_attrs; | 376 | pps_class->dev_attrs = pps_attrs; |
293 | 377 | ||
294 | err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps"); | 378 | err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps"); |
295 | if (err < 0) { | 379 | if (err < 0) { |
296 | printk(KERN_ERR "pps: failed to allocate char device region\n"); | 380 | pr_err("failed to allocate char device region\n"); |
297 | goto remove_class; | 381 | goto remove_class; |
298 | } | 382 | } |
299 | 383 | ||