Commit | Line | Data |
---|---|---|
35728b82 | 1 | // SPDX-License-Identifier: GPL-2.0+ |
0606f422 | 2 | /* |
58c5fc2b | 3 | * Support for dynamic clock devices |
0606f422 RC |
4 | * |
5 | * Copyright (C) 2010 OMICRON electronics GmbH | |
0606f422 RC |
6 | */ |
7 | #include <linux/device.h> | |
6e5fdeed | 8 | #include <linux/export.h> |
0606f422 | 9 | #include <linux/file.h> |
0606f422 RC |
10 | #include <linux/posix-clock.h> |
11 | #include <linux/slab.h> | |
12 | #include <linux/syscalls.h> | |
13 | #include <linux/uaccess.h> | |
14 | ||
bab0aae9 TG |
15 | #include "posix-timers.h" |
16 | ||
0606f422 RC |
17 | /* |
18 | * Returns NULL if the posix_clock instance attached to 'fp' is old and stale. | |
19 | */ | |
20 | static struct posix_clock *get_posix_clock(struct file *fp) | |
21 | { | |
60c69466 XM |
22 | struct posix_clock_context *pccontext = fp->private_data; |
23 | struct posix_clock *clk = pccontext->clk; | |
0606f422 | 24 | |
1791f881 | 25 | down_read(&clk->rwsem); |
0606f422 RC |
26 | |
27 | if (!clk->zombie) | |
28 | return clk; | |
29 | ||
1791f881 | 30 | up_read(&clk->rwsem); |
0606f422 RC |
31 | |
32 | return NULL; | |
33 | } | |
34 | ||
35 | static void put_posix_clock(struct posix_clock *clk) | |
36 | { | |
1791f881 | 37 | up_read(&clk->rwsem); |
0606f422 RC |
38 | } |
39 | ||
40 | static ssize_t posix_clock_read(struct file *fp, char __user *buf, | |
41 | size_t count, loff_t *ppos) | |
42 | { | |
60c69466 | 43 | struct posix_clock_context *pccontext = fp->private_data; |
0606f422 RC |
44 | struct posix_clock *clk = get_posix_clock(fp); |
45 | int err = -EINVAL; | |
46 | ||
47 | if (!clk) | |
48 | return -ENODEV; | |
49 | ||
50 | if (clk->ops.read) | |
60c69466 | 51 | err = clk->ops.read(pccontext, fp->f_flags, buf, count); |
0606f422 RC |
52 | |
53 | put_posix_clock(clk); | |
54 | ||
55 | return err; | |
56 | } | |
57 | ||
9dd95748 | 58 | static __poll_t posix_clock_poll(struct file *fp, poll_table *wait) |
0606f422 | 59 | { |
60c69466 | 60 | struct posix_clock_context *pccontext = fp->private_data; |
0606f422 | 61 | struct posix_clock *clk = get_posix_clock(fp); |
9dd95748 | 62 | __poll_t result = 0; |
0606f422 RC |
63 | |
64 | if (!clk) | |
a9a08845 | 65 | return EPOLLERR; |
0606f422 RC |
66 | |
67 | if (clk->ops.poll) | |
60c69466 | 68 | result = clk->ops.poll(pccontext, fp, wait); |
0606f422 RC |
69 | |
70 | put_posix_clock(clk); | |
71 | ||
72 | return result; | |
73 | } | |
74 | ||
0606f422 RC |
75 | static long posix_clock_ioctl(struct file *fp, |
76 | unsigned int cmd, unsigned long arg) | |
77 | { | |
60c69466 | 78 | struct posix_clock_context *pccontext = fp->private_data; |
0606f422 RC |
79 | struct posix_clock *clk = get_posix_clock(fp); |
80 | int err = -ENOTTY; | |
81 | ||
82 | if (!clk) | |
83 | return -ENODEV; | |
84 | ||
85 | if (clk->ops.ioctl) | |
60c69466 | 86 | err = clk->ops.ioctl(pccontext, cmd, arg); |
0606f422 RC |
87 | |
88 | put_posix_clock(clk); | |
89 | ||
90 | return err; | |
91 | } | |
92 | ||
93 | #ifdef CONFIG_COMPAT | |
94 | static long posix_clock_compat_ioctl(struct file *fp, | |
95 | unsigned int cmd, unsigned long arg) | |
96 | { | |
60c69466 | 97 | struct posix_clock_context *pccontext = fp->private_data; |
0606f422 RC |
98 | struct posix_clock *clk = get_posix_clock(fp); |
99 | int err = -ENOTTY; | |
100 | ||
101 | if (!clk) | |
102 | return -ENODEV; | |
103 | ||
104 | if (clk->ops.ioctl) | |
60c69466 | 105 | err = clk->ops.ioctl(pccontext, cmd, arg); |
0606f422 RC |
106 | |
107 | put_posix_clock(clk); | |
108 | ||
109 | return err; | |
110 | } | |
111 | #endif | |
112 | ||
113 | static int posix_clock_open(struct inode *inode, struct file *fp) | |
114 | { | |
115 | int err; | |
116 | struct posix_clock *clk = | |
117 | container_of(inode->i_cdev, struct posix_clock, cdev); | |
60c69466 | 118 | struct posix_clock_context *pccontext; |
0606f422 | 119 | |
1791f881 | 120 | down_read(&clk->rwsem); |
0606f422 RC |
121 | |
122 | if (clk->zombie) { | |
123 | err = -ENODEV; | |
124 | goto out; | |
125 | } | |
60c69466 XM |
126 | pccontext = kzalloc(sizeof(*pccontext), GFP_KERNEL); |
127 | if (!pccontext) { | |
128 | err = -ENOMEM; | |
129 | goto out; | |
130 | } | |
131 | pccontext->clk = clk; | |
5b4cdd9c | 132 | if (clk->ops.open) { |
60c69466 | 133 | err = clk->ops.open(pccontext, fp->f_mode); |
5b4cdd9c LT |
134 | if (err) { |
135 | kfree(pccontext); | |
136 | goto out; | |
137 | } | |
0606f422 | 138 | } |
5b4cdd9c LT |
139 | |
140 | fp->private_data = pccontext; | |
141 | get_device(clk->dev); | |
142 | err = 0; | |
0606f422 | 143 | out: |
1791f881 | 144 | up_read(&clk->rwsem); |
0606f422 RC |
145 | return err; |
146 | } | |
147 | ||
148 | static int posix_clock_release(struct inode *inode, struct file *fp) | |
149 | { | |
60c69466 XM |
150 | struct posix_clock_context *pccontext = fp->private_data; |
151 | struct posix_clock *clk; | |
0606f422 RC |
152 | int err = 0; |
153 | ||
60c69466 XM |
154 | if (!pccontext) |
155 | return -ENODEV; | |
156 | clk = pccontext->clk; | |
157 | ||
0606f422 | 158 | if (clk->ops.release) |
60c69466 | 159 | err = clk->ops.release(pccontext); |
0606f422 | 160 | |
a33121e5 | 161 | put_device(clk->dev); |
0606f422 | 162 | |
60c69466 | 163 | kfree(pccontext); |
0606f422 RC |
164 | fp->private_data = NULL; |
165 | ||
166 | return err; | |
167 | } | |
168 | ||
169 | static const struct file_operations posix_clock_file_operations = { | |
170 | .owner = THIS_MODULE, | |
0606f422 RC |
171 | .read = posix_clock_read, |
172 | .poll = posix_clock_poll, | |
173 | .unlocked_ioctl = posix_clock_ioctl, | |
174 | .open = posix_clock_open, | |
175 | .release = posix_clock_release, | |
0606f422 RC |
176 | #ifdef CONFIG_COMPAT |
177 | .compat_ioctl = posix_clock_compat_ioctl, | |
178 | #endif | |
179 | }; | |
180 | ||
a33121e5 | 181 | int posix_clock_register(struct posix_clock *clk, struct device *dev) |
0606f422 RC |
182 | { |
183 | int err; | |
184 | ||
1791f881 | 185 | init_rwsem(&clk->rwsem); |
0606f422 RC |
186 | |
187 | cdev_init(&clk->cdev, &posix_clock_file_operations); | |
a33121e5 VD |
188 | err = cdev_device_add(&clk->cdev, dev); |
189 | if (err) { | |
190 | pr_err("%s unable to add device %d:%d\n", | |
191 | dev_name(dev), MAJOR(dev->devt), MINOR(dev->devt)); | |
192 | return err; | |
193 | } | |
0606f422 | 194 | clk->cdev.owner = clk->ops.owner; |
a33121e5 | 195 | clk->dev = dev; |
0606f422 | 196 | |
a33121e5 | 197 | return 0; |
0606f422 RC |
198 | } |
199 | EXPORT_SYMBOL_GPL(posix_clock_register); | |
200 | ||
0606f422 RC |
201 | void posix_clock_unregister(struct posix_clock *clk) |
202 | { | |
a33121e5 | 203 | cdev_device_del(&clk->cdev, clk->dev); |
0606f422 | 204 | |
1791f881 | 205 | down_write(&clk->rwsem); |
0606f422 | 206 | clk->zombie = true; |
1791f881 | 207 | up_write(&clk->rwsem); |
0606f422 | 208 | |
a33121e5 | 209 | put_device(clk->dev); |
0606f422 RC |
210 | } |
211 | EXPORT_SYMBOL_GPL(posix_clock_unregister); | |
212 | ||
213 | struct posix_clock_desc { | |
214 | struct file *fp; | |
215 | struct posix_clock *clk; | |
216 | }; | |
217 | ||
218 | static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd) | |
219 | { | |
29f1b2b0 | 220 | struct file *fp = fget(clockid_to_fd(id)); |
0606f422 RC |
221 | int err = -EINVAL; |
222 | ||
223 | if (!fp) | |
224 | return err; | |
225 | ||
226 | if (fp->f_op->open != posix_clock_open || !fp->private_data) | |
227 | goto out; | |
228 | ||
229 | cd->fp = fp; | |
230 | cd->clk = get_posix_clock(fp); | |
231 | ||
232 | err = cd->clk ? 0 : -ENODEV; | |
233 | out: | |
234 | if (err) | |
235 | fput(fp); | |
236 | return err; | |
237 | } | |
238 | ||
239 | static void put_clock_desc(struct posix_clock_desc *cd) | |
240 | { | |
241 | put_posix_clock(cd->clk); | |
242 | fput(cd->fp); | |
243 | } | |
244 | ||
ead25417 | 245 | static int pc_clock_adjtime(clockid_t id, struct __kernel_timex *tx) |
0606f422 RC |
246 | { |
247 | struct posix_clock_desc cd; | |
248 | int err; | |
249 | ||
250 | err = get_clock_desc(id, &cd); | |
251 | if (err) | |
252 | return err; | |
253 | ||
6e6823d1 TH |
254 | if ((cd.fp->f_mode & FMODE_WRITE) == 0) { |
255 | err = -EACCES; | |
256 | goto out; | |
257 | } | |
258 | ||
0606f422 RC |
259 | if (cd.clk->ops.clock_adjtime) |
260 | err = cd.clk->ops.clock_adjtime(cd.clk, tx); | |
261 | else | |
262 | err = -EOPNOTSUPP; | |
6e6823d1 | 263 | out: |
0606f422 RC |
264 | put_clock_desc(&cd); |
265 | ||
266 | return err; | |
267 | } | |
268 | ||
3c9c12f4 | 269 | static int pc_clock_gettime(clockid_t id, struct timespec64 *ts) |
0606f422 RC |
270 | { |
271 | struct posix_clock_desc cd; | |
272 | int err; | |
273 | ||
274 | err = get_clock_desc(id, &cd); | |
275 | if (err) | |
276 | return err; | |
277 | ||
3c9c12f4 DD |
278 | if (cd.clk->ops.clock_gettime) |
279 | err = cd.clk->ops.clock_gettime(cd.clk, ts); | |
0606f422 RC |
280 | else |
281 | err = -EOPNOTSUPP; | |
282 | ||
283 | put_clock_desc(&cd); | |
284 | ||
285 | return err; | |
286 | } | |
287 | ||
d2e3e0ca | 288 | static int pc_clock_getres(clockid_t id, struct timespec64 *ts) |
0606f422 RC |
289 | { |
290 | struct posix_clock_desc cd; | |
291 | int err; | |
292 | ||
293 | err = get_clock_desc(id, &cd); | |
294 | if (err) | |
295 | return err; | |
296 | ||
d2e3e0ca DD |
297 | if (cd.clk->ops.clock_getres) |
298 | err = cd.clk->ops.clock_getres(cd.clk, ts); | |
0606f422 RC |
299 | else |
300 | err = -EOPNOTSUPP; | |
301 | ||
302 | put_clock_desc(&cd); | |
303 | ||
304 | return err; | |
305 | } | |
306 | ||
0fe6afe3 | 307 | static int pc_clock_settime(clockid_t id, const struct timespec64 *ts) |
0606f422 RC |
308 | { |
309 | struct posix_clock_desc cd; | |
310 | int err; | |
311 | ||
312 | err = get_clock_desc(id, &cd); | |
313 | if (err) | |
314 | return err; | |
315 | ||
6e6823d1 TH |
316 | if ((cd.fp->f_mode & FMODE_WRITE) == 0) { |
317 | err = -EACCES; | |
318 | goto out; | |
319 | } | |
320 | ||
0606f422 | 321 | if (cd.clk->ops.clock_settime) |
0fe6afe3 | 322 | err = cd.clk->ops.clock_settime(cd.clk, ts); |
0606f422 RC |
323 | else |
324 | err = -EOPNOTSUPP; | |
6e6823d1 | 325 | out: |
0606f422 RC |
326 | put_clock_desc(&cd); |
327 | ||
328 | return err; | |
329 | } | |
330 | ||
d3ba5a9a | 331 | const struct k_clock clock_posix_dynamic = { |
819a95fe AV |
332 | .clock_getres = pc_clock_getres, |
333 | .clock_set = pc_clock_settime, | |
334 | .clock_get_timespec = pc_clock_gettime, | |
335 | .clock_adj = pc_clock_adjtime, | |
0606f422 | 336 | }; |