| 1 | // SPDX-License-Identifier: GPL-2.0-or-later | 
|---|
| 2 | /* | 
|---|
| 3 | * PPS core file | 
|---|
| 4 | * | 
|---|
| 5 | * Copyright (C) 2005-2009   Rodolfo Giometti <giometti@linux.it> | 
|---|
| 6 | */ | 
|---|
| 7 |  | 
|---|
| 8 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | 
|---|
| 9 |  | 
|---|
| 10 | #include <linux/kernel.h> | 
|---|
| 11 | #include <linux/module.h> | 
|---|
| 12 | #include <linux/init.h> | 
|---|
| 13 | #include <linux/sched.h> | 
|---|
| 14 | #include <linux/uaccess.h> | 
|---|
| 15 | #include <linux/idr.h> | 
|---|
| 16 | #include <linux/mutex.h> | 
|---|
| 17 | #include <linux/cdev.h> | 
|---|
| 18 | #include <linux/poll.h> | 
|---|
| 19 | #include <linux/pps_kernel.h> | 
|---|
| 20 | #include <linux/slab.h> | 
|---|
| 21 |  | 
|---|
| 22 | #include "kc.h" | 
|---|
| 23 |  | 
|---|
| 24 | /* | 
|---|
| 25 | * Local variables | 
|---|
| 26 | */ | 
|---|
| 27 |  | 
|---|
| 28 | static int pps_major; | 
|---|
| 29 | static struct class *pps_class; | 
|---|
| 30 |  | 
|---|
| 31 | static DEFINE_MUTEX(pps_idr_lock); | 
|---|
| 32 | static DEFINE_IDR(pps_idr); | 
|---|
| 33 |  | 
|---|
| 34 | /* | 
|---|
| 35 | * Char device methods | 
|---|
| 36 | */ | 
|---|
| 37 |  | 
|---|
| 38 | static __poll_t pps_cdev_poll(struct file *file, poll_table *wait) | 
|---|
| 39 | { | 
|---|
| 40 | struct pps_device *pps = file->private_data; | 
|---|
| 41 |  | 
|---|
| 42 | poll_wait(filp: file, wait_address: &pps->queue, p: wait); | 
|---|
| 43 |  | 
|---|
| 44 | if (pps->last_fetched_ev == pps->last_ev) | 
|---|
| 45 | return 0; | 
|---|
| 46 |  | 
|---|
| 47 | return EPOLLIN | EPOLLRDNORM; | 
|---|
| 48 | } | 
|---|
| 49 |  | 
|---|
| 50 | static int pps_cdev_fasync(int fd, struct file *file, int on) | 
|---|
| 51 | { | 
|---|
| 52 | struct pps_device *pps = file->private_data; | 
|---|
| 53 | return fasync_helper(fd, file, on, &pps->async_queue); | 
|---|
| 54 | } | 
|---|
| 55 |  | 
|---|
| 56 | static int pps_cdev_pps_fetch(struct pps_device *pps, struct pps_fdata *fdata) | 
|---|
| 57 | { | 
|---|
| 58 | unsigned int ev = pps->last_ev; | 
|---|
| 59 | int err = 0; | 
|---|
| 60 |  | 
|---|
| 61 | /* Manage the timeout */ | 
|---|
| 62 | if (fdata->timeout.flags & PPS_TIME_INVALID) | 
|---|
| 63 | err = wait_event_interruptible(pps->queue, | 
|---|
| 64 | ev != pps->last_ev); | 
|---|
| 65 | else { | 
|---|
| 66 | unsigned long ticks; | 
|---|
| 67 |  | 
|---|
| 68 | dev_dbg(&pps->dev, "timeout %lld.%09d\n", | 
|---|
| 69 | (long long) fdata->timeout.sec, | 
|---|
| 70 | fdata->timeout.nsec); | 
|---|
| 71 | ticks = fdata->timeout.sec * HZ; | 
|---|
| 72 | ticks += fdata->timeout.nsec / (NSEC_PER_SEC / HZ); | 
|---|
| 73 |  | 
|---|
| 74 | if (ticks != 0) { | 
|---|
| 75 | err = wait_event_interruptible_timeout( | 
|---|
| 76 | pps->queue, | 
|---|
| 77 | ev != pps->last_ev, | 
|---|
| 78 | ticks); | 
|---|
| 79 | if (err == 0) | 
|---|
| 80 | return -ETIMEDOUT; | 
|---|
| 81 | } | 
|---|
| 82 | } | 
|---|
| 83 |  | 
|---|
| 84 | /* Check for pending signals */ | 
|---|
| 85 | if (err == -ERESTARTSYS) { | 
|---|
| 86 | dev_dbg(&pps->dev, "pending signal caught\n"); | 
|---|
| 87 | return -EINTR; | 
|---|
| 88 | } | 
|---|
| 89 |  | 
|---|
| 90 | return 0; | 
|---|
| 91 | } | 
|---|
| 92 |  | 
|---|
| 93 | static long pps_cdev_ioctl(struct file *file, | 
|---|
| 94 | unsigned int cmd, unsigned long arg) | 
|---|
| 95 | { | 
|---|
| 96 | struct pps_device *pps = file->private_data; | 
|---|
| 97 | struct pps_kparams params; | 
|---|
| 98 | void __user *uarg = (void __user *) arg; | 
|---|
| 99 | int __user *iuarg = (int __user *) arg; | 
|---|
| 100 | int err; | 
|---|
| 101 |  | 
|---|
| 102 | switch (cmd) { | 
|---|
| 103 | case PPS_GETPARAMS: | 
|---|
| 104 | dev_dbg(&pps->dev, "PPS_GETPARAMS\n"); | 
|---|
| 105 |  | 
|---|
| 106 | spin_lock_irq(lock: &pps->lock); | 
|---|
| 107 |  | 
|---|
| 108 | /* Get the current parameters */ | 
|---|
| 109 | params = pps->params; | 
|---|
| 110 |  | 
|---|
| 111 | spin_unlock_irq(lock: &pps->lock); | 
|---|
| 112 |  | 
|---|
| 113 | err = copy_to_user(to: uarg, from: ¶ms, n: sizeof(struct pps_kparams)); | 
|---|
| 114 | if (err) | 
|---|
| 115 | return -EFAULT; | 
|---|
| 116 |  | 
|---|
| 117 | break; | 
|---|
| 118 |  | 
|---|
| 119 | case PPS_SETPARAMS: | 
|---|
| 120 | dev_dbg(&pps->dev, "PPS_SETPARAMS\n"); | 
|---|
| 121 |  | 
|---|
| 122 | /* Check the capabilities */ | 
|---|
| 123 | if (!capable(CAP_SYS_TIME)) | 
|---|
| 124 | return -EPERM; | 
|---|
| 125 |  | 
|---|
| 126 | err = copy_from_user(to: ¶ms, from: uarg, n: sizeof(struct pps_kparams)); | 
|---|
| 127 | if (err) | 
|---|
| 128 | return -EFAULT; | 
|---|
| 129 | if (!(params.mode & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR))) { | 
|---|
| 130 | dev_dbg(&pps->dev, "capture mode unspecified (%x)\n", | 
|---|
| 131 | params.mode); | 
|---|
| 132 | return -EINVAL; | 
|---|
| 133 | } | 
|---|
| 134 |  | 
|---|
| 135 | /* Check for supported capabilities */ | 
|---|
| 136 | if ((params.mode & ~pps->info.mode) != 0) { | 
|---|
| 137 | dev_dbg(&pps->dev, "unsupported capabilities (%x)\n", | 
|---|
| 138 | params.mode); | 
|---|
| 139 | return -EINVAL; | 
|---|
| 140 | } | 
|---|
| 141 |  | 
|---|
| 142 | spin_lock_irq(lock: &pps->lock); | 
|---|
| 143 |  | 
|---|
| 144 | /* Save the new parameters */ | 
|---|
| 145 | pps->params = params; | 
|---|
| 146 |  | 
|---|
| 147 | /* Restore the read only parameters */ | 
|---|
| 148 | if ((params.mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) { | 
|---|
| 149 | /* section 3.3 of RFC 2783 interpreted */ | 
|---|
| 150 | dev_dbg(&pps->dev, "time format unspecified (%x)\n", | 
|---|
| 151 | params.mode); | 
|---|
| 152 | pps->params.mode |= PPS_TSFMT_TSPEC; | 
|---|
| 153 | } | 
|---|
| 154 | if (pps->info.mode & PPS_CANWAIT) | 
|---|
| 155 | pps->params.mode |= PPS_CANWAIT; | 
|---|
| 156 | pps->params.api_version = PPS_API_VERS; | 
|---|
| 157 |  | 
|---|
| 158 | /* | 
|---|
| 159 | * Clear unused fields of pps_kparams to avoid leaking | 
|---|
| 160 | * uninitialized data of the PPS_SETPARAMS caller via | 
|---|
| 161 | * PPS_GETPARAMS | 
|---|
| 162 | */ | 
|---|
| 163 | pps->params.assert_off_tu.flags = 0; | 
|---|
| 164 | pps->params.clear_off_tu.flags = 0; | 
|---|
| 165 |  | 
|---|
| 166 | spin_unlock_irq(lock: &pps->lock); | 
|---|
| 167 |  | 
|---|
| 168 | break; | 
|---|
| 169 |  | 
|---|
| 170 | case PPS_GETCAP: | 
|---|
| 171 | dev_dbg(&pps->dev, "PPS_GETCAP\n"); | 
|---|
| 172 |  | 
|---|
| 173 | err = put_user(pps->info.mode, iuarg); | 
|---|
| 174 | if (err) | 
|---|
| 175 | return -EFAULT; | 
|---|
| 176 |  | 
|---|
| 177 | break; | 
|---|
| 178 |  | 
|---|
| 179 | case PPS_FETCH: { | 
|---|
| 180 | struct pps_fdata fdata; | 
|---|
| 181 |  | 
|---|
| 182 | dev_dbg(&pps->dev, "PPS_FETCH\n"); | 
|---|
| 183 |  | 
|---|
| 184 | err = copy_from_user(to: &fdata, from: uarg, n: sizeof(struct pps_fdata)); | 
|---|
| 185 | if (err) | 
|---|
| 186 | return -EFAULT; | 
|---|
| 187 |  | 
|---|
| 188 | err = pps_cdev_pps_fetch(pps, fdata: &fdata); | 
|---|
| 189 | if (err) | 
|---|
| 190 | return err; | 
|---|
| 191 |  | 
|---|
| 192 | /* Return the fetched timestamp and save last fetched event  */ | 
|---|
| 193 | spin_lock_irq(lock: &pps->lock); | 
|---|
| 194 |  | 
|---|
| 195 | pps->last_fetched_ev = pps->last_ev; | 
|---|
| 196 |  | 
|---|
| 197 | fdata.info.assert_sequence = pps->assert_sequence; | 
|---|
| 198 | fdata.info.clear_sequence = pps->clear_sequence; | 
|---|
| 199 | fdata.info.assert_tu = pps->assert_tu; | 
|---|
| 200 | fdata.info.clear_tu = pps->clear_tu; | 
|---|
| 201 | fdata.info.current_mode = pps->current_mode; | 
|---|
| 202 |  | 
|---|
| 203 | spin_unlock_irq(lock: &pps->lock); | 
|---|
| 204 |  | 
|---|
| 205 | err = copy_to_user(to: uarg, from: &fdata, n: sizeof(struct pps_fdata)); | 
|---|
| 206 | if (err) | 
|---|
| 207 | return -EFAULT; | 
|---|
| 208 |  | 
|---|
| 209 | break; | 
|---|
| 210 | } | 
|---|
| 211 | case PPS_KC_BIND: { | 
|---|
| 212 | struct pps_bind_args bind_args; | 
|---|
| 213 |  | 
|---|
| 214 | dev_dbg(&pps->dev, "PPS_KC_BIND\n"); | 
|---|
| 215 |  | 
|---|
| 216 | /* Check the capabilities */ | 
|---|
| 217 | if (!capable(CAP_SYS_TIME)) | 
|---|
| 218 | return -EPERM; | 
|---|
| 219 |  | 
|---|
| 220 | if (copy_from_user(to: &bind_args, from: uarg, | 
|---|
| 221 | n: sizeof(struct pps_bind_args))) | 
|---|
| 222 | return -EFAULT; | 
|---|
| 223 |  | 
|---|
| 224 | /* Check for supported capabilities */ | 
|---|
| 225 | if ((bind_args.edge & ~pps->info.mode) != 0) { | 
|---|
| 226 | dev_err(&pps->dev, "unsupported capabilities (%x)\n", | 
|---|
| 227 | bind_args.edge); | 
|---|
| 228 | return -EINVAL; | 
|---|
| 229 | } | 
|---|
| 230 |  | 
|---|
| 231 | /* Validate parameters roughly */ | 
|---|
| 232 | if (bind_args.tsformat != PPS_TSFMT_TSPEC || | 
|---|
| 233 | (bind_args.edge & ~PPS_CAPTUREBOTH) != 0 || | 
|---|
| 234 | bind_args.consumer != PPS_KC_HARDPPS) { | 
|---|
| 235 | dev_err(&pps->dev, "invalid kernel consumer bind" | 
|---|
| 236 | " parameters (%x)\n", bind_args.edge); | 
|---|
| 237 | return -EINVAL; | 
|---|
| 238 | } | 
|---|
| 239 |  | 
|---|
| 240 | err = pps_kc_bind(pps, bind_args: &bind_args); | 
|---|
| 241 | if (err < 0) | 
|---|
| 242 | return err; | 
|---|
| 243 |  | 
|---|
| 244 | break; | 
|---|
| 245 | } | 
|---|
| 246 | default: | 
|---|
| 247 | return -ENOTTY; | 
|---|
| 248 | } | 
|---|
| 249 |  | 
|---|
| 250 | return 0; | 
|---|
| 251 | } | 
|---|
| 252 |  | 
|---|
| 253 | #ifdef CONFIG_COMPAT | 
|---|
| 254 | static long pps_cdev_compat_ioctl(struct file *file, | 
|---|
| 255 | unsigned int cmd, unsigned long arg) | 
|---|
| 256 | { | 
|---|
| 257 | struct pps_device *pps = file->private_data; | 
|---|
| 258 | void __user *uarg = (void __user *) arg; | 
|---|
| 259 |  | 
|---|
| 260 | cmd = _IOC(_IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), sizeof(void *)); | 
|---|
| 261 |  | 
|---|
| 262 | if (cmd == PPS_FETCH) { | 
|---|
| 263 | struct pps_fdata_compat compat; | 
|---|
| 264 | struct pps_fdata fdata; | 
|---|
| 265 | int err; | 
|---|
| 266 |  | 
|---|
| 267 | dev_dbg(&pps->dev, "PPS_FETCH\n"); | 
|---|
| 268 |  | 
|---|
| 269 | err = copy_from_user(to: &compat, from: uarg, n: sizeof(struct pps_fdata_compat)); | 
|---|
| 270 | if (err) | 
|---|
| 271 | return -EFAULT; | 
|---|
| 272 |  | 
|---|
| 273 | memcpy(to: &fdata.timeout, from: &compat.timeout, | 
|---|
| 274 | len: sizeof(struct pps_ktime_compat)); | 
|---|
| 275 |  | 
|---|
| 276 | err = pps_cdev_pps_fetch(pps, fdata: &fdata); | 
|---|
| 277 | if (err) | 
|---|
| 278 | return err; | 
|---|
| 279 |  | 
|---|
| 280 | /* Return the fetched timestamp and save last fetched event  */ | 
|---|
| 281 | spin_lock_irq(lock: &pps->lock); | 
|---|
| 282 |  | 
|---|
| 283 | pps->last_fetched_ev = pps->last_ev; | 
|---|
| 284 |  | 
|---|
| 285 | compat.info.assert_sequence = pps->assert_sequence; | 
|---|
| 286 | compat.info.clear_sequence = pps->clear_sequence; | 
|---|
| 287 | compat.info.current_mode = pps->current_mode; | 
|---|
| 288 |  | 
|---|
| 289 | memcpy(to: &compat.info.assert_tu, from: &pps->assert_tu, | 
|---|
| 290 | len: sizeof(struct pps_ktime_compat)); | 
|---|
| 291 | memcpy(to: &compat.info.clear_tu, from: &pps->clear_tu, | 
|---|
| 292 | len: sizeof(struct pps_ktime_compat)); | 
|---|
| 293 |  | 
|---|
| 294 | spin_unlock_irq(lock: &pps->lock); | 
|---|
| 295 |  | 
|---|
| 296 | return copy_to_user(to: uarg, from: &compat, | 
|---|
| 297 | n: sizeof(struct pps_fdata_compat)) ? -EFAULT : 0; | 
|---|
| 298 | } | 
|---|
| 299 |  | 
|---|
| 300 | return pps_cdev_ioctl(file, cmd, arg); | 
|---|
| 301 | } | 
|---|
| 302 | #else | 
|---|
| 303 | #define pps_cdev_compat_ioctl	NULL | 
|---|
| 304 | #endif | 
|---|
| 305 |  | 
|---|
| 306 | static struct pps_device *pps_idr_get(unsigned long id) | 
|---|
| 307 | { | 
|---|
| 308 | struct pps_device *pps; | 
|---|
| 309 |  | 
|---|
| 310 | mutex_lock(lock: &pps_idr_lock); | 
|---|
| 311 | pps = idr_find(&pps_idr, id); | 
|---|
| 312 | if (pps) | 
|---|
| 313 | get_device(dev: &pps->dev); | 
|---|
| 314 |  | 
|---|
| 315 | mutex_unlock(lock: &pps_idr_lock); | 
|---|
| 316 | return pps; | 
|---|
| 317 | } | 
|---|
| 318 |  | 
|---|
| 319 | static int pps_cdev_open(struct inode *inode, struct file *file) | 
|---|
| 320 | { | 
|---|
| 321 | struct pps_device *pps = pps_idr_get(id: iminor(inode)); | 
|---|
| 322 |  | 
|---|
| 323 | if (!pps) | 
|---|
| 324 | return -ENODEV; | 
|---|
| 325 |  | 
|---|
| 326 | file->private_data = pps; | 
|---|
| 327 | return 0; | 
|---|
| 328 | } | 
|---|
| 329 |  | 
|---|
| 330 | static int pps_cdev_release(struct inode *inode, struct file *file) | 
|---|
| 331 | { | 
|---|
| 332 | struct pps_device *pps = file->private_data; | 
|---|
| 333 |  | 
|---|
| 334 | WARN_ON(pps->id != iminor(inode)); | 
|---|
| 335 | put_device(dev: &pps->dev); | 
|---|
| 336 | return 0; | 
|---|
| 337 | } | 
|---|
| 338 |  | 
|---|
| 339 | /* | 
|---|
| 340 | * Char device stuff | 
|---|
| 341 | */ | 
|---|
| 342 |  | 
|---|
| 343 | static const struct file_operations pps_cdev_fops = { | 
|---|
| 344 | .owner		= THIS_MODULE, | 
|---|
| 345 | .poll		= pps_cdev_poll, | 
|---|
| 346 | .fasync		= pps_cdev_fasync, | 
|---|
| 347 | .compat_ioctl	= pps_cdev_compat_ioctl, | 
|---|
| 348 | .unlocked_ioctl	= pps_cdev_ioctl, | 
|---|
| 349 | .open		= pps_cdev_open, | 
|---|
| 350 | .release	= pps_cdev_release, | 
|---|
| 351 | }; | 
|---|
| 352 |  | 
|---|
| 353 | static void pps_device_destruct(struct device *dev) | 
|---|
| 354 | { | 
|---|
| 355 | struct pps_device *pps = dev_get_drvdata(dev); | 
|---|
| 356 |  | 
|---|
| 357 | pr_debug( "deallocating pps%d\n", pps->id); | 
|---|
| 358 | kfree(objp: pps); | 
|---|
| 359 | } | 
|---|
| 360 |  | 
|---|
| 361 | int pps_register_cdev(struct pps_device *pps) | 
|---|
| 362 | { | 
|---|
| 363 | int err; | 
|---|
| 364 |  | 
|---|
| 365 | mutex_lock(lock: &pps_idr_lock); | 
|---|
| 366 | /* | 
|---|
| 367 | * Get new ID for the new PPS source.  After idr_alloc() calling | 
|---|
| 368 | * the new source will be freely available into the kernel. | 
|---|
| 369 | */ | 
|---|
| 370 | err = idr_alloc(&pps_idr, ptr: pps, start: 0, PPS_MAX_SOURCES, GFP_KERNEL); | 
|---|
| 371 | if (err < 0) { | 
|---|
| 372 | if (err == -ENOSPC) { | 
|---|
| 373 | pr_err( "%s: too many PPS sources in the system\n", | 
|---|
| 374 | pps->info.name); | 
|---|
| 375 | err = -EBUSY; | 
|---|
| 376 | } | 
|---|
| 377 | kfree(objp: pps); | 
|---|
| 378 | goto out_unlock; | 
|---|
| 379 | } | 
|---|
| 380 | pps->id = err; | 
|---|
| 381 |  | 
|---|
| 382 | pps->dev.class = pps_class; | 
|---|
| 383 | pps->dev.parent = pps->info.dev; | 
|---|
| 384 | pps->dev.devt = MKDEV(pps_major, pps->id); | 
|---|
| 385 | dev_set_drvdata(dev: &pps->dev, data: pps); | 
|---|
| 386 | dev_set_name(dev: &pps->dev, name: "pps%d", pps->id); | 
|---|
| 387 | pps->dev.release = pps_device_destruct; | 
|---|
| 388 | err = device_register(dev: &pps->dev); | 
|---|
| 389 | if (err) | 
|---|
| 390 | goto free_idr; | 
|---|
| 391 |  | 
|---|
| 392 | pr_debug( "source %s got cdev (%d:%d)\n", pps->info.name, pps_major, | 
|---|
| 393 | pps->id); | 
|---|
| 394 |  | 
|---|
| 395 | get_device(dev: &pps->dev); | 
|---|
| 396 | mutex_unlock(lock: &pps_idr_lock); | 
|---|
| 397 | return 0; | 
|---|
| 398 |  | 
|---|
| 399 | free_idr: | 
|---|
| 400 | idr_remove(&pps_idr, id: pps->id); | 
|---|
| 401 | put_device(dev: &pps->dev); | 
|---|
| 402 | out_unlock: | 
|---|
| 403 | mutex_unlock(lock: &pps_idr_lock); | 
|---|
| 404 | return err; | 
|---|
| 405 | } | 
|---|
| 406 |  | 
|---|
| 407 | void pps_unregister_cdev(struct pps_device *pps) | 
|---|
| 408 | { | 
|---|
| 409 | pr_debug( "unregistering pps%d\n", pps->id); | 
|---|
| 410 | pps->lookup_cookie = NULL; | 
|---|
| 411 | device_destroy(cls: pps_class, devt: pps->dev.devt); | 
|---|
| 412 |  | 
|---|
| 413 | /* Now we can release the ID for re-use */ | 
|---|
| 414 | mutex_lock(lock: &pps_idr_lock); | 
|---|
| 415 | idr_remove(&pps_idr, id: pps->id); | 
|---|
| 416 | put_device(dev: &pps->dev); | 
|---|
| 417 | mutex_unlock(lock: &pps_idr_lock); | 
|---|
| 418 | } | 
|---|
| 419 |  | 
|---|
| 420 | /* | 
|---|
| 421 | * Look up a pps device by magic cookie. | 
|---|
| 422 | * The cookie is usually a pointer to some enclosing device, but this | 
|---|
| 423 | * code doesn't care; you should never be dereferencing it. | 
|---|
| 424 | * | 
|---|
| 425 | * This is a bit of a kludge that is currently used only by the PPS | 
|---|
| 426 | * serial line discipline.  It may need to be tweaked when a second user | 
|---|
| 427 | * is found. | 
|---|
| 428 | * | 
|---|
| 429 | * There is no function interface for setting the lookup_cookie field. | 
|---|
| 430 | * It's initialized to NULL when the pps device is created, and if a | 
|---|
| 431 | * client wants to use it, just fill it in afterward. | 
|---|
| 432 | * | 
|---|
| 433 | * The cookie is automatically set to NULL in pps_unregister_source() | 
|---|
| 434 | * so that it will not be used again, even if the pps device cannot | 
|---|
| 435 | * be removed from the idr due to pending references holding the minor | 
|---|
| 436 | * number in use. | 
|---|
| 437 | * | 
|---|
| 438 | * Since pps_idr holds a reference to the device, the returned | 
|---|
| 439 | * pps_device is guaranteed to be valid until pps_unregister_cdev() is | 
|---|
| 440 | * called on it. But after calling pps_unregister_cdev(), it may be | 
|---|
| 441 | * freed at any time. | 
|---|
| 442 | */ | 
|---|
| 443 | struct pps_device *pps_lookup_dev(void const *cookie) | 
|---|
| 444 | { | 
|---|
| 445 | struct pps_device *pps; | 
|---|
| 446 | unsigned id; | 
|---|
| 447 |  | 
|---|
| 448 | rcu_read_lock(); | 
|---|
| 449 | idr_for_each_entry(&pps_idr, pps, id) | 
|---|
| 450 | if (cookie == pps->lookup_cookie) | 
|---|
| 451 | break; | 
|---|
| 452 | rcu_read_unlock(); | 
|---|
| 453 | return pps; | 
|---|
| 454 | } | 
|---|
| 455 | EXPORT_SYMBOL(pps_lookup_dev); | 
|---|
| 456 |  | 
|---|
| 457 | /* | 
|---|
| 458 | * Module stuff | 
|---|
| 459 | */ | 
|---|
| 460 |  | 
|---|
| 461 | static void __exit pps_exit(void) | 
|---|
| 462 | { | 
|---|
| 463 | class_destroy(cls: pps_class); | 
|---|
| 464 | __unregister_chrdev(major: pps_major, baseminor: 0, PPS_MAX_SOURCES, name: "pps"); | 
|---|
| 465 | } | 
|---|
| 466 |  | 
|---|
| 467 | static int __init pps_init(void) | 
|---|
| 468 | { | 
|---|
| 469 | pps_class = class_create(name: "pps"); | 
|---|
| 470 | if (IS_ERR(ptr: pps_class)) { | 
|---|
| 471 | pr_err( "failed to allocate class\n"); | 
|---|
| 472 | return PTR_ERR(ptr: pps_class); | 
|---|
| 473 | } | 
|---|
| 474 | pps_class->dev_groups = pps_groups; | 
|---|
| 475 |  | 
|---|
| 476 | pps_major = __register_chrdev(major: 0, baseminor: 0, PPS_MAX_SOURCES, name: "pps", | 
|---|
| 477 | fops: &pps_cdev_fops); | 
|---|
| 478 | if (pps_major < 0) { | 
|---|
| 479 | pr_err( "failed to allocate char device region\n"); | 
|---|
| 480 | goto remove_class; | 
|---|
| 481 | } | 
|---|
| 482 |  | 
|---|
| 483 | pr_info( "LinuxPPS API ver. %d registered\n", PPS_API_VERS); | 
|---|
| 484 | pr_info( "Software ver. %s - Copyright 2005-2007 Rodolfo Giometti " | 
|---|
| 485 | "<giometti@linux.it>\n", PPS_VERSION); | 
|---|
| 486 |  | 
|---|
| 487 | return 0; | 
|---|
| 488 |  | 
|---|
| 489 | remove_class: | 
|---|
| 490 | class_destroy(cls: pps_class); | 
|---|
| 491 | return pps_major; | 
|---|
| 492 | } | 
|---|
| 493 |  | 
|---|
| 494 | subsys_initcall(pps_init); | 
|---|
| 495 | module_exit(pps_exit); | 
|---|
| 496 |  | 
|---|
| 497 | MODULE_AUTHOR( "Rodolfo Giometti <giometti@linux.it>"); | 
|---|
| 498 | MODULE_DESCRIPTION( "LinuxPPS support (RFC 2783) - ver. "PPS_VERSION); | 
|---|
| 499 | MODULE_LICENSE( "GPL"); | 
|---|
| 500 |  | 
|---|