xref: /openbmc/linux/kernel/time/posix-clock.c (revision 82e6fdd6)
1 /*
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>
21 #include <linux/export.h>
22 #include <linux/file.h>
23 #include <linux/posix-clock.h>
24 #include <linux/slab.h>
25 #include <linux/syscalls.h>
26 #include <linux/uaccess.h>
27 
28 #include "posix-timers.h"
29 
30 static void delete_clock(struct kref *kref);
31 
32 /*
33  * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
34  */
35 static struct posix_clock *get_posix_clock(struct file *fp)
36 {
37 	struct posix_clock *clk = fp->private_data;
38 
39 	down_read(&clk->rwsem);
40 
41 	if (!clk->zombie)
42 		return clk;
43 
44 	up_read(&clk->rwsem);
45 
46 	return NULL;
47 }
48 
49 static void put_posix_clock(struct posix_clock *clk)
50 {
51 	up_read(&clk->rwsem);
52 }
53 
54 static ssize_t posix_clock_read(struct file *fp, char __user *buf,
55 				size_t count, loff_t *ppos)
56 {
57 	struct posix_clock *clk = get_posix_clock(fp);
58 	int err = -EINVAL;
59 
60 	if (!clk)
61 		return -ENODEV;
62 
63 	if (clk->ops.read)
64 		err = clk->ops.read(clk, fp->f_flags, buf, count);
65 
66 	put_posix_clock(clk);
67 
68 	return err;
69 }
70 
71 static __poll_t posix_clock_poll(struct file *fp, poll_table *wait)
72 {
73 	struct posix_clock *clk = get_posix_clock(fp);
74 	__poll_t result = 0;
75 
76 	if (!clk)
77 		return EPOLLERR;
78 
79 	if (clk->ops.poll)
80 		result = clk->ops.poll(clk, fp, wait);
81 
82 	put_posix_clock(clk);
83 
84 	return result;
85 }
86 
87 static long posix_clock_ioctl(struct file *fp,
88 			      unsigned int cmd, unsigned long arg)
89 {
90 	struct posix_clock *clk = get_posix_clock(fp);
91 	int err = -ENOTTY;
92 
93 	if (!clk)
94 		return -ENODEV;
95 
96 	if (clk->ops.ioctl)
97 		err = clk->ops.ioctl(clk, cmd, arg);
98 
99 	put_posix_clock(clk);
100 
101 	return err;
102 }
103 
104 #ifdef CONFIG_COMPAT
105 static long posix_clock_compat_ioctl(struct file *fp,
106 				     unsigned int cmd, unsigned long arg)
107 {
108 	struct posix_clock *clk = get_posix_clock(fp);
109 	int err = -ENOTTY;
110 
111 	if (!clk)
112 		return -ENODEV;
113 
114 	if (clk->ops.ioctl)
115 		err = clk->ops.ioctl(clk, cmd, arg);
116 
117 	put_posix_clock(clk);
118 
119 	return err;
120 }
121 #endif
122 
123 static int posix_clock_open(struct inode *inode, struct file *fp)
124 {
125 	int err;
126 	struct posix_clock *clk =
127 		container_of(inode->i_cdev, struct posix_clock, cdev);
128 
129 	down_read(&clk->rwsem);
130 
131 	if (clk->zombie) {
132 		err = -ENODEV;
133 		goto out;
134 	}
135 	if (clk->ops.open)
136 		err = clk->ops.open(clk, fp->f_mode);
137 	else
138 		err = 0;
139 
140 	if (!err) {
141 		kref_get(&clk->kref);
142 		fp->private_data = clk;
143 	}
144 out:
145 	up_read(&clk->rwsem);
146 	return err;
147 }
148 
149 static int posix_clock_release(struct inode *inode, struct file *fp)
150 {
151 	struct posix_clock *clk = fp->private_data;
152 	int err = 0;
153 
154 	if (clk->ops.release)
155 		err = clk->ops.release(clk);
156 
157 	kref_put(&clk->kref, delete_clock);
158 
159 	fp->private_data = NULL;
160 
161 	return err;
162 }
163 
164 static const struct file_operations posix_clock_file_operations = {
165 	.owner		= THIS_MODULE,
166 	.llseek		= no_llseek,
167 	.read		= posix_clock_read,
168 	.poll		= posix_clock_poll,
169 	.unlocked_ioctl	= posix_clock_ioctl,
170 	.open		= posix_clock_open,
171 	.release	= posix_clock_release,
172 #ifdef CONFIG_COMPAT
173 	.compat_ioctl	= posix_clock_compat_ioctl,
174 #endif
175 };
176 
177 int posix_clock_register(struct posix_clock *clk, dev_t devid)
178 {
179 	int err;
180 
181 	kref_init(&clk->kref);
182 	init_rwsem(&clk->rwsem);
183 
184 	cdev_init(&clk->cdev, &posix_clock_file_operations);
185 	clk->cdev.owner = clk->ops.owner;
186 	err = cdev_add(&clk->cdev, devid, 1);
187 
188 	return err;
189 }
190 EXPORT_SYMBOL_GPL(posix_clock_register);
191 
192 static void delete_clock(struct kref *kref)
193 {
194 	struct posix_clock *clk = container_of(kref, struct posix_clock, kref);
195 
196 	if (clk->release)
197 		clk->release(clk);
198 }
199 
200 void posix_clock_unregister(struct posix_clock *clk)
201 {
202 	cdev_del(&clk->cdev);
203 
204 	down_write(&clk->rwsem);
205 	clk->zombie = true;
206 	up_write(&clk->rwsem);
207 
208 	kref_put(&clk->kref, delete_clock);
209 }
210 EXPORT_SYMBOL_GPL(posix_clock_unregister);
211 
212 struct posix_clock_desc {
213 	struct file *fp;
214 	struct posix_clock *clk;
215 };
216 
217 static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd)
218 {
219 	struct file *fp = fget(clockid_to_fd(id));
220 	int err = -EINVAL;
221 
222 	if (!fp)
223 		return err;
224 
225 	if (fp->f_op->open != posix_clock_open || !fp->private_data)
226 		goto out;
227 
228 	cd->fp = fp;
229 	cd->clk = get_posix_clock(fp);
230 
231 	err = cd->clk ? 0 : -ENODEV;
232 out:
233 	if (err)
234 		fput(fp);
235 	return err;
236 }
237 
238 static void put_clock_desc(struct posix_clock_desc *cd)
239 {
240 	put_posix_clock(cd->clk);
241 	fput(cd->fp);
242 }
243 
244 static int pc_clock_adjtime(clockid_t id, struct timex *tx)
245 {
246 	struct posix_clock_desc cd;
247 	int err;
248 
249 	err = get_clock_desc(id, &cd);
250 	if (err)
251 		return err;
252 
253 	if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
254 		err = -EACCES;
255 		goto out;
256 	}
257 
258 	if (cd.clk->ops.clock_adjtime)
259 		err = cd.clk->ops.clock_adjtime(cd.clk, tx);
260 	else
261 		err = -EOPNOTSUPP;
262 out:
263 	put_clock_desc(&cd);
264 
265 	return err;
266 }
267 
268 static int pc_clock_gettime(clockid_t id, struct timespec64 *ts)
269 {
270 	struct posix_clock_desc cd;
271 	int err;
272 
273 	err = get_clock_desc(id, &cd);
274 	if (err)
275 		return err;
276 
277 	if (cd.clk->ops.clock_gettime)
278 		err = cd.clk->ops.clock_gettime(cd.clk, ts);
279 	else
280 		err = -EOPNOTSUPP;
281 
282 	put_clock_desc(&cd);
283 
284 	return err;
285 }
286 
287 static int pc_clock_getres(clockid_t id, struct timespec64 *ts)
288 {
289 	struct posix_clock_desc cd;
290 	int err;
291 
292 	err = get_clock_desc(id, &cd);
293 	if (err)
294 		return err;
295 
296 	if (cd.clk->ops.clock_getres)
297 		err = cd.clk->ops.clock_getres(cd.clk, ts);
298 	else
299 		err = -EOPNOTSUPP;
300 
301 	put_clock_desc(&cd);
302 
303 	return err;
304 }
305 
306 static int pc_clock_settime(clockid_t id, const struct timespec64 *ts)
307 {
308 	struct posix_clock_desc cd;
309 	int err;
310 
311 	err = get_clock_desc(id, &cd);
312 	if (err)
313 		return err;
314 
315 	if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
316 		err = -EACCES;
317 		goto out;
318 	}
319 
320 	if (cd.clk->ops.clock_settime)
321 		err = cd.clk->ops.clock_settime(cd.clk, ts);
322 	else
323 		err = -EOPNOTSUPP;
324 out:
325 	put_clock_desc(&cd);
326 
327 	return err;
328 }
329 
330 const struct k_clock clock_posix_dynamic = {
331 	.clock_getres	= pc_clock_getres,
332 	.clock_set	= pc_clock_settime,
333 	.clock_get	= pc_clock_gettime,
334 	.clock_adj	= pc_clock_adjtime,
335 };
336