]> pilppa.org Git - linux-2.6-omap-h63xx.git/blobdiff - drivers/s390/char/vmur.c
Merge branch 'for-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/jikos/hid
[linux-2.6-omap-h63xx.git] / drivers / s390 / char / vmur.c
index 161867cebd8c5bd2e0d9d8f09dfa0dac5996d8cf..7689b500a1046ffbddfd0426c7229847959bc0f0 100644 (file)
@@ -14,6 +14,7 @@
 #include <asm/cio.h>
 #include <asm/ccwdev.h>
 #include <asm/debug.h>
+#include <asm/diag.h>
 
 #include "vmur.h"
 
@@ -68,8 +69,26 @@ static struct ccw_driver ur_driver = {
        .set_offline    = ur_set_offline,
 };
 
+static DEFINE_MUTEX(vmur_mutex);
+
 /*
  * Allocation, freeing, getting and putting of urdev structures
+ *
+ * Each ur device (urd) contains a reference to its corresponding ccw device
+ * (cdev) using the urd->cdev pointer. Each ccw device has a reference to the
+ * ur device using the cdev->dev.driver_data pointer.
+ *
+ * urd references:
+ * - ur_probe gets a urd reference, ur_remove drops the reference
+ *   (cdev->dev.driver_data)
+ * - ur_open gets a urd reference, ur_relase drops the reference
+ *   (urf->urd)
+ *
+ * cdev references:
+ * - urdev_alloc get a cdev reference (urd->cdev)
+ * - urdev_free drops the cdev reference (urd->cdev)
+ *
+ * Setting and clearing of cdev->dev.driver_data is protected by the ccwdev lock
  */
 static struct urdev *urdev_alloc(struct ccw_device *cdev)
 {
@@ -78,51 +97,72 @@ static struct urdev *urdev_alloc(struct ccw_device *cdev)
        urd = kzalloc(sizeof(struct urdev), GFP_KERNEL);
        if (!urd)
                return NULL;
-       urd->cdev = cdev;
        urd->reclen = cdev->id.driver_info;
        ccw_device_get_id(cdev, &urd->dev_id);
        mutex_init(&urd->io_mutex);
        mutex_init(&urd->open_mutex);
+       atomic_set(&urd->ref_count,  1);
+       urd->cdev = cdev;
+       get_device(&cdev->dev);
        return urd;
 }
 
 static void urdev_free(struct urdev *urd)
 {
+       TRACE("urdev_free: %p\n", urd);
+       if (urd->cdev)
+               put_device(&urd->cdev->dev);
        kfree(urd);
 }
 
-/*
- * This is how the character device driver gets a reference to a
- * ur device. When this call returns successfully, a reference has
- * been taken (by get_device) on the underlying kobject. The recipient
- * of this urdev pointer must eventually drop it with urdev_put(urd)
- * which does the corresponding put_device().
- */
+static void urdev_get(struct urdev *urd)
+{
+       atomic_inc(&urd->ref_count);
+}
+
+static struct urdev *urdev_get_from_cdev(struct ccw_device *cdev)
+{
+       struct urdev *urd;
+       unsigned long flags;
+
+       spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+       urd = cdev->dev.driver_data;
+       if (urd)
+               urdev_get(urd);
+       spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+       return urd;
+}
+
 static struct urdev *urdev_get_from_devno(u16 devno)
 {
        char bus_id[16];
        struct ccw_device *cdev;
+       struct urdev *urd;
 
        sprintf(bus_id, "0.0.%04x", devno);
        cdev = get_ccwdev_by_busid(&ur_driver, bus_id);
        if (!cdev)
                return NULL;
-
-       return cdev->dev.driver_data;
+       urd = urdev_get_from_cdev(cdev);
+       put_device(&cdev->dev);
+       return urd;
 }
 
 static void urdev_put(struct urdev *urd)
 {
-       put_device(&urd->cdev->dev);
+       if (atomic_dec_and_test(&urd->ref_count))
+               urdev_free(urd);
 }
 
 /*
  * Low-level functions to do I/O to a ur device.
  *     alloc_chan_prog
+ *     free_chan_prog
  *     do_ur_io
  *     ur_int_handler
  *
  * alloc_chan_prog allocates and builds the channel program
+ * free_chan_prog frees memory of the channel program
  *
  * do_ur_io issues the channel program to the device and blocks waiting
  * on a completion event it publishes at urd->io_done. The function
@@ -137,6 +177,16 @@ static void urdev_put(struct urdev *urd)
  * address pointer that alloc_chan_prog returned.
  */
 
