Commit 717c033669ed3ceaee8df57d4562fafcc1a6267a
Committed by
Linus Torvalds
1 parent
e2c18e49a0
Exists in
master
and in
20 other branches
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>
Showing 7 changed files with 220 additions and 2 deletions Side-by-side Diff
Documentation/ioctl/ioctl-number.txt
... | ... | @@ -247,7 +247,7 @@ |
247 | 247 | 'p' 40-7F linux/nvram.h |
248 | 248 | 'p' 80-9F linux/ppdev.h user-space parport |
249 | 249 | <mailto:tim@cyberelk.net> |
250 | -'p' A1-A4 linux/pps.h LinuxPPS | |
250 | +'p' A1-A5 linux/pps.h LinuxPPS | |
251 | 251 | <mailto:giometti@linux.it> |
252 | 252 | 'q' 00-1F linux/serio.h |
253 | 253 | 'q' 80-FF linux/telephony.h Internet PhoneJACK, Internet LineJACK |
drivers/pps/Makefile
drivers/pps/kapi.c
... | ... | @@ -26,11 +26,14 @@ |
26 | 26 | #include <linux/init.h> |
27 | 27 | #include <linux/sched.h> |
28 | 28 | #include <linux/time.h> |
29 | +#include <linux/timex.h> | |
29 | 30 | #include <linux/spinlock.h> |
30 | 31 | #include <linux/fs.h> |
31 | 32 | #include <linux/pps_kernel.h> |
32 | 33 | #include <linux/slab.h> |
33 | 34 | |
35 | +#include "kc.h" | |
36 | + | |
34 | 37 | /* |
35 | 38 | * Local functions |
36 | 39 | */ |
... | ... | @@ -139,6 +142,7 @@ |
139 | 142 | |
140 | 143 | void pps_unregister_source(struct pps_device *pps) |
141 | 144 | { |
145 | + pps_kc_remove(pps); | |
142 | 146 | pps_unregister_cdev(pps); |
143 | 147 | |
144 | 148 | /* don't have to kfree(pps) here because it will be done on |
... | ... | @@ -210,6 +214,8 @@ |
210 | 214 | |
211 | 215 | captured = ~0; |
212 | 216 | } |
217 | + | |
218 | + pps_kc_event(pps, ts, event); | |
213 | 219 | |
214 | 220 | /* Wake up if captured something */ |
215 | 221 | if (captured) { |
drivers/pps/kc.c
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 | +} |
drivers/pps/kc.h
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 */ |
drivers/pps/pps.c
... | ... | @@ -33,6 +33,8 @@ |
33 | 33 | #include <linux/pps_kernel.h> |
34 | 34 | #include <linux/slab.h> |
35 | 35 | |
36 | +#include "kc.h" | |
37 | + | |
36 | 38 | /* |
37 | 39 | * Local variables |
38 | 40 | */ |
39 | 41 | |
... | ... | @@ -198,9 +200,43 @@ |
198 | 200 | |
199 | 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 | 238 | default: |
202 | 239 | return -ENOTTY; |
203 | - break; | |
204 | 240 | } |
205 | 241 | |
206 | 242 | return 0; |
include/linux/pps.h
... | ... | @@ -114,12 +114,19 @@ |
114 | 114 | struct pps_ktime timeout; |
115 | 115 | }; |
116 | 116 | |
117 | +struct pps_bind_args { | |
118 | + int tsformat; /* format of time stamps */ | |
119 | + int edge; /* selected event type */ | |
120 | + int consumer; /* selected kernel consumer */ | |
121 | +}; | |
122 | + | |
117 | 123 | #include <linux/ioctl.h> |
118 | 124 | |
119 | 125 | #define PPS_GETPARAMS _IOR('p', 0xa1, struct pps_kparams *) |
120 | 126 | #define PPS_SETPARAMS _IOW('p', 0xa2, struct pps_kparams *) |
121 | 127 | #define PPS_GETCAP _IOR('p', 0xa3, int *) |
122 | 128 | #define PPS_FETCH _IOWR('p', 0xa4, struct pps_fdata *) |
129 | +#define PPS_KC_BIND _IOW('p', 0xa5, struct pps_bind_args *) | |
123 | 130 | |
124 | 131 | #endif /* _PPS_H_ */ |