blob: 45a0a26023d4bcb9f064301d18886a31e17dd474 [file] [log] [blame]
Richard Cochran0606f422011-02-01 13:52:35 +00001/*
2 * posix-clock.c - support for dynamic clock devices
3 *
4 * Copyright (C) 2010 OMICRON electronics GmbH
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#include <linux/device.h>
Paul Gortmaker6e5fdee2011-05-26 16:00:52 -040021#include <linux/export.h>
Richard Cochran0606f422011-02-01 13:52:35 +000022#include <linux/file.h>
Richard Cochran0606f422011-02-01 13:52:35 +000023#include <linux/posix-clock.h>
24#include <linux/slab.h>
25#include <linux/syscalls.h>
26#include <linux/uaccess.h>
27
Richard Cochran0606f422011-02-01 13:52:35 +000028/*
29 * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
30 */
31static struct posix_clock *get_posix_clock(struct file *fp)
32{
33 struct posix_clock *clk = fp->private_data;
34
Richard Cochran1791f882011-03-30 15:24:21 +020035 down_read(&clk->rwsem);
Richard Cochran0606f422011-02-01 13:52:35 +000036
37 if (!clk->zombie)
38 return clk;
39
Richard Cochran1791f882011-03-30 15:24:21 +020040 up_read(&clk->rwsem);
Richard Cochran0606f422011-02-01 13:52:35 +000041
42 return NULL;
43}
44
45static void put_posix_clock(struct posix_clock *clk)
46{
Richard Cochran1791f882011-03-30 15:24:21 +020047 up_read(&clk->rwsem);
Richard Cochran0606f422011-02-01 13:52:35 +000048}
49
50static ssize_t posix_clock_read(struct file *fp, char __user *buf,
51 size_t count, loff_t *ppos)
52{
53 struct posix_clock *clk = get_posix_clock(fp);
54 int err = -EINVAL;
55
56 if (!clk)
57 return -ENODEV;
58
59 if (clk->ops.read)
60 err = clk->ops.read(clk, fp->f_flags, buf, count);
61
62 put_posix_clock(clk);
63
64 return err;
65}
66
67static unsigned int posix_clock_poll(struct file *fp, poll_table *wait)
68{
69 struct posix_clock *clk = get_posix_clock(fp);
Richard Cochran1b9f2372015-12-22 22:19:58 +010070 unsigned int result = 0;
Richard Cochran0606f422011-02-01 13:52:35 +000071
72 if (!clk)
Richard Cochran1b9f2372015-12-22 22:19:58 +010073 return POLLERR;
Richard Cochran0606f422011-02-01 13:52:35 +000074
75 if (clk->ops.poll)
76 result = clk->ops.poll(clk, fp, wait);
77
78 put_posix_clock(clk);
79
80 return result;
81}
82
83static int posix_clock_fasync(int fd, struct file *fp, int on)
84{
85 struct posix_clock *clk = get_posix_clock(fp);
86 int err = 0;
87
88 if (!clk)
89 return -ENODEV;
90
91 if (clk->ops.fasync)
92 err = clk->ops.fasync(clk, fd, fp, on);
93
94 put_posix_clock(clk);
95
96 return err;
97}
98
99static int posix_clock_mmap(struct file *fp, struct vm_area_struct *vma)
100{
101 struct posix_clock *clk = get_posix_clock(fp);
102 int err = -ENODEV;
103
104 if (!clk)
105 return -ENODEV;
106
107 if (clk->ops.mmap)
108 err = clk->ops.mmap(clk, vma);
109
110 put_posix_clock(clk);
111
112 return err;
113}
114
115static long posix_clock_ioctl(struct file *fp,
116 unsigned int cmd, unsigned long arg)
117{
118 struct posix_clock *clk = get_posix_clock(fp);
119 int err = -ENOTTY;
120
121 if (!clk)
122 return -ENODEV;
123
124 if (clk->ops.ioctl)
125 err = clk->ops.ioctl(clk, cmd, arg);
126
127 put_posix_clock(clk);
128
129 return err;
130}
131
132#ifdef CONFIG_COMPAT
133static long posix_clock_compat_ioctl(struct file *fp,
134 unsigned int cmd, unsigned long arg)
135{
136 struct posix_clock *clk = get_posix_clock(fp);
137 int err = -ENOTTY;
138
139 if (!clk)
140 return -ENODEV;
141
142 if (clk->ops.ioctl)
143 err = clk->ops.ioctl(clk, cmd, arg);
144
145 put_posix_clock(clk);
146
147 return err;
148}
149#endif
150
151static int posix_clock_open(struct inode *inode, struct file *fp)
152{
153 int err;
154 struct posix_clock *clk =
155 container_of(inode->i_cdev, struct posix_clock, cdev);
156
Richard Cochran1791f882011-03-30 15:24:21 +0200157 down_read(&clk->rwsem);
Richard Cochran0606f422011-02-01 13:52:35 +0000158
159 if (clk->zombie) {
160 err = -ENODEV;
161 goto out;
162 }
163 if (clk->ops.open)
164 err = clk->ops.open(clk, fp->f_mode);
165 else
166 err = 0;
167
168 if (!err) {
Vladis Dronov89e8fc92019-12-27 03:26:27 +0100169 get_device(clk->dev);
Richard Cochran0606f422011-02-01 13:52:35 +0000170 fp->private_data = clk;
171 }
172out:
Richard Cochran1791f882011-03-30 15:24:21 +0200173 up_read(&clk->rwsem);
Richard Cochran0606f422011-02-01 13:52:35 +0000174 return err;
175}
176
177static int posix_clock_release(struct inode *inode, struct file *fp)
178{
179 struct posix_clock *clk = fp->private_data;
180 int err = 0;
181
182 if (clk->ops.release)
183 err = clk->ops.release(clk);
184
Vladis Dronov89e8fc92019-12-27 03:26:27 +0100185 put_device(clk->dev);
Richard Cochran0606f422011-02-01 13:52:35 +0000186
187 fp->private_data = NULL;
188
189 return err;
190}
191
192static const struct file_operations posix_clock_file_operations = {
193 .owner = THIS_MODULE,
194 .llseek = no_llseek,
195 .read = posix_clock_read,
196 .poll = posix_clock_poll,
197 .unlocked_ioctl = posix_clock_ioctl,
198 .open = posix_clock_open,
199 .release = posix_clock_release,
200 .fasync = posix_clock_fasync,
201 .mmap = posix_clock_mmap,
202#ifdef CONFIG_COMPAT
203 .compat_ioctl = posix_clock_compat_ioctl,
204#endif
205};
206
Vladis Dronov89e8fc92019-12-27 03:26:27 +0100207int posix_clock_register(struct posix_clock *clk, struct device *dev)
Richard Cochran0606f422011-02-01 13:52:35 +0000208{
209 int err;
210
Richard Cochran1791f882011-03-30 15:24:21 +0200211 init_rwsem(&clk->rwsem);
Richard Cochran0606f422011-02-01 13:52:35 +0000212
213 cdev_init(&clk->cdev, &posix_clock_file_operations);
Vladis Dronov89e8fc92019-12-27 03:26:27 +0100214 err = cdev_device_add(&clk->cdev, dev);
215 if (err) {
216 pr_err("%s unable to add device %d:%d\n",
217 dev_name(dev), MAJOR(dev->devt), MINOR(dev->devt));
218 return err;
219 }
Richard Cochran0606f422011-02-01 13:52:35 +0000220 clk->cdev.owner = clk->ops.owner;
Vladis Dronov89e8fc92019-12-27 03:26:27 +0100221 clk->dev = dev;
Richard Cochran0606f422011-02-01 13:52:35 +0000222
Vladis Dronov89e8fc92019-12-27 03:26:27 +0100223 return 0;
Richard Cochran0606f422011-02-01 13:52:35 +0000224}
225EXPORT_SYMBOL_GPL(posix_clock_register);
226
Richard Cochran0606f422011-02-01 13:52:35 +0000227void posix_clock_unregister(struct posix_clock *clk)
228{
Vladis Dronov89e8fc92019-12-27 03:26:27 +0100229 cdev_device_del(&clk->cdev, clk->dev);
Richard Cochran0606f422011-02-01 13:52:35 +0000230
Richard Cochran1791f882011-03-30 15:24:21 +0200231 down_write(&clk->rwsem);
Richard Cochran0606f422011-02-01 13:52:35 +0000232 clk->zombie = true;
Richard Cochran1791f882011-03-30 15:24:21 +0200233 up_write(&clk->rwsem);
Richard Cochran0606f422011-02-01 13:52:35 +0000234
Vladis Dronov89e8fc92019-12-27 03:26:27 +0100235 put_device(clk->dev);
Richard Cochran0606f422011-02-01 13:52:35 +0000236}
237EXPORT_SYMBOL_GPL(posix_clock_unregister);
238
239struct posix_clock_desc {
240 struct file *fp;
241 struct posix_clock *clk;
242};
243
244static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd)
245{
246 struct file *fp = fget(CLOCKID_TO_FD(id));
247 int err = -EINVAL;
248
249 if (!fp)
250 return err;
251
252 if (fp->f_op->open != posix_clock_open || !fp->private_data)
253 goto out;
254
255 cd->fp = fp;
256 cd->clk = get_posix_clock(fp);
257
258 err = cd->clk ? 0 : -ENODEV;
259out:
260 if (err)
261 fput(fp);
262 return err;
263}
264
265static void put_clock_desc(struct posix_clock_desc *cd)
266{
267 put_posix_clock(cd->clk);
268 fput(cd->fp);
269}
270
271static int pc_clock_adjtime(clockid_t id, struct timex *tx)
272{
273 struct posix_clock_desc cd;
274 int err;
275
276 err = get_clock_desc(id, &cd);
277 if (err)
278 return err;
279
Torben Hohn6e6823d2011-03-03 18:26:14 +0100280 if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
281 err = -EACCES;
282 goto out;
283 }
284
Richard Cochran0606f422011-02-01 13:52:35 +0000285 if (cd.clk->ops.clock_adjtime)
286 err = cd.clk->ops.clock_adjtime(cd.clk, tx);
287 else
288 err = -EOPNOTSUPP;
Torben Hohn6e6823d2011-03-03 18:26:14 +0100289out:
Richard Cochran0606f422011-02-01 13:52:35 +0000290 put_clock_desc(&cd);
291
292 return err;
293}
294
295static int pc_clock_gettime(clockid_t id, struct timespec *ts)
296{
297 struct posix_clock_desc cd;
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700298 struct timespec64 ts64;
Richard Cochran0606f422011-02-01 13:52:35 +0000299 int err;
300
301 err = get_clock_desc(id, &cd);
302 if (err)
303 return err;
304
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700305 if (cd.clk->ops.clock_gettime) {
306 err = cd.clk->ops.clock_gettime(cd.clk, &ts64);
307 *ts = timespec64_to_timespec(ts64);
308 }
Richard Cochran0606f422011-02-01 13:52:35 +0000309 else
310 err = -EOPNOTSUPP;
311
312 put_clock_desc(&cd);
313
314 return err;
315}
316
317static int pc_clock_getres(clockid_t id, struct timespec *ts)
318{
319 struct posix_clock_desc cd;
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700320 struct timespec64 ts64;
Richard Cochran0606f422011-02-01 13:52:35 +0000321 int err;
322
323 err = get_clock_desc(id, &cd);
324 if (err)
325 return err;
326
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700327 if (cd.clk->ops.clock_getres) {
328 err = cd.clk->ops.clock_getres(cd.clk, &ts64);
329 *ts = timespec64_to_timespec(ts64);
330 }
Richard Cochran0606f422011-02-01 13:52:35 +0000331 else
332 err = -EOPNOTSUPP;
333
334 put_clock_desc(&cd);
335
336 return err;
337}
338
339static int pc_clock_settime(clockid_t id, const struct timespec *ts)
340{
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700341 struct timespec64 ts64 = timespec_to_timespec64(*ts);
Richard Cochran0606f422011-02-01 13:52:35 +0000342 struct posix_clock_desc cd;
343 int err;
344
345 err = get_clock_desc(id, &cd);
346 if (err)
347 return err;
348
Torben Hohn6e6823d2011-03-03 18:26:14 +0100349 if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
350 err = -EACCES;
351 goto out;
352 }
353
Richard Cochran0606f422011-02-01 13:52:35 +0000354 if (cd.clk->ops.clock_settime)
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700355 err = cd.clk->ops.clock_settime(cd.clk, &ts64);
Richard Cochran0606f422011-02-01 13:52:35 +0000356 else
357 err = -EOPNOTSUPP;
Torben Hohn6e6823d2011-03-03 18:26:14 +0100358out:
Richard Cochran0606f422011-02-01 13:52:35 +0000359 put_clock_desc(&cd);
360
361 return err;
362}
363
364static int pc_timer_create(struct k_itimer *kit)
365{
366 clockid_t id = kit->it_clock;
367 struct posix_clock_desc cd;
368 int err;
369
370 err = get_clock_desc(id, &cd);
371 if (err)
372 return err;
373
374 if (cd.clk->ops.timer_create)
375 err = cd.clk->ops.timer_create(cd.clk, kit);
376 else
377 err = -EOPNOTSUPP;
378
379 put_clock_desc(&cd);
380
381 return err;
382}
383
384static int pc_timer_delete(struct k_itimer *kit)
385{
386 clockid_t id = kit->it_clock;
387 struct posix_clock_desc cd;
388 int err;
389
390 err = get_clock_desc(id, &cd);
391 if (err)
392 return err;
393
394 if (cd.clk->ops.timer_delete)
395 err = cd.clk->ops.timer_delete(cd.clk, kit);
396 else
397 err = -EOPNOTSUPP;
398
399 put_clock_desc(&cd);
400
401 return err;
402}
403
404static void pc_timer_gettime(struct k_itimer *kit, struct itimerspec *ts)
405{
406 clockid_t id = kit->it_clock;
407 struct posix_clock_desc cd;
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700408 struct itimerspec64 ts64;
Richard Cochran0606f422011-02-01 13:52:35 +0000409
410 if (get_clock_desc(id, &cd))
411 return;
412
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700413 if (cd.clk->ops.timer_gettime) {
414 cd.clk->ops.timer_gettime(cd.clk, kit, &ts64);
415 *ts = itimerspec64_to_itimerspec(&ts64);
416 }
Richard Cochran0606f422011-02-01 13:52:35 +0000417 put_clock_desc(&cd);
418}
419
420static int pc_timer_settime(struct k_itimer *kit, int flags,
421 struct itimerspec *ts, struct itimerspec *old)
422{
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700423 struct itimerspec64 ts64 = itimerspec_to_itimerspec64(ts);
Richard Cochran0606f422011-02-01 13:52:35 +0000424 clockid_t id = kit->it_clock;
425 struct posix_clock_desc cd;
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700426 struct itimerspec64 old64;
Richard Cochran0606f422011-02-01 13:52:35 +0000427 int err;
428
429 err = get_clock_desc(id, &cd);
430 if (err)
431 return err;
432
Deepa Dinamani58e7fd92017-03-26 12:04:13 -0700433 if (cd.clk->ops.timer_settime) {
434 err = cd.clk->ops.timer_settime(cd.clk, kit, flags, &ts64, &old64);
435 if (old)
436 *old = itimerspec64_to_itimerspec(&old64);
437 }
Richard Cochran0606f422011-02-01 13:52:35 +0000438 else
439 err = -EOPNOTSUPP;
440
441 put_clock_desc(&cd);
442
443 return err;
444}
445
446struct k_clock clock_posix_dynamic = {
447 .clock_getres = pc_clock_getres,
448 .clock_set = pc_clock_settime,
449 .clock_get = pc_clock_gettime,
450 .clock_adj = pc_clock_adjtime,
451 .timer_create = pc_timer_create,
452 .timer_set = pc_timer_settime,
453 .timer_del = pc_timer_delete,
454 .timer_get = pc_timer_gettime,
455};