linux/drivers/char/tile-srom.c
<<
>>
Prefs
   1/*
   2 * Copyright 2011 Tilera Corporation. All Rights Reserved.
   3 *
   4 *   This program is free software; you can redistribute it and/or
   5 *   modify it under the terms of the GNU General Public License
   6 *   as published by the Free Software Foundation, version 2.
   7 *
   8 *   This program is distributed in the hope that it will be useful, but
   9 *   WITHOUT ANY WARRANTY; without even the implied warranty of
  10 *   MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
  11 *   NON INFRINGEMENT.  See the GNU General Public License for
  12 *   more details.
  13 *
  14 * SPI Flash ROM driver
  15 *
  16 * This source code is derived from code provided in "Linux Device
  17 * Drivers, Third Edition", by Jonathan Corbet, Alessandro Rubini, and
  18 * Greg Kroah-Hartman, published by O'Reilly Media, Inc.
  19 */
  20
  21#include <linux/module.h>
  22#include <linux/moduleparam.h>
  23#include <linux/init.h>
  24#include <linux/kernel.h>       /* printk() */
  25#include <linux/slab.h>         /* kmalloc() */
  26#include <linux/fs.h>           /* everything... */
  27#include <linux/errno.h>        /* error codes */
  28#include <linux/types.h>        /* size_t */
  29#include <linux/proc_fs.h>
  30#include <linux/fcntl.h>        /* O_ACCMODE */
  31#include <linux/aio.h>
  32#include <linux/pagemap.h>
  33#include <linux/hugetlb.h>
  34#include <linux/uaccess.h>
  35#include <linux/platform_device.h>
  36#include <hv/hypervisor.h>
  37#include <linux/ioctl.h>
  38#include <linux/cdev.h>
  39#include <linux/delay.h>
  40#include <hv/drv_srom_intf.h>
  41
  42/*
  43 * Size of our hypervisor I/O requests.  We break up large transfers
  44 * so that we don't spend large uninterrupted spans of time in the
  45 * hypervisor.  Erasing an SROM sector takes a significant fraction of
  46 * a second, so if we allowed the user to, say, do one I/O to write the
  47 * entire ROM, we'd get soft lockup timeouts, or worse.
  48 */
  49#define SROM_CHUNK_SIZE ((size_t)4096)
  50
  51/*
  52 * When hypervisor is busy (e.g. erasing), poll the status periodically.
  53 */
  54
  55/*
  56 * Interval to poll the state in msec
  57 */
  58#define SROM_WAIT_TRY_INTERVAL 20
  59
  60/*
  61 * Maximum times to poll the state
  62 */
  63#define SROM_MAX_WAIT_TRY_TIMES 1000
  64
  65struct srom_dev {
  66        int hv_devhdl;                  /* Handle for hypervisor device */
  67        u32 total_size;                 /* Size of this device */
  68        u32 sector_size;                /* Size of a sector */
  69        u32 page_size;                  /* Size of a page */
  70        struct mutex lock;              /* Allow only one accessor at a time */
  71};
  72
  73static int srom_major;                  /* Dynamic major by default */
  74module_param(srom_major, int, 0);
  75MODULE_AUTHOR("Tilera Corporation");
  76MODULE_LICENSE("GPL");
  77
  78static int srom_devs;                   /* Number of SROM partitions */
  79static struct cdev srom_cdev;
  80static struct class *srom_class;
  81static struct srom_dev *srom_devices;
  82
  83/*
  84 * Handle calling the hypervisor and managing EAGAIN/EBUSY.
  85 */
  86
  87static ssize_t _srom_read(int hv_devhdl, void *buf,
  88                          loff_t off, size_t count)
  89{
  90        int retval, retries = SROM_MAX_WAIT_TRY_TIMES;
  91        for (;;) {
  92                retval = hv_dev_pread(hv_devhdl, 0, (HV_VirtAddr)buf,
  93                                      count, off);
  94                if (retval >= 0)
  95                        return retval;
  96                if (retval == HV_EAGAIN)
  97                        continue;
  98                if (retval == HV_EBUSY && --retries > 0) {
  99                        msleep(SROM_WAIT_TRY_INTERVAL);
 100                        continue;
 101                }
 102                pr_err("_srom_read: error %d\n", retval);
 103                return -EIO;
 104        }
 105}
 106
 107static ssize_t _srom_write(int hv_devhdl, const void *buf,
 108                           loff_t off, size_t count)
 109{
 110        int retval, retries = SROM_MAX_WAIT_TRY_TIMES;
 111        for (;;) {
 112                retval = hv_dev_pwrite(hv_devhdl, 0, (HV_VirtAddr)buf,
 113                                       count, off);
 114                if (retval >= 0)
 115                        return retval;
 116                if (retval == HV_EAGAIN)
 117                        continue;
 118                if (retval == HV_EBUSY && --retries > 0) {
 119                        msleep(SROM_WAIT_TRY_INTERVAL);
 120                        continue;
 121                }
 122                pr_err("_srom_write: error %d\n", retval);
 123                return -EIO;
 124        }
 125}
 126
 127/**
 128 * srom_open() - Device open routine.
 129 * @inode: Inode for this device.
 130 * @filp: File for this specific open of the device.
 131 *
 132 * Returns zero, or an error code.
 133 */
 134static int srom_open(struct inode *inode, struct file *filp)
 135{
 136        filp->private_data = &srom_devices[iminor(inode)];
 137        return 0;
 138}
 139
 140
 141/**
 142 * srom_release() - Device release routine.
 143 * @inode: Inode for this device.
 144 * @filp: File for this specific open of the device.
 145 *
 146 * Returns zero, or an error code.
 147 */
 148static int srom_release(struct inode *inode, struct file *filp)
 149{
 150        struct srom_dev *srom = filp->private_data;
 151        char dummy;
 152
 153        /* Make sure we've flushed anything written to the ROM. */
 154        mutex_lock(&srom->lock);
 155        if (srom->hv_devhdl >= 0)
 156                _srom_write(srom->hv_devhdl, &dummy, SROM_FLUSH_OFF, 1);
 157        mutex_unlock(&srom->lock);
 158
 159        filp->private_data = NULL;
 160
 161        return 0;
 162}
 163
 164
 165/**
 166 * srom_read() - Read data from the device.
 167 * @filp: File for this specific open of the device.
 168 * @buf: User's data buffer.
 169 * @count: Number of bytes requested.
 170 * @f_pos: File position.
 171 *
 172 * Returns number of bytes read, or an error code.
 173 */
 174static ssize_t srom_read(struct file *filp, char __user *buf,
 175                         size_t count, loff_t *f_pos)
 176{
 177        int retval = 0;
 178        void *kernbuf;
 179        struct srom_dev *srom = filp->private_data;
 180
 181        kernbuf = kmalloc(SROM_CHUNK_SIZE, GFP_KERNEL);
 182        if (!kernbuf)
 183                return -ENOMEM;
 184
 185        if (mutex_lock_interruptible(&srom->lock)) {
 186                retval = -ERESTARTSYS;
 187                kfree(kernbuf);
 188                return retval;
 189        }
 190
 191        while (count) {
 192                int hv_retval;
 193                int bytes_this_pass = min(count, SROM_CHUNK_SIZE);
 194
 195                hv_retval = _srom_read(srom->hv_devhdl, kernbuf,
 196                                       *f_pos, bytes_this_pass);
 197                if (hv_retval <= 0) {
 198                        if (retval == 0)
 199                                retval = hv_retval;
 200                        break;
 201                }
 202
 203                if (copy_to_user(buf, kernbuf, hv_retval) != 0) {
 204                        retval = -EFAULT;
 205                        break;
 206                }
 207
 208                retval += hv_retval;
 209                *f_pos += hv_retval;
 210                buf += hv_retval;
 211                count -= hv_retval;
 212        }
 213
 214        mutex_unlock(&srom->lock);
 215        kfree(kernbuf);
 216
 217        return retval;
 218}
 219
 220/**
 221 * srom_write() - Write data to the device.
 222 * @filp: File for this specific open of the device.
 223 * @buf: User's data buffer.
 224 * @count: Number of bytes requested.
 225 * @f_pos: File position.
 226 *
 227 * Returns number of bytes written, or an error code.
 228 */
 229static ssize_t srom_write(struct file *filp, const char __user *buf,
 230                          size_t count, loff_t *f_pos)
 231{
 232        int retval = 0;
 233        void *kernbuf;
 234        struct srom_dev *srom = filp->private_data;
 235
 236        kernbuf = kmalloc(SROM_CHUNK_SIZE, GFP_KERNEL);
 237        if (!kernbuf)
 238                return -ENOMEM;
 239
 240        if (mutex_lock_interruptible(&srom->lock)) {
 241                retval = -ERESTARTSYS;
 242                kfree(kernbuf);
 243                return retval;
 244        }
 245
 246        while (count) {
 247                int hv_retval;
 248                int bytes_this_pass = min(count, SROM_CHUNK_SIZE);
 249
 250                if (copy_from_user(kernbuf, buf, bytes_this_pass) != 0) {
 251                        retval = -EFAULT;
 252                        break;
 253                }
 254
 255                hv_retval = _srom_write(srom->hv_devhdl, kernbuf,
 256                                        *f_pos, bytes_this_pass);
 257                if (hv_retval <= 0) {
 258                        if (retval == 0)
 259                                retval = hv_retval;
 260                        break;
 261                }
 262
 263                retval += hv_retval;
 264                *f_pos += hv_retval;
 265                buf += hv_retval;
 266                count -= hv_retval;
 267        }
 268
 269        mutex_unlock(&srom->lock);
 270        kfree(kernbuf);
 271
 272        return retval;
 273}
 274
 275/* Provide our own implementation so we can use srom->total_size. */
 276loff_t srom_llseek(struct file *file, loff_t offset, int origin)
 277{
 278        struct srom_dev *srom = file->private_data;
 279        return fixed_size_llseek(file, offset, origin, srom->total_size);
 280}
 281
 282static ssize_t total_show(struct device *dev,
 283                          struct device_attribute *attr, char *buf)
 284{
 285        struct srom_dev *srom = dev_get_drvdata(dev);
 286        return sprintf(buf, "%u\n", srom->total_size);
 287}
 288
 289static ssize_t sector_show(struct device *dev,
 290                           struct device_attribute *attr, char *buf)
 291{
 292        struct srom_dev *srom = dev_get_drvdata(dev);
 293        return sprintf(buf, "%u\n", srom->sector_size);
 294}
 295
 296static ssize_t page_show(struct device *dev,
 297                         struct device_attribute *attr, char *buf)
 298{
 299        struct srom_dev *srom = dev_get_drvdata(dev);
 300        return sprintf(buf, "%u\n", srom->page_size);
 301}
 302
 303static struct device_attribute srom_dev_attrs[] = {
 304        __ATTR(total_size, S_IRUGO, total_show, NULL),
 305        __ATTR(sector_size, S_IRUGO, sector_show, NULL),
 306        __ATTR(page_size, S_IRUGO, page_show, NULL),
 307        __ATTR_NULL
 308};
 309
 310static char *srom_devnode(struct device *dev, umode_t *mode)
 311{
 312        *mode = S_IRUGO | S_IWUSR;
 313        return kasprintf(GFP_KERNEL, "srom/%s", dev_name(dev));
 314}
 315
 316/*
 317 * The fops
 318 */
 319static const struct file_operations srom_fops = {
 320        .owner =     THIS_MODULE,
 321        .llseek =    srom_llseek,
 322        .read =      srom_read,
 323        .write =     srom_write,
 324        .open =      srom_open,
 325        .release =   srom_release,
 326};
 327
 328/**
 329 * srom_setup_minor() - Initialize per-minor information.
 330 * @srom: Per-device SROM state.
 331 * @index: Device to set up.
 332 */
 333static int srom_setup_minor(struct srom_dev *srom, int index)
 334{
 335        struct device *dev;
 336        int devhdl = srom->hv_devhdl;
 337
 338        mutex_init(&srom->lock);
 339
 340        if (_srom_read(devhdl, &srom->total_size,
 341                       SROM_TOTAL_SIZE_OFF, sizeof(srom->total_size)) < 0)
 342                return -EIO;
 343        if (_srom_read(devhdl, &srom->sector_size,
 344                       SROM_SECTOR_SIZE_OFF, sizeof(srom->sector_size)) < 0)
 345                return -EIO;
 346        if (_srom_read(devhdl, &srom->page_size,
 347                       SROM_PAGE_SIZE_OFF, sizeof(srom->page_size)) < 0)
 348                return -EIO;
 349
 350        dev = device_create(srom_class, &platform_bus,
 351                            MKDEV(srom_major, index), srom, "%d", index);
 352        return PTR_RET(dev);
 353}
 354
 355/** srom_init() - Initialize the driver's module. */
 356static int srom_init(void)
 357{
 358        int result, i;
 359        dev_t dev = MKDEV(srom_major, 0);
 360
 361        /*
 362         * Start with a plausible number of partitions; the krealloc() call
 363         * below will yield about log(srom_devs) additional allocations.
 364         */
 365        srom_devices = kzalloc(4 * sizeof(struct srom_dev), GFP_KERNEL);
 366
 367        /* Discover the number of srom partitions. */
 368        for (i = 0; ; i++) {
 369                int devhdl;
 370                char buf[20];
 371                struct srom_dev *new_srom_devices =
 372                        krealloc(srom_devices, (i+1) * sizeof(struct srom_dev),
 373                                 GFP_KERNEL | __GFP_ZERO);
 374                if (!new_srom_devices) {
 375                        result = -ENOMEM;
 376                        goto fail_mem;
 377                }
 378                srom_devices = new_srom_devices;
 379                sprintf(buf, "srom/0/%d", i);
 380                devhdl = hv_dev_open((HV_VirtAddr)buf, 0);
 381                if (devhdl < 0) {
 382                        if (devhdl != HV_ENODEV)
 383                                pr_notice("srom/%d: hv_dev_open failed: %d.\n",
 384                                          i, devhdl);
 385                        break;
 386                }
 387                srom_devices[i].hv_devhdl = devhdl;
 388        }
 389        srom_devs = i;
 390
 391        /* Bail out early if we have no partitions at all. */
 392        if (srom_devs == 0) {
 393                result = -ENODEV;
 394                goto fail_mem;
 395        }
 396
 397        /* Register our major, and accept a dynamic number. */
 398        if (srom_major)
 399                result = register_chrdev_region(dev, srom_devs, "srom");
 400        else {
 401                result = alloc_chrdev_region(&dev, 0, srom_devs, "srom");
 402                srom_major = MAJOR(dev);
 403        }
 404        if (result < 0)
 405                goto fail_mem;
 406
 407        /* Register a character device. */
 408        cdev_init(&srom_cdev, &srom_fops);
 409        srom_cdev.owner = THIS_MODULE;
 410        srom_cdev.ops = &srom_fops;
 411        result = cdev_add(&srom_cdev, dev, srom_devs);
 412        if (result < 0)
 413                goto fail_chrdev;
 414
 415        /* Create a sysfs class. */
 416        srom_class = class_create(THIS_MODULE, "srom");
 417        if (IS_ERR(srom_class)) {
 418                result = PTR_ERR(srom_class);
 419                goto fail_cdev;
 420        }
 421        srom_class->dev_attrs = srom_dev_attrs;
 422        srom_class->devnode = srom_devnode;
 423
 424        /* Do per-partition initialization */
 425        for (i = 0; i < srom_devs; i++) {
 426                result = srom_setup_minor(srom_devices + i, i);
 427                if (result < 0)
 428                        goto fail_class;
 429        }
 430
 431        return 0;
 432
 433fail_class:
 434        for (i = 0; i < srom_devs; i++)
 435                device_destroy(srom_class, MKDEV(srom_major, i));
 436        class_destroy(srom_class);
 437fail_cdev:
 438        cdev_del(&srom_cdev);
 439fail_chrdev:
 440        unregister_chrdev_region(dev, srom_devs);
 441fail_mem:
 442        kfree(srom_devices);
 443        return result;
 444}
 445
 446/** srom_cleanup() - Clean up the driver's module. */
 447static void srom_cleanup(void)
 448{
 449        int i;
 450        for (i = 0; i < srom_devs; i++)
 451                device_destroy(srom_class, MKDEV(srom_major, i));
 452        class_destroy(srom_class);
 453        cdev_del(&srom_cdev);
 454        unregister_chrdev_region(MKDEV(srom_major, 0), srom_devs);
 455        kfree(srom_devices);
 456}
 457
 458module_init(srom_init);
 459module_exit(srom_cleanup);
 460
lxr.linux.no kindly hosted by Redpill Linpro AS, provider of Linux consulting and operations services since 1995.