linux/kernel/time/posix-clock.c
<<
>>
Prefs
   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
  17static void delete_clock(struct kref *kref);
  18
  19/*
  20 * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
  21 */
  22static 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
  36static void put_posix_clock(struct posix_clock *clk)
  37{
  38        up_read(&clk->rwsem);
  39}
  40
  41static 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
  58static __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
  74static 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
  92static 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
 110static 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        }
 131out:
 132        up_read(&clk->rwsem);
 133        return err;
 134}
 135
 136static 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
 151static 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
 164int 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}
 177EXPORT_SYMBOL_GPL(posix_clock_register);
 178
 179static 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
 187void 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}
 197EXPORT_SYMBOL_GPL(posix_clock_unregister);
 198
 199struct posix_clock_desc {
 200        struct file *fp;
 201        struct posix_clock *clk;
 202};
 203
 204static 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;
 219out:
 220        if (err)
 221                fput(fp);
 222        return err;
 223}
 224
 225static void put_clock_desc(struct posix_clock_desc *cd)
 226{
 227        put_posix_clock(cd->clk);
 228        fput(cd->fp);
 229}
 230
 231static int pc_clock_adjtime(clockid_t id, struct __kernel_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;
 249out:
 250        put_clock_desc(&cd);
 251
 252        return err;
 253}
 254
 255static 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
 274static 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
 293static 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;
 311out:
 312        put_clock_desc(&cd);
 313
 314        return err;
 315}
 316
 317const 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