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 | if (clk->ops.open) { |
133 | err = clk->ops.open(pccontext, fp->f_mode); |
134 | if (err) { |
135 | kfree(objp: pccontext); |
136 | goto out; |
137 | } |
138 | } |
139 | |
140 | fp->private_data = pccontext; |
141 | get_device(dev: clk->dev); |
142 | err = 0; |
143 | out: |
144 | up_read(sem: &clk->rwsem); |
145 | return err; |
146 | } |
147 | |
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(dev: clk->dev); |
162 | |
163 | kfree(objp: 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 | .llseek = no_llseek, |
172 | .read = posix_clock_read, |
173 | .poll = posix_clock_poll, |
174 | .unlocked_ioctl = posix_clock_ioctl, |
175 | .open = posix_clock_open, |
176 | .release = posix_clock_release, |
177 | #ifdef CONFIG_COMPAT |
178 | .compat_ioctl = posix_clock_compat_ioctl, |
179 | #endif |
180 | }; |
181 | |
182 | int posix_clock_register(struct posix_clock *clk, struct device *dev) |
183 | { |
184 | int err; |
185 | |
186 | init_rwsem(&clk->rwsem); |
187 | |
188 | cdev_init(&clk->cdev, &posix_clock_file_operations); |
189 | err = cdev_device_add(cdev: &clk->cdev, dev); |
190 | if (err) { |
191 | pr_err("%s unable to add device %d:%d\n", |
192 | dev_name(dev), MAJOR(dev->devt), MINOR(dev->devt)); |
193 | return err; |
194 | } |
195 | clk->cdev.owner = clk->ops.owner; |
196 | clk->dev = dev; |
197 | |
198 | return 0; |
199 | } |
200 | EXPORT_SYMBOL_GPL(posix_clock_register); |
201 | |
202 | void posix_clock_unregister(struct posix_clock *clk) |
203 | { |
204 | cdev_device_del(cdev: &clk->cdev, dev: clk->dev); |
205 | |
206 | down_write(sem: &clk->rwsem); |
207 | clk->zombie = true; |
208 | up_write(sem: &clk->rwsem); |
209 | |
210 | put_device(dev: clk->dev); |
211 | } |
212 | EXPORT_SYMBOL_GPL(posix_clock_unregister); |
213 | |
214 | struct posix_clock_desc { |
215 | struct file *fp; |
216 | struct posix_clock *clk; |
217 | }; |
218 | |
219 | static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd) |
220 | { |
221 | struct file *fp = fget(fd: clockid_to_fd(clk: id)); |
222 | int err = -EINVAL; |
223 | |
224 | if (!fp) |
225 | return err; |
226 | |
227 | if (fp->f_op->open != posix_clock_open || !fp->private_data) |
228 | goto out; |
229 | |
230 | cd->fp = fp; |
231 | cd->clk = get_posix_clock(fp); |
232 | |
233 | err = cd->clk ? 0 : -ENODEV; |
234 | out: |
235 | if (err) |
236 | fput(fp); |
237 | return err; |
238 | } |
239 | |
240 | static void put_clock_desc(struct posix_clock_desc *cd) |
241 | { |
242 | put_posix_clock(clk: cd->clk); |
243 | fput(cd->fp); |
244 | } |
245 | |
246 | static int pc_clock_adjtime(clockid_t id, struct __kernel_timex *tx) |
247 | { |
248 | struct posix_clock_desc cd; |
249 | int err; |
250 | |
251 | err = get_clock_desc(id, cd: &cd); |
252 | if (err) |
253 | return err; |
254 | |
255 | if ((cd.fp->f_mode & FMODE_WRITE) == 0) { |
256 | err = -EACCES; |
257 | goto out; |
258 | } |
259 | |
260 | if (cd.clk->ops.clock_adjtime) |
261 | err = cd.clk->ops.clock_adjtime(cd.clk, tx); |
262 | else |
263 | err = -EOPNOTSUPP; |
264 | out: |
265 | put_clock_desc(cd: &cd); |
266 | |
267 | return err; |
268 | } |
269 | |
270 | static int pc_clock_gettime(clockid_t id, struct timespec64 *ts) |
271 | { |
272 | struct posix_clock_desc cd; |
273 | int err; |
274 | |
275 | err = get_clock_desc(id, cd: &cd); |
276 | if (err) |
277 | return err; |
278 | |
279 | if (cd.clk->ops.clock_gettime) |
280 | err = cd.clk->ops.clock_gettime(cd.clk, ts); |
281 | else |
282 | err = -EOPNOTSUPP; |
283 | |
284 | put_clock_desc(cd: &cd); |
285 | |
286 | return err; |
287 | } |
288 | |
289 | static int pc_clock_getres(clockid_t id, struct timespec64 *ts) |
290 | { |
291 | struct posix_clock_desc cd; |
292 | int err; |
293 | |
294 | err = get_clock_desc(id, cd: &cd); |
295 | if (err) |
296 | return err; |
297 | |
298 | if (cd.clk->ops.clock_getres) |
299 | err = cd.clk->ops.clock_getres(cd.clk, ts); |
300 | else |
301 | err = -EOPNOTSUPP; |
302 | |
303 | put_clock_desc(cd: &cd); |
304 | |
305 | return err; |
306 | } |
307 | |
308 | static int pc_clock_settime(clockid_t id, const struct timespec64 *ts) |
309 | { |
310 | struct posix_clock_desc cd; |
311 | int err; |
312 | |
313 | err = get_clock_desc(id, cd: &cd); |
314 | if (err) |
315 | return err; |
316 | |
317 | if ((cd.fp->f_mode & FMODE_WRITE) == 0) { |
318 | err = -EACCES; |
319 | goto out; |
320 | } |
321 | |
322 | if (cd.clk->ops.clock_settime) |
323 | err = cd.clk->ops.clock_settime(cd.clk, ts); |
324 | else |
325 | err = -EOPNOTSUPP; |
326 | out: |
327 | put_clock_desc(cd: &cd); |
328 | |
329 | return err; |
330 | } |
331 | |
332 | const struct k_clock clock_posix_dynamic = { |
333 | .clock_getres = pc_clock_getres, |
334 | .clock_set = pc_clock_settime, |
335 | .clock_get_timespec = pc_clock_gettime, |
336 | .clock_adj = pc_clock_adjtime, |
337 | }; |
338 |
Definitions
- get_posix_clock
- put_posix_clock
- posix_clock_read
- posix_clock_poll
- posix_clock_ioctl
- posix_clock_compat_ioctl
- posix_clock_open
- posix_clock_release
- posix_clock_file_operations
- posix_clock_register
- posix_clock_unregister
- posix_clock_desc
- get_clock_desc
- put_clock_desc
- pc_clock_adjtime
- pc_clock_gettime
- pc_clock_getres
- pc_clock_settime
Improve your Profiling and Debugging skills
Find out more