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