Merge tag 's390-6.1-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
[platform/kernel/linux-starfive.git] / drivers / s390 / block / dasd_devmap.c
index 0062a70..cb83f81 100644 (file)
@@ -26,7 +26,6 @@
 
 /* This is ugly... */
 #define PRINTK_HEADER "dasd_devmap:"
-#define DASD_BUS_ID_SIZE 20
 #define DASD_MAX_PARAMS 256
 
 #include "dasd_int.h"
@@ -50,6 +49,7 @@ struct dasd_devmap {
         unsigned int devindex;
         unsigned short features;
        struct dasd_device *device;
+       struct dasd_copy_relation *copy;
 };
 
 /*
@@ -130,7 +130,7 @@ __setup ("dasd=", dasd_call_setup);
 /*
  * Read a device busid/devno from a string.
  */
-static int __init dasd_busid(char *str, int *id0, int *id1, int *devno)
+static int dasd_busid(char *str, int *id0, int *id1, int *devno)
 {
        unsigned int val;
        char *tok;
@@ -438,16 +438,12 @@ dasd_add_busid(const char *bus_id, int features)
        return devmap;
 }
 
-/*
- * Find devmap for device with given bus_id.
- */
 static struct dasd_devmap *
-dasd_find_busid(const char *bus_id)
+dasd_find_busid_locked(const char *bus_id)
 {
        struct dasd_devmap *devmap, *tmp;
        int hash;
 
-       spin_lock(&dasd_devmap_lock);
        devmap = ERR_PTR(-ENODEV);
        hash = dasd_hash_busid(bus_id);
        list_for_each_entry(tmp, &dasd_hashlists[hash], list) {
@@ -456,6 +452,19 @@ dasd_find_busid(const char *bus_id)
                        break;
                }
        }
+       return devmap;
+}
+
+/*
+ * Find devmap for device with given bus_id.
+ */
+static struct dasd_devmap *
+dasd_find_busid(const char *bus_id)
+{
+       struct dasd_devmap *devmap;
+
+       spin_lock(&dasd_devmap_lock);
+       devmap = dasd_find_busid_locked(bus_id);
        spin_unlock(&dasd_devmap_lock);
        return devmap;
 }
