These changes are the raw update to linux-4.4.6-rt14. Kernel sources
[kvmfornfv.git] / kernel / drivers / s390 / cio / cmf.c
index 23054f8..b2afad5 100644 (file)
@@ -113,7 +113,6 @@ module_param(format, bint, 0444);
  * @readall:   read a measurement block in a common format
  * @reset:     clear the data in the associated measurement block and
  *             reset its time stamp
- * @align:     align an allocated block so that the hardware can use it
  */
 struct cmb_operations {
        int  (*alloc)  (struct ccw_device *);
@@ -122,7 +121,6 @@ struct cmb_operations {
        u64  (*read)   (struct ccw_device *, int);
        int  (*readall)(struct ccw_device *, struct cmbdata *);
        void (*reset)  (struct ccw_device *);
-       void *(*align) (void *);
 /* private: */
        struct attribute_group *attr_group;
 };
@@ -186,9 +184,8 @@ static inline void cmf_activate(void *area, unsigned int onoff)
 static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc,
                     unsigned long address)
 {
-       struct subchannel *sch;
-
-       sch = to_subchannel(cdev->dev.parent);
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       int ret;
 
        sch->config.mme = mme;
        sch->config.mbfc = mbfc;
@@ -198,7 +195,15 @@ static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc,
        else
                sch->config.mbi = address;
 
-       return cio_commit_config(sch);
+       ret = cio_commit_config(sch);
+       if (!mme && ret == -ENODEV) {
+               /*
+                * The task was to disable measurement block updates but
+                * the subchannel is already gone. Report success.
+                */
+               ret = 0;
+       }
+       return ret;
 }
 
 struct set_schib_struct {
@@ -314,7 +319,7 @@ static int cmf_copy_block(struct ccw_device *cdev)
                        return -EBUSY;
        }
        cmb_data = cdev->private->cmb;
-       hw_block = cmbops->align(cmb_data->hw_block);
+       hw_block = cmb_data->hw_block;
        if (!memcmp(cmb_data->last_block, hw_block, cmb_data->size))
                /* No need to copy. */
                return 0;
@@ -425,7 +430,7 @@ static void cmf_generic_reset(struct ccw_device *cdev)
                 * Need to reset hw block as well to make the hardware start
                 * from 0 again.
                 */
-               memset(cmbops->align(cmb_data->hw_block), 0, cmb_data->size);
+               memset(cmb_data->hw_block, 0, cmb_data->size);
                cmb_data->last_update = 0;
        }
        cdev->private->cmb_start_time = get_tod_clock();
@@ -606,12 +611,6 @@ static void free_cmb(struct ccw_device *cdev)
        spin_lock_irq(cdev->ccwlock);
 
        priv = cdev->private;
-
-       if (list_empty(&priv->cmb_list)) {
-               /* already freed */
-               goto out;
-       }
-
        cmb_data = priv->cmb;
        priv->cmb = NULL;
        if (cmb_data)
@@ -626,7 +625,6 @@ static void free_cmb(struct ccw_device *cdev)
                free_pages((unsigned long)cmb_area.mem, get_order(size));
                cmb_area.mem = NULL;
        }
-out:
        spin_unlock_irq(cdev->ccwlock);
        spin_unlock(&cmb_area.lock);
 }
@@ -755,11 +753,6 @@ static void reset_cmb(struct ccw_device *cdev)
        cmf_generic_reset(cdev);
 }
 
-static void * align_cmb(void *area)
-{
-       return area;
-}
-
 static struct attribute_group cmf_attr_group;
 
 static struct cmb_operations cmbops_basic = {
@@ -769,7 +762,6 @@ static struct cmb_operations cmbops_basic = {
        .read   = read_cmb,
        .readall    = readall_cmb,
        .reset      = reset_cmb,
-       .align      = align_cmb,
        .attr_group = &cmf_attr_group,
 };
 
@@ -804,64 +796,57 @@ struct cmbe {
        u32 device_busy_time;
        u32 initial_command_response_time;
        u32 reserved[7];
-};
+} __packed __aligned(64);
 
