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 | static int posix_clock_open(struct inode *inode, struct file *fp) |
94 | { |
95 | int err; |
96 | struct posix_clock *clk = |
97 | container_of(inode->i_cdev, struct posix_clock, cdev); |
98 | struct posix_clock_context *pccontext; |
99 | |
100 | down_read(sem: &clk->rwsem); |
101 | |
102 | if (clk->zombie) { |
103 | err = -ENODEV; |
104 | goto out; |
105 | } |
106 | pccontext = kzalloc(sizeof(*pccontext), GFP_KERNEL); |
107 | if (!pccontext) { |
108 | err = -ENOMEM; |
109 | goto out; |
110 | } |
111 | pccontext->clk = clk; |
112 | pccontext->fp = fp; |
113 | if (clk->ops.open) { |
114 | err = clk->ops.open(pccontext, fp->f_mode); |
115 | if (err) { |
116 | kfree(objp: pccontext); |
117 | goto out; |
118 | } |
119 | } |
120 | |
121 | fp->private_data = pccontext; |
122 | get_device(dev: clk->dev); |
123 | err = 0; |
124 | out: |
125 | up_read(sem: &clk->rwsem); |
126 | return err; |
127 | } |
128 | |
129 | static int posix_clock_release(struct inode *inode, struct file *fp) |
130 | { |
131 | struct posix_clock_context *pccontext = fp->private_data; |
132 | struct posix_clock *clk; |
133 | int err = 0; |
134 | |
135 | if (!pccontext) |
136 | return -ENODEV; |
137 | clk = pccontext->clk; |
138 | |
139 | if (clk->ops.release) |
140 | err = clk->ops.release(pccontext); |
141 | |
142 | put_device(dev: clk->dev); |
143 | |
144 | kfree(objp: pccontext); |
145 | fp->private_data = NULL; |
146 | |
147 | return err; |
148 | } |
149 | |
150 | static const struct file_operations posix_clock_file_operations = { |
151 | .owner = THIS_MODULE, |
152 | .read = posix_clock_read, |
153 | .poll = posix_clock_poll, |
154 | .unlocked_ioctl = posix_clock_ioctl, |
155 | .compat_ioctl = posix_clock_ioctl, |
156 | .open = posix_clock_open, |
157 | .release = posix_clock_release, |
158 | }; |
159 | |
160 | int posix_clock_register(struct posix_clock *clk, struct device *dev) |
161 | { |
162 | int err; |
163 | |
164 | init_rwsem(&clk->rwsem); |
165 | |
166 | cdev_init(&clk->cdev, &posix_clock_file_operations); |
167 | err = cdev_device_add(cdev: &clk->cdev, dev); |
168 | if (err) { |
169 | pr_err("%s unable to add device %d:%d\n" , |
170 | dev_name(dev), MAJOR(dev->devt), MINOR(dev->devt)); |
171 | return err; |
172 | } |
173 | clk->cdev.owner = clk->ops.owner; |
174 | clk->dev = dev; |
175 | |
176 | return 0; |
177 | } |
178 | EXPORT_SYMBOL_GPL(posix_clock_register); |
179 | |
180 | void posix_clock_unregister(struct posix_clock *clk) |
181 | { |
182 | cdev_device_del(cdev: &clk->cdev, dev: clk->dev); |
183 | |
184 | down_write(sem: &clk->rwsem); |
185 | clk->zombie = true; |
186 | up_write(sem: &clk->rwsem); |
187 | |
188 | put_device(dev: clk->dev); |
189 | } |
190 | EXPORT_SYMBOL_GPL(posix_clock_unregister); |
191 | |
192 | struct posix_clock_desc { |
193 | struct file *fp; |
194 | struct posix_clock *clk; |
195 | }; |
196 | |
197 | static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd) |
198 | { |
199 | struct file *fp = fget(fd: clockid_to_fd(clk: id)); |
200 | int err = -EINVAL; |
201 | |
202 | if (!fp) |
203 | return err; |
204 | |
205 | if (fp->f_op->open != posix_clock_open || !fp->private_data) |
206 | goto out; |
207 | |
208 | cd->fp = fp; |
209 | cd->clk = get_posix_clock(fp); |
210 | |
211 | err = cd->clk ? 0 : -ENODEV; |
212 | out: |
213 | if (err) |
214 | fput(fp); |
215 | return err; |
216 | } |
217 | |
218 | static void put_clock_desc(struct posix_clock_desc *cd) |
219 | { |
220 | put_posix_clock(clk: cd->clk); |
221 | fput(cd->fp); |
222 | } |
223 | |
224 | static int pc_clock_adjtime(clockid_t id, struct __kernel_timex *tx) |
225 | { |
226 | struct posix_clock_desc cd; |
227 | int err; |
228 | |
229 | err = get_clock_desc(id, cd: &cd); |
230 | if (err) |
231 | return err; |
232 | |
233 | if (tx->modes && (cd.fp->f_mode & FMODE_WRITE) == 0) { |
234 | err = -EACCES; |
235 | goto out; |
236 | } |
237 | |
238 | if (cd.clk->ops.clock_adjtime) |
239 | err = cd.clk->ops.clock_adjtime(cd.clk, tx); |
240 | else |
241 | err = -EOPNOTSUPP; |
242 | out: |
243 | put_clock_desc(cd: &cd); |
244 | |
245 | return err; |
246 | } |
247 | |
248 | static int pc_clock_gettime(clockid_t id, struct timespec64 *ts) |
249 | { |
250 | struct posix_clock_desc cd; |
251 | int err; |
252 | |
253 | err = get_clock_desc(id, cd: &cd); |
254 | if (err) |
255 | return err; |
256 | |
257 | if (cd.clk->ops.clock_gettime) |
258 | err = cd.clk->ops.clock_gettime(cd.clk, ts); |
259 | else |
260 | err = -EOPNOTSUPP; |
261 | |
262 | put_clock_desc(cd: &cd); |
263 | |
264 | return err; |
265 | } |
266 | |
267 | static int pc_clock_getres(clockid_t id, struct timespec64 *ts) |
268 | { |
269 | struct posix_clock_desc cd; |
270 | int err; |
271 | |
272 | err = get_clock_desc(id, cd: &cd); |
273 | if (err) |
274 | return err; |
275 | |
276 | if (cd.clk->ops.clock_getres) |
277 | err = cd.clk->ops.clock_getres(cd.clk, ts); |
278 | else |
279 | err = -EOPNOTSUPP; |
280 | |
281 | put_clock_desc(cd: &cd); |
282 | |
283 | return err; |
284 | } |
285 | |
286 | static int pc_clock_settime(clockid_t id, const struct timespec64 *ts) |
287 | { |
288 | struct posix_clock_desc cd; |
289 | int err; |
290 | |
291 | if (!timespec64_valid_strict(ts)) |
292 | return -EINVAL; |
293 | |
294 | err = get_clock_desc(id, cd: &cd); |
295 | if (err) |
296 | return err; |
297 | |
298 | if ((cd.fp->f_mode & FMODE_WRITE) == 0) { |
299 | err = -EACCES; |
300 | goto out; |
301 | } |
302 | |
303 | if (cd.clk->ops.clock_settime) |
304 | err = cd.clk->ops.clock_settime(cd.clk, ts); |
305 | else |
306 | err = -EOPNOTSUPP; |
307 | out: |
308 | put_clock_desc(cd: &cd); |
309 | |
310 | return err; |
311 | } |
312 | |
313 | const struct k_clock clock_posix_dynamic = { |
314 | .clock_getres = pc_clock_getres, |
315 | .clock_set = pc_clock_settime, |
316 | .clock_get_timespec = pc_clock_gettime, |
317 | .clock_adj = pc_clock_adjtime, |
318 | }; |
319 | |