@@ -585,6 +594,238 @@ dasd_create_device(struct ccw_device *cdev)
 }
 
 /*
+ * allocate a PPRC data structure and call the discipline function to fill
+ */
+static int dasd_devmap_get_pprc_status(struct dasd_device *device,
+                                      struct dasd_pprc_data_sc4 **data)
+{
+       struct dasd_pprc_data_sc4 *temp;
+
+       if (!device->discipline || !device->discipline->pprc_status) {
+               dev_warn(&device->cdev->dev, "Unable to query copy relation status\n");
+               return -EOPNOTSUPP;
+       }
+       temp = kzalloc(sizeof(*temp), GFP_KERNEL);
+       if (!temp)
+               return -ENOMEM;
+
+       /* get PPRC information from storage */
+       if (device->discipline->pprc_status(device, temp)) {
+               dev_warn(&device->cdev->dev, "Error during copy relation status query\n");
+               kfree(temp);
+               return -EINVAL;
+       }
+       *data = temp;
+
+       return 0;
+}
+
+/*
+ * find an entry in a PPRC device_info array by a given UID
+ * depending on the primary/secondary state of the device it has to be
+ * matched with the respective fields
+ */
+static int dasd_devmap_entry_from_pprc_data(struct dasd_pprc_data_sc4 *data,
+                                           struct dasd_uid uid,
+                                           bool primary)
+{
+       int i;
+
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (primary) {
+                       if (data->dev_info[i].prim_cu_ssid == uid.ssid &&
+                           data->dev_info[i].primary == uid.real_unit_addr)
+                               return i;
+               } else {
+                       if (data->dev_info[i].sec_cu_ssid == uid.ssid &&
+                           data->dev_info[i].secondary == uid.real_unit_addr)
+                               return i;
+               }
+       }
+       return -1;
+}
+
+/*
+ * check the consistency of a specified copy relation by checking
+ * the following things:
+ *
+ *   - is the given device part of a copy pair setup
+ *   - does the state of the device match the state in the PPRC status data
+ *   - does the device UID match with the UID in the PPRC status data
+ *   - to prevent misrouted IO check if the given device is present in all
+ *     related PPRC status data
+ */
+static int dasd_devmap_check_copy_relation(struct dasd_device *device,
+                                          struct dasd_copy_entry *entry,
+                                          struct dasd_pprc_data_sc4 *data,
+                                          struct dasd_copy_relation *copy)
+{
+       struct dasd_pprc_data_sc4 *tmp_dat;
+       struct dasd_device *tmp_dev;
+       struct dasd_uid uid;
+       int i, j;
+
+       if (!device->discipline || !device->discipline->get_uid ||
+           device->discipline->get_uid(device, &uid))
+               return 1;
+
+       i = dasd_devmap_entry_from_pprc_data(data, uid, entry->primary);
+       if (i < 0) {
+               dev_warn(&device->cdev->dev, "Device not part of a copy relation\n");
+               return 1;
+       }
+
+       /* double check which role the current device has */
+       if (entry->primary) {
+               if (data->dev_info[i].flags & 0x80) {
+                       dev_warn(&device->cdev->dev, "Copy pair secondary is setup as primary\n");
+                       return 1;
+               }
+               if (data->dev_info[i].prim_cu_ssid != uid.ssid ||
+                   data->dev_info[i].primary != uid.real_unit_addr) {
+                       dev_warn(&device->cdev->dev,
+                                "Primary device %s does not match copy pair status primary device %04x\n",
+                                dev_name(&device->cdev->dev),
+                                data->dev_info[i].prim_cu_ssid |
+                                data->dev_info[i].primary);
+                       return 1;
+               }
+       } else {
+               if (!(data->dev_info[i].flags & 0x80)) {
+                       dev_warn(&device->cdev->dev, "Copy pair primary is setup as secondary\n");
+                       return 1;
+               }
+               if (data->dev_info[i].sec_cu_ssid != uid.ssid ||
+                   data->dev_info[i].secondary != uid.real_unit_addr) {
+                       dev_warn(&device->cdev->dev,
+                                "Secondary device %s does not match copy pair status secondary device %04x\n",
+                                dev_name(&device->cdev->dev),
+                                data->dev_info[i].sec_cu_ssid |
+                                data->dev_info[i].secondary);
+                       return 1;
+               }
+       }
+
+       /*
+        * the current device has to be part of the copy relation of all
+        * entries to prevent misrouted IO to another copy pair
+        */
+       for (j = 0; j < DASD_CP_ENTRIES; j++) {
+               if (entry == &copy->entry[j])
+                       tmp_dev = device;
+               else
+                       tmp_dev = copy->entry[j].device;
+
+               if (!tmp_dev)
+                       continue;
+
+               if (dasd_devmap_get_pprc_status(tmp_dev, &tmp_dat))
+                       return 1;
+
+               if (dasd_devmap_entry_from_pprc_data(tmp_dat, uid, entry->primary) < 0) {
+                       dev_warn(&tmp_dev->cdev->dev,
+                                "Copy pair relation does not contain device: %s\n",
+                                dev_name(&device->cdev->dev));
+                       kfree(tmp_dat);
+                       return 1;
+               }
+               kfree(tmp_dat);
+       }
+       return 0;
+}
+
+/* delete device from copy relation entry */
+static void dasd_devmap_delete_copy_relation_device(struct dasd_device *device)
+{
+       struct dasd_copy_relation *copy;
+       int i;
+
+       if (!device->copy)
+               return;
+
+       copy = device->copy;
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (copy->entry[i].device == device)
+                       copy->entry[i].device = NULL;
+       }
+       dasd_put_device(device);
+       device->copy = NULL;
+}
+
+/*
+ * read all required information for a copy relation setup and setup the device
+ * accordingly
+ */
+int dasd_devmap_set_device_copy_relation(struct ccw_device *cdev,
+                                        bool pprc_enabled)
+{
+       struct dasd_pprc_data_sc4 *data = NULL;
+       struct dasd_copy_entry *entry = NULL;
+       struct dasd_copy_relation *copy;
+       struct dasd_devmap *devmap;
+       struct dasd_device *device;
+       int i, rc = 0;
+
+       devmap = dasd_devmap_from_cdev(cdev);
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+
+       device = devmap->device;
+       if (!device)
+               return -ENODEV;
+
+       copy = devmap->copy;
+       /* no copy pair setup for this device */
+       if (!copy)
+               goto out;
+
+       rc = dasd_devmap_get_pprc_status(device, &data);
+       if (rc)
+               return rc;
+
+       /* print error if PPRC is requested but not enabled on storage server */
+       if (!pprc_enabled) {
+               dev_err(&cdev->dev, "Copy relation not enabled on storage server\n");
+               rc = -EINVAL;
+               goto out;
+       }
+
+       if (!data->dev_info[0].state) {
+               dev_warn(&device->cdev->dev, "Copy pair setup requested for device not in copy relation\n");
+               rc = -EINVAL;
+               goto out;
+       }
+       /* find entry */
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (copy->entry[i].configured &&
+                   strncmp(dev_name(&cdev->dev),
+                           copy->entry[i].busid, DASD_BUS_ID_SIZE) == 0) {
+                       entry = &copy->entry[i];
+                       break;
+               }
+       }
+       if (!entry) {
+               dev_warn(&device->cdev->dev, "Copy relation entry not found\n");
+               rc = -EINVAL;
+               goto out;
+       }
+       /* check if the copy relation is valid */
+       if (dasd_devmap_check_copy_relation(device, entry, data, copy)) {
+               dev_warn(&device->cdev->dev, "Copy relation faulty\n");
+               rc = -EINVAL;
+               goto out;
+       }
+
+       dasd_get_device(device);
+       copy->entry[i].device = device;
+       device->copy = copy;
+out:
+       kfree(data);
+       return rc;
+}
+EXPORT_SYMBOL_GPL(dasd_devmap_set_device_copy_relation);
+
+/*
  * Wait queue for dasd_delete_device waits.
  */
 static DECLARE_WAIT_QUEUE_HEAD(dasd_delete_wq);
