linux/drivers/pps/kapi.c
<<
>>
Prefs
   1/*
   2 * kernel API
   3 *
   4 *
   5 * Copyright (C) 2005-2009   Rodolfo Giometti <giometti@linux.it>
   6 *
   7 *   This program is free software; you can redistribute it and/or modify
   8 *   it under the terms of the GNU General Public License as published by
   9 *   the Free Software Foundation; either version 2 of the License, or
  10 *   (at your option) any later version.
  11 *
  12 *   This program is distributed in the hope that it will be useful,
  13 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
  14 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  15 *   GNU General Public License for more details.
  16 *
  17 *   You should have received a copy of the GNU General Public License
  18 *   along with this program; if not, write to the Free Software
  19 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  20 */
  21
  22
  23#include <linux/kernel.h>
  24#include <linux/module.h>
  25#include <linux/init.h>
  26#include <linux/sched.h>
  27#include <linux/time.h>
  28#include <linux/spinlock.h>
  29#include <linux/idr.h>
  30#include <linux/fs.h>
  31#include <linux/pps_kernel.h>
  32#include <linux/slab.h>
  33
  34/*
  35 * Global variables
  36 */
  37
  38DEFINE_SPINLOCK(pps_idr_lock);
  39DEFINE_IDR(pps_idr);
  40
  41/*
  42 * Local functions
  43 */
  44
  45static void pps_add_offset(struct pps_ktime *ts, struct pps_ktime *offset)
  46{
  47        ts->nsec += offset->nsec;
  48        while (ts->nsec >= NSEC_PER_SEC) {
  49                ts->nsec -= NSEC_PER_SEC;
  50                ts->sec++;
  51        }
  52        while (ts->nsec < 0) {
  53                ts->nsec += NSEC_PER_SEC;
  54                ts->sec--;
  55        }
  56        ts->sec += offset->sec;
  57}
  58
  59/*
  60 * Exported functions
  61 */
  62
  63/* pps_get_source - find a PPS source
  64 * @source: the PPS source ID.
  65 *
  66 * This function is used to find an already registered PPS source into the
  67 * system.
  68 *
  69 * The function returns NULL if found nothing, otherwise it returns a pointer
  70 * to the PPS source data struct (the refcounter is incremented by 1).
  71 */
  72
  73struct pps_device *pps_get_source(int source)
  74{
  75        struct pps_device *pps;
  76        unsigned long flags;
  77
  78        spin_lock_irqsave(&pps_idr_lock, flags);
  79
  80        pps = idr_find(&pps_idr, source);
  81        if (pps != NULL)
  82                atomic_inc(&pps->usage);
  83
  84        spin_unlock_irqrestore(&pps_idr_lock, flags);
  85
  86        return pps;
  87}
  88
  89/* pps_put_source - free the PPS source data
  90 * @pps: a pointer to the PPS source.
  91 *
  92 * This function is used to free a PPS data struct if its refcount is 0.
  93 */
  94
  95void pps_put_source(struct pps_device *pps)
  96{
  97        unsigned long flags;
  98
  99        spin_lock_irqsave(&pps_idr_lock, flags);
 100        BUG_ON(atomic_read(&pps->usage) == 0);
 101
 102        if (!atomic_dec_and_test(&pps->usage)) {
 103                pps = NULL;
 104                goto exit;
 105        }
 106
 107        /* No more reference to the PPS source. We can safely remove the
 108         * PPS data struct.
 109         */
 110        idr_remove(&pps_idr, pps->id);
 111
 112exit:
 113        spin_unlock_irqrestore(&pps_idr_lock, flags);
 114        kfree(pps);
 115}
 116
 117/* pps_register_source - add a PPS source in the system
 118 * @info: the PPS info struct
 119 * @default_params: the default PPS parameters of the new source
 120 *
 121 * This function is used to add a new PPS source in the system. The new
 122 * source is described by info's fields and it will have, as default PPS
 123 * parameters, the ones specified into default_params.
 124 *
 125 * The function returns, in case of success, the PPS source ID.
 126 */
 127
 128int pps_register_source(struct pps_source_info *info, int default_params)
 129{
 130        struct pps_device *pps;
 131        int id;
 132        int err;
 133
 134        /* Sanity checks */
 135        if ((info->mode & default_params) != default_params) {
 136                printk(KERN_ERR "pps: %s: unsupported default parameters\n",
 137                                        info->name);
 138                err = -EINVAL;
 139                goto pps_register_source_exit;
 140        }
 141        if ((info->mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)) != 0 &&
 142                        info->echo == NULL) {
 143                printk(KERN_ERR "pps: %s: echo function is not defined\n",
 144                                        info->name);
 145                err = -EINVAL;
 146                goto pps_register_source_exit;
 147        }
 148        if ((info->mode & (PPS_TSFMT_TSPEC | PPS_TSFMT_NTPFP)) == 0) {
 149                printk(KERN_ERR "pps: %s: unspecified time format\n",
 150                                        info->name);
 151                err = -EINVAL;
 152                goto pps_register_source_exit;
 153        }
 154
 155        /* Allocate memory for the new PPS source struct */
 156        pps = kzalloc(sizeof(struct pps_device), GFP_KERNEL);
 157        if (pps == NULL) {
 158                err = -ENOMEM;
 159                goto pps_register_source_exit;
 160        }
 161
 162        /* These initializations must be done before calling idr_get_new()
 163         * in order to avoid reces into pps_event().
 164         */
 165        pps->params.api_version = PPS_API_VERS;
 166        pps->params.mode = default_params;
 167        pps->info = *info;
 168
 169        init_waitqueue_head(&pps->queue);
 170        spin_lock_init(&pps->lock);
 171        atomic_set(&pps->usage, 1);
 172
 173        /* Get new ID for the new PPS source */
 174        if (idr_pre_get(&pps_idr, GFP_KERNEL) == 0) {
 175                err = -ENOMEM;
 176                goto kfree_pps;
 177        }
 178
 179        spin_lock_irq(&pps_idr_lock);
 180
 181        /* Now really allocate the PPS source.
 182         * After idr_get_new() calling the new source will be freely available
 183         * into the kernel.
 184         */
 185        err = idr_get_new(&pps_idr, pps, &id);
 186        if (err < 0) {
 187                spin_unlock_irq(&pps_idr_lock);
 188                goto kfree_pps;
 189        }
 190
 191        id = id & MAX_ID_MASK;
 192        if (id >= PPS_MAX_SOURCES) {
 193                spin_unlock_irq(&pps_idr_lock);
 194
 195                printk(KERN_ERR "pps: %s: too many PPS sources in the system\n",
 196                                        info->name);
 197                err = -EBUSY;
 198                goto free_idr;
 199        }
 200        pps->id = id;
 201
 202        spin_unlock_irq(&pps_idr_lock);
 203
 204        /* Create the char device */
 205        err = pps_register_cdev(pps);
 206        if (err < 0) {
 207                printk(KERN_ERR "pps: %s: unable to create char device\n",
 208                                        info->name);
 209                goto free_idr;
 210        }
 211
 212        pr_info("new PPS source %s at ID %d\n", info->name, id);
 213
 214        return id;
 215
 216free_idr:
 217        spin_lock_irq(&pps_idr_lock);
 218        idr_remove(&pps_idr, id);
 219        spin_unlock_irq(&pps_idr_lock);
 220
 221kfree_pps:
 222        kfree(pps);
 223
 224pps_register_source_exit:
 225        printk(KERN_ERR "pps: %s: unable to register source\n", info->name);
 226
 227        return err;
 228}
 229EXPORT_SYMBOL(pps_register_source);
 230
 231/* pps_unregister_source - remove a PPS source from the system
 232 * @source: the PPS source ID
 233 *
 234 * This function is used to remove a previously registered PPS source from
 235 * the system.
 236 */
 237
 238void pps_unregister_source(int source)
 239{
 240        struct pps_device *pps;
 241
 242        spin_lock_irq(&pps_idr_lock);
 243        pps = idr_find(&pps_idr, source);
 244
 245        if (!pps) {
 246                BUG();
 247                spin_unlock_irq(&pps_idr_lock);
 248                return;
 249        }
 250        spin_unlock_irq(&pps_idr_lock);
 251
 252        pps_unregister_cdev(pps);
 253        pps_put_source(pps);
 254}
 255EXPORT_SYMBOL(pps_unregister_source);
 256
 257/* pps_event - register a PPS event into the system
 258 * @source: the PPS source ID
 259 * @ts: the event timestamp
 260 * @event: the event type
 261 * @data: userdef pointer
 262 *
 263 * This function is used by each PPS client in order to register a new
 264 * PPS event into the system (it's usually called inside an IRQ handler).
 265 *
 266 * If an echo function is associated with the PPS source it will be called
 267 * as:
 268 *      pps->info.echo(source, event, data);
 269 */
 270
 271void pps_event(int source, struct pps_ktime *ts, int event, void *data)
 272{
 273        struct pps_device *pps;
 274        unsigned long flags;
 275        int captured = 0;
 276
 277        if ((event & (PPS_CAPTUREASSERT | PPS_CAPTURECLEAR)) == 0) {
 278                printk(KERN_ERR "pps: unknown event (%x) for source %d\n",
 279                        event, source);
 280                return;
 281        }
 282
 283        pps = pps_get_source(source);
 284        if (!pps)
 285                return;
 286
 287        pr_debug("PPS event on source %d at %llu.%06u\n",
 288                        pps->id, (unsigned long long) ts->sec, ts->nsec);
 289
 290        spin_lock_irqsave(&pps->lock, flags);
 291
 292        /* Must call the echo function? */
 293        if ((pps->params.mode & (PPS_ECHOASSERT | PPS_ECHOCLEAR)))
 294                pps->info.echo(source, event, data);
 295
 296        /* Check the event */
 297        pps->current_mode = pps->params.mode;
 298        if ((event & PPS_CAPTUREASSERT) &
 299                        (pps->params.mode & PPS_CAPTUREASSERT)) {
 300                /* We have to add an offset? */
 301                if (pps->params.mode & PPS_OFFSETASSERT)
 302                        pps_add_offset(ts, &pps->params.assert_off_tu);
 303
 304                /* Save the time stamp */
 305                pps->assert_tu = *ts;
 306                pps->assert_sequence++;
 307                pr_debug("capture assert seq #%u for source %d\n",
 308                        pps->assert_sequence, source);
 309
 310                captured = ~0;
 311        }
 312        if ((event & PPS_CAPTURECLEAR) &
 313                        (pps->params.mode & PPS_CAPTURECLEAR)) {
 314                /* We have to add an offset? */
 315                if (pps->params.mode & PPS_OFFSETCLEAR)
 316                        pps_add_offset(ts, &pps->params.clear_off_tu);
 317
 318                /* Save the time stamp */
 319                pps->clear_tu = *ts;
 320                pps->clear_sequence++;
 321                pr_debug("capture clear seq #%u for source %d\n",
 322                        pps->clear_sequence, source);
 323
 324                captured = ~0;
 325        }
 326
 327        /* Wake up iif captured somthing */
 328        if (captured) {
 329                pps->go = ~0;
 330                wake_up_interruptible(&pps->queue);
 331
 332                kill_fasync(&pps->async_queue, SIGIO, POLL_IN);
 333        }
 334
 335        spin_unlock_irqrestore(&pps->lock, flags);
 336
 337        /* Now we can release the PPS source for (possible) deregistration */
 338        pps_put_source(pps);
 339}
 340EXPORT_SYMBOL(pps_event);
 341
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.