xref: /linux/kernel/time/posix-clock.c (revision d44cd8226449114780a8554fd253c7e3d171a0a6)
1 // SPDX-License-Identifier: GPL-2.0+
2 /*
3  * Support for dynamic clock devices
4  *
5  * Copyright (C) 2010 OMICRON electronics GmbH
6  */
7 #include <linux/device.h>
8 #include <linux/export.h>
9 #include <linux/file.h>
10 #include <linux/posix-clock.h>
11 #include <linux/slab.h>
12 #include <linux/syscalls.h>
13 #include <linux/uaccess.h>
14 
15 #include "posix-timers.h"
16 
17 /*
18  * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
19  */
get_posix_clock(struct file * fp)20 static struct posix_clock *get_posix_clock(struct file *fp)
21 {
22 	struct posix_clock_context *pccontext = fp->private_data;
23 	struct posix_clock *clk = pccontext->clk;
24 
25 	down_read(&clk->rwsem);
26 
27 	if (!clk->zombie)
28 		return clk;
29 
30 	up_read(&clk->rwsem);
31 
32 	return NULL;
33 }
34 
put_posix_clock(struct posix_clock * clk)35 static void put_posix_clock(struct posix_clock *clk)
36 {
37 	up_read(&clk->rwsem);
38 }
39 
posix_clock_read(struct file * fp,char __user * buf,size_t count,loff_t * ppos)40 static ssize_t posix_clock_read(struct file *fp, char __user *buf,
41 				size_t count, loff_t *ppos)
42 {
43 	struct posix_clock_context *pccontext = fp->private_data;
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)
51 		err = clk->ops.read(pccontext, fp->f_flags, buf, count);
52 
53 	put_posix_clock(clk);
54 
55 	return err;
56 }
57 
posix_clock_poll(struct file * fp,poll_table * wait)58 static __poll_t posix_clock_poll(struct file *fp, poll_table *wait)
59 {
60 	struct posix_clock_context *pccontext = fp->private_data;
61 	struct posix_clock *clk = get_posix_clock(fp);
62 	__poll_t result = 0;
63 
64 	if (!clk)
65 		return EPOLLERR;
66 
67 	if (clk->ops.poll)
68 		result = clk->ops.poll(pccontext, fp, wait);
69 
70 	put_posix_clock(clk);
71 
72 	return result;
73 }
74 
posix_clock_ioctl(struct file * fp,unsigned int cmd,unsigned long arg)75 static long posix_clock_ioctl(struct file *fp,
76 			      unsigned int cmd, unsigned long arg)
77 {
78 	struct posix_clock_context *pccontext = fp->private_data;
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)
86 		err = clk->ops.ioctl(pccontext, cmd, arg);
87 
88 	put_posix_clock(clk);
89 
90 	return err;
91 }
92 
93 #ifdef CONFIG_COMPAT
posix_clock_compat_ioctl(struct file * fp,unsigned int cmd,unsigned long arg)94 static long posix_clock_compat_ioctl(struct file *fp,
95 				     unsigned int cmd, unsigned long arg)
96 {
97 	struct posix_clock_context *pccontext = fp->private_data;
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)
105 		err = clk->ops.ioctl(pccontext, cmd, arg);
106 
107 	put_posix_clock(clk);
108 
109 	return err;
110 }
111 #endif
112 
posix_clock_open(struct inode * inode,struct file * fp)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);
118 	struct posix_clock_context *pccontext;
119 
120 	down_read(&clk->rwsem);
121 
122 	if (clk->zombie) {
123 		err = -ENODEV;
124 		goto out;
125 	}
126 	pccontext = kzalloc(sizeof(*pccontext), GFP_KERNEL);
127 	if (!pccontext) {
128 		err = -ENOMEM;
129 		goto out;
130 	}
131 	pccontext->clk = clk;
132 	if (clk->ops.open) {
133 		err = clk->ops.open(pccontext, fp->f_mode);
134 		if (err) {
135 			kfree(pccontext);
136 			goto out;
137 		}
138 	}
139 
140 	fp->private_data = pccontext;
141 	get_device(clk->dev);
142 	err = 0;
143 out:
144 	up_read(&clk->rwsem);
145 	return err;
146 }
147 
posix_clock_release(struct inode * inode,struct file * fp)148 static int posix_clock_release(struct inode *inode, struct file *fp)
149 {
150 	struct posix_clock_context *pccontext = fp->private_data;
151 	struct posix_clock *clk;
152 	int err = 0;
153 
154 	if (!pccontext)
155 		return -ENODEV;
156 	clk = pccontext->clk;
157 
158 	if (clk->ops.release)
159 		err = clk->ops.release(pccontext);
160 
161 	put_device(clk->dev);
162 
163 	kfree(pccontext);
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,
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,
176 #ifdef CONFIG_COMPAT
177 	.compat_ioctl	= posix_clock_compat_ioctl,
178 #endif
179 };
180 
posix_clock_register(struct posix_clock * clk,struct device * dev)181 int posix_clock_register(struct posix_clock *clk, struct device *dev)
182 {
183 	int err;
184 
185 	init_rwsem(&clk->rwsem);
186 
187 	cdev_init(&clk->cdev, &posix_clock_file_operations);
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 	}
194 	clk->cdev.owner = clk->ops.owner;
195 	clk->dev = dev;
196 
197 	return 0;
198 }
199 EXPORT_SYMBOL_GPL(posix_clock_register);
200 
posix_clock_unregister(struct posix_clock * clk)201 void posix_clock_unregister(struct posix_clock *clk)
202 {
203 	cdev_device_del(&clk->cdev, clk->dev);
204 
205 	down_write(&clk->rwsem);
206 	clk->zombie = true;
207 	up_write(&clk->rwsem);
208 
209 	put_device(clk->dev);
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 
get_clock_desc(const clockid_t id,struct posix_clock_desc * cd)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 
put_clock_desc(struct posix_clock_desc * cd)239 static void put_clock_desc(struct posix_clock_desc *cd)
240 {
241 	put_posix_clock(cd->clk);
242 	fput(cd->fp);
243 }
244 
pc_clock_adjtime(clockid_t id,struct __kernel_timex * tx)245 static int pc_clock_adjtime(clockid_t id, struct __kernel_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 
pc_clock_gettime(clockid_t id,struct timespec64 * ts)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 
pc_clock_getres(clockid_t id,struct timespec64 * ts)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 
pc_clock_settime(clockid_t id,const struct timespec64 * ts)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 	if (!timespec64_valid_strict(ts))
313 		return -EINVAL;
314 
315 	err = get_clock_desc(id, &cd);
316 	if (err)
317 		return err;
318 
319 	if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
320 		err = -EACCES;
321 		goto out;
322 	}
323 
324 	if (cd.clk->ops.clock_settime)
325 		err = cd.clk->ops.clock_settime(cd.clk, ts);
326 	else
327 		err = -EOPNOTSUPP;
328 out:
329 	put_clock_desc(&cd);
330 
331 	return err;
332 }
333 
334 const struct k_clock clock_posix_dynamic = {
335 	.clock_getres		= pc_clock_getres,
336 	.clock_set		= pc_clock_settime,
337 	.clock_get_timespec	= pc_clock_gettime,
338 	.clock_adj		= pc_clock_adjtime,
339 };
340