@@ -617,6 +858,8 @@ dasd_delete_device(struct dasd_device *device)
        dev_set_drvdata(&device->cdev->dev, NULL);
        spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
 
+       /* Removve copy relation */
+       dasd_devmap_delete_copy_relation_device(device);
        /*
         * Drop ref_count by 3, one for the devmap reference, one for
         * the cdev reference and one for the passed reference.
@@ -694,6 +937,7 @@ void dasd_add_link_to_gendisk(struct gendisk *gdp, struct dasd_device *device)
        gdp->private_data = devmap;
        spin_unlock(&dasd_devmap_lock);
 }
+EXPORT_SYMBOL(dasd_add_link_to_gendisk);
 
 struct dasd_device *dasd_device_from_gendisk(struct gendisk *gdp)
 {
@@ -1334,7 +1578,6 @@ dasd_timeout_store(struct device *dev, struct device_attribute *attr,
                   const char *buf, size_t count)
 {
        struct dasd_device *device;
-       struct request_queue *q;
        unsigned long val;
 
        device = dasd_device_from_cdev(to_ccwdev(dev));
@@ -1346,15 +1589,13 @@ dasd_timeout_store(struct device *dev, struct device_attribute *attr,
                dasd_put_device(device);
                return -EINVAL;
        }
-       q = device->block->request_queue;
-       if (!q) {
+       if (!device->block->gdp) {
                dasd_put_device(device);
                return -ENODEV;
        }
 
        device->blk_timeout = val;
-
-       blk_queue_rq_timeout(q, device->blk_timeout * HZ);
+       blk_queue_rq_timeout(device->block->gdp->queue, val * HZ);
 
        dasd_put_device(device);
        return count;
@@ -1683,6 +1924,347 @@ dasd_path_fcs_show(struct kobject *kobj, struct kobj_attribute *attr, char *buf)
 static struct kobj_attribute path_fcs_attribute =
        __ATTR(fc_security, 0444, dasd_path_fcs_show, NULL);
 
+/*
+ * print copy relation in the form
+ * primary,secondary[1] primary,secondary[2], ...
+ */
+static ssize_t
+dasd_copy_pair_show(struct device *dev,
+                   struct device_attribute *attr, char *buf)
+{
+       char prim_busid[DASD_BUS_ID_SIZE];
+       struct dasd_copy_relation *copy;
+       struct dasd_devmap *devmap;
+       int len = 0;
+       int i;
+
+       devmap = dasd_find_busid(dev_name(dev));
+       if (IS_ERR(devmap))
+               return -ENODEV;
+
+       if (!devmap->copy)
+               return -ENODEV;
+
+       copy = devmap->copy;
+       /* find primary */
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (copy->entry[i].configured && copy->entry[i].primary) {
+                       strscpy(prim_busid, copy->entry[i].busid,
+                               DASD_BUS_ID_SIZE);
+                       break;
+               }
+       }
+       if (!copy->entry[i].primary)
+               goto out;
+
+       /* print all secondary */
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (copy->entry[i].configured && !copy->entry[i].primary)
+                       len += sysfs_emit_at(buf, len, "%s,%s ", prim_busid,
+                                            copy->entry[i].busid);
+       }
+
+       len += sysfs_emit_at(buf, len, "\n");
+out:
+       return len;
+}
+
+static int dasd_devmap_set_copy_relation(struct dasd_devmap *devmap,
+                                        struct dasd_copy_relation *copy,
+                                        char *busid, bool primary)
+{
+       int i;
+
+       /* find free entry */
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               /* current bus_id already included, nothing to do */
+               if (copy->entry[i].configured &&
+                   strncmp(copy->entry[i].busid, busid, DASD_BUS_ID_SIZE) == 0)
+                       return 0;
+
+               if (!copy->entry[i].configured)
+                       break;
+       }
+       if (i == DASD_CP_ENTRIES)
+               return -EINVAL;
+
+       copy->entry[i].configured = true;
+       strscpy(copy->entry[i].busid, busid, DASD_BUS_ID_SIZE);
+       if (primary) {
+               copy->active = &copy->entry[i];
+               copy->entry[i].primary = true;
+       }
+       if (!devmap->copy)
+               devmap->copy = copy;
+
+       return 0;
+}
+
+static void dasd_devmap_del_copy_relation(struct dasd_copy_relation *copy,
+                                         char *busid)
+{
+       int i;
+
+       spin_lock(&dasd_devmap_lock);
+       /* find entry */
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (copy->entry[i].configured &&
+                   strncmp(copy->entry[i].busid, busid, DASD_BUS_ID_SIZE) == 0)
+                       break;
+       }
+       if (i == DASD_CP_ENTRIES || !copy->entry[i].configured) {
+               spin_unlock(&dasd_devmap_lock);
+               return;
+       }
+
+       copy->entry[i].configured = false;
+       memset(copy->entry[i].busid, 0, DASD_BUS_ID_SIZE);
+       if (copy->active == &copy->entry[i]) {
+               copy->active = NULL;
+               copy->entry[i].primary = false;
+       }
+       spin_unlock(&dasd_devmap_lock);
+}
+
+static int dasd_devmap_clear_copy_relation(struct device *dev)
+{
+       struct dasd_copy_relation *copy;
+       struct dasd_devmap *devmap;
+       int i, rc = 1;
+
+       devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
+       if (IS_ERR(devmap))
+               return 1;
+
+       spin_lock(&dasd_devmap_lock);
+       if (!devmap->copy)
+               goto out;
+
+       copy = devmap->copy;
+       /* first check if all secondary devices are offline*/
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (!copy->entry[i].configured)
+                       continue;
+
+               if (copy->entry[i].device == copy->active->device)
+                       continue;
+
+               if (copy->entry[i].device)
+                       goto out;
+       }
+       /* clear all devmap entries */
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (strlen(copy->entry[i].busid) == 0)
+                       continue;
+               if (copy->entry[i].device) {
+                       dasd_put_device(copy->entry[i].device);
+                       copy->entry[i].device->copy = NULL;
+                       copy->entry[i].device = NULL;
+               }
+               devmap = dasd_find_busid_locked(copy->entry[i].busid);
+               devmap->copy = NULL;
+               memset(copy->entry[i].busid, 0, DASD_BUS_ID_SIZE);
+       }
+       kfree(copy);
+       rc = 0;
+out:
+       spin_unlock(&dasd_devmap_lock);
+       return rc;
+}
+
+/*
+ * parse BUSIDs from a copy pair
+ */
+static int dasd_devmap_parse_busid(const char *buf, char *prim_busid,
+                                  char *sec_busid)
+{
+       char *primary, *secondary, *tmp, *pt;
+       int id0, id1, id2;
+
+       pt =  kstrdup(buf, GFP_KERNEL);
+       tmp = pt;
+       if (!tmp)
+               return -ENOMEM;
+
+       primary = strsep(&tmp, ",");
+       if (!primary) {
+               kfree(pt);
+               return -EINVAL;
+       }
+       secondary = strsep(&tmp, ",");
+       if (!secondary) {
+               kfree(pt);
+               return -EINVAL;
+       }
+       if (dasd_busid(primary, &id0, &id1, &id2)) {
+               kfree(pt);
+               return -EINVAL;
+       }
+       sprintf(prim_busid, "%01x.%01x.%04x", id0, id1, id2);
+       if (dasd_busid(secondary, &id0, &id1, &id2)) {
+               kfree(pt);
+               return -EINVAL;
+       }
+       sprintf(sec_busid, "%01x.%01x.%04x", id0, id1, id2);
+       kfree(pt);
+
+       return 0;
+}
+
+static ssize_t dasd_copy_pair_store(struct device *dev,
+                                   struct device_attribute *attr,
+                                   const char *buf, size_t count)
+{
+       struct dasd_devmap *prim_devmap, *sec_devmap;
+       char prim_busid[DASD_BUS_ID_SIZE];
+       char sec_busid[DASD_BUS_ID_SIZE];
+       struct dasd_copy_relation *copy;
+       struct dasd_device *device;
+       bool pprc_enabled;
+       int rc;
+
+       if (strncmp(buf, "clear", strlen("clear")) == 0) {
+               if (dasd_devmap_clear_copy_relation(dev))
+                       return -EINVAL;
+               return count;
+       }
+
+       rc = dasd_devmap_parse_busid(buf, prim_busid, sec_busid);
+       if (rc)
+               return rc;
+
+       if (strncmp(dev_name(dev), prim_busid, DASD_BUS_ID_SIZE) != 0 &&
+           strncmp(dev_name(dev), sec_busid, DASD_BUS_ID_SIZE) != 0)
+               return -EINVAL;
+
+       /* allocate primary devmap if needed */
+       prim_devmap = dasd_find_busid(prim_busid);
+       if (IS_ERR(prim_devmap))
+               prim_devmap = dasd_add_busid(prim_busid, DASD_FEATURE_DEFAULT);
+
+       /* allocate secondary devmap if needed */
+       sec_devmap = dasd_find_busid(sec_busid);
+       if (IS_ERR(sec_devmap))
+               sec_devmap = dasd_add_busid(sec_busid, DASD_FEATURE_DEFAULT);
+
+       /* setting copy relation is only allowed for offline secondary */
+       if (sec_devmap->device)
+               return -EINVAL;
+
+       if (prim_devmap->copy) {
+               copy = prim_devmap->copy;
+       } else if (sec_devmap->copy) {
+               copy = sec_devmap->copy;
+       } else {
+               copy = kzalloc(sizeof(*copy), GFP_KERNEL);
+               if (!copy)
+                       return -ENOMEM;
+       }
+       spin_lock(&dasd_devmap_lock);
+       rc = dasd_devmap_set_copy_relation(prim_devmap, copy, prim_busid, true);
+       if (rc) {
+               spin_unlock(&dasd_devmap_lock);
+               return rc;
+       }
+       rc = dasd_devmap_set_copy_relation(sec_devmap, copy, sec_busid, false);
+       if (rc) {
+               spin_unlock(&dasd_devmap_lock);
+               return rc;
+       }
+       spin_unlock(&dasd_devmap_lock);
+
+       /* if primary device is already online call device setup directly */
+       if (prim_devmap->device && !prim_devmap->device->copy) {
+               device = prim_devmap->device;
+               if (device->discipline->pprc_enabled) {
+                       pprc_enabled = device->discipline->pprc_enabled(device);
+                       rc = dasd_devmap_set_device_copy_relation(device->cdev,
+                                                                 pprc_enabled);
+               } else {
+                       rc = -EOPNOTSUPP;
+               }
+       }
+       if (rc) {
+               dasd_devmap_del_copy_relation(copy, prim_busid);
+               dasd_devmap_del_copy_relation(copy, sec_busid);
+               count = rc;
+       }
+
+       return count;
+}
+static DEVICE_ATTR(copy_pair, 0644, dasd_copy_pair_show,
+                  dasd_copy_pair_store);
+
+static ssize_t
+dasd_copy_role_show(struct device *dev,
+                   struct device_attribute *attr, char *buf)
+{
+       struct dasd_copy_relation *copy;
+       struct dasd_device *device;
+       int len, i;
+
+       device = dasd_device_from_cdev(to_ccwdev(dev));
+       if (IS_ERR(device))
+               return -ENODEV;
+
+       if (!device->copy) {
+               len = sysfs_emit(buf, "none\n");
+               goto out;
+       }
+       copy = device->copy;
+       /* only the active device is primary */
+       if (copy->active->device == device) {
+               len = sysfs_emit(buf, "primary\n");
+               goto out;
+       }
+       for (i = 0; i < DASD_CP_ENTRIES; i++) {
+               if (copy->entry[i].device == device) {
+                       len = sysfs_emit(buf, "secondary\n");
+                       goto out;
+               }
+       }
+       /* not in the list, no COPY role */
+       len = sysfs_emit(buf, "none\n");
+out:
+       dasd_put_device(device);
+       return len;
+}
+static DEVICE_ATTR(copy_role, 0444, dasd_copy_role_show, NULL);
+
+static ssize_t dasd_device_ping(struct device *dev,
+                               struct device_attribute *attr,
+                               const char *buf, size_t count)
+{
+       struct dasd_device *device;
+       size_t rc;
+
+       device = dasd_device_from_cdev(to_ccwdev(dev));
+       if (IS_ERR(device))
+               return -ENODEV;
+
+       /*
+        * do not try during offline processing
+        * early check only
+        * the sleep_on function itself checks for offline
+        * processing again
+        */
+       if (test_bit(DASD_FLAG_OFFLINE, &device->flags)) {
+               rc = -EBUSY;
+               goto out;
+       }
+       if (!device->discipline || !device->discipline->device_ping) {
+               rc = -EOPNOTSUPP;
+               goto out;
+       }
+       rc = device->discipline->device_ping(device);
+       if (!rc)
+               rc = count;
+out:
+       dasd_put_device(device);
+       return rc;
+}
+static DEVICE_ATTR(ping, 0200, NULL, dasd_device_ping);
+
 #define DASD_DEFINE_ATTR(_name, _func)                                 \
 static ssize_t dasd_##_name##_show(struct device *dev,                 \
                                   struct device_attribute *attr,       \
@@ -1739,6 +2321,9 @@ static struct attribute * dasd_attrs[] = {
        &dev_attr_hpf.attr,
        &dev_attr_ese.attr,
        &dev_attr_fc_security.attr,
+       &dev_attr_copy_pair.attr,
+       &dev_attr_copy_role.attr,
+       &dev_attr_ping.attr,
        NULL,
 };