diff options
| author | Alexander Gordeev <lasaine@lvk.cs.msu.su> | 2011-01-12 20:00:58 -0500 |
|---|---|---|
| committer | Linus Torvalds <torvalds@linux-foundation.org> | 2011-01-13 11:03:21 -0500 |
| commit | 717c033669ed3ceaee8df57d4562fafcc1a6267a (patch) | |
| tree | 892e922c08b5f5eee8fbe7c8e0fdc774db660c67 /drivers/pps | |
| parent | e2c18e49a0d4f822ffc29fb4958943beb1ff08b7 (diff) | |
pps: add kernel consumer support
Add an optional feature of PPSAPI, kernel consumer support, which uses the
added hardpps() function.
Signed-off-by: Alexander Gordeev <lasaine@lvk.cs.msu.su>
Acked-by: Rodolfo Giometti <giometti@linux.it>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'drivers/pps')
| -rw-r--r-- | drivers/pps/Makefile | 1 | ||||
| -rw-r--r-- | drivers/pps/kapi.c | 6 | ||||
| -rw-r--r-- | drivers/pps/kc.c | 122 | ||||
| -rw-r--r-- | drivers/pps/kc.h | 46 | ||||
| -rw-r--r-- | drivers/pps/pps.c | 38 |
5 files changed, 212 insertions, 1 deletions
diff --git a/drivers/pps/Makefile b/drivers/pps/Makefile index 98960ddd3188..77c23457b739 100644 --- a/drivers/pps/Makefile +++ b/drivers/pps/Makefile | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | # | 3 | # |
| 4 | 4 | ||
| 5 | pps_core-y := pps.o kapi.o sysfs.o | 5 | pps_core-y := pps.o kapi.o sysfs.o |
| 6 | pps_core-$(CONFIG_NTP_PPS) += kc.o | ||
| 6 | obj-$(CONFIG_PPS) := pps_core.o | 7 | obj-$(CONFIG_PPS) := pps_core.o |
| 7 | obj-y += clients/ | 8 | obj-y += clients/ |
| 8 | 9 | ||
diff --git a/drivers/pps/kapi.c b/drivers/pps/kapi.c index ebf33746c37c..cba1b43f7519 100644 --- a/drivers/pps/kapi.c +++ b/drivers/pps/kapi.c | |||
| @@ -26,11 +26,14 @@ | |||
| 26 | #include <linux/init.h> | 26 | #include <linux/init.h> |
| 27 | #include <linux/sched.h> | 27 | #include <linux/sched.h> |
| 28 | #include <linux/time.h> | 28 | #include <linux/time.h> |
| 29 | #include <linux/timex.h> | ||
| 29 | #include <linux/spinlock.h> | 30 | #include <linux/spinlock.h> |
| 30 | #include <linux/fs.h> | 31 | #include <linux/fs.h> |
| 31 | #include <linux/pps_kernel.h> | 32 | #include <linux/pps_kernel.h> |
| 32 | #include <linux/slab.h> | 33 | #include <linux/slab.h> |
| 33 | 34 | ||
| 35 | #include "kc.h" | ||
| 36 | |||
| 34 | /* | 37 | /* |
| 35 | * Local functions | 38 | * Local functions |
| 36 | */ | 39 | */ |
| @@ -139,6 +142,7 @@ EXPORT_SYMBOL(pps_register_source); | |||
| 139 | 142 | ||
| 140 | void pps_unregister_source(struct pps_device *pps) | 143 | void pps_unregister_source(struct pps_device *pps) |
| 141 | { | 144 | { |
| 145 | pps_kc_remove(pps); | ||
| 142 | pps_unregister_cdev(pps); | 146 | pps_unregister_cdev(pps); |
| 143 | 147 | ||
| 144 | /* don't have to kfree(pps) here because it will be done on | 148 | /* don't have to kfree(pps) here because it will be done on |
| @@ -211,6 +215,8 @@ void pps_event(struct pps_device *pps, struct pps_event_time *ts, int event, | |||
| 211 | captured = ~0; | 215 | captured = ~0; |
| 212 | } | 216 | } |
| 213 | 217 | ||
| 218 | pps_kc_event(pps, ts, event); | ||
| 219 | |||
| 214 | /* Wake up if captured something */ | 220 | /* Wake up if captured something */ |
| 215 | if (captured) { | 221 | if (captured) { |
| 216 | pps->last_ev++; | 222 | pps->last_ev++; |
diff --git a/drivers/pps/kc.c b/drivers/pps/kc.c new file mode 100644 index 000000000000..079e930b1938 --- /dev/null +++ b/drivers/pps/kc.c | |||
| @@ -0,0 +1,122 @@ | |||
| 1 | /* | ||
| 2 | * PPS kernel consumer API | ||
| 3 | * | ||
| 4 | * Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su> | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or modify | ||
| 7 | * it under the terms of the GNU General Public License as published by | ||
| 8 | * the Free Software Foundation; either version 2 of the License, or | ||
| 9 | * (at your option) any later version. | ||
| 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 | * You should have received a copy of the GNU General Public License | ||
| 17 | * along with this program; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 19 | */ | ||
| 20 | |||
| 21 | #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt | ||
| 22 | |||
| 23 | #include <linux/kernel.h> | ||
| 24 | #include <linux/module.h> | ||
| 25 | #include <linux/device.h> | ||
| 26 | #include <linux/init.h> | ||
| 27 | #include <linux/spinlock.h> | ||
| 28 | #include <linux/pps_kernel.h> | ||
| 29 | |||
| 30 | #include "kc.h" | ||
| 31 | |||
| 32 | /* | ||
| 33 | * Global variables | ||
| 34 | */ | ||
| 35 | |||
| 36 | /* state variables to bind kernel consumer */ | ||
| 37 | DEFINE_SPINLOCK(pps_kc_hardpps_lock); | ||
| 38 | /* PPS API (RFC 2783): current source and mode for kernel consumer */ | ||
| 39 | struct pps_device *pps_kc_hardpps_dev; /* unique pointer to device */ | ||
| 40 | int pps_kc_hardpps_mode; /* mode bits for kernel consumer */ | ||
| 41 | |||
| 42 | /* pps_kc_bind - control PPS kernel consumer binding | ||
| 43 | * @pps: the PPS source | ||
| 44 | * @bind_args: kernel consumer bind parameters | ||
| 45 | * | ||
| 46 | * This function is used to bind or unbind PPS kernel consumer according to | ||
| 47 | * supplied parameters. Should not be called in interrupt context. | ||
| 48 | */ | ||
| 49 | int pps_kc_bind(struct pps_device *pps, struct pps_bind_args *bind_args) | ||
| 50 | { | ||
| 51 | /* Check if another consumer is already bound */ | ||
| 52 | spin_lock_irq(&pps_kc_hardpps_lock); | ||
| 53 | |||
| 54 | if (bind_args->edge == 0) | ||
| 55 | if (pps_kc_hardpps_dev == pps) { | ||
| 56 | pps_kc_hardpps_mode = 0; | ||
| 57 | pps_kc_hardpps_dev = NULL; | ||
| 58 | spin_unlock_irq(&pps_kc_hardpps_lock); | ||
| 59 | dev_info(pps->dev, "unbound kernel" | ||
| 60 | " consumer\n"); | ||
| 61 | } else { | ||
| 62 | spin_unlock_irq(&pps_kc_hardpps_lock); | ||
| 63 | dev_err(pps->dev, "selected kernel consumer" | ||
| 64 | " is not bound\n"); | ||
| 65 | return -EINVAL; | ||
| 66 | } | ||
| 67 | else | ||
| 68 | if (pps_kc_hardpps_dev == NULL || | ||
| 69 | pps_kc_hardpps_dev == pps) { | ||
| 70 | pps_kc_hardpps_mode = bind_args->edge; | ||
| 71 | pps_kc_hardpps_dev = pps; | ||
| 72 | spin_unlock_irq(&pps_kc_hardpps_lock); | ||
| 73 | dev_info(pps->dev, "bound kernel consumer: " | ||
| 74 | "edge=0x%x\n", bind_args->edge); | ||
| 75 | } else { | ||
| 76 | spin_unlock_irq(&pps_kc_hardpps_lock); | ||
| 77 | dev_err(pps->dev, "another kernel consumer" | ||
| 78 | " is already bound\n"); | ||
| 79 | return -EINVAL; | ||
| 80 | } | ||
| 81 | |||
| 82 | return 0; | ||
| 83 | } | ||
| 84 | |||
| 85 | /* pps_kc_remove - unbind kernel consumer on PPS source removal | ||
| 86 | * @pps: the PPS source | ||
| 87 | * | ||
| 88 | * This function is used to disable kernel consumer on PPS source removal | ||
| 89 | * if this source was bound to PPS kernel consumer. Can be called on any | ||
| 90 | * source safely. Should not be called in interrupt context. | ||
| 91 | */ | ||
| 92 | void pps_kc_remove(struct pps_device *pps) | ||
| 93 | { | ||
| 94 | spin_lock_irq(&pps_kc_hardpps_lock); | ||
| 95 | if (pps == pps_kc_hardpps_dev) { | ||
| 96 | pps_kc_hardpps_mode = 0; | ||
| 97 | pps_kc_hardpps_dev = NULL; | ||
| 98 | spin_unlock_irq(&pps_kc_hardpps_lock); | ||
| 99 | dev_info(pps->dev, "unbound kernel consumer" | ||
| 100 | " on device removal\n"); | ||
| 101 | } else | ||
| 102 | spin_unlock_irq(&pps_kc_hardpps_lock); | ||
| 103 | } | ||
| 104 | |||
| 105 | /* pps_kc_event - call hardpps() on PPS event | ||
| 106 | * @pps: the PPS source | ||
| 107 | * @ts: PPS event timestamp | ||
| 108 | * @event: PPS event edge | ||
| 109 | * | ||
| 110 | * This function calls hardpps() when an event from bound PPS source occurs. | ||
| 111 | */ | ||
| 112 | void pps_kc_event(struct pps_device *pps, struct pps_event_time *ts, | ||
| 113 | int event) | ||
| 114 | { | ||
| 115 | unsigned long flags; | ||
| 116 | |||
| 117 | /* Pass some events to kernel consumer if activated */ | ||
| 118 | spin_lock_irqsave(&pps_kc_hardpps_lock, flags); | ||
| 119 | if (pps == pps_kc_hardpps_dev && event & pps_kc_hardpps_mode) | ||
| 120 | hardpps(&ts->ts_real, &ts->ts_raw); | ||
| 121 | spin_unlock_irqrestore(&pps_kc_hardpps_lock, flags); | ||
| 122 | } | ||
diff --git a/drivers/pps/kc.h b/drivers/pps/kc.h new file mode 100644 index 000000000000..d296fcd0a175 --- /dev/null +++ b/drivers/pps/kc.h | |||
| @@ -0,0 +1,46 @@ | |||
| 1 | /* | ||
| 2 | * PPS kernel consumer API header | ||
| 3 | * | ||
| 4 | * Copyright (C) 2009-2010 Alexander Gordeev <lasaine@lvk.cs.msu.su> | ||
| 5 | * | ||
| 6 | * This program is free software; you can redistribute it and/or modify | ||
| 7 | * it under the terms of the GNU General Public License as published by | ||
| 8 | * the Free Software Foundation; either version 2 of the License, or | ||
| 9 | * (at your option) any later version. | ||
| 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 | * You should have received a copy of the GNU General Public License | ||
| 17 | * along with this program; if not, write to the Free Software | ||
| 18 | * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. | ||
| 19 | */ | ||
| 20 | |||
| 21 | #ifndef LINUX_PPS_KC_H | ||
| 22 | #define LINUX_PPS_KC_H | ||
| 23 | |||
| 24 | #include <linux/errno.h> | ||
| 25 | #include <linux/pps_kernel.h> | ||
| 26 | |||
| 27 | #ifdef CONFIG_NTP_PPS | ||
| 28 | |||
| 29 | extern int pps_kc_bind(struct pps_device *pps, | ||
| 30 | struct pps_bind_args *bind_args); | ||
| 31 | extern void pps_kc_remove(struct pps_device *pps); | ||
| 32 | extern void pps_kc_event(struct pps_device *pps, | ||
| 33 | struct pps_event_time *ts, int event); | ||
| 34 | |||
| 35 | |||
| 36 | #else /* CONFIG_NTP_PPS */ | ||
| 37 | |||
| 38 | static inline int pps_kc_bind(struct pps_device *pps, | ||
| 39 | struct pps_bind_args *bind_args) { return -EOPNOTSUPP; } | ||
| 40 | static inline void pps_kc_remove(struct pps_device *pps) {} | ||
| 41 | static inline void pps_kc_event(struct pps_device *pps, | ||
| 42 | struct pps_event_time *ts, int event) {} | ||
| 43 | |||
| 44 | #endif /* CONFIG_NTP_PPS */ | ||
| 45 | |||
| 46 | #endif /* LINUX_PPS_KC_H */ | ||
diff --git a/drivers/pps/pps.c b/drivers/pps/pps.c index 9e15cf1da946..2baadd21b7a6 100644 --- a/drivers/pps/pps.c +++ b/drivers/pps/pps.c | |||
| @@ -33,6 +33,8 @@ | |||
| 33 | #include <linux/pps_kernel.h> | 33 | #include <linux/pps_kernel.h> |
| 34 | #include <linux/slab.h> | 34 | #include <linux/slab.h> |
| 35 | 35 | ||
| 36 | #include "kc.h" | ||
| 37 | |||
| 36 | /* | 38 | /* |
| 37 | * Local variables | 39 | * Local variables |
| 38 | */ | 40 | */ |
| @@ -198,9 +200,43 @@ static long pps_cdev_ioctl(struct file *file, | |||
| 198 | 200 | ||
| 199 | break; | 201 | break; |
| 200 | } | 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; | ||
| 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 | } | ||
| 201 | default: | 238 | default: |
| 202 | return -ENOTTY; | 239 | return -ENOTTY; |
| 203 | break; | ||
| 204 | } | 240 | } |
| 205 | 241 | ||
| 206 | return 0; | 242 | return 0; |
