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 | */ |
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(sem: &clk->rwsem); |
26 | |
27 | if (!clk->zombie) |
28 | return clk; |
29 | |
30 | up_read(sem: &clk->rwsem); |
31 | |
32 | return NULL; |
33 | } |
34 | |
35 | static void put_posix_clock(struct posix_clock *clk) |
36 | { |
37 | up_read(sem: &clk->rwsem); |
38 | } |
39 | |
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 | |
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 | |
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 |
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 | |
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(sem: &clk->rwsem); |
121 | |
122 | if (clk->zombie) { |
123 | err = -ENODEV; |
124 | goto out; |
125 | } |
126 | pccontext = kzalloc(size: sizeof(*pccontext), GFP_KERNEL); |
127 | if (!pccontext) { |
128 | err = -ENOMEM; |
129 | goto out; |
130 | } |
131 | pccontext->clk = clk; |
132 | fp->private_data = pccontext; |
133 | if (clk->ops.open) |
134 | err = clk->ops.open(pccontext, fp->f_mode); |
135 | else |
136 | err = 0; |
137 | |
138 | if (!err) { |
139 | get_device(dev: clk->dev); |
140 | } |
141 | out: |
142 | up_read(sem: &clk->rwsem); |
143 | return err; |
144 | } |
145 | |
146 | static int posix_clock_release(struct inode *inode, struct file *fp) |
147 | { |
148 | struct posix_clock_context *pccontext = fp->private_data; |
149 | struct posix_clock *clk; |
150 | int err = 0; |
151 | |
152 | if (!pccontext) |
153 | return -ENODEV; |
154 | clk = pccontext->clk; |
155 | |
156 | if (clk->ops.release) |
157 | err = clk->ops.release(pccontext); |
158 | |
159 | put_device(dev: clk->dev); |
160 | |
161 | kfree(objp: pccontext); |
162 | fp->private_data = NULL; |
163 | |
164 | return err; |
165 | } |
166 | |
167 | static const struct file_operations posix_clock_file_operations = { |
168 | .owner = THIS_MODULE, |
169 | .llseek = no_llseek, |
170 | .read = posix_clock_read, |
171 | .poll = posix_clock_poll, |
172 | .unlocked_ioctl = posix_clock_ioctl, |
173 | .open = posix_clock_open, |
174 | .release = posix_clock_release, |
175 | #ifdef CONFIG_COMPAT |
176 | .compat_ioctl = posix_clock_compat_ioctl, |
177 | #endif |
178 | }; |
179 | |
180 | int posix_clock_register(struct posix_clock *clk, struct device *dev) |
181 | { |
182 | int err; |
183 | |
184 | init_rwsem(&clk->rwsem); |
185 | |
186 | cdev_init(&clk->cdev, &posix_clock_file_operations); |
187 | err = cdev_device_add(cdev: &clk->cdev, dev); |
188 | if (err) { |
189 | pr_err("%s unable to add device %d:%d\n" , |
190 | dev_name(dev), MAJOR(dev->devt), MINOR(dev->devt)); |
191 | return err; |
192 | } |
193 | clk->cdev.owner = clk->ops.owner; |
194 | clk->dev = dev; |
195 | |
196 | return 0; |
197 | } |
198 | EXPORT_SYMBOL_GPL(posix_clock_register); |
199 | |
200 | void posix_clock_unregister(struct posix_clock *clk) |
201 | { |
202 | cdev_device_del(cdev: &clk->cdev, dev: clk->dev); |
203 | |
204 | down_write(sem: &clk->rwsem); |
205 | clk->zombie = true; |
206 | up_write(sem: &clk->rwsem); |
207 | |
208 | put_device(dev: clk->dev); |
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(fd: clockid_to_fd(clk: 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(clk: cd->clk); |
241 | fput(cd->fp); |
242 | } |
243 | |
244 | static int pc_clock_adjtime(clockid_t id, struct __kernel_timex *tx) |
245 | { |
246 | struct posix_clock_desc cd; |
247 | int err; |
248 | |
249 | err = get_clock_desc(id, cd: &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: &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: &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: &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: &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: &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: &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: &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_timespec = pc_clock_gettime, |
334 | .clock_adj = pc_clock_adjtime, |
335 | }; |
336 | |