+static void free_chan_prog(struct ccw1 *cpa)
+{
+       struct ccw1 *ptr = cpa;
+
+       while (ptr->cda) {
+               kfree((void *)(addr_t) ptr->cda);
+               ptr++;
+       }
+       kfree(cpa);
+}
 
 /*
  * alloc_chan_prog
@@ -144,44 +194,45 @@ static void urdev_put(struct urdev *urd)
  * with a final NOP CCW command-chained on (which ensures that CE and DE
  * are presented together in a single interrupt instead of as separate
  * interrupts unless an incorrect length indication kicks in first). The
- * data length in each CCW is reclen. The caller must ensure that count
- * is an integral multiple of reclen.
- * The channel program pointer returned by this function must be freed
- * with kfree. The caller is responsible for checking that
- * count/reclen is not ridiculously large.
+ * data length in each CCW is reclen.
  */
-static struct ccw1 *alloc_chan_prog(char *buf, size_t count, size_t reclen)
+static struct ccw1 *alloc_chan_prog(const char __user *ubuf, int rec_count,
+                                   int reclen)
 {
-       size_t num_ccws;
        struct ccw1 *cpa;
+       void *kbuf;
        int i;
 
-       TRACE("alloc_chan_prog(%p, %zu, %zu)\n", buf, count, reclen);
+       TRACE("alloc_chan_prog(%p, %i, %i)\n", ubuf, rec_count, reclen);
 
        /*
         * We chain a NOP onto the writes to force CE+DE together.
         * That means we allocate room for CCWs to cover count/reclen
         * records plus a NOP.
         */
-       num_ccws = count / reclen + 1;
-       cpa = kmalloc(num_ccws * sizeof(struct ccw1), GFP_KERNEL | GFP_DMA);
+       cpa = kzalloc((rec_count + 1) * sizeof(struct ccw1),
+                     GFP_KERNEL | GFP_DMA);
        if (!cpa)
-               return NULL;
+               return ERR_PTR(-ENOMEM);
 
-       for (i = 0; count; i++) {
+       for (i = 0; i < rec_count; i++) {
                cpa[i].cmd_code = WRITE_CCW_CMD;
                cpa[i].flags = CCW_FLAG_CC | CCW_FLAG_SLI;
                cpa[i].count = reclen;
-               cpa[i].cda = __pa(buf);
-               buf += reclen;
-               count -= reclen;
+               kbuf = kmalloc(reclen, GFP_KERNEL | GFP_DMA);
+               if (!kbuf) {
+                       free_chan_prog(cpa);
+                       return ERR_PTR(-ENOMEM);
+               }
+               cpa[i].cda = (u32)(addr_t) kbuf;
+               if (copy_from_user(kbuf, ubuf, reclen)) {
+                       free_chan_prog(cpa);
+                       return ERR_PTR(-EFAULT);
+               }
+               ubuf += reclen;
        }
        /* The following NOP CCW forces CE+DE to be presented together */
        cpa[i].cmd_code = CCW_CMD_NOOP;
-       cpa[i].flags = 0;
-       cpa[i].count = 0;
-       cpa[i].cda = 0;
-
        return cpa;
 }
 
@@ -189,7 +240,7 @@ static int do_ur_io(struct urdev *urd, struct ccw1 *cpa)
 {
        int rc;
        struct ccw_device *cdev = urd->cdev;
-       DECLARE_COMPLETION(event);
+       DECLARE_COMPLETION_ONSTACK(event);
 
        TRACE("do_ur_io: cpa=%p\n", cpa);
 
@@ -232,6 +283,7 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm,
                return;
        }
        urd = cdev->dev.driver_data;
+       BUG_ON(!urd);
        /* On special conditions irb is an error pointer */
        if (IS_ERR(irb))
                urd->io_request_rc = PTR_ERR(irb);
@@ -249,9 +301,15 @@ static void ur_int_handler(struct ccw_device *cdev, unsigned long intparm,
 static ssize_t ur_attr_reclen_show(struct device *dev,
                                   struct device_attribute *attr, char *buf)
 {
-       struct urdev *urd = dev->driver_data;
+       struct urdev *urd;
+       int rc;
 
-       return sprintf(buf, "%zu\n", urd->reclen);
+       urd = urdev_get_from_cdev(to_ccwdev(dev));
+       if (!urd)
+               return -ENODEV;
+       rc = sprintf(buf, "%zu\n", urd->reclen);
+       urdev_put(urd);
+       return rc;
 }
 
 static DEVICE_ATTR(reclen, 0444, ur_attr_reclen_show, NULL);
@@ -325,24 +383,11 @@ static ssize_t do_write(struct urdev *urd, const char __user *udata,
                        size_t count, size_t reclen, loff_t *ppos)
 {
        struct ccw1 *cpa;
-       char *buf;
        int rc;
 
-       /* Data buffer must be under 2GB line for fmt1 CCWs: hence GFP_DMA */
-       buf = kmalloc(count, GFP_KERNEL | GFP_DMA);
-       if (!buf)
-               return -ENOMEM;
-
-       if (copy_from_user(buf, udata, count)) {
-               rc = -EFAULT;
-               goto fail_kfree_buf;
-       }
-
-       cpa = alloc_chan_prog(buf, count, reclen);
-       if (!cpa) {
-               rc = -ENOMEM;
-               goto fail_kfree_buf;
-       }
+       cpa = alloc_chan_prog(udata, count / reclen, reclen);
+       if (IS_ERR(cpa))
+               return PTR_ERR(cpa);
 
        rc = do_ur_io(urd, cpa);
        if (rc)
@@ -354,10 +399,9 @@ static ssize_t do_write(struct urdev *urd, const char __user *udata,
        }
        *ppos += count;
        rc = count;
+
 fail_kfree_cpa:
-       kfree(cpa);
-fail_kfree_buf:
-       kfree(buf);
+       free_chan_prog(cpa);
        return rc;
 }
 
@@ -380,31 +424,6 @@ static ssize_t ur_write(struct file *file, const char __user *udata,
        return do_write(urf->urd, udata, count, urf->dev_reclen, ppos);
 }
 
-static int do_diag_14(unsigned long rx, unsigned long ry1,
-                     unsigned long subcode)
-{
-       register unsigned long _ry1 asm("2") = ry1;
-       register unsigned long _ry2 asm("3") = subcode;
-       int rc = 0;
-
-       asm volatile(
-#ifdef CONFIG_64BIT
-               "   sam31\n"
-               "   diag    %2,2,0x14\n"
-               "   sam64\n"
-#else
-               "   diag    %2,2,0x14\n"
-#endif
-               "   ipm     %0\n"
-               "   srl     %0,28\n"
-               : "=d" (rc), "+d" (_ry2)
-               : "d" (rx), "d" (_ry1)
-               : "cc");
-
-       TRACE("diag 14: subcode=0x%lx, cc=%i\n", subcode, rc);
-       return rc;
-}
-
 /*
  * diagnose code 0x14 subcode 0x0028 - position spool file to designated
  *                                    record
@@ -416,7 +435,7 @@ static int diag_position_to_record(int devno, int record)
 {
        int cc;
 
-       cc = do_diag_14(record, devno, 0x28);
+       cc = diag14(record, devno, 0x28);
        switch (cc) {
        case 0:
                return 0;
@@ -441,7 +460,7 @@ static int diag_read_file(int devno, char *buf)
 {
        int cc;
 
-       cc = do_diag_14((unsigned long) buf, devno, 0x00);
+       cc = diag14((unsigned long) buf, devno, 0x00);
        switch (cc) {
        case 0:
                return 0;
@@ -473,7 +492,7 @@ static ssize_t diag14_read(struct file *file, char __user *ubuf, size_t count,
                return rc;
 
        len = min((size_t) PAGE_SIZE, count);
-       buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
+       buf = (char *) __get_free_page(GFP_KERNEL | GFP_DMA);
        if (!buf)
                return -ENOMEM;
 
@@ -500,7 +519,7 @@ static ssize_t diag14_read(struct file *file, char __user *ubuf, size_t count,
        *offs += copied;
        rc = copied;
 fail:
-       kfree(buf);
+       free_page((unsigned long) buf);
        return rc;
 }
 
@@ -534,7 +553,7 @@ static int diag_read_next_file_info(struct file_control_block *buf, int spid)
 {
        int cc;
 
-       cc = do_diag_14((unsigned long) buf, spid, 0xfff);
+       cc = diag14((unsigned long) buf, spid, 0xfff);
        switch (cc) {
        case 0:
                return 0;
@@ -543,56 +562,97 @@ static int diag_read_next_file_info(struct file_control_block *buf, int spid)
        }
 }
 
-static int verify_device(struct urdev *urd)
+static int verify_uri_device(struct urdev *urd)
 {
-       struct file_control_block fcb;
+       struct file_control_block *fcb;
        char *buf;
        int rc;
 
+       fcb = kmalloc(sizeof(*fcb), GFP_KERNEL | GFP_DMA);
+       if (!fcb)
+               return -ENOMEM;
+
+       /* check for empty reader device (beginning of chain) */
+       rc = diag_read_next_file_info(fcb, 0);
+       if (rc)
+               goto fail_free_fcb;
+
+       /* if file is in hold status, we do not read it */
+       if (fcb->file_stat & (FLG_SYSTEM_HOLD | FLG_USER_HOLD)) {
+               rc = -EPERM;
+               goto fail_free_fcb;
+       }
+
+       /* open file on virtual reader  */
+       buf = (char *) __get_free_page(GFP_KERNEL | GFP_DMA);
+       if (!buf) {
+               rc = -ENOMEM;
+               goto fail_free_fcb;
+       }
+       rc = diag_read_file(urd->dev_id.devno, buf);
+       if ((rc != 0) && (rc != -ENODATA)) /* EOF does not hurt */
+               goto fail_free_buf;
+
+       /* check if the file on top of the queue is open now */
+       rc = diag_read_next_file_info(fcb, 0);
+       if (rc)
+               goto fail_free_buf;
+       if (!(fcb->file_stat & FLG_IN_USE)) {
+               rc = -EMFILE;
+               goto fail_free_buf;
+       }
+       rc = 0;
+
+fail_free_buf:
+       free_page((unsigned long) buf);
+fail_free_fcb:
+       kfree(fcb);
+       return rc;
+}
+
+static int verify_device(struct urdev *urd)
+{
        switch (urd->class) {
        case DEV_CLASS_UR_O:
                return 0; /* no check needed here */
        case DEV_CLASS_UR_I:
-               /* check for empty reader device (beginning of chain) */
-               rc = diag_read_next_file_info(&fcb, 0);
-               if (rc)
-                       return rc;
-
-               /* open file on virtual reader  */
-               buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
-               if (!buf)
-                       return -ENOMEM;
-               rc = diag_read_file(urd->dev_id.devno, buf);
-               kfree(buf);
-
-               if ((rc != 0) && (rc != -ENODATA)) /* EOF does not hurt */
-                       return rc;
-               return 0;
+               return verify_uri_device(urd);
        default:
                return -ENOTSUPP;
        }
 }
 
-static int get_file_reclen(struct urdev *urd)
+static int get_uri_file_reclen(struct urdev *urd)
 {
-       struct file_control_block fcb;
+       struct file_control_block *fcb;
        int rc;
 
+       fcb = kmalloc(sizeof(*fcb), GFP_KERNEL | GFP_DMA);
+       if (!fcb)
+               return -ENOMEM;
+       rc = diag_read_next_file_info(fcb, 0);
+       if (rc)
+               goto fail_free;
+       if (fcb->file_stat & FLG_CP_DUMP)
+               rc = 0;
+       else
+               rc = fcb->rec_len;
+
+fail_free:
+       kfree(fcb);
+       return rc;
+}
+
+static int get_file_reclen(struct urdev *urd)
+{
        switch (urd->class) {
        case DEV_CLASS_UR_O:
                return 0;
        case DEV_CLASS_UR_I:
-               rc = diag_read_next_file_info(&fcb, 0);
-               if (rc)
-                       return rc;
-               break;
+               return get_uri_file_reclen(urd);
        default:
                return -ENOTSUPP;
        }
-       if (fcb.file_stat & FLG_CP_DUMP)
-               return 0;
-
-       return fcb.rec_len;
 }
 
 static int ur_open(struct inode *inode, struct file *file)
@@ -699,7 +759,7 @@ static loff_t ur_llseek(struct file *file, loff_t offset, int whence)
        return newpos;
 }
 
