xref: /openbmc/linux/kernel/time/posix-clock.c (revision 35728b82)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Support for dynamic clock devices
4  *
5  * Copyright (C) 2010 OMICRON electronics GmbH
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software
19  *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
20  */
21 #include <linux/device.h>
22 #include <linux/export.h>
23 #include <linux/file.h>
24 #include <linux/posix-clock.h>
25 #include <linux/slab.h>
26 #include <linux/syscalls.h>
27 #include <linux/uaccess.h>
28 
29 #include "posix-timers.h"
30 
31 static void delete_clock(struct kref *kref);
32 
33 /*
34  * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
35  */
36 static struct posix_clock *get_posix_clock(struct file *fp)
37 {
38 	struct posix_clock *clk = fp->private_data;
39 
40 	down_read(&clk->rwsem);
41 
42 	if (!clk->zombie)
43 		return clk;
44 
45 	up_read(&clk->rwsem);
46 
47 	return NULL;
48 }
49 
50 static void put_posix_clock(struct posix_clock *clk)
51 {
52 	up_read(&clk->rwsem);
53 }
54 
55 static ssize_t posix_clock_read(struct file *fp, char __user *buf,
56 				size_t count, loff_t *ppos)
57 {
58 	struct posix_clock *clk = get_posix_clock(fp);
59 	int err = -EINVAL;
60 
61 	if (!clk)
62 		return -ENODEV;
63 
64 	if (clk->ops.read)
65 		err = clk->ops.read(clk, fp->f_flags, buf, count);
66 
67 	put_posix_clock(clk);
68 
69 	return err;
70 }
71 
72 static __poll_t posix_clock_poll(struct file *fp, poll_table *wait)
73 {
74 	struct posix_clock *clk = get_posix_clock(fp);
75 	__poll_t result = 0;
76 
77 	if (!clk)
78 		return EPOLLERR;
79 
80 	if (clk->ops.poll)
81 		result = clk->ops.poll(clk, fp, wait);
82 
83 	put_posix_clock(clk);
84 
85 	return result;
86 }
87 
88 static long posix_clock_ioctl(struct file *fp,
89 			      unsigned int cmd, unsigned long arg)
90 {
91 	struct posix_clock *clk = get_posix_clock(fp);
92 	int err = -ENOTTY;
93 
94 	if (!clk)
95 		return -ENODEV;
96 
97 	if (clk->ops.ioctl)
98 		err = clk->ops.ioctl(clk, cmd, arg);
99 
100 	put_posix_clock(clk);
101 
102 	return err;
103 }
104 
105 #ifdef CONFIG_COMPAT
106 static long posix_clock_compat_ioctl(struct file *fp,
107 				     unsigned int cmd, unsigned long arg)
108 {
109 	struct posix_clock *clk = get_posix_clock(fp);
110 	int err = -ENOTTY;
111 
112 	if (!clk)
113 		return -ENODEV;
114 
115 	if (clk->ops.ioctl)
116 		err = clk->ops.ioctl(clk, cmd, arg);
117 
118 	put_posix_clock(clk);
119 
120 	return err;
121 }
122 #endif
123 
124 static int posix_clock_open(struct inode *inode, struct file *fp)
125 {
126 	int err;
127 	struct posix_clock *clk =
128 		container_of(inode->i_cdev, struct posix_clock, cdev);
129 
130 	down_read(&clk->rwsem);
131 
132 	if (clk->zombie) {
133 		err = -ENODEV;
134 		goto out;
135 	}
136 	if (clk->ops.open)
137 		err = clk->ops.open(clk, fp->f_mode);
138 	else
139 		err = 0;
140 
141 	if (!err) {
142 		kref_get(&clk->kref);
143 		fp->private_data = clk;
144 	}
145 out:
146 	up_read(&clk->rwsem);
147 	return err;
148 }
149 
150 static int posix_clock_release(struct inode *inode, struct file *fp)
151 {
152 	struct posix_clock *clk = fp->private_data;
153 	int err = 0;
154 
155 	if (clk->ops.release)
156 		err = clk->ops.release(clk);
157 
158 	kref_put(&clk->kref, delete_clock);
159 
160 	fp->private_data = NULL;
161 
162 	return err;
163 }
164 
165 static const struct file_operations posix_clock_file_operations = {
166 	.owner		= THIS_MODULE,
167 	.llseek		= no_llseek,
168 	.read		= posix_clock_read,
169 	.poll		= posix_clock_poll,
170 	.unlocked_ioctl	= posix_clock_ioctl,
171 	.open		= posix_clock_open,
172 	.release	= posix_clock_release,
173 #ifdef CONFIG_COMPAT
174 	.compat_ioctl	= posix_clock_compat_ioctl,
175 #endif
176 };
177 
178 int posix_clock_register(struct posix_clock *clk, dev_t devid)
179 {
180 	int err;
181 
182 	kref_init(&clk->kref);
183 	init_rwsem(&clk->rwsem);
184 
185 	cdev_init(&clk->cdev, &posix_clock_file_operations);
186 	clk->cdev.owner = clk->ops.owner;
187 	err = cdev_add(&clk->cdev, devid, 1);
188 
189 	return err;
190 }
191 EXPORT_SYMBOL_GPL(posix_clock_register);
192 
193 static void delete_clock(struct kref *kref)
194 {
195 	struct posix_clock *clk = container_of(kref, struct posix_clock, kref);
196 
197 	if (clk->release)
198 		clk->release(clk);
199 }
200 
201 void posix_clock_unregister(struct posix_clock *clk)
202 {
203 	cdev_del(&clk->cdev);
204 
205 	down_write(&clk->rwsem);
206 	clk->zombie = true;
207 	up_write(&clk->rwsem);
208 
209 	kref_put(&clk->kref, delete_clock);
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 {
220 	struct file *fp = fget(clockid_to_fd(id));
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 
245 static int pc_clock_adjtime(clockid_t id, struct timex *tx)
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 
254 	if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
255 		err = -EACCES;
256 		goto out;
257 	}
258 
259 	if (cd.clk->ops.clock_adjtime)
260 		err = cd.clk->ops.clock_adjtime(cd.clk, tx);
261 	else
262 		err = -EOPNOTSUPP;
263 out:
264 	put_clock_desc(&cd);
265 
266 	return err;
267 }
268 
269 static int pc_clock_gettime(clockid_t id, struct timespec64 *ts)
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 
278 	if (cd.clk->ops.clock_gettime)
279 		err = cd.clk->ops.clock_gettime(cd.clk, ts);
280 	else
281 		err = -EOPNOTSUPP;
282 
283 	put_clock_desc(&cd);
284 
285 	return err;
286 }
287 
288 static int pc_clock_getres(clockid_t id, struct timespec64 *ts)
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 
297 	if (cd.clk->ops.clock_getres)
298 		err = cd.clk->ops.clock_getres(cd.clk, ts);
299 	else
300 		err = -EOPNOTSUPP;
301 
302 	put_clock_desc(&cd);
303 
304 	return err;
305 }
306 
307 static int pc_clock_settime(clockid_t id, const struct timespec64 *ts)
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 
316 	if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
317 		err = -EACCES;
318 		goto out;
319 	}
320 
321 	if (cd.clk->ops.clock_settime)
322 		err = cd.clk->ops.clock_settime(cd.clk, ts);
323 	else
324 		err = -EOPNOTSUPP;
325 out:
326 	put_clock_desc(&cd);
327 
328 	return err;
329 }
330 
331 const struct k_clock clock_posix_dynamic = {
332 	.clock_getres	= pc_clock_getres,
333 	.clock_set	= pc_clock_settime,
334 	.clock_get	= pc_clock_gettime,
335 	.clock_adj	= pc_clock_adjtime,
336 };
337