-/*
- * kmalloc only guarantees 8 byte alignment, but we need cmbe
- * pointers to be naturally aligned. Make sure to allocate
- * enough space for two cmbes.
- */
-static inline struct cmbe *cmbe_align(struct cmbe *c)
-{
-       unsigned long addr;
-       addr = ((unsigned long)c + sizeof (struct cmbe) - sizeof(long)) &
-                                ~(sizeof (struct cmbe) - sizeof(long));
-       return (struct cmbe*)addr;
-}
+static struct kmem_cache *cmbe_cache;
 
 static int alloc_cmbe(struct ccw_device *cdev)
 {
-       struct cmbe *cmbe;
        struct cmb_data *cmb_data;
-       int ret;
+       struct cmbe *cmbe;
+       int ret = -ENOMEM;
 
-       cmbe = kzalloc (sizeof (*cmbe) * 2, GFP_KERNEL);
+       cmbe = kmem_cache_zalloc(cmbe_cache, GFP_KERNEL);
        if (!cmbe)
-               return -ENOMEM;
-       cmb_data = kzalloc(sizeof(struct cmb_data), GFP_KERNEL);
-       if (!cmb_data) {
-               ret = -ENOMEM;
+               return ret;
+
+       cmb_data = kzalloc(sizeof(*cmb_data), GFP_KERNEL);
+       if (!cmb_data)
                goto out_free;
-       }
+
        cmb_data->last_block = kzalloc(sizeof(struct cmbe), GFP_KERNEL);
-       if (!cmb_data->last_block) {
-               ret = -ENOMEM;
+       if (!cmb_data->last_block)
                goto out_free;
-       }
-       cmb_data->size = sizeof(struct cmbe);
-       spin_lock_irq(cdev->ccwlock);
-       if (cdev->private->cmb) {
-               spin_unlock_irq(cdev->ccwlock);
-               ret = -EBUSY;
-               goto out_free;
-       }
+
+       cmb_data->size = sizeof(*cmbe);
        cmb_data->hw_block = cmbe;
+
+       spin_lock(&cmb_area.lock);
+       spin_lock_irq(cdev->ccwlock);
+       if (cdev->private->cmb)
+               goto out_unlock;
+
        cdev->private->cmb = cmb_data;
-       spin_unlock_irq(cdev->ccwlock);
 
        /* activate global measurement if this is the first channel */
-       spin_lock(&cmb_area.lock);
        if (list_empty(&cmb_area.list))
                cmf_activate(NULL, 1);
        list_add_tail(&cdev->private->cmb_list, &cmb_area.list);
-       spin_unlock(&cmb_area.lock);
 
+       spin_unlock_irq(cdev->ccwlock);
+       spin_unlock(&cmb_area.lock);
        return 0;
+
+out_unlock:
+       spin_unlock_irq(cdev->ccwlock);
+       spin_unlock(&cmb_area.lock);
+       ret = -EBUSY;
 out_free:
        if (cmb_data)
                kfree(cmb_data->last_block);
        kfree(cmb_data);
-       kfree(cmbe);
+       kmem_cache_free(cmbe_cache, cmbe);
+
        return ret;
 }
 
@@ -869,19 +854,21 @@ static void free_cmbe(struct ccw_device *cdev)
 {
        struct cmb_data *cmb_data;
 
+       spin_lock(&cmb_area.lock);
        spin_lock_irq(cdev->ccwlock);
        cmb_data = cdev->private->cmb;
        cdev->private->cmb = NULL;
-       if (cmb_data)
+       if (cmb_data) {
                kfree(cmb_data->last_block);
+               kmem_cache_free(cmbe_cache, cmb_data->hw_block);
+       }
        kfree(cmb_data);
-       spin_unlock_irq(cdev->ccwlock);
 
        /* deactivate global measurement if this is the last channel */
-       spin_lock(&cmb_area.lock);
        list_del_init(&cdev->private->cmb_list);
        if (list_empty(&cmb_area.list))
                cmf_activate(NULL, 0);
+       spin_unlock_irq(cdev->ccwlock);
        spin_unlock(&cmb_area.lock);
 }
 
@@ -897,7 +884,7 @@ static int set_cmbe(struct ccw_device *cdev, u32 mme)
                return -EINVAL;
        }
        cmb_data = cdev->private->cmb;
-       mba = mme ? (unsigned long) cmbe_align(cmb_data->hw_block) : 0;
+       mba = mme ? (unsigned long) cmb_data->hw_block : 0;
        spin_unlock_irqrestore(cdev->ccwlock, flags);
 
        return set_schib_wait(cdev, mme, 1, mba);
@@ -1022,11 +1009,6 @@ static void reset_cmbe(struct ccw_device *cdev)
        cmf_generic_reset(cdev);
 }
 
