linux/drivers/staging/comedi/drivers.c
<<
>>
Prefs
   1/*
   2    module/drivers.c
   3    functions for manipulating drivers
   4
   5    COMEDI - Linux Control and Measurement Device Interface
   6    Copyright (C) 1997-2000 David A. Schleef <ds@schleef.org>
   7
   8    This program is free software; you can redistribute it and/or modify
   9    it under the terms of the GNU General Public License as published by
  10    the Free Software Foundation; either version 2 of the License, or
  11    (at your option) any later version.
  12
  13    This program is distributed in the hope that it will be useful,
  14    but WITHOUT ANY WARRANTY; without even the implied warranty of
  15    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16    GNU General Public License for more details.
  17
  18    You should have received a copy of the GNU General Public License
  19    along with this program; if not, write to the Free Software
  20    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
  21
  22*/
  23
  24#include <linux/device.h>
  25#include <linux/module.h>
  26#include <linux/errno.h>
  27#include <linux/kconfig.h>
  28#include <linux/kernel.h>
  29#include <linux/sched.h>
  30#include <linux/fcntl.h>
  31#include <linux/delay.h>
  32#include <linux/ioport.h>
  33#include <linux/mm.h>
  34#include <linux/slab.h>
  35#include <linux/highmem.h>      /* for SuSE brokenness */
  36#include <linux/vmalloc.h>
  37#include <linux/cdev.h>
  38#include <linux/dma-mapping.h>
  39#include <linux/io.h>
  40#include <linux/interrupt.h>
  41
  42#include "comedidev.h"
  43#include "comedi_internal.h"
  44
  45struct comedi_driver *comedi_drivers;
  46
  47int comedi_set_hw_dev(struct comedi_device *dev, struct device *hw_dev)
  48{
  49        if (hw_dev == dev->hw_dev)
  50                return 0;
  51        if (dev->hw_dev != NULL)
  52                return -EEXIST;
  53        dev->hw_dev = get_device(hw_dev);
  54        return 0;
  55}
  56EXPORT_SYMBOL_GPL(comedi_set_hw_dev);
  57
  58static void comedi_clear_hw_dev(struct comedi_device *dev)
  59{
  60        put_device(dev->hw_dev);
  61        dev->hw_dev = NULL;
  62}
  63
  64int comedi_alloc_subdevices(struct comedi_device *dev, int num_subdevices)
  65{
  66        struct comedi_subdevice *s;
  67        int i;
  68
  69        if (num_subdevices < 1)
  70                return -EINVAL;
  71
  72        s = kcalloc(num_subdevices, sizeof(*s), GFP_KERNEL);
  73        if (!s)
  74                return -ENOMEM;
  75        dev->subdevices = s;
  76        dev->n_subdevices = num_subdevices;
  77
  78        for (i = 0; i < num_subdevices; ++i) {
  79                s = &dev->subdevices[i];
  80                s->device = dev;
  81                s->index = i;
  82                s->async_dma_dir = DMA_NONE;
  83                spin_lock_init(&s->spin_lock);
  84                s->minor = -1;
  85        }
  86        return 0;
  87}
  88EXPORT_SYMBOL_GPL(comedi_alloc_subdevices);
  89
  90void comedi_spriv_free(struct comedi_device *dev, int subdev_num)
  91{
  92        struct comedi_subdevice *s;
  93
  94        if (dev->subdevices && subdev_num < dev->n_subdevices) {
  95                s = &dev->subdevices[subdev_num];
  96                kfree(s->private);
  97                s->private = NULL;
  98        }
  99}
 100EXPORT_SYMBOL_GPL(comedi_spriv_free);
 101
 102static void cleanup_device(struct comedi_device *dev)
 103{
 104        int i;
 105        struct comedi_subdevice *s;
 106
 107        if (dev->subdevices) {
 108                for (i = 0; i < dev->n_subdevices; i++) {
 109                        s = &dev->subdevices[i];
 110                        comedi_free_subdevice_minor(s);
 111                        if (s->async) {
 112                                comedi_buf_alloc(dev, s, 0);
 113                                kfree(s->async);
 114                        }
 115                }
 116                kfree(dev->subdevices);
 117                dev->subdevices = NULL;
 118                dev->n_subdevices = 0;
 119        }
 120        kfree(dev->private);
 121        dev->private = NULL;
 122        dev->driver = NULL;
 123        dev->board_name = NULL;
 124        dev->board_ptr = NULL;
 125        dev->iobase = 0;
 126        dev->iolen = 0;
 127        dev->ioenabled = false;
 128        dev->irq = 0;
 129        dev->read_subdev = NULL;
 130        dev->write_subdev = NULL;
 131        dev->open = NULL;
 132        dev->close = NULL;
 133        comedi_clear_hw_dev(dev);
 134}
 135
 136void comedi_device_detach(struct comedi_device *dev)
 137{
 138        dev->attached = false;
 139        if (dev->driver)
 140                dev->driver->detach(dev);
 141        cleanup_device(dev);
 142}
 143
 144static int poll_invalid(struct comedi_device *dev, struct comedi_subdevice *s)
 145{
 146        return -EINVAL;
 147}
 148
 149int insn_inval(struct comedi_device *dev, struct comedi_subdevice *s,
 150               struct comedi_insn *insn, unsigned int *data)
 151{
 152        return -EINVAL;
 153}
 154
 155static int insn_rw_emulate_bits(struct comedi_device *dev,
 156                                struct comedi_subdevice *s,
 157                                struct comedi_insn *insn, unsigned int *data)
 158{
 159        struct comedi_insn new_insn;
 160        int ret;
 161        static const unsigned channels_per_bitfield = 32;
 162
 163        unsigned chan = CR_CHAN(insn->chanspec);
 164        const unsigned base_bitfield_channel =
 165            (chan < channels_per_bitfield) ? 0 : chan;
 166        unsigned int new_data[2];
 167        memset(new_data, 0, sizeof(new_data));
 168        memset(&new_insn, 0, sizeof(new_insn));
 169        new_insn.insn = INSN_BITS;
 170        new_insn.chanspec = base_bitfield_channel;
 171        new_insn.n = 2;
 172        new_insn.subdev = insn->subdev;
 173
 174        if (insn->insn == INSN_WRITE) {
 175                if (!(s->subdev_flags & SDF_WRITABLE))
 176                        return -EINVAL;
 177                new_data[0] = 1 << (chan - base_bitfield_channel); /* mask */
 178                new_data[1] = data[0] ? (1 << (chan - base_bitfield_channel))
 179                              : 0; /* bits */
 180        }
 181
 182        ret = s->insn_bits(dev, s, &new_insn, new_data);
 183        if (ret < 0)
 184                return ret;
 185
 186        if (insn->insn == INSN_READ)
 187                data[0] = (new_data[1] >> (chan - base_bitfield_channel)) & 1;
 188
 189        return 1;
 190}
 191
 192static int __comedi_device_postconfig_async(struct comedi_device *dev,
 193                                            struct comedi_subdevice *s)
 194{
 195        struct comedi_async *async;
 196        unsigned int buf_size;
 197        int ret;
 198
 199        if ((s->subdev_flags & (SDF_CMD_READ | SDF_CMD_WRITE)) == 0) {
 200                dev_warn(dev->class_dev,
 201                         "async subdevices must support SDF_CMD_READ or SDF_CMD_WRITE\n");
 202                return -EINVAL;
 203        }
 204        if (!s->do_cmdtest) {
 205                dev_warn(dev->class_dev,
 206                         "async subdevices must have a do_cmdtest() function\n");
 207                return -EINVAL;
 208        }
 209
 210        async = kzalloc(sizeof(*async), GFP_KERNEL);
 211        if (!async)
 212                return -ENOMEM;
 213
 214        init_waitqueue_head(&async->wait_head);
 215        async->subdevice = s;
 216        s->async = async;
 217
 218        async->max_bufsize = comedi_default_buf_maxsize_kb * 1024;
 219        buf_size = comedi_default_buf_size_kb * 1024;
 220        if (buf_size > async->max_bufsize)
 221                buf_size = async->max_bufsize;
 222
 223        if (comedi_buf_alloc(dev, s, buf_size) < 0) {
 224                dev_warn(dev->class_dev, "Buffer allocation failed\n");
 225                return -ENOMEM;
 226        }
 227        if (s->buf_change) {
 228                ret = s->buf_change(dev, s, buf_size);
 229                if (ret < 0)
 230                        return ret;
 231        }
 232
 233        comedi_alloc_subdevice_minor(s);
 234
 235        return 0;
 236}
 237
 238static int __comedi_device_postconfig(struct comedi_device *dev)
 239{
 240        struct comedi_subdevice *s;
 241        int ret;
 242        int i;
 243
 244        for (i = 0; i < dev->n_subdevices; i++) {
 245                s = &dev->subdevices[i];
 246
 247                if (s->type == COMEDI_SUBD_UNUSED)
 248                        continue;
 249
 250                if (s->len_chanlist == 0)
 251                        s->len_chanlist = 1;
 252
 253                if (s->do_cmd) {
 254                        ret = __comedi_device_postconfig_async(dev, s);
 255                        if (ret)
 256                                return ret;
 257                }
 258
 259                if (!s->range_table && !s->range_table_list)
 260                        s->range_table = &range_unknown;
 261
 262                if (!s->insn_read && s->insn_bits)
 263                        s->insn_read = insn_rw_emulate_bits;
 264                if (!s->insn_write && s->insn_bits)
 265                        s->insn_write = insn_rw_emulate_bits;
 266
 267                if (!s->insn_read)
 268                        s->insn_read = insn_inval;
 269                if (!s->insn_write)
 270                        s->insn_write = insn_inval;
 271                if (!s->insn_bits)
 272                        s->insn_bits = insn_inval;
 273                if (!s->insn_config)
 274                        s->insn_config = insn_inval;
 275
 276                if (!s->poll)
 277                        s->poll = poll_invalid;
 278        }
 279
 280        return 0;
 281}
 282
 283/* do a little post-config cleanup */
 284static int comedi_device_postconfig(struct comedi_device *dev)
 285{
 286        int ret;
 287
 288        ret = __comedi_device_postconfig(dev);
 289        if (ret < 0)
 290                return ret;
 291        smp_wmb();
 292        dev->attached = true;
 293        return 0;
 294}
 295
 296/*
 297 * Generic recognize function for drivers that register their supported
 298 * board names.
 299 *
 300 * 'driv->board_name' points to a 'const char *' member within the
 301 * zeroth element of an array of some private board information
 302 * structure, say 'struct foo_board' containing a member 'const char
 303 * *board_name' that is initialized to point to a board name string that
 304 * is one of the candidates matched against this function's 'name'
 305 * parameter.
 306 *
 307 * 'driv->offset' is the size of the private board information
 308 * structure, say 'sizeof(struct foo_board)', and 'driv->num_names' is
 309 * the length of the array of private board information structures.
 310 *
 311 * If one of the board names in the array of private board information
 312 * structures matches the name supplied to this function, the function
 313 * returns a pointer to the pointer to the board name, otherwise it
 314 * returns NULL.  The return value ends up in the 'board_ptr' member of
 315 * a 'struct comedi_device' that the low-level comedi driver's
 316 * 'attach()' hook can convert to a point to a particular element of its
 317 * array of private board information structures by subtracting the
 318 * offset of the member that points to the board name.  (No subtraction
 319 * is required if the board name pointer is the first member of the
 320 * private board information structure, which is generally the case.)
 321 */
 322static void *comedi_recognize(struct comedi_driver *driv, const char *name)
 323{
 324        char **name_ptr = (char **)driv->board_name;
 325        int i;
 326
 327        for (i = 0; i < driv->num_names; i++) {
 328                if (strcmp(*name_ptr, name) == 0)
 329                        return name_ptr;
 330                name_ptr = (void *)name_ptr + driv->offset;
 331        }
 332
 333        return NULL;
 334}
 335
 336static void comedi_report_boards(struct comedi_driver *driv)
 337{
 338        unsigned int i;
 339        const char *const *name_ptr;
 340
 341        pr_info("comedi: valid board names for %s driver are:\n",
 342                driv->driver_name);
 343
 344        name_ptr = driv->board_name;
 345        for (i = 0; i < driv->num_names; i++) {
 346                pr_info(" %s\n", *name_ptr);
 347                name_ptr = (const char **)((char *)name_ptr + driv->offset);
 348        }
 349
 350        if (driv->num_names == 0)
 351                pr_info(" %s\n", driv->driver_name);
 352}
 353
 354/**
 355 * __comedi_request_region() - Request an I/O reqion for a legacy driver.
 356 * @dev: comedi_device struct
 357 * @start: base address of the I/O reqion
 358 * @len: length of the I/O region
 359 */
 360int __comedi_request_region(struct comedi_device *dev,
 361                            unsigned long start, unsigned long len)
 362{
 363        if (!start) {
 364                dev_warn(dev->class_dev,
 365                         "%s: a I/O base address must be specified\n",
 366                         dev->board_name);
 367                return -EINVAL;
 368        }
 369
 370        if (!request_region(start, len, dev->board_name)) {
 371                dev_warn(dev->class_dev, "%s: I/O port conflict (%#lx,%lu)\n",
 372                         dev->board_name, start, len);
 373                return -EIO;
 374        }
 375
 376        return 0;
 377}
 378EXPORT_SYMBOL_GPL(__comedi_request_region);
 379
 380/**
 381 * comedi_request_region() - Request an I/O reqion for a legacy driver.
 382 * @dev: comedi_device struct
 383 * @start: base address of the I/O reqion
 384 * @len: length of the I/O region
 385 */
 386int comedi_request_region(struct comedi_device *dev,
 387                          unsigned long start, unsigned long len)
 388{
 389        int ret;
 390
 391        ret = __comedi_request_region(dev, start, len);
 392        if (ret == 0) {
 393                dev->iobase = start;
 394                dev->iolen = len;
 395        }
 396
 397        return ret;
 398}
 399EXPORT_SYMBOL_GPL(comedi_request_region);
 400
 401/**
 402 * comedi_legacy_detach() - A generic (*detach) function for legacy drivers.
 403 * @dev: comedi_device struct
 404 */
 405void comedi_legacy_detach(struct comedi_device *dev)
 406{
 407        if (dev->irq) {
 408                free_irq(dev->irq, dev);
 409                dev->irq = 0;
 410        }
 411        if (dev->iobase && dev->iolen) {
 412                release_region(dev->iobase, dev->iolen);
 413                dev->iobase = 0;
 414                dev->iolen = 0;
 415        }
 416}
 417EXPORT_SYMBOL_GPL(comedi_legacy_detach);
 418
 419int comedi_device_attach(struct comedi_device *dev, struct comedi_devconfig *it)
 420{
 421        struct comedi_driver *driv;
 422        int ret;
 423
 424        if (dev->attached)
 425                return -EBUSY;
 426
 427        for (driv = comedi_drivers; driv; driv = driv->next) {
 428                if (!try_module_get(driv->module))
 429                        continue;
 430                if (driv->num_names) {
 431                        dev->board_ptr = comedi_recognize(driv, it->board_name);
 432                        if (dev->board_ptr)
 433                                break;
 434                } else if (strcmp(driv->driver_name, it->board_name) == 0)
 435                        break;
 436                module_put(driv->module);
 437        }
 438        if (driv == NULL) {
 439                /*  recognize has failed if we get here */
 440                /*  report valid board names before returning error */
 441                for (driv = comedi_drivers; driv; driv = driv->next) {
 442                        if (!try_module_get(driv->module))
 443                                continue;
 444                        comedi_report_boards(driv);
 445                        module_put(driv->module);
 446                }
 447                return -EIO;
 448        }
 449        if (driv->attach == NULL) {
 450                /* driver does not support manual configuration */
 451                dev_warn(dev->class_dev,
 452                         "driver '%s' does not support attach using comedi_config\n",
 453                         driv->driver_name);
 454                module_put(driv->module);
 455                return -ENOSYS;
 456        }
 457        /* initialize dev->driver here so
 458         * comedi_error() can be called from attach */
 459        dev->driver = driv;
 460        dev->board_name = dev->board_ptr ? *(const char **)dev->board_ptr
 461                                         : dev->driver->driver_name;
 462        ret = driv->attach(dev, it);
 463        if (ret >= 0)
 464                ret = comedi_device_postconfig(dev);
 465        if (ret < 0) {
 466                comedi_device_detach(dev);
 467                module_put(dev->driver->module);
 468        }
 469        /* On success, the driver module count has been incremented. */
 470        return ret;
 471}
 472
 473int comedi_auto_config(struct device *hardware_device,
 474                       struct comedi_driver *driver, unsigned long context)
 475{
 476        struct comedi_device *dev;
 477        int ret;
 478
 479        if (!hardware_device) {
 480                pr_warn("BUG! comedi_auto_config called with NULL hardware_device\n");
 481                return -EINVAL;
 482        }
 483        if (!driver) {
 484                dev_warn(hardware_device,
 485                         "BUG! comedi_auto_config called with NULL comedi driver\n");
 486                return -EINVAL;
 487        }
 488
 489        if (!driver->auto_attach) {
 490                dev_warn(hardware_device,
 491                         "BUG! comedi driver '%s' has no auto_attach handler\n",
 492                         driver->driver_name);
 493                return -EINVAL;
 494        }
 495
 496        dev = comedi_alloc_board_minor(hardware_device);
 497        if (IS_ERR(dev))
 498                return PTR_ERR(dev);
 499        /* Note: comedi_alloc_board_minor() locked dev->mutex. */
 500
 501        dev->driver = driver;
 502        dev->board_name = dev->driver->driver_name;
 503        ret = driver->auto_attach(dev, context);
 504        if (ret >= 0)
 505                ret = comedi_device_postconfig(dev);
 506        if (ret < 0)
 507                comedi_device_detach(dev);
 508        mutex_unlock(&dev->mutex);
 509
 510        if (ret < 0)
 511                comedi_release_hardware_device(hardware_device);
 512        return ret;
 513}
 514EXPORT_SYMBOL_GPL(comedi_auto_config);
 515
 516void comedi_auto_unconfig(struct device *hardware_device)
 517{
 518        if (hardware_device == NULL)
 519                return;
 520        comedi_release_hardware_device(hardware_device);
 521}
 522EXPORT_SYMBOL_GPL(comedi_auto_unconfig);
 523
 524int comedi_driver_register(struct comedi_driver *driver)
 525{
 526        driver->next = comedi_drivers;
 527        comedi_drivers = driver;
 528
 529        return 0;
 530}
 531EXPORT_SYMBOL_GPL(comedi_driver_register);
 532
 533int comedi_driver_unregister(struct comedi_driver *driver)
 534{
 535        struct comedi_driver *prev;
 536        int i;
 537
 538        /* check for devices using this driver */
 539        for (i = 0; i < COMEDI_NUM_BOARD_MINORS; i++) {
 540                struct comedi_device *dev = comedi_dev_from_minor(i);
 541
 542                if (!dev)
 543                        continue;
 544
 545                mutex_lock(&dev->mutex);
 546                if (dev->attached && dev->driver == driver) {
 547                        if (dev->use_count)
 548                                dev_warn(dev->class_dev,
 549                                         "BUG! detaching device with use_count=%d\n",
 550                                         dev->use_count);
 551                        comedi_device_detach(dev);
 552                }
 553                mutex_unlock(&dev->mutex);
 554        }
 555
 556        if (comedi_drivers == driver) {
 557                comedi_drivers = driver->next;
 558                return 0;
 559        }
 560
 561        for (prev = comedi_drivers; prev->next; prev = prev->next) {
 562                if (prev->next == driver) {
 563                        prev->next = driver->next;
 564                        return 0;
 565                }
 566        }
 567        return -EINVAL;
 568}
 569EXPORT_SYMBOL_GPL(comedi_driver_unregister);
 570