Logo Search packages:      
Sourcecode: linux version File versions  Download package

core.c

/*
        Added support for the AMD Geode LX RNG
      (c) Copyright 2004-2005 Advanced Micro Devices, Inc.

      derived from

      Hardware driver for the Intel/AMD/VIA Random Number Generators (RNG)
      (c) Copyright 2003 Red Hat Inc <jgarzik@redhat.com>

      derived from

        Hardware driver for the AMD 768 Random Number Generator (RNG)
        (c) Copyright 2001 Red Hat Inc <alan@redhat.com>

      derived from

      Hardware driver for Intel i810 Random Number Generator (RNG)
      Copyright 2000,2001 Jeff Garzik <jgarzik@pobox.com>
      Copyright 2000,2001 Philipp Rumpf <prumpf@mandrakesoft.com>

      Added generic RNG API
      Copyright 2006 Michael Buesch <mbuesch@freenet.de>
      Copyright 2005 (c) MontaVista Software, Inc.

      Please read Documentation/hw_random.txt for details on use.

      ----------------------------------------------------------
      This software may be used and distributed according to the terms
        of the GNU General Public License, incorporated herein by reference.

 */


#include <linux/device.h>
#include <linux/hw_random.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/init.h>
#include <linux/miscdevice.h>
#include <linux/delay.h>
#include <asm/uaccess.h>


#define RNG_MODULE_NAME       "hw_random"
#define PFX             RNG_MODULE_NAME ": "
#define RNG_MISCDEV_MINOR     183 /* official */


static struct hwrng *current_rng;
static LIST_HEAD(rng_list);
static DEFINE_MUTEX(rng_mutex);


static inline int hwrng_init(struct hwrng *rng)
{
      if (!rng->init)
            return 0;
      return rng->init(rng);
}

static inline void hwrng_cleanup(struct hwrng *rng)
{
      if (rng && rng->cleanup)
            rng->cleanup(rng);
}

static inline int hwrng_data_present(struct hwrng *rng)
{
      if (!rng->data_present)
            return 1;
      return rng->data_present(rng);
}

static inline int hwrng_data_read(struct hwrng *rng, u32 *data)
{
      return rng->data_read(rng, data);
}


static int rng_dev_open(struct inode *inode, struct file *filp)
{
      /* enforce read-only access to this chrdev */
      if ((filp->f_mode & FMODE_READ) == 0)
            return -EINVAL;
      if (filp->f_mode & FMODE_WRITE)
            return -EINVAL;
      return 0;
}

static ssize_t rng_dev_read(struct file *filp, char __user *buf,
                      size_t size, loff_t *offp)
{
      u32 data;
      ssize_t ret = 0;
      int i, err = 0;
      int data_present;
      int bytes_read;

      while (size) {
            err = -ERESTARTSYS;
            if (mutex_lock_interruptible(&rng_mutex))
                  goto out;
            if (!current_rng) {
                  mutex_unlock(&rng_mutex);
                  err = -ENODEV;
                  goto out;
            }
            if (filp->f_flags & O_NONBLOCK) {
                  data_present = hwrng_data_present(current_rng);
            } else {
                  /* Some RNG require some time between data_reads to gather
                   * new entropy. Poll it.
                   */
                  for (i = 0; i < 20; i++) {
                        data_present = hwrng_data_present(current_rng);
                        if (data_present)
                              break;
                        udelay(10);
                  }
            }
            bytes_read = 0;
            if (data_present)
                  bytes_read = hwrng_data_read(current_rng, &data);
            mutex_unlock(&rng_mutex);

            err = -EAGAIN;
            if (!bytes_read && (filp->f_flags & O_NONBLOCK))
                  goto out;

            err = -EFAULT;
            while (bytes_read && size) {
                  if (put_user((u8)data, buf++))
                        goto out;
                  size--;
                  ret++;
                  bytes_read--;
                  data >>= 8;
            }

            if (need_resched())
                  schedule_timeout_interruptible(1);
            err = -ERESTARTSYS;
            if (signal_pending(current))
                  goto out;
      }
out:
      return ret ? : err;
}


static const struct file_operations rng_chrdev_ops = {
      .owner            = THIS_MODULE,
      .open       = rng_dev_open,
      .read       = rng_dev_read,
};

static struct miscdevice rng_miscdev = {
      .minor            = RNG_MISCDEV_MINOR,
      .name       = RNG_MODULE_NAME,
      .fops       = &rng_chrdev_ops,
};


static ssize_t hwrng_attr_current_store(struct device *dev,
                              struct device_attribute *attr,
                              const char *buf, size_t len)
{
      int err;
      struct hwrng *rng;

      err = mutex_lock_interruptible(&rng_mutex);
      if (err)
            return -ERESTARTSYS;
      err = -ENODEV;
      list_for_each_entry(rng, &rng_list, list) {
            if (strcmp(rng->name, buf) == 0) {
                  if (rng == current_rng) {
                        err = 0;
                        break;
                  }
                  err = hwrng_init(rng);
                  if (err)
                        break;
                  hwrng_cleanup(current_rng);
                  current_rng = rng;
                  err = 0;
                  break;
            }
      }
      mutex_unlock(&rng_mutex);

      return err ? : len;
}

