xref: /openbmc/linux/kernel/time/posix-clock.c (revision 3a06c7ac)
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 static void delete_clock(struct kref *kref);
29 
30 /*
31  * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
32  */
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 
47 static void put_posix_clock(struct posix_clock *clk)
48 {
49 	up_read(&clk->rwsem);
50 }
51 
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 
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 
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
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 
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 		kref_get(&clk->kref);
140 		fp->private_data = clk;
141 	}
142 out:
143 	up_read(&clk->rwsem);
144 	return err;
145 }
146 
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 	kref_put(&clk->kref, delete_clock);
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 
175 int posix_clock_register(struct posix_clock *clk, dev_t devid)
176 {
177 	int err;
178 
179 	kref_init(&clk->kref);
180 	init_rwsem(&clk->rwsem);
181 
182 	cdev_init(&clk->cdev, &posix_clock_file_operations);
183 	clk->cdev.owner = clk->ops.owner;
184 	err = cdev_add(&clk->cdev, devid, 1);
185 
186 	return err;
187 }
188 EXPORT_SYMBOL_GPL(posix_clock_register);
189 
190 static void delete_clock(struct kref *kref)
191 {
192 	struct posix_clock *clk = container_of(kref, struct posix_clock, kref);
193 
194 	if (clk->release)
195 		clk->release(clk);
196 }
197 
198 void posix_clock_unregister(struct posix_clock *clk)
199 {
200 	cdev_del(&clk->cdev);
201 
202 	down_write(&clk->rwsem);
203 	clk->zombie = true;
204 	up_write(&clk->rwsem);
205 
206 	kref_put(&clk->kref, delete_clock);
207 }
208 EXPORT_SYMBOL_GPL(posix_clock_unregister);
209 
210 struct posix_clock_desc {
211 	struct file *fp;
212 	struct posix_clock *clk;
213 };
214 
215 static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd)
216 {
217 	struct file *fp = fget(CLOCKID_TO_FD(id));
218 	int err = -EINVAL;
219 
220 	if (!fp)
221 		return err;
222 
223 	if (fp->f_op->open != posix_clock_open || !fp->private_data)
224 		goto out;
225 
226 	cd->fp = fp;
227 	cd->clk = get_posix_clock(fp);
228 
229 	err = cd->clk ? 0 : -ENODEV;
230 out:
231 	if (err)
232 		fput(fp);
233 	return err;
234 }
235 
236 static void put_clock_desc(struct posix_clock_desc *cd)
237 {
238 	put_posix_clock(cd->clk);
239 	fput(cd->fp);
240 }
241 
242 static int pc_clock_adjtime(clockid_t id, struct timex *tx)
243 {
244 	struct posix_clock_desc cd;
245 	int err;
246 
247 	err = get_clock_desc(id, &cd);
248 	if (err)
249 		return err;
250 
251 	if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
252 		err = -EACCES;
253 		goto out;
254 	}
255 
256 	if (cd.clk->ops.clock_adjtime)
257 		err = cd.clk->ops.clock_adjtime(cd.clk, tx);
258 	else
259 		err = -EOPNOTSUPP;
260 out:
261 	put_clock_desc(&cd);
262 
263 	return err;
264 }
265 
266 static int pc_clock_gettime(clockid_t id, struct timespec64 *ts)
267 {
268 	struct posix_clock_desc cd;
269 	int err;
270 
271 	err = get_clock_desc(id, &cd);
272 	if (err)
273 		return err;
274 
275 	if (cd.clk->ops.clock_gettime)
276 		err = cd.clk->ops.clock_gettime(cd.clk, ts);
277 	else
278 		err = -EOPNOTSUPP;
279 
280 	put_clock_desc(&cd);
281 
282 	return err;
283 }
284 
285 static int pc_clock_getres(clockid_t id, struct timespec64 *ts)
286 {
287 	struct posix_clock_desc cd;
288 	int err;
289 
290 	err = get_clock_desc(id, &cd);
291 	if (err)
292 		return err;
293 
294 	if (cd.clk->ops.clock_getres)
295 		err = cd.clk->ops.clock_getres(cd.clk, ts);
296 	else
297 		err = -EOPNOTSUPP;
298 
299 	put_clock_desc(&cd);
300 
301 	return err;
302 }
303 
304 static int pc_clock_settime(clockid_t id, const struct timespec64 *ts)
305 {
306 	struct posix_clock_desc cd;
307 	int err;
308 
309 	err = get_clock_desc(id, &cd);
310 	if (err)
311 		return err;
312 
313 	if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
314 		err = -EACCES;
315 		goto out;
316 	}
317 
318 	if (cd.clk->ops.clock_settime)
319 		err = cd.clk->ops.clock_settime(cd.clk, ts);
320 	else
321 		err = -EOPNOTSUPP;
322 out:
323 	put_clock_desc(&cd);
324 
325 	return err;
326 }
327 
328 const struct k_clock clock_posix_dynamic = {
329 	.clock_getres	= pc_clock_getres,
330 	.clock_set	= pc_clock_settime,
331 	.clock_get	= pc_clock_gettime,
332 	.clock_adj	= pc_clock_adjtime,
333 };
334