Blame view
drivers/pps/pps.c
8.87 KB
eae9d2ba0 LinuxPPS: core su... |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
/* * PPS core file * * * Copyright (C) 2005-2009 Rodolfo Giometti <giometti@linux.it> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ |
7f7cce741 pps: convert prin... |
21 |
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt |
eae9d2ba0 LinuxPPS: core su... |
22 23 24 25 26 27 28 |
#include <linux/kernel.h> #include <linux/module.h> #include <linux/init.h> #include <linux/sched.h> #include <linux/uaccess.h> #include <linux/idr.h> |
2a5cd6e2f pps: make idr loc... |
29 |
#include <linux/mutex.h> |
eae9d2ba0 LinuxPPS: core su... |
30 31 32 |
#include <linux/cdev.h> #include <linux/poll.h> #include <linux/pps_kernel.h> |
083e58666 pps: move idr stu... |
33 |
#include <linux/slab.h> |
eae9d2ba0 LinuxPPS: core su... |
34 |
|
717c03366 pps: add kernel c... |
35 |
#include "kc.h" |
eae9d2ba0 LinuxPPS: core su... |
36 37 38 39 40 41 |
/* * Local variables */ static dev_t pps_devt; static struct class *pps_class; |
2a5cd6e2f pps: make idr loc... |
42 |
static DEFINE_MUTEX(pps_idr_lock); |
083e58666 pps: move idr stu... |
43 |
static DEFINE_IDR(pps_idr); |
eae9d2ba0 LinuxPPS: core su... |
44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
/* * Char device methods */ static unsigned int pps_cdev_poll(struct file *file, poll_table *wait) { struct pps_device *pps = file->private_data; poll_wait(file, &pps->queue, wait); return POLLIN | POLLRDNORM; } static int pps_cdev_fasync(int fd, struct file *file, int on) { struct pps_device *pps = file->private_data; return fasync_helper(fd, file, on, &pps->async_queue); } static long pps_cdev_ioctl(struct file *file, unsigned int cmd, unsigned long arg) { struct pps_device *pps = file->private_data; struct pps_kparams params; |
eae9d2ba0 LinuxPPS: core su... |
68 69 70 71 72 73 |
void __user *uarg = (void __user *) arg; int __user *iuarg = (int __user *) arg; int err; switch (cmd) { case PPS_GETPARAMS: |
7f7cce741 pps: convert prin... |
74 75 |
dev_dbg(pps->dev, "PPS_GETPARAMS "); |
eae9d2ba0 LinuxPPS: core su... |
76 |
|
cbf83cc5a pps: locking sche... |
77 78 79 80 81 82 83 84 |
spin_lock_irq(&pps->lock); /* Get the current parameters */ params = pps->params; spin_unlock_irq(&pps->lock); err = copy_to_user(uarg, ¶ms, sizeof(struct pps_kparams)); |
eae9d2ba0 LinuxPPS: core su... |
85 86 87 88 89 90 |
if (err) return -EFAULT; break; case PPS_SETPARAMS: |
7f7cce741 pps: convert prin... |
91 92 |
dev_dbg(pps->dev, "PPS_SETPARAMS "); |
eae9d2ba0 LinuxPPS: core su... |
93 94 95 96 97 98 99 100 101 |
/* Check the capabilities */ if (!capable(CAP_SYS_TIME)) return -EPERM; err = copy_from_user(¶ms, uarg, sizeof(struct pps_kparams)); if (err) return -EFAULT; if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) { |
7f7cce741 pps: convert prin... |
102 103 |
dev_dbg(pps->dev, "capture mode unspecified (%x) ", |
eae9d2ba0 LinuxPPS: core su... |
104 105 106 107 108 109 |
params.mode); return -EINVAL; } /* Check for supported capabilities */ if ((params.mode & ~pps->info.mode) != 0) { |
7f7cce741 pps: convert prin... |
110 111 |
dev_dbg(pps->dev, "unsupported capabilities (%x) ", |
eae9d2ba0 LinuxPPS: core su... |
112 113 114 115 116 117 118 119 120 121 122 123 |
params.mode); return -EINVAL; } spin_lock_irq(&pps->lock); /* Save the new parameters */ pps->params = params; /* Restore the read only parameters */ if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { /* section 3.3 of RFC 2783 interpreted */ |
7f7cce741 pps: convert prin... |
124 125 |
dev_dbg(pps->dev, "time format unspecified (%x) ", |
eae9d2ba0 LinuxPPS: core su... |
126 127 128 129 130 131 132 133 134 135 136 137 |
params.mode); pps->params.mode |= PPS_TSFMT_TSPEC; } if (pps->info.mode & PPS_CANWAIT) pps->params.mode |= PPS_CANWAIT; pps->params.api_version = PPS_API_VERS; spin_unlock_irq(&pps->lock); break; case PPS_GETCAP: |
7f7cce741 pps: convert prin... |
138 139 |
dev_dbg(pps->dev, "PPS_GETCAP "); |
eae9d2ba0 LinuxPPS: core su... |
140 141 142 143 144 145 |
err = put_user(pps->info.mode, iuarg); if (err) return -EFAULT; break; |
86d921f9e pps: declare vari... |
146 147 |
case PPS_FETCH: { struct pps_fdata fdata; |
3003d55b5 pps: fix race in ... |
148 |
unsigned int ev; |
86d921f9e pps: declare vari... |
149 |
|
7f7cce741 pps: convert prin... |
150 151 |
dev_dbg(pps->dev, "PPS_FETCH "); |
eae9d2ba0 LinuxPPS: core su... |
152 153 154 155 |
err = copy_from_user(&fdata, uarg, sizeof(struct pps_fdata)); if (err) return -EFAULT; |
3003d55b5 pps: fix race in ... |
156 |
ev = pps->last_ev; |
eae9d2ba0 LinuxPPS: core su... |
157 158 159 |
/* Manage the timeout */ if (fdata.timeout.flags & PPS_TIME_INVALID) |
3003d55b5 pps: fix race in ... |
160 161 |
err = wait_event_interruptible(pps->queue, ev != pps->last_ev); |
eae9d2ba0 LinuxPPS: core su... |
162 |
else { |
86d921f9e pps: declare vari... |
163 |
unsigned long ticks; |
7f7cce741 pps: convert prin... |
164 165 |
dev_dbg(pps->dev, "timeout %lld.%09d ", |
eae9d2ba0 LinuxPPS: core su... |
166 167 168 169 170 171 172 |
(long long) fdata.timeout.sec, fdata.timeout.nsec); ticks = fdata.timeout.sec * HZ; ticks += fdata.timeout.nsec / (NSEC_PER_SEC / HZ); if (ticks != 0) { err = wait_event_interruptible_timeout( |
3003d55b5 pps: fix race in ... |
173 174 175 |
pps->queue, ev != pps->last_ev, ticks); |
eae9d2ba0 LinuxPPS: core su... |
176 177 178 179 180 181 182 |
if (err == 0) return -ETIMEDOUT; } } /* Check for pending signals */ if (err == -ERESTARTSYS) { |
7f7cce741 pps: convert prin... |
183 184 |
dev_dbg(pps->dev, "pending signal caught "); |
eae9d2ba0 LinuxPPS: core su... |
185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 |
return -EINTR; } /* Return the fetched timestamp */ spin_lock_irq(&pps->lock); fdata.info.assert_sequence = pps->assert_sequence; fdata.info.clear_sequence = pps->clear_sequence; fdata.info.assert_tu = pps->assert_tu; fdata.info.clear_tu = pps->clear_tu; fdata.info.current_mode = pps->current_mode; spin_unlock_irq(&pps->lock); err = copy_to_user(uarg, &fdata, sizeof(struct pps_fdata)); if (err) return -EFAULT; break; |
86d921f9e pps: declare vari... |
204 |
} |
717c03366 pps: add kernel c... |
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 |
case PPS_KC_BIND: { struct pps_bind_args bind_args; dev_dbg(pps->dev, "PPS_KC_BIND "); /* Check the capabilities */ if (!capable(CAP_SYS_TIME)) return -EPERM; if (copy_from_user(&bind_args, uarg, sizeof(struct pps_bind_args))) return -EFAULT; /* Check for supported capabilities */ if ((bind_args.edge & ~pps->info.mode) != 0) { dev_err(pps->dev, "unsupported capabilities (%x) ", bind_args.edge); return -EINVAL; } /* Validate parameters roughly */ if (bind_args.tsformat != PPS_TSFMT_TSPEC || (bind_args.edge & ~PPS_CAPTUREBOTH) != 0 || bind_args.consumer != PPS_KC_HARDPPS) { dev_err(pps->dev, "invalid kernel consumer bind" " parameters (%x) ", bind_args.edge); return -EINVAL; } err = pps_kc_bind(pps, &bind_args); if (err < 0) return err; break; } |
eae9d2ba0 LinuxPPS: core su... |
243 244 |
default: return -ENOTTY; |
eae9d2ba0 LinuxPPS: core su... |
245 246 247 248 249 250 251 252 253 |
} return 0; } static int pps_cdev_open(struct inode *inode, struct file *file) { struct pps_device *pps = container_of(inode->i_cdev, struct pps_device, cdev); |
eae9d2ba0 LinuxPPS: core su... |
254 255 256 257 258 259 260 |
file->private_data = pps; return 0; } static int pps_cdev_release(struct inode *inode, struct file *file) { |
eae9d2ba0 LinuxPPS: core su... |
261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
return 0; } /* * Char device stuff */ static const struct file_operations pps_cdev_fops = { .owner = THIS_MODULE, .llseek = no_llseek, .poll = pps_cdev_poll, .fasync = pps_cdev_fasync, .unlocked_ioctl = pps_cdev_ioctl, .open = pps_cdev_open, .release = pps_cdev_release, }; |
083e58666 pps: move idr stu... |
277 278 279 280 281 282 |
static void pps_device_destruct(struct device *dev) { struct pps_device *pps = dev_get_drvdata(dev); /* release id here to protect others from using it while it's * still in use */ |
2a5cd6e2f pps: make idr loc... |
283 |
mutex_lock(&pps_idr_lock); |
083e58666 pps: move idr stu... |
284 |
idr_remove(&pps_idr, pps->id); |
2a5cd6e2f pps: make idr loc... |
285 |
mutex_unlock(&pps_idr_lock); |
083e58666 pps: move idr stu... |
286 287 288 289 |
kfree(dev); kfree(pps); } |
eae9d2ba0 LinuxPPS: core su... |
290 291 292 |
int pps_register_cdev(struct pps_device *pps) { int err; |
5e196d34a pps: access pps d... |
293 |
dev_t devt; |
2a5cd6e2f pps: make idr loc... |
294 |
mutex_lock(&pps_idr_lock); |
083e58666 pps: move idr stu... |
295 |
/* Get new ID for the new PPS source */ |
2a5cd6e2f pps: make idr loc... |
296 297 |
if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) { mutex_unlock(&pps_idr_lock); |
083e58666 pps: move idr stu... |
298 |
return -ENOMEM; |
2a5cd6e2f pps: make idr loc... |
299 |
} |
083e58666 pps: move idr stu... |
300 301 302 303 304 |
/* Now really allocate the PPS source. * After idr_get_new() calling the new source will be freely available * into the kernel. */ |
083e58666 pps: move idr stu... |
305 |
err = idr_get_new(&pps_idr, pps, &pps->id); |
2a5cd6e2f pps: make idr loc... |
306 |
mutex_unlock(&pps_idr_lock); |
083e58666 pps: move idr stu... |
307 308 309 310 311 312 313 314 315 316 317 318 |
if (err < 0) return err; pps->id &= MAX_ID_MASK; if (pps->id >= PPS_MAX_SOURCES) { pr_err("%s: too many PPS sources in the system ", pps->info.name); err = -EBUSY; goto free_idr; } |
5e196d34a pps: access pps d... |
319 |
devt = MKDEV(MAJOR(pps_devt), pps->id); |
eae9d2ba0 LinuxPPS: core su... |
320 |
|
eae9d2ba0 LinuxPPS: core su... |
321 322 |
cdev_init(&pps->cdev, &pps_cdev_fops); pps->cdev.owner = pps->info.owner; |
5e196d34a pps: access pps d... |
323 |
err = cdev_add(&pps->cdev, devt, 1); |
eae9d2ba0 LinuxPPS: core su... |
324 |
if (err) { |
7f7cce741 pps: convert prin... |
325 326 |
pr_err("%s: failed to add char device %d:%d ", |
eae9d2ba0 LinuxPPS: core su... |
327 |
pps->info.name, MAJOR(pps_devt), pps->id); |
083e58666 pps: move idr stu... |
328 |
goto free_idr; |
eae9d2ba0 LinuxPPS: core su... |
329 |
} |
5e196d34a pps: access pps d... |
330 |
pps->dev = device_create(pps_class, pps->info.dev, devt, pps, |
eae9d2ba0 LinuxPPS: core su... |
331 |
"pps%d", pps->id); |
668f06b9f pps: return PTR_E... |
332 333 |
if (IS_ERR(pps->dev)) { err = PTR_ERR(pps->dev); |
eae9d2ba0 LinuxPPS: core su... |
334 |
goto del_cdev; |
668f06b9f pps: return PTR_E... |
335 |
} |
eae9d2ba0 LinuxPPS: core su... |
336 |
|
083e58666 pps: move idr stu... |
337 |
pps->dev->release = pps_device_destruct; |
eae9d2ba0 LinuxPPS: core su... |
338 339 340 341 342 343 344 345 |
pr_debug("source %s got cdev (%d:%d) ", pps->info.name, MAJOR(pps_devt), pps->id); return 0; del_cdev: cdev_del(&pps->cdev); |
083e58666 pps: move idr stu... |
346 |
free_idr: |
2a5cd6e2f pps: make idr loc... |
347 |
mutex_lock(&pps_idr_lock); |
083e58666 pps: move idr stu... |
348 |
idr_remove(&pps_idr, pps->id); |
2a5cd6e2f pps: make idr loc... |
349 |
mutex_unlock(&pps_idr_lock); |
083e58666 pps: move idr stu... |
350 |
|
eae9d2ba0 LinuxPPS: core su... |
351 352 353 354 355 |
return err; } void pps_unregister_cdev(struct pps_device *pps) { |
5e196d34a pps: access pps d... |
356 |
device_destroy(pps_class, pps->dev->devt); |
eae9d2ba0 LinuxPPS: core su... |
357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 |
cdev_del(&pps->cdev); } /* * Module stuff */ static void __exit pps_exit(void) { class_destroy(pps_class); unregister_chrdev_region(pps_devt, PPS_MAX_SOURCES); } static int __init pps_init(void) { int err; pps_class = class_create(THIS_MODULE, "pps"); |
7ad12566d pps: class_create... |
375 |
if (IS_ERR(pps_class)) { |
7f7cce741 pps: convert prin... |
376 377 |
pr_err("failed to allocate class "); |
7ad12566d pps: class_create... |
378 |
return PTR_ERR(pps_class); |
eae9d2ba0 LinuxPPS: core su... |
379 380 381 382 383 |
} pps_class->dev_attrs = pps_attrs; err = alloc_chrdev_region(&pps_devt, 0, PPS_MAX_SOURCES, "pps"); if (err < 0) { |
7f7cce741 pps: convert prin... |
384 385 |
pr_err("failed to allocate char device region "); |
eae9d2ba0 LinuxPPS: core su... |
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 |
goto remove_class; } pr_info("LinuxPPS API ver. %d registered ", PPS_API_VERS); pr_info("Software ver. %s - Copyright 2005-2007 Rodolfo Giometti " "<giometti@linux.it> ", PPS_VERSION); return 0; remove_class: class_destroy(pps_class); return err; } subsys_initcall(pps_init); module_exit(pps_exit); MODULE_AUTHOR("Rodolfo Giometti <giometti@linux.it>"); MODULE_DESCRIPTION("LinuxPPS support (RFC 2783) - ver. " PPS_VERSION); MODULE_LICENSE("GPL"); |