static ssize_t hwrng_attr_current_show(struct device *dev,
                               struct device_attribute *attr,
                               char *buf)
{
      int err;
      ssize_t ret;
      const char *name = "none";

      err = mutex_lock_interruptible(&rng_mutex);
      if (err)
            return -ERESTARTSYS;
      if (current_rng)
            name = current_rng->name;
      ret = snprintf(buf, PAGE_SIZE, "%s\n", name);
      mutex_unlock(&rng_mutex);

      return ret;
}

static ssize_t hwrng_attr_available_show(struct device *dev,
                               struct device_attribute *attr,
                               char *buf)
{
      int err;
      ssize_t ret = 0;
      struct hwrng *rng;

      err = mutex_lock_interruptible(&rng_mutex);
      if (err)
            return -ERESTARTSYS;
      buf[0] = '\0';
      list_for_each_entry(rng, &rng_list, list) {
            strncat(buf, rng->name, PAGE_SIZE - ret - 1);
            ret += strlen(rng->name);
            strncat(buf, " ", PAGE_SIZE - ret - 1);
            ret++;
      }
      strncat(buf, "\n", PAGE_SIZE - ret - 1);
      ret++;
      mutex_unlock(&rng_mutex);

      return ret;
}

static DEVICE_ATTR(rng_current, S_IRUGO | S_IWUSR,
               hwrng_attr_current_show,
               hwrng_attr_current_store);
static DEVICE_ATTR(rng_available, S_IRUGO,
               hwrng_attr_available_show,
               NULL);


static void unregister_miscdev(void)
{
      device_remove_file(rng_miscdev.this_device, &dev_attr_rng_available);
      device_remove_file(rng_miscdev.this_device, &dev_attr_rng_current);
      misc_deregister(&rng_miscdev);
}

static int register_miscdev(void)
{
      int err;

      err = misc_register(&rng_miscdev);
      if (err)
            goto out;
      err = device_create_file(rng_miscdev.this_device,
                         &dev_attr_rng_current);
      if (err)
            goto err_misc_dereg;
      err = device_create_file(rng_miscdev.this_device,
                         &dev_attr_rng_available);
      if (err)
            goto err_remove_current;
out:
      return err;

err_remove_current:
      device_remove_file(rng_miscdev.this_device, &dev_attr_rng_current);
err_misc_dereg:
      misc_deregister(&rng_miscdev);
      goto out;
}

int hwrng_register(struct hwrng *rng)
{
      int must_register_misc;
      int err = -EINVAL;
      struct hwrng *old_rng, *tmp;

      if (rng->name == NULL ||
          rng->data_read == NULL)
            goto out;

      mutex_lock(&rng_mutex);

      /* Must not register two RNGs with the same name. */
      err = -EEXIST;
      list_for_each_entry(tmp, &rng_list, list) {
            if (strcmp(tmp->name, rng->name) == 0)
                  goto out_unlock;
      }

      must_register_misc = (current_rng == NULL);
      old_rng = current_rng;
      if (!old_rng) {
            err = hwrng_init(rng);
            if (err)
                  goto out_unlock;
            current_rng = rng;
      }
      err = 0;
      if (must_register_misc) {
            err = register_miscdev();
            if (err) {
                  if (!old_rng) {
                        hwrng_cleanup(rng);
                        current_rng = NULL;
                  }
                  goto out_unlock;
            }
      }
      INIT_LIST_HEAD(&rng->list);
      list_add_tail(&rng->list, &rng_list);
out_unlock:
      mutex_unlock(&rng_mutex);
out:
      return err;
}
EXPORT_SYMBOL_GPL(hwrng_register);

void hwrng_unregister(struct hwrng *rng)
{
      int err;

      mutex_lock(&rng_mutex);

      list_del(&rng->list);
      if (current_rng == rng) {
            hwrng_cleanup(rng);
            if (list_empty(&rng_list)) {
                  current_rng = NULL;
            } else {
                  current_rng = list_entry(rng_list.prev, struct hwrng, list);
                  err = hwrng_init(current_rng);
                  if (err)
                        current_rng = NULL;
            }
      }
      if (list_empty(&rng_list))
            unregister_miscdev();

      mutex_unlock(&rng_mutex);
}
EXPORT_SYMBOL_GPL(hwrng_unregister);


MODULE_DESCRIPTION("H/W Random Number Generator (RNG) driver");
MODULE_LICENSE("GPL");

Generated by  Doxygen 1.6.0   Back to index