135728b82SThomas Gleixner // SPDX-License-Identifier: GPL-2.0+ 20606f422SRichard Cochran /* 358c5fc2bSThomas Gleixner * Support for dynamic clock devices 40606f422SRichard Cochran * 50606f422SRichard Cochran * Copyright (C) 2010 OMICRON electronics GmbH 60606f422SRichard Cochran */ 70606f422SRichard Cochran #include <linux/device.h> 86e5fdeedSPaul Gortmaker #include <linux/export.h> 90606f422SRichard Cochran #include <linux/file.h> 100606f422SRichard Cochran #include <linux/posix-clock.h> 110606f422SRichard Cochran #include <linux/slab.h> 120606f422SRichard Cochran #include <linux/syscalls.h> 130606f422SRichard Cochran #include <linux/uaccess.h> 140606f422SRichard Cochran 15bab0aae9SThomas Gleixner #include "posix-timers.h" 16bab0aae9SThomas Gleixner 170606f422SRichard Cochran static void delete_clock(struct kref *kref); 180606f422SRichard Cochran 190606f422SRichard Cochran /* 200606f422SRichard Cochran * Returns NULL if the posix_clock instance attached to 'fp' is old and stale. 210606f422SRichard Cochran */ 220606f422SRichard Cochran static struct posix_clock *get_posix_clock(struct file *fp) 230606f422SRichard Cochran { 240606f422SRichard Cochran struct posix_clock *clk = fp->private_data; 250606f422SRichard Cochran 261791f881SRichard Cochran down_read(&clk->rwsem); 270606f422SRichard Cochran 280606f422SRichard Cochran if (!clk->zombie) 290606f422SRichard Cochran return clk; 300606f422SRichard Cochran 311791f881SRichard Cochran up_read(&clk->rwsem); 320606f422SRichard Cochran 330606f422SRichard Cochran return NULL; 340606f422SRichard Cochran } 350606f422SRichard Cochran 360606f422SRichard Cochran static void put_posix_clock(struct posix_clock *clk) 370606f422SRichard Cochran { 381791f881SRichard Cochran up_read(&clk->rwsem); 390606f422SRichard Cochran } 400606f422SRichard Cochran 410606f422SRichard Cochran static ssize_t posix_clock_read(struct file *fp, char __user *buf, 420606f422SRichard Cochran size_t count, loff_t *ppos) 430606f422SRichard Cochran { 440606f422SRichard Cochran struct posix_clock *clk = get_posix_clock(fp); 450606f422SRichard Cochran int err = -EINVAL; 460606f422SRichard Cochran 470606f422SRichard Cochran if (!clk) 480606f422SRichard Cochran return -ENODEV; 490606f422SRichard Cochran 500606f422SRichard Cochran if (clk->ops.read) 510606f422SRichard Cochran err = clk->ops.read(clk, fp->f_flags, buf, count); 520606f422SRichard Cochran 530606f422SRichard Cochran put_posix_clock(clk); 540606f422SRichard Cochran 550606f422SRichard Cochran return err; 560606f422SRichard Cochran } 570606f422SRichard Cochran 589dd95748SAl Viro static __poll_t posix_clock_poll(struct file *fp, poll_table *wait) 590606f422SRichard Cochran { 600606f422SRichard Cochran struct posix_clock *clk = get_posix_clock(fp); 619dd95748SAl Viro __poll_t result = 0; 620606f422SRichard Cochran 630606f422SRichard Cochran if (!clk) 64a9a08845SLinus Torvalds return EPOLLERR; 650606f422SRichard Cochran 660606f422SRichard Cochran if (clk->ops.poll) 670606f422SRichard Cochran result = clk->ops.poll(clk, fp, wait); 680606f422SRichard Cochran 690606f422SRichard Cochran put_posix_clock(clk); 700606f422SRichard Cochran 710606f422SRichard Cochran return result; 720606f422SRichard Cochran } 730606f422SRichard Cochran 740606f422SRichard Cochran static long posix_clock_ioctl(struct file *fp, 750606f422SRichard Cochran unsigned int cmd, unsigned long arg) 760606f422SRichard Cochran { 770606f422SRichard Cochran struct posix_clock *clk = get_posix_clock(fp); 780606f422SRichard Cochran int err = -ENOTTY; 790606f422SRichard Cochran 800606f422SRichard Cochran if (!clk) 810606f422SRichard Cochran return -ENODEV; 820606f422SRichard Cochran 830606f422SRichard Cochran if (clk->ops.ioctl) 840606f422SRichard Cochran err = clk->ops.ioctl(clk, cmd, arg); 850606f422SRichard Cochran 860606f422SRichard Cochran put_posix_clock(clk); 870606f422SRichard Cochran 880606f422SRichard Cochran return err; 890606f422SRichard Cochran } 900606f422SRichard Cochran 910606f422SRichard Cochran #ifdef CONFIG_COMPAT 920606f422SRichard Cochran static long posix_clock_compat_ioctl(struct file *fp, 930606f422SRichard Cochran unsigned int cmd, unsigned long arg) 940606f422SRichard Cochran { 950606f422SRichard Cochran struct posix_clock *clk = get_posix_clock(fp); 960606f422SRichard Cochran int err = -ENOTTY; 970606f422SRichard Cochran 980606f422SRichard Cochran if (!clk) 990606f422SRichard Cochran return -ENODEV; 1000606f422SRichard Cochran 1010606f422SRichard Cochran if (clk->ops.ioctl) 1020606f422SRichard Cochran err = clk->ops.ioctl(clk, cmd, arg); 1030606f422SRichard Cochran 1040606f422SRichard Cochran put_posix_clock(clk); 1050606f422SRichard Cochran 1060606f422SRichard Cochran return err; 1070606f422SRichard Cochran } 1080606f422SRichard Cochran #endif 1090606f422SRichard Cochran 1100606f422SRichard Cochran static int posix_clock_open(struct inode *inode, struct file *fp) 1110606f422SRichard Cochran { 1120606f422SRichard Cochran int err; 1130606f422SRichard Cochran struct posix_clock *clk = 1140606f422SRichard Cochran container_of(inode->i_cdev, struct posix_clock, cdev); 1150606f422SRichard Cochran 1161791f881SRichard Cochran down_read(&clk->rwsem); 1170606f422SRichard Cochran 1180606f422SRichard Cochran if (clk->zombie) { 1190606f422SRichard Cochran err = -ENODEV; 1200606f422SRichard Cochran goto out; 1210606f422SRichard Cochran } 1220606f422SRichard Cochran if (clk->ops.open) 1230606f422SRichard Cochran err = clk->ops.open(clk, fp->f_mode); 1240606f422SRichard Cochran else 1250606f422SRichard Cochran err = 0; 1260606f422SRichard Cochran 1270606f422SRichard Cochran if (!err) { 1280606f422SRichard Cochran kref_get(&clk->kref); 1290606f422SRichard Cochran fp->private_data = clk; 1300606f422SRichard Cochran } 1310606f422SRichard Cochran out: 1321791f881SRichard Cochran up_read(&clk->rwsem); 1330606f422SRichard Cochran return err; 1340606f422SRichard Cochran } 1350606f422SRichard Cochran 1360606f422SRichard Cochran static int posix_clock_release(struct inode *inode, struct file *fp) 1370606f422SRichard Cochran { 1380606f422SRichard Cochran struct posix_clock *clk = fp->private_data; 1390606f422SRichard Cochran int err = 0; 1400606f422SRichard Cochran 1410606f422SRichard Cochran if (clk->ops.release) 1420606f422SRichard Cochran err = clk->ops.release(clk); 1430606f422SRichard Cochran 1440606f422SRichard Cochran kref_put(&clk->kref, delete_clock); 1450606f422SRichard Cochran 1460606f422SRichard Cochran fp->private_data = NULL; 1470606f422SRichard Cochran 1480606f422SRichard Cochran return err; 1490606f422SRichard Cochran } 1500606f422SRichard Cochran 1510606f422SRichard Cochran static const struct file_operations posix_clock_file_operations = { 1520606f422SRichard Cochran .owner = THIS_MODULE, 1530606f422SRichard Cochran .llseek = no_llseek, 1540606f422SRichard Cochran .read = posix_clock_read, 1550606f422SRichard Cochran .poll = posix_clock_poll, 1560606f422SRichard Cochran .unlocked_ioctl = posix_clock_ioctl, 1570606f422SRichard Cochran .open = posix_clock_open, 1580606f422SRichard Cochran .release = posix_clock_release, 1590606f422SRichard Cochran #ifdef CONFIG_COMPAT 1600606f422SRichard Cochran .compat_ioctl = posix_clock_compat_ioctl, 1610606f422SRichard Cochran #endif 1620606f422SRichard Cochran }; 1630606f422SRichard Cochran 1640606f422SRichard Cochran int posix_clock_register(struct posix_clock *clk, dev_t devid) 1650606f422SRichard Cochran { 1660606f422SRichard Cochran int err; 1670606f422SRichard Cochran 1680606f422SRichard Cochran kref_init(&clk->kref); 1691791f881SRichard Cochran init_rwsem(&clk->rwsem); 1700606f422SRichard Cochran 1710606f422SRichard Cochran cdev_init(&clk->cdev, &posix_clock_file_operations); 1720606f422SRichard Cochran clk->cdev.owner = clk->ops.owner; 1730606f422SRichard Cochran err = cdev_add(&clk->cdev, devid, 1); 1740606f422SRichard Cochran 1750606f422SRichard Cochran return err; 1760606f422SRichard Cochran } 1770606f422SRichard Cochran EXPORT_SYMBOL_GPL(posix_clock_register); 1780606f422SRichard Cochran 1790606f422SRichard Cochran static void delete_clock(struct kref *kref) 1800606f422SRichard Cochran { 1810606f422SRichard Cochran struct posix_clock *clk = container_of(kref, struct posix_clock, kref); 1821791f881SRichard Cochran 1830606f422SRichard Cochran if (clk->release) 1840606f422SRichard Cochran clk->release(clk); 1850606f422SRichard Cochran } 1860606f422SRichard Cochran 1870606f422SRichard Cochran void posix_clock_unregister(struct posix_clock *clk) 1880606f422SRichard Cochran { 1890606f422SRichard Cochran cdev_del(&clk->cdev); 1900606f422SRichard Cochran 1911791f881SRichard Cochran down_write(&clk->rwsem); 1920606f422SRichard Cochran clk->zombie = true; 1931791f881SRichard Cochran up_write(&clk->rwsem); 1940606f422SRichard Cochran 1950606f422SRichard Cochran kref_put(&clk->kref, delete_clock); 1960606f422SRichard Cochran } 1970606f422SRichard Cochran EXPORT_SYMBOL_GPL(posix_clock_unregister); 1980606f422SRichard Cochran 1990606f422SRichard Cochran struct posix_clock_desc { 2000606f422SRichard Cochran struct file *fp; 2010606f422SRichard Cochran struct posix_clock *clk; 2020606f422SRichard Cochran }; 2030606f422SRichard Cochran 2040606f422SRichard Cochran static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd) 2050606f422SRichard Cochran { 20629f1b2b0SNick Desaulniers struct file *fp = fget(clockid_to_fd(id)); 2070606f422SRichard Cochran int err = -EINVAL; 2080606f422SRichard Cochran 2090606f422SRichard Cochran if (!fp) 2100606f422SRichard Cochran return err; 2110606f422SRichard Cochran 2120606f422SRichard Cochran if (fp->f_op->open != posix_clock_open || !fp->private_data) 2130606f422SRichard Cochran goto out; 2140606f422SRichard Cochran 2150606f422SRichard Cochran cd->fp = fp; 2160606f422SRichard Cochran cd->clk = get_posix_clock(fp); 2170606f422SRichard Cochran 2180606f422SRichard Cochran err = cd->clk ? 0 : -ENODEV; 2190606f422SRichard Cochran out: 2200606f422SRichard Cochran if (err) 2210606f422SRichard Cochran fput(fp); 2220606f422SRichard Cochran return err; 2230606f422SRichard Cochran } 2240606f422SRichard Cochran 2250606f422SRichard Cochran static void put_clock_desc(struct posix_clock_desc *cd) 2260606f422SRichard Cochran { 2270606f422SRichard Cochran put_posix_clock(cd->clk); 2280606f422SRichard Cochran fput(cd->fp); 2290606f422SRichard Cochran } 2300606f422SRichard Cochran 231ead25417SDeepa Dinamani static int pc_clock_adjtime(clockid_t id, struct __kernel_timex *tx) 2320606f422SRichard Cochran { 2330606f422SRichard Cochran struct posix_clock_desc cd; 2340606f422SRichard Cochran int err; 2350606f422SRichard Cochran 2360606f422SRichard Cochran err = get_clock_desc(id, &cd); 2370606f422SRichard Cochran if (err) 2380606f422SRichard Cochran return err; 2390606f422SRichard Cochran 2406e6823d1STorben Hohn if ((cd.fp->f_mode & FMODE_WRITE) == 0) { 2416e6823d1STorben Hohn err = -EACCES; 2426e6823d1STorben Hohn goto out; 2436e6823d1STorben Hohn } 2446e6823d1STorben Hohn 2450606f422SRichard Cochran if (cd.clk->ops.clock_adjtime) 2460606f422SRichard Cochran err = cd.clk->ops.clock_adjtime(cd.clk, tx); 2470606f422SRichard Cochran else 2480606f422SRichard Cochran err = -EOPNOTSUPP; 2496e6823d1STorben Hohn out: 2500606f422SRichard Cochran put_clock_desc(&cd); 2510606f422SRichard Cochran 2520606f422SRichard Cochran return err; 2530606f422SRichard Cochran } 2540606f422SRichard Cochran 2553c9c12f4SDeepa Dinamani static int pc_clock_gettime(clockid_t id, struct timespec64 *ts) 2560606f422SRichard Cochran { 2570606f422SRichard Cochran struct posix_clock_desc cd; 2580606f422SRichard Cochran int err; 2590606f422SRichard Cochran 2600606f422SRichard Cochran err = get_clock_desc(id, &cd); 2610606f422SRichard Cochran if (err) 2620606f422SRichard Cochran return err; 2630606f422SRichard Cochran 2643c9c12f4SDeepa Dinamani if (cd.clk->ops.clock_gettime) 2653c9c12f4SDeepa Dinamani err = cd.clk->ops.clock_gettime(cd.clk, ts); 2660606f422SRichard Cochran else 2670606f422SRichard Cochran err = -EOPNOTSUPP; 2680606f422SRichard Cochran 2690606f422SRichard Cochran put_clock_desc(&cd); 2700606f422SRichard Cochran 2710606f422SRichard Cochran return err; 2720606f422SRichard Cochran } 2730606f422SRichard Cochran 274d2e3e0caSDeepa Dinamani static int pc_clock_getres(clockid_t id, struct timespec64 *ts) 2750606f422SRichard Cochran { 2760606f422SRichard Cochran struct posix_clock_desc cd; 2770606f422SRichard Cochran int err; 2780606f422SRichard Cochran 2790606f422SRichard Cochran err = get_clock_desc(id, &cd); 2800606f422SRichard Cochran if (err) 2810606f422SRichard Cochran return err; 2820606f422SRichard Cochran 283d2e3e0caSDeepa Dinamani if (cd.clk->ops.clock_getres) 284d2e3e0caSDeepa Dinamani err = cd.clk->ops.clock_getres(cd.clk, ts); 2850606f422SRichard Cochran else 2860606f422SRichard Cochran err = -EOPNOTSUPP; 2870606f422SRichard Cochran 2880606f422SRichard Cochran put_clock_desc(&cd); 2890606f422SRichard Cochran 2900606f422SRichard Cochran return err; 2910606f422SRichard Cochran } 2920606f422SRichard Cochran 2930fe6afe3SDeepa Dinamani static int pc_clock_settime(clockid_t id, const struct timespec64 *ts) 2940606f422SRichard Cochran { 2950606f422SRichard Cochran struct posix_clock_desc cd; 2960606f422SRichard Cochran int err; 2970606f422SRichard Cochran 2980606f422SRichard Cochran err = get_clock_desc(id, &cd); 2990606f422SRichard Cochran if (err) 3000606f422SRichard Cochran return err; 3010606f422SRichard Cochran 3026e6823d1STorben Hohn if ((cd.fp->f_mode & FMODE_WRITE) == 0) { 3036e6823d1STorben Hohn err = -EACCES; 3046e6823d1STorben Hohn goto out; 3056e6823d1STorben Hohn } 3066e6823d1STorben Hohn 3070606f422SRichard Cochran if (cd.clk->ops.clock_settime) 3080fe6afe3SDeepa Dinamani err = cd.clk->ops.clock_settime(cd.clk, ts); 3090606f422SRichard Cochran else 3100606f422SRichard Cochran err = -EOPNOTSUPP; 3116e6823d1STorben Hohn out: 3120606f422SRichard Cochran put_clock_desc(&cd); 3130606f422SRichard Cochran 3140606f422SRichard Cochran return err; 3150606f422SRichard Cochran } 3160606f422SRichard Cochran 317d3ba5a9aSChristoph Hellwig const struct k_clock clock_posix_dynamic = { 3180606f422SRichard Cochran .clock_getres = pc_clock_getres, 3190606f422SRichard Cochran .clock_set = pc_clock_settime, 3200606f422SRichard Cochran .clock_get = pc_clock_gettime, 3210606f422SRichard Cochran .clock_adj = pc_clock_adjtime, 3220606f422SRichard Cochran }; 323