linux/kernel/time/posix-clock.c
<<
>>
Prefs
   1/*
   2 * posix-clock.c - support for dynamic clock devices
   3 *
   4 * Copyright (C) 2010 OMICRON electronics GmbH
   5 *
   6 *  This program is free software; you can redistribute it and/or modify
   7 *  it under the terms of the GNU General Public License as published by
   8 *  the Free Software Foundation; either version 2 of the License, or
   9 *  (at your option) any later version.
  10 *
  11 *  This program is distributed in the hope that it will be useful,
  12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14 *  GNU General Public License for more details.
  15 *
  16 *  You should have received a copy of the GNU General Public License
  17 *  along with this program; if not, write to the Free Software
  18 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  19 */
  20#include <linux/device.h>
  21#include <linux/export.h>
  22#include <linux/file.h>
  23#include <linux/posix-clock.h>
  24#include <linux/slab.h>
  25#include <linux/syscalls.h>
  26#include <linux/uaccess.h>
  27
  28#include "posix-timers.h"
  29
  30static void delete_clock(struct kref *kref);
  31
  32/*
  33 * Returns NULL if the posix_clock instance attached to 'fp' is old and stale.
  34 */
  35static struct posix_clock *get_posix_clock(struct file *fp)
  36{
  37        struct posix_clock *clk = fp->private_data;
  38
  39        down_read(&clk->rwsem);
  40
  41        if (!clk->zombie)
  42                return clk;
  43
  44        up_read(&clk->rwsem);
  45
  46        return NULL;
  47}
  48
  49static void put_posix_clock(struct posix_clock *clk)
  50{
  51        up_read(&clk->rwsem);
  52}
  53
  54static ssize_t posix_clock_read(struct file *fp, char __user *buf,
  55                                size_t count, loff_t *ppos)
  56{
  57        struct posix_clock *clk = get_posix_clock(fp);
  58        int err = -EINVAL;
  59
  60        if (!clk)
  61                return -ENODEV;
  62
  63        if (clk->ops.read)
  64                err = clk->ops.read(clk, fp->f_flags, buf, count);
  65
  66        put_posix_clock(clk);
  67
  68        return err;
  69}
  70
  71static __poll_t posix_clock_poll(struct file *fp, poll_table *wait)
  72{
  73        struct posix_clock *clk = get_posix_clock(fp);
  74        __poll_t result = 0;
  75
  76        if (!clk)
  77                return EPOLLERR;
  78
  79        if (clk->ops.poll)
  80                result = clk->ops.poll(clk, fp, wait);
  81
  82        put_posix_clock(clk);
  83
  84        return result;
  85}
  86
  87static long posix_clock_ioctl(struct file *fp,
  88                              unsigned int cmd, unsigned long arg)
  89{
  90        struct posix_clock *clk = get_posix_clock(fp);
  91        int err = -ENOTTY;
  92
  93        if (!clk)
  94                return -ENODEV;
  95
  96        if (clk->ops.ioctl)
  97                err = clk->ops.ioctl(clk, cmd, arg);
  98
  99        put_posix_clock(clk);
 100
 101        return err;
 102}
 103
 104#ifdef CONFIG_COMPAT
 105static long posix_clock_compat_ioctl(struct file *fp,
 106                                     unsigned int cmd, unsigned long arg)
 107{
 108        struct posix_clock *clk = get_posix_clock(fp);
 109        int err = -ENOTTY;
 110
 111        if (!clk)
 112                return -ENODEV;
 113
 114        if (clk->ops.ioctl)
 115                err = clk->ops.ioctl(clk, cmd, arg);
 116
 117        put_posix_clock(clk);
 118
 119        return err;
 120}
 121#endif
 122
 123static int posix_clock_open(struct inode *inode, struct file *fp)
 124{
 125        int err;
 126        struct posix_clock *clk =
 127                container_of(inode->i_cdev, struct posix_clock, cdev);
 128
 129        down_read(&clk->rwsem);
 130
 131        if (clk->zombie) {
 132                err = -ENODEV;
 133                goto out;
 134        }
 135        if (clk->ops.open)
 136                err = clk->ops.open(clk, fp->f_mode);
 137        else
 138                err = 0;
 139
 140        if (!err) {
 141                kref_get(&clk->kref);
 142                fp->private_data = clk;
 143        }
 144out:
 145        up_read(&clk->rwsem);
 146        return err;
 147}
 148
 149static int posix_clock_release(struct inode *inode, struct file *fp)
 150{
 151        struct posix_clock *clk = fp->private_data;
 152        int err = 0;
 153
 154        if (clk->ops.release)
 155                err = clk->ops.release(clk);
 156
 157        kref_put(&clk->kref, delete_clock);
 158
 159        fp->private_data = NULL;
 160
 161        return err;
 162}
 163
 164static const struct file_operations posix_clock_file_operations = {
 165        .owner          = THIS_MODULE,
 166        .llseek         = no_llseek,
 167        .read           = posix_clock_read,
 168        .poll           = posix_clock_poll,
 169        .unlocked_ioctl = posix_clock_ioctl,
 170        .open           = posix_clock_open,
 171        .release        = posix_clock_release,
 172#ifdef CONFIG_COMPAT
 173        .compat_ioctl   = posix_clock_compat_ioctl,
 174#endif
 175};
 176
 177int posix_clock_register(struct posix_clock *clk, dev_t devid)
 178{
 179        int err;
 180
 181        kref_init(&clk->kref);
 182        init_rwsem(&clk->rwsem);
 183
 184        cdev_init(&clk->cdev, &posix_clock_file_operations);
 185        clk->cdev.owner = clk->ops.owner;
 186        err = cdev_add(&clk->cdev, devid, 1);
 187
 188        return err;
 189}
 190EXPORT_SYMBOL_GPL(posix_clock_register);
 191
 192static void delete_clock(struct kref *kref)
 193{
 194        struct posix_clock *clk = container_of(kref, struct posix_clock, kref);
 195
 196        if (clk->release)
 197                clk->release(clk);
 198}
 199
 200void posix_clock_unregister(struct posix_clock *clk)
 201{
 202        cdev_del(&clk->cdev);
 203
 204        down_write(&clk->rwsem);
 205        clk->zombie = true;
 206        up_write(&clk->rwsem);
 207
 208        kref_put(&clk->kref, delete_clock);
 209}
 210EXPORT_SYMBOL_GPL(posix_clock_unregister);
 211
 212struct posix_clock_desc {
 213        struct file *fp;
 214        struct posix_clock *clk;
 215};
 216
 217static int get_clock_desc(const clockid_t id, struct posix_clock_desc *cd)
 218{
 219        struct file *fp = fget(clockid_to_fd(id));
 220        int err = -EINVAL;
 221
 222        if (!fp)
 223                return err;
 224
 225        if (fp->f_op->open != posix_clock_open || !fp->private_data)
 226                goto out;
 227
 228        cd->fp = fp;
 229        cd->clk = get_posix_clock(fp);
 230
 231        err = cd->clk ? 0 : -ENODEV;
 232out:
 233        if (err)
 234                fput(fp);
 235        return err;
 236}
 237
 238static void put_clock_desc(struct posix_clock_desc *cd)
 239{
 240        put_posix_clock(cd->clk);
 241        fput(cd->fp);
 242}
 243
 244static int pc_clock_adjtime(clockid_t id, struct timex *tx)
 245{
 246        struct posix_clock_desc cd;
 247        int err;
 248
 249        err = get_clock_desc(id, &cd);
 250        if (err)
 251                return err;
 252
 253        if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
 254                err = -EACCES;
 255                goto out;
 256        }
 257
 258        if (cd.clk->ops.clock_adjtime)
 259                err = cd.clk->ops.clock_adjtime(cd.clk, tx);
 260        else
 261                err = -EOPNOTSUPP;
 262out:
 263        put_clock_desc(&cd);
 264
 265        return err;
 266}
 267
 268static int pc_clock_gettime(clockid_t id, struct timespec64 *ts)
 269{
 270        struct posix_clock_desc cd;
 271        int err;
 272
 273        err = get_clock_desc(id, &cd);
 274        if (err)
 275                return err;
 276
 277        if (cd.clk->ops.clock_gettime)
 278                err = cd.clk->ops.clock_gettime(cd.clk, ts);
 279        else
 280                err = -EOPNOTSUPP;
 281
 282        put_clock_desc(&cd);
 283
 284        return err;
 285}
 286
 287static int pc_clock_getres(clockid_t id, struct timespec64 *ts)
 288{
 289        struct posix_clock_desc cd;
 290        int err;
 291
 292        err = get_clock_desc(id, &cd);
 293        if (err)
 294                return err;
 295
 296        if (cd.clk->ops.clock_getres)
 297                err = cd.clk->ops.clock_getres(cd.clk, ts);
 298        else
 299                err = -EOPNOTSUPP;
 300
 301        put_clock_desc(&cd);
 302
 303        return err;
 304}
 305
 306static int pc_clock_settime(clockid_t id, const struct timespec64 *ts)
 307{
 308        struct posix_clock_desc cd;
 309        int err;
 310
 311        err = get_clock_desc(id, &cd);
 312        if (err)
 313                return err;
 314
 315        if ((cd.fp->f_mode & FMODE_WRITE) == 0) {
 316                err = -EACCES;
 317                goto out;
 318        }
 319
 320        if (cd.clk->ops.clock_settime)
 321                err = cd.clk->ops.clock_settime(cd.clk, ts);
 322        else
 323                err = -EOPNOTSUPP;
 324out:
 325        put_clock_desc(&cd);
 326
 327        return err;
 328}
 329
 330const struct k_clock clock_posix_dynamic = {
 331        .clock_getres   = pc_clock_getres,
 332        .clock_set      = pc_clock_settime,
 333        .clock_get      = pc_clock_gettime,
 334        .clock_adj      = pc_clock_adjtime,
 335};
 336