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