-static void * align_cmbe(void *area)
-{
-       return cmbe_align(area);
-}
-
 static struct attribute_group cmf_attr_group_ext;
 
 static struct cmb_operations cmbops_extended = {
@@ -1036,7 +1018,6 @@ static struct cmb_operations cmbops_extended = {
        .read       = read_cmbe,
        .readall    = readall_cmbe,
        .reset      = reset_cmbe,
-       .align      = align_cmbe,
        .attr_group = &cmf_attr_group_ext,
 };
 
@@ -1171,23 +1152,28 @@ static ssize_t cmb_enable_show(struct device *dev,
                               struct device_attribute *attr,
                               char *buf)
 {
-       return sprintf(buf, "%d\n", to_ccwdev(dev)->private->cmb ? 1 : 0);
+       struct ccw_device *cdev = to_ccwdev(dev);
+       int enabled;
+
+       spin_lock_irq(cdev->ccwlock);
+       enabled = !!cdev->private->cmb;
+       spin_unlock_irq(cdev->ccwlock);
+
+       return sprintf(buf, "%d\n", enabled);
 }
 
 static ssize_t cmb_enable_store(struct device *dev,
                                struct device_attribute *attr, const char *buf,
                                size_t c)
 {
-       struct ccw_device *cdev;
-       int ret;
+       struct ccw_device *cdev = to_ccwdev(dev);
        unsigned long val;
+       int ret;
 
        ret = kstrtoul(buf, 16, &val);
        if (ret)
                return ret;
 
-       cdev = to_ccwdev(dev);
-
        switch (val) {
        case 0:
                ret = disable_cmf(cdev);
@@ -1195,12 +1181,13 @@ static ssize_t cmb_enable_store(struct device *dev,
        case 1:
                ret = enable_cmf(cdev);
                break;
+       default:
+               ret = -EINVAL;
        }
 
-       return c;
+       return ret ? ret : c;
 }
-
-DEVICE_ATTR(cmb_enable, 0644, cmb_enable_show, cmb_enable_store);
+DEVICE_ATTR_RW(cmb_enable);
 
 int ccw_set_cmf(struct ccw_device *cdev, int enable)
 {
@@ -1220,41 +1207,71 @@ int enable_cmf(struct ccw_device *cdev)
 {
        int ret;
 
+       device_lock(&cdev->dev);
+       get_device(&cdev->dev);
        ret = cmbops->alloc(cdev);
-       cmbops->reset(cdev);
        if (ret)
-               return ret;
+               goto out;
+       cmbops->reset(cdev);
+       ret = sysfs_create_group(&cdev->dev.kobj, cmbops->attr_group);
+       if (ret) {
+               cmbops->free(cdev);
+               goto out;
+       }
        ret = cmbops->set(cdev, 2);
        if (ret) {
+               sysfs_remove_group(&cdev->dev.kobj, cmbops->attr_group);
                cmbops->free(cdev);
-               return ret;
        }
-       ret = sysfs_create_group(&cdev->dev.kobj, cmbops->attr_group);
-       if (!ret)
-               return 0;
-       cmbops->set(cdev, 0);  //FIXME: this can fail
-       cmbops->free(cdev);
+out:
+       if (ret)
+               put_device(&cdev->dev);
+
+       device_unlock(&cdev->dev);
        return ret;
 }
 
 /**
- * disable_cmf() - switch off the channel measurement for a specific device
+ * __disable_cmf() - switch off the channel measurement for a specific device
  *  @cdev:     The ccw device to be disabled
  *
  *  Returns %0 for success or a negative error value.
  *
  *  Context:
- *    non-atomic
+ *    non-atomic, device_lock() held.
  */
-int disable_cmf(struct ccw_device *cdev)
+int __disable_cmf(struct ccw_device *cdev)
 {
        int ret;
 
        ret = cmbops->set(cdev, 0);
        if (ret)
                return ret;
-       cmbops->free(cdev);
+
        sysfs_remove_group(&cdev->dev.kobj, cmbops->attr_group);
+       cmbops->free(cdev);
+       put_device(&cdev->dev);
+
+       return ret;
+}
+
+/**
+ * disable_cmf() - switch off the channel measurement for a specific device
+ *  @cdev:     The ccw device to be disabled
+ *
+ *  Returns %0 for success or a negative error value.
+ *
+ *  Context:
+ *    non-atomic
+ */
+int disable_cmf(struct ccw_device *cdev)
+{
+       int ret;
+
+       device_lock(&cdev->dev);
+       ret = __disable_cmf(cdev);
+       device_unlock(&cdev->dev);
+
        return ret;
 }
 
@@ -1295,10 +1312,32 @@ int cmf_reenable(struct ccw_device *cdev)
        return cmbops->set(cdev, 2);
 }
 
+/**
+ * cmf_reactivate() - reactivate measurement block updates
+ *
+ * Use this during resume from hibernate.
+ */
+void cmf_reactivate(void)
+{
+       spin_lock(&cmb_area.lock);
+       if (!list_empty(&cmb_area.list))
+               cmf_activate(cmb_area.mem, 1);
+       spin_unlock(&cmb_area.lock);
+}
+
+static int __init init_cmbe(void)
+{
+       cmbe_cache = kmem_cache_create("cmbe_cache", sizeof(struct cmbe),
+                                      __alignof__(struct cmbe), 0, NULL);
+
+       return cmbe_cache ? 0 : -ENOMEM;
+}
+
 static int __init init_cmf(void)
 {
        char *format_string;
-       char *detect_string = "parameter";
+       char *detect_string;
+       int ret;
 
        /*
         * If the user did not give a parameter, see if we are running on a
@@ -1324,15 +1363,18 @@ static int __init init_cmf(void)
        case CMF_EXTENDED:
                format_string = "extended";
                cmbops = &cmbops_extended;
+
+               ret = init_cmbe();
+               if (ret)
+                       return ret;
                break;
        default:
-               return 1;
+               return -EINVAL;
        }
        pr_info("Channel measurement facility initialized using format "
                "%s (mode %s)\n", format_string, detect_string);
        return 0;
 }
-
 module_init(init_cmf);