-static struct file_operations ur_fops = {
+static const struct file_operations ur_fops = {
        .owner   = THIS_MODULE,
        .open    = ur_open,
        .release = ur_release,
@@ -710,64 +770,63 @@ static struct file_operations ur_fops = {
 
 /*
  * ccw_device infrastructure:
- *     ur_probe gets its own ref to the device (i.e. get_device),
- *     creates the struct urdev, the device attributes, sets up
- *     the interrupt handler and validates the virtual unit record device.
- *     ur_remove removes the device attributes, frees the struct urdev
- *     and drops (put_device) the ref to the device we got in ur_probe.
+ *     ur_probe creates the struct urdev (with refcount = 1), the device
+ *     attributes, sets up the interrupt handler and validates the virtual
+ *     unit record device.
+ *     ur_remove removes the device attributes and drops the reference to
+ *     struct urdev.
+ *
+ *     ur_probe, ur_remove, ur_set_online and ur_set_offline are serialized
+ *     by the vmur_mutex lock.
+ *
+ *     urd->char_device is used as indication that the online function has
+ *     been completed successfully.
  */
 static int ur_probe(struct ccw_device *cdev)
 {
        struct urdev *urd;
        int rc;
 
-       TRACE("ur_probe: cdev=%p state=%d\n", cdev, *(int *) cdev->private);
-
-       if (!get_device(&cdev->dev))
-               return -ENODEV;
+       TRACE("ur_probe: cdev=%p\n", cdev);
 
+       mutex_lock(&vmur_mutex);
        urd = urdev_alloc(cdev);
        if (!urd) {
                rc = -ENOMEM;
-               goto fail;
+               goto fail_unlock;
        }
+
        rc = ur_create_attributes(&cdev->dev);
        if (rc) {
                rc = -ENOMEM;
-               goto fail;
+               goto fail_urdev_put;
        }
-       cdev->dev.driver_data = urd;
        cdev->handler = ur_int_handler;
 
        /* validate virtual unit record device */
        urd->class = get_urd_class(urd);
        if (urd->class < 0) {
                rc = urd->class;
-               goto fail;
+               goto fail_remove_attr;
        }
        if ((urd->class != DEV_CLASS_UR_I) && (urd->class != DEV_CLASS_UR_O)) {
                rc = -ENOTSUPP;
-               goto fail;
+               goto fail_remove_attr;
        }
+       spin_lock_irq(get_ccwdev_lock(cdev));
+       cdev->dev.driver_data = urd;
+       spin_unlock_irq(get_ccwdev_lock(cdev));
 
+       mutex_unlock(&vmur_mutex);
        return 0;
 
-fail:
-       urdev_free(urd);
-       put_device(&cdev->dev);
-       return rc;
-}
-
-static void ur_remove(struct ccw_device *cdev)
-{
-       struct urdev *urd = cdev->dev.driver_data;
-
-       TRACE("ur_remove\n");
-       if (cdev->online)
-               ur_set_offline(cdev);
+fail_remove_attr:
        ur_remove_attributes(&cdev->dev);
-       urdev_free(urd);
-       put_device(&cdev->dev);
+fail_urdev_put:
+       urdev_put(urd);
+fail_unlock:
+       mutex_unlock(&vmur_mutex);
+       return rc;
 }
 
 static int ur_set_online(struct ccw_device *cdev)
@@ -776,20 +835,29 @@ static int ur_set_online(struct ccw_device *cdev)
        int minor, major, rc;
        char node_id[16];
 
-       TRACE("ur_set_online: cdev=%p state=%d\n", cdev,
-             *(int *) cdev->private);
+       TRACE("ur_set_online: cdev=%p\n", cdev);
 
-       if (!try_module_get(ur_driver.owner))
-               return -EINVAL;
+       mutex_lock(&vmur_mutex);
+       urd = urdev_get_from_cdev(cdev);
+       if (!urd) {
+               /* ur_remove already deleted our urd */
+               rc = -ENODEV;
+               goto fail_unlock;
+       }
+
+       if (urd->char_device) {
+               /* Another ur_set_online was faster */
+               rc = -EBUSY;
+               goto fail_urdev_put;
+       }
 
-       urd = (struct urdev *) cdev->dev.driver_data;
        minor = urd->dev_id.devno;
        major = MAJOR(ur_first_dev_maj_min);
 
        urd->char_device = cdev_alloc();
        if (!urd->char_device) {
                rc = -ENOMEM;
-               goto fail_module_put;
+               goto fail_urdev_put;
        }
 
        cdev_init(urd->char_device, &ur_fops);
@@ -818,29 +886,79 @@ static int ur_set_online(struct ccw_device *cdev)
                TRACE("ur_set_online: device_create rc=%d\n", rc);
                goto fail_free_cdev;
        }
-
+       urdev_put(urd);
+       mutex_unlock(&vmur_mutex);
        return 0;
 
 fail_free_cdev:
        cdev_del(urd->char_device);
-fail_module_put:
-       module_put(ur_driver.owner);
-
+       urd->char_device = NULL;
+fail_urdev_put:
+       urdev_put(urd);
+fail_unlock:
+       mutex_unlock(&vmur_mutex);
        return rc;
 }
 
-static int ur_set_offline(struct ccw_device *cdev)
+static int ur_set_offline_force(struct ccw_device *cdev, int force)
 {
        struct urdev *urd;
+       int rc;
 
-       TRACE("ur_set_offline: cdev=%p cdev->private=%p state=%d\n",
-               cdev, cdev->private, *(int *) cdev->private);
-       urd = (struct urdev *) cdev->dev.driver_data;
+       TRACE("ur_set_offline: cdev=%p\n", cdev);
+       urd = urdev_get_from_cdev(cdev);
+       if (!urd)
+               /* ur_remove already deleted our urd */
+               return -ENODEV;
+       if (!urd->char_device) {
+               /* Another ur_set_offline was faster */
+               rc = -EBUSY;
+               goto fail_urdev_put;
+       }
+       if (!force && (atomic_read(&urd->ref_count) > 2)) {
+               /* There is still a user of urd (e.g. ur_open) */
+               TRACE("ur_set_offline: BUSY\n");
+               rc = -EBUSY;
+               goto fail_urdev_put;
+       }
        device_destroy(vmur_class, urd->char_device->dev);
        cdev_del(urd->char_device);
-       module_put(ur_driver.owner);
+       urd->char_device = NULL;
+       rc = 0;
 
-       return 0;
+fail_urdev_put:
+       urdev_put(urd);
+       return rc;
+}
+
+static int ur_set_offline(struct ccw_device *cdev)
+{
+       int rc;
+
+       mutex_lock(&vmur_mutex);
+       rc = ur_set_offline_force(cdev, 0);
+       mutex_unlock(&vmur_mutex);
+       return rc;
+}
+
+static void ur_remove(struct ccw_device *cdev)
+{
+       unsigned long flags;
+
+       TRACE("ur_remove\n");
+
+       mutex_lock(&vmur_mutex);
+
+       if (cdev->online)
+               ur_set_offline_force(cdev, 1);
+       ur_remove_attributes(&cdev->dev);
+
+       spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+       urdev_put(cdev->dev.driver_data);
+       cdev->dev.driver_data = NULL;
+       spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+
+       mutex_unlock(&vmur_mutex);
 }
 
 /*