fedora core 6 1.2949 + vserver 2.2.0
[linux-2.6.git] / drivers / s390 / block / dasd_devmap.c
index 0fad4d5..5943266 100644 (file)
  * functions may not be called from interrupt context. In particular
  * dasd_get_device is a no-no from interrupt context.
  *
- * $Revision: 1.27 $
  */
 
-#include <linux/config.h>
 #include <linux/ctype.h>
 #include <linux/init.h>
+#include <linux/module.h>
 
 #include <asm/debug.h>
 #include <asm/uaccess.h>
@@ -26,6 +25,9 @@
 
 #include "dasd_int.h"
 
+struct kmem_cache *dasd_page_cache;
+EXPORT_SYMBOL_GPL(dasd_page_cache);
+
 /*
  * dasd_devmap_t is used to store the features and the relation
  * between device number and device index. To find a dasd_devmap_t
@@ -42,8 +44,25 @@ struct dasd_devmap {
         unsigned int devindex;
         unsigned short features;
        struct dasd_device *device;
+       struct dasd_uid uid;
+};
+
+/*
+ * dasd_server_ssid_map contains a globally unique storage server subsystem ID.
+ * dasd_server_ssid_list contains the list of all subsystem IDs accessed by
+ * the DASD device driver.
+ */
+struct dasd_server_ssid_map {
+       struct list_head list;
+       struct system_id {
+               char vendor[4];
+               char serial[15];
+               __u16 ssid;
+       } sid;
 };
 
+static struct list_head dasd_server_ssid_list;
+
 /*
  * Parameter parsing functions for dasd= parameter. The syntax is:
  *   <devno>           : (0x)?[0-9a-fA-F]+
@@ -60,6 +79,8 @@ struct dasd_devmap {
 
 int dasd_probeonly =  0;       /* is true, when probeonly mode is active */
 int dasd_autodetect = 0;       /* is true, when autodetection is active */
+int dasd_nopav = 0;            /* is true, when PAV is disabled */
+EXPORT_SYMBOL_GPL(dasd_nopav);
 
 /*
  * char *dasd[] is intended to hold the ranges supplied by the dasd= statement
@@ -67,11 +88,12 @@ int dasd_autodetect = 0;    /* is true, when autodetection is active */
  * strings when running as a module.
  */
 static char *dasd[256];
+module_param_array(dasd, charp, NULL, 0);
 
 /*
- * Single spinlock to protect devmap structures and lists.
+ * Single spinlock to protect devmap and servermap structures and lists.
  */
-static spinlock_t dasd_devmap_lock = SPIN_LOCK_UNLOCKED;
+static DEFINE_SPINLOCK(dasd_devmap_lock);
 
 /*
  * Hash lists for devmap structures.
@@ -118,7 +140,7 @@ static inline int
 dasd_busid(char **str, int *id0, int *id1, int *devno)
 {
        int val, old_style;
+
        /* check for leading '0x' */
        old_style = 0;
        if ((*str)[0] == '0' && (*str)[1] == 'x') {
@@ -174,12 +196,14 @@ dasd_feature_list(char *str, char **endp)
        features = 0;
 
        while (1) {
-               for (len = 0; 
+               for (len = 0;
                     str[len] && str[len] != ':' && str[len] != ')'; len++);
                if (len == 2 && !strncmp(str, "ro", 2))
                        features |= DASD_FEATURE_READONLY;
                else if (len == 4 && !strncmp(str, "diag", 4))
                        features |= DASD_FEATURE_USEDIAG;
+               else if (len == 6 && !strncmp(str, "erplog", 6))
+                       features |= DASD_FEATURE_ERPLOG;
                else {
                        MESSAGE(KERN_WARNING,
                                "unsupported feature: %*s, "
@@ -204,94 +228,156 @@ dasd_feature_list(char *str, char **endp)
 }
 
 /*
- * Read comma separated list of dasd ranges.
+ * Try to match the first element on the comma separated parse string
+ * with one of the known keywords. If a keyword is found, take the approprate
+ * action and return a pointer to the residual string. If the first element
+ * could not be matched to any keyword then return an error code.
  */
-static inline int
-dasd_ranges_list(char *str)
-{
+static char *
+dasd_parse_keyword( char *parsestring ) {
+
+       char *nextcomma, *residual_str;
+       int length;
+
+       nextcomma = strchr(parsestring,',');
+       if (nextcomma) {
+               length = nextcomma - parsestring;
+               residual_str = nextcomma + 1;
+       } else {
+               length = strlen(parsestring);
+               residual_str = parsestring + length;
+        }
+       if (strncmp("autodetect", parsestring, length) == 0) {
+               dasd_autodetect = 1;
+               MESSAGE (KERN_INFO, "%s",
+                        "turning to autodetection mode");
+                return residual_str;
+        }
+       if (strncmp("probeonly", parsestring, length) == 0) {
+               dasd_probeonly = 1;
+               MESSAGE(KERN_INFO, "%s",
+                       "turning to probeonly mode");
+                return residual_str;
+        }
+       if (strncmp("nopav", parsestring, length) == 0) {
+               if (MACHINE_IS_VM)
+                       MESSAGE(KERN_INFO, "%s", "'nopav' not supported on VM");
+               else {
+                       dasd_nopav = 1;
+                       MESSAGE(KERN_INFO, "%s", "disable PAV mode");
+               }
+               return residual_str;
+       }
+       if (strncmp("fixedbuffers", parsestring, length) == 0) {
+               if (dasd_page_cache)
+                       return residual_str;
+               dasd_page_cache =
+                       kmem_cache_create("dasd_page_cache", PAGE_SIZE,
+                                         PAGE_SIZE, SLAB_CACHE_DMA,
+                                         NULL, NULL );
+               if (!dasd_page_cache)
+                       MESSAGE(KERN_WARNING, "%s", "Failed to create slab, "
+                               "fixed buffer mode disabled.");
+               else
+                       MESSAGE (KERN_INFO, "%s",
+                                "turning on fixed buffer mode");
+                return residual_str;
+        }
+       return ERR_PTR(-EINVAL);
+}
+
+/*
+ * Try to interprete the first element on the comma separated parse string
+ * as a device number or a range of devices. If the interpretation is
+ * successfull, create the matching dasd_devmap entries and return a pointer
+ * to the residual string.
+ * If interpretation fails or in case of an error, return an error code.
+ */
+static char *
+dasd_parse_range( char *parsestring ) {
+
        struct dasd_devmap *devmap;
        int from, from_id0, from_id1;
        int to, to_id0, to_id1;
        int features, rc;
-       char bus_id[BUS_ID_SIZE+1], *orig_str;
-
-       orig_str = str;
-       while (1) {
-               rc = dasd_busid(&str, &from_id0, &from_id1, &from);
-               if (rc == 0) {
-                       to = from;
-                       to_id0 = from_id0;
-                       to_id1 = from_id1;
-                       if (*str == '-') {
-                               str++;
-                               rc = dasd_busid(&str, &to_id0, &to_id1, &to);
-                       }
-               }
-               if (rc == 0 &&
-                   (from_id0 != to_id0 || from_id1 != to_id1 || from > to))
-                       rc = -EINVAL;
-               if (rc) {
-                       MESSAGE(KERN_ERR, "Invalid device range %s", orig_str);
-                       return rc;
-               }
-               features = dasd_feature_list(str, &str);
-               if (features < 0)
-                       return -EINVAL;
-               while (from <= to) {
-                       sprintf(bus_id, "%01x.%01x.%04x",
-                               from_id0, from_id1, from++);
-                       devmap = dasd_add_busid(bus_id, features);
-                       if (IS_ERR(devmap))
-                               return PTR_ERR(devmap);
+       char bus_id[BUS_ID_SIZE+1], *str;
+
+       str = parsestring;
+       rc = dasd_busid(&str, &from_id0, &from_id1, &from);
+       if (rc == 0) {
+               to = from;
+               to_id0 = from_id0;
+               to_id1 = from_id1;
+               if (*str == '-') {
+                       str++;
+                       rc = dasd_busid(&str, &to_id0, &to_id1, &to);
                }
-               if (*str != ',')
-                       break;
-               str++;
        }
-       if (*str != '\0') {
-               MESSAGE(KERN_WARNING,
-                       "junk at end of dasd parameter string: %s\n", str);
-               return -EINVAL;
+       if (rc == 0 &&
+           (from_id0 != to_id0 || from_id1 != to_id1 || from > to))
+               rc = -EINVAL;
+       if (rc) {
+               MESSAGE(KERN_ERR, "Invalid device range %s", parsestring);
+               return ERR_PTR(rc);
        }
-       return 0;
+       features = dasd_feature_list(str, &str);
+       if (features < 0)
+               return ERR_PTR(-EINVAL);
+       /* each device in dasd= parameter should be set initially online */
+       features |= DASD_FEATURE_INITIAL_ONLINE;
+       while (from <= to) {
+               sprintf(bus_id, "%01x.%01x.%04x",
+                       from_id0, from_id1, from++);
+               devmap = dasd_add_busid(bus_id, features);
+               if (IS_ERR(devmap))
+                       return (char *)devmap;
+       }
+       if (*str == ',')
+               return str + 1;
+       if (*str == '\0')
+               return str;
+       MESSAGE(KERN_WARNING,
+               "junk at end of dasd parameter string: %s\n", str);
+       return ERR_PTR(-EINVAL);
 }
 
-/*
- * Parse a single dasd= parameter.
- */
-static int
-dasd_parameter(char *str)
-{
-       if (strcmp ("autodetect", str) == 0) {
-               dasd_autodetect = 1;
-               MESSAGE (KERN_INFO, "%s",
-                        "turning to autodetection mode");
-               return 0;
-       }
-       if (strcmp ("probeonly", str) == 0) {
-               dasd_probeonly = 1;
-               MESSAGE(KERN_INFO, "%s",
-                       "turning to probeonly mode");
-               return 0;
-       }
-       /* turn off autodetect mode and scan for dasd ranges */
-       dasd_autodetect = 0;
-       return dasd_ranges_list(str);
+static inline char *
+dasd_parse_next_element( char *parsestring ) {
+       char * residual_str;
+       residual_str = dasd_parse_keyword(parsestring);
+       if (!IS_ERR(residual_str))
+               return residual_str;
+       residual_str = dasd_parse_range(parsestring);
+       return residual_str;
 }
 
 /*
- * Parse parameters stored in dasd[] and dasd_disciplines[].
+ * Parse parameters stored in dasd[]
+ * The 'dasd=...' parameter allows to specify a comma separated list of
+ * keywords and device ranges. When the dasd driver is build into the kernel,
+ * the complete list will be stored as one element of the dasd[] array.
+ * When the dasd driver is build as a module, then the list is broken into
+ * it's elements and each dasd[] entry contains one element.
  */
 int
 dasd_parse(void)
 {
        int rc, i;
+       char *parsestring;
 
        rc = 0;
        for (i = 0; i < 256; i++) {
                if (dasd[i] == NULL)
                        break;
-               rc = dasd_parameter(dasd[i]);
+               parsestring = dasd[i];
+               /* loop over the comma separated list in the parsestring */
+               while (*parsestring) {
+                       parsestring = dasd_parse_next_element(parsestring);
+                       if(IS_ERR(parsestring)) {
+                               rc = PTR_ERR(parsestring);
+                               break;
+                       }
+               }
                if (rc) {
                        DBF_EVENT(DBF_ALERT, "%s", "invalid range found");
                        break;
@@ -304,7 +390,7 @@ dasd_parse(void)
  * Add a devmap for the device specified by busid. It is possible that
  * the devmap already exists (dasd= parameter). The order of the devices
  * added through this function will define the kdevs for the individual
- * devices. 
+ * devices.
  */
 static struct dasd_devmap *
 dasd_add_busid(char *bus_id, int features)
@@ -313,11 +399,11 @@ dasd_add_busid(char *bus_id, int features)
        int hash;
 
        new = (struct dasd_devmap *)
-               kmalloc(sizeof(struct dasd_devmap), GFP_KERNEL);
+               kzalloc(sizeof(struct dasd_devmap), GFP_KERNEL);
        if (!new)
                return ERR_PTR(-ENOMEM);
        spin_lock(&dasd_devmap_lock);
-       devmap = 0;
+       devmap = NULL;
        hash = dasd_hash_busid(bus_id);
        list_for_each_entry(tmp, &dasd_hashlists[hash], list)
                if (strncmp(tmp->bus_id, bus_id, BUS_ID_SIZE) == 0) {
@@ -329,14 +415,13 @@ dasd_add_busid(char *bus_id, int features)
                new->devindex = dasd_max_devindex++;
                strncpy(new->bus_id, bus_id, BUS_ID_SIZE);
                new->features = features;
-               new->device = 0;
+               new->device = NULL;
                list_add(&new->list, &dasd_hashlists[hash]);
                devmap = new;
-               new = 0;
+               new = NULL;
        }
        spin_unlock(&dasd_devmap_lock);
-       if (new)
-               kfree(new);
+       kfree(new);
        return devmap;
 }
 
@@ -384,8 +469,7 @@ dasd_forget_ranges(void)
        spin_lock(&dasd_devmap_lock);
        for (i = 0; i < 256; i++) {
                list_for_each_entry_safe(devmap, n, &dasd_hashlists[i], list) {
-                       if (devmap->device != NULL)
-                               BUG();
+                       BUG_ON(devmap->device != NULL);
                        list_del(&devmap->list);
                        kfree(devmap);
                }
@@ -404,7 +488,7 @@ dasd_device_from_devindex(int devindex)
        int i;
 
        spin_lock(&dasd_devmap_lock);
-       devmap = 0;
+       devmap = NULL;
        for (i = 0; (i < 256) && !devmap; i++)
                list_for_each_entry(tmp, &dasd_hashlists[i], list)
                        if (tmp->devindex == devindex) {
@@ -430,16 +514,10 @@ dasd_devmap_from_cdev(struct ccw_device *cdev)
 {
        struct dasd_devmap *devmap;
 
-       if (cdev->dev.driver_data)
-               return (struct dasd_devmap *) cdev->dev.driver_data;
        devmap = dasd_find_busid(cdev->dev.bus_id);
-       if (!IS_ERR(devmap)) {
-               cdev->dev.driver_data = devmap;
-               return devmap;
-       }
-       devmap = dasd_add_busid(cdev->dev.bus_id, DASD_FEATURE_DEFAULT);
-       if (!IS_ERR(devmap))
-               cdev->dev.driver_data = devmap;
+       if (IS_ERR(devmap))
+               devmap = dasd_add_busid(cdev->dev.bus_id,
+                                       DASD_FEATURE_DEFAULT);
        return devmap;
 }
 
@@ -451,6 +529,7 @@ dasd_create_device(struct ccw_device *cdev)
 {
        struct dasd_devmap *devmap;
        struct dasd_device *device;
+       unsigned long flags;
        int rc;
 
        devmap = dasd_devmap_from_cdev(cdev);
@@ -460,20 +539,13 @@ dasd_create_device(struct ccw_device *cdev)
        device = dasd_alloc_device();
        if (IS_ERR(device))
                return device;
-       atomic_set(&device->ref_count, 2);
+       atomic_set(&device->ref_count, 3);
 
        spin_lock(&dasd_devmap_lock);
        if (!devmap->device) {
                devmap->device = device;
                device->devindex = devmap->devindex;
-               if (devmap->features & DASD_FEATURE_READONLY)
-                       set_bit(DASD_FLAG_RO, &device->flags);
-               else
-                       clear_bit(DASD_FLAG_RO, &device->flags);
-               if (devmap->features & DASD_FEATURE_USEDIAG)
-                       set_bit(DASD_FLAG_USE_DIAG, &device->flags);
-               else
-                       clear_bit(DASD_FLAG_USE_DIAG, &device->flags);
+               device->features = devmap->features;
                get_device(&cdev->dev);
                device->cdev = cdev;
                rc = 0;
@@ -486,6 +558,11 @@ dasd_create_device(struct ccw_device *cdev)
                dasd_free_device(device);
                return ERR_PTR(rc);
        }
+
+       spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+       cdev->dev.driver_data = device;
+       spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+
        return device;
 }
 
@@ -503,9 +580,11 @@ dasd_delete_device(struct dasd_device *device)
 {
        struct ccw_device *cdev;
        struct dasd_devmap *devmap;
+       unsigned long flags;
 
        /* First remove device pointer from devmap. */
        devmap = dasd_find_busid(device->cdev->dev.bus_id);
+       BUG_ON(IS_ERR(devmap));
        spin_lock(&dasd_devmap_lock);
        if (devmap->device != device) {
                spin_unlock(&dasd_devmap_lock);
@@ -515,9 +594,16 @@ dasd_delete_device(struct dasd_device *device)
        devmap->device = NULL;
        spin_unlock(&dasd_devmap_lock);
 
-       /* Drop ref_count by 2, one for the devmap reference and
-        * one for the passed reference. */
-       atomic_sub(2, &device->ref_count);
+       /* Disconnect dasd_device structure from ccw_device structure. */
+       spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+       device->cdev->dev.driver_data = NULL;
+       spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+
+       /*
+        * Drop ref_count by 3, one for the devmap reference, one for
+        * the cdev reference and one for the passed reference.
+        */
+       atomic_sub(3, &device->ref_count);
 
        /* Wait for reference counter to drop to zero. */
        wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0);
@@ -543,23 +629,34 @@ dasd_put_device_wake(struct dasd_device *device)
        wake_up(&dasd_delete_wq);
 }
 
+/*
+ * Return dasd_device structure associated with cdev.
+ * This function needs to be called with the ccw device
+ * lock held. It can be used from interrupt context.
+ */
+struct dasd_device *
+dasd_device_from_cdev_locked(struct ccw_device *cdev)
+{
+       struct dasd_device *device = cdev->dev.driver_data;
+
+       if (!device)
+               return ERR_PTR(-ENODEV);
+       dasd_get_device(device);
+       return device;
+}
+
 /*
  * Return dasd_device structure associated with cdev.
  */
 struct dasd_device *
 dasd_device_from_cdev(struct ccw_device *cdev)
 {
-       struct dasd_devmap *devmap;
        struct dasd_device *device;
+       unsigned long flags;
 
-       device = ERR_PTR(-ENODEV);
-       spin_lock(&dasd_devmap_lock);
-       devmap = cdev->dev.driver_data;
-       if (devmap && devmap->device) {
-               device = devmap->device;
-               dasd_get_device(device);
-       }
-       spin_unlock(&dasd_devmap_lock);
+       spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+       device = dasd_device_from_cdev_locked(cdev);
+       spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
        return device;
 }
 
@@ -571,13 +668,13 @@ dasd_device_from_cdev(struct ccw_device *cdev)
  * readonly controls the readonly status of a dasd
  */
 static ssize_t
-dasd_ro_show(struct device *dev, char *buf)
+dasd_ro_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct dasd_devmap *devmap;
        int ro_flag;
 
-       devmap = dev->driver_data;
-       if (devmap)
+       devmap = dasd_find_busid(dev->bus_id);
+       if (!IS_ERR(devmap))
                ro_flag = (devmap->features & DASD_FEATURE_READONLY) != 0;
        else
                ro_flag = (DASD_FEATURE_DEFAULT & DASD_FEATURE_READONLY) != 0;
@@ -585,45 +682,94 @@ dasd_ro_show(struct device *dev, char *buf)
 }
 
 static ssize_t
-dasd_ro_store(struct device *dev, const char *buf, size_t count)
+dasd_ro_store(struct device *dev, struct device_attribute *attr,
+             const char *buf, size_t count)
 {
        struct dasd_devmap *devmap;
-       int ro_flag;
+       int val;
+       char *endp;
 
        devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
-       ro_flag = buf[0] == '1';
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+
+       val = simple_strtoul(buf, &endp, 0);
+       if (((endp + 1) < (buf + count)) || (val > 1))
+               return -EINVAL;
+
        spin_lock(&dasd_devmap_lock);
-       if (ro_flag)
+       if (val)
                devmap->features |= DASD_FEATURE_READONLY;
        else
                devmap->features &= ~DASD_FEATURE_READONLY;
-       if (devmap->device) {
-               if (devmap->device->gdp)
-                       set_disk_ro(devmap->device->gdp, ro_flag);
-               if (ro_flag)
-                       set_bit(DASD_FLAG_RO, &devmap->device->flags);
-               else
-                       clear_bit(DASD_FLAG_RO, &devmap->device->flags);
-       }
+       if (devmap->device)
+               devmap->device->features = devmap->features;
+       if (devmap->device && devmap->device->gdp)
+               set_disk_ro(devmap->device->gdp, val);
        spin_unlock(&dasd_devmap_lock);
        return count;
 }
 
 static DEVICE_ATTR(readonly, 0644, dasd_ro_show, dasd_ro_store);
+/*
+ * erplog controls the logging of ERP related data
+ * (e.g. failing channel programs).
+ */
+static ssize_t
+dasd_erplog_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct dasd_devmap *devmap;
+       int erplog;
+
+       devmap = dasd_find_busid(dev->bus_id);
+       if (!IS_ERR(devmap))
+               erplog = (devmap->features & DASD_FEATURE_ERPLOG) != 0;
+       else
+               erplog = (DASD_FEATURE_DEFAULT & DASD_FEATURE_ERPLOG) != 0;
+       return snprintf(buf, PAGE_SIZE, erplog ? "1\n" : "0\n");
+}
+
+static ssize_t
+dasd_erplog_store(struct device *dev, struct device_attribute *attr,
+             const char *buf, size_t count)
+{
+       struct dasd_devmap *devmap;
+       int val;
+       char *endp;
+
+       devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+
+       val = simple_strtoul(buf, &endp, 0);
+       if (((endp + 1) < (buf + count)) || (val > 1))
+               return -EINVAL;
+
+       spin_lock(&dasd_devmap_lock);
+       if (val)
+               devmap->features |= DASD_FEATURE_ERPLOG;
+       else
+               devmap->features &= ~DASD_FEATURE_ERPLOG;
+       if (devmap->device)
+               devmap->device->features = devmap->features;
+       spin_unlock(&dasd_devmap_lock);
+       return count;
+}
+
+static DEVICE_ATTR(erplog, 0644, dasd_erplog_show, dasd_erplog_store);
 
 /*
  * use_diag controls whether the driver should use diag rather than ssch
  * to talk to the device
  */
-/* TODO: Implement */
-static ssize_t 
-dasd_use_diag_show(struct device *dev, char *buf)
+static ssize_t
+dasd_use_diag_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct dasd_devmap *devmap;
        int use_diag;
 
-       devmap = dev->driver_data;
-       if (devmap)
+       devmap = dasd_find_busid(dev->bus_id);
+       if (!IS_ERR(devmap))
                use_diag = (devmap->features & DASD_FEATURE_USEDIAG) != 0;
        else
                use_diag = (DASD_FEATURE_DEFAULT & DASD_FEATURE_USEDIAG) != 0;
@@ -631,50 +777,174 @@ dasd_use_diag_show(struct device *dev, char *buf)
 }
 
 static ssize_t
-dasd_use_diag_store(struct device *dev, const char *buf, size_t count)
+dasd_use_diag_store(struct device *dev, struct device_attribute *attr,
+                   const char *buf, size_t count)
 {
        struct dasd_devmap *devmap;
-       int use_diag;
+       ssize_t rc;
+       int val;
+       char *endp;
 
        devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
-       use_diag = buf[0] == '1';
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+
+       val = simple_strtoul(buf, &endp, 0);
+       if (((endp + 1) < (buf + count)) || (val > 1))
+               return -EINVAL;
+
        spin_lock(&dasd_devmap_lock);
        /* Changing diag discipline flag is only allowed in offline state. */
+       rc = count;
        if (!devmap->device) {
-               if (use_diag)
+               if (val)
                        devmap->features |= DASD_FEATURE_USEDIAG;
                else
                        devmap->features &= ~DASD_FEATURE_USEDIAG;
        } else
-               count = -EPERM;
+               rc = -EPERM;
        spin_unlock(&dasd_devmap_lock);
-       return count;
+       return rc;
+}
+
+static DEVICE_ATTR(use_diag, 0644, dasd_use_diag_show, dasd_use_diag_store);
+
+static ssize_t
+dasd_discipline_show(struct device *dev, struct device_attribute *attr,
+                    char *buf)
+{
+       struct dasd_device *device;
+       ssize_t len;
+
+       device = dasd_device_from_cdev(to_ccwdev(dev));
+       if (!IS_ERR(device) && device->discipline) {
+               len = snprintf(buf, PAGE_SIZE, "%s\n",
+                              device->discipline->name);
+               dasd_put_device(device);
+       } else
+               len = snprintf(buf, PAGE_SIZE, "none\n");
+       return len;
 }
 
-static
-DEVICE_ATTR(use_diag, 0644, dasd_use_diag_show, dasd_use_diag_store);
+static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL);
 
 static ssize_t
-dasd_discipline_show(struct device *dev, char *buf)
+dasd_alias_show(struct device *dev, struct device_attribute *attr, char *buf)
 {
        struct dasd_devmap *devmap;
-       char *dname;
+       int alias;
 
+       devmap = dasd_find_busid(dev->bus_id);
        spin_lock(&dasd_devmap_lock);
-       dname = "none";
-       devmap = dev->driver_data;
-       if (devmap && devmap->device && devmap->device->discipline)
-               dname = devmap->device->discipline->name;
+       if (!IS_ERR(devmap))
+               alias = devmap->uid.alias;
+       else
+               alias = 0;
        spin_unlock(&dasd_devmap_lock);
-       return snprintf(buf, PAGE_SIZE, "%s\n", dname);
+
+       return sprintf(buf, alias ? "1\n" : "0\n");
 }
 
-static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL);
+static DEVICE_ATTR(alias, 0444, dasd_alias_show, NULL);
+
+static ssize_t
+dasd_vendor_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct dasd_devmap *devmap;
+       char *vendor;
+
+       devmap = dasd_find_busid(dev->bus_id);
+       spin_lock(&dasd_devmap_lock);
+       if (!IS_ERR(devmap) && strlen(devmap->uid.vendor) > 0)
+               vendor = devmap->uid.vendor;
+       else
+               vendor = "";
+       spin_unlock(&dasd_devmap_lock);
+
+       return snprintf(buf, PAGE_SIZE, "%s\n", vendor);
+}
+
+static DEVICE_ATTR(vendor, 0444, dasd_vendor_show, NULL);
+
+#define UID_STRLEN ( /* vendor */ 3 + 1 + /* serial    */ 14 + 1 +\
+                    /* SSID   */ 4 + 1 + /* unit addr */ 2 + 1)
+
+static ssize_t
+dasd_uid_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct dasd_devmap *devmap;
+       char uid[UID_STRLEN];
+
+       devmap = dasd_find_busid(dev->bus_id);
+       spin_lock(&dasd_devmap_lock);
+       if (!IS_ERR(devmap) && strlen(devmap->uid.vendor) > 0)
+               snprintf(uid, sizeof(uid), "%s.%s.%04x.%02x",
+                        devmap->uid.vendor, devmap->uid.serial,
+                        devmap->uid.ssid, devmap->uid.unit_addr);
+       else
+               uid[0] = 0;
+       spin_unlock(&dasd_devmap_lock);
+
+       return snprintf(buf, PAGE_SIZE, "%s\n", uid);
+}
+
+static DEVICE_ATTR(uid, 0444, dasd_uid_show, NULL);
+
+/*
+ * extended error-reporting
+ */
+static ssize_t
+dasd_eer_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct dasd_devmap *devmap;
+       int eer_flag;
+
+       devmap = dasd_find_busid(dev->bus_id);
+       if (!IS_ERR(devmap) && devmap->device)
+               eer_flag = dasd_eer_enabled(devmap->device);
+       else
+               eer_flag = 0;
+       return snprintf(buf, PAGE_SIZE, eer_flag ? "1\n" : "0\n");
+}
+
+static ssize_t
+dasd_eer_store(struct device *dev, struct device_attribute *attr,
+              const char *buf, size_t count)
+{
+       struct dasd_devmap *devmap;
+       int val, rc;
+       char *endp;
+
+       devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+       if (!devmap->device)
+               return -ENODEV;
+
+       val = simple_strtoul(buf, &endp, 0);
+       if (((endp + 1) < (buf + count)) || (val > 1))
+               return -EINVAL;
+
+       if (val) {
+               rc = dasd_eer_enable(devmap->device);
+               if (rc)
+                       return rc;
+       } else
+               dasd_eer_disable(devmap->device);
+       return count;
+}
+
+static DEVICE_ATTR(eer_enabled, 0644, dasd_eer_show, dasd_eer_store);
 
 static struct attribute * dasd_attrs[] = {
        &dev_attr_readonly.attr,
        &dev_attr_discipline.attr,
+       &dev_attr_alias.attr,
+       &dev_attr_vendor.attr,
+       &dev_attr_uid.attr,
        &dev_attr_use_diag.attr,
+       &dev_attr_eer_enabled.attr,
+       &dev_attr_erplog.attr,
        NULL,
 };
 
@@ -682,6 +952,112 @@ static struct attribute_group dasd_attr_group = {
        .attrs = dasd_attrs,
 };
 
+/*
+ * Return copy of the device unique identifier.
+ */
+int
+dasd_get_uid(struct ccw_device *cdev, struct dasd_uid *uid)
+{
+       struct dasd_devmap *devmap;
+
+       devmap = dasd_find_busid(cdev->dev.bus_id);
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+       spin_lock(&dasd_devmap_lock);
+       *uid = devmap->uid;
+       spin_unlock(&dasd_devmap_lock);
+       return 0;
+}
+
+/*
+ * Register the given device unique identifier into devmap struct.
+ * In addition check if the related storage server subsystem ID is already
+ * contained in the dasd_server_ssid_list. If subsystem ID is not contained,
+ * create new entry.
+ * Return 0 if server was already in serverlist,
+ *       1 if the server was added successful
+ *      <0 in case of error.
+ */
+int
+dasd_set_uid(struct ccw_device *cdev, struct dasd_uid *uid)
+{
+       struct dasd_devmap *devmap;
+       struct dasd_server_ssid_map *srv, *tmp;
+
+       devmap = dasd_find_busid(cdev->dev.bus_id);
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+
+       /* generate entry for server_ssid_map */
+       srv = (struct dasd_server_ssid_map *)
+               kzalloc(sizeof(struct dasd_server_ssid_map), GFP_KERNEL);
+       if (!srv)
+               return -ENOMEM;
+       strncpy(srv->sid.vendor, uid->vendor, sizeof(srv->sid.vendor) - 1);
+       strncpy(srv->sid.serial, uid->serial, sizeof(srv->sid.serial) - 1);
+       srv->sid.ssid = uid->ssid;
+
+       /* server is already contained ? */
+       spin_lock(&dasd_devmap_lock);
+       devmap->uid = *uid;
+       list_for_each_entry(tmp, &dasd_server_ssid_list, list) {
+               if (!memcmp(&srv->sid, &tmp->sid,
+                           sizeof(struct system_id))) {
+                       kfree(srv);
+                       srv = NULL;
+                       break;
+               }
+       }
+
+       /* add servermap to serverlist */
+       if (srv)
+               list_add(&srv->list, &dasd_server_ssid_list);
+       spin_unlock(&dasd_devmap_lock);
+
+       return (srv ? 1 : 0);
+}
+EXPORT_SYMBOL_GPL(dasd_set_uid);
+
+/*
+ * Return value of the specified feature.
+ */
+int
+dasd_get_feature(struct ccw_device *cdev, int feature)
+{
+       struct dasd_devmap *devmap;
+
+       devmap = dasd_find_busid(cdev->dev.bus_id);
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+
+       return ((devmap->features & feature) != 0);
+}
+
+/*
+ * Set / reset given feature.
+ * Flag indicates wether to set (!=0) or the reset (=0) the feature.
+ */
+int
+dasd_set_feature(struct ccw_device *cdev, int feature, int flag)
+{
+       struct dasd_devmap *devmap;
+
+       devmap = dasd_find_busid(cdev->dev.bus_id);
+       if (IS_ERR(devmap))
+               return PTR_ERR(devmap);
+
+       spin_lock(&dasd_devmap_lock);
+       if (flag)
+               devmap->features |= feature;
+       else
+               devmap->features &= ~feature;
+       if (devmap->device)
+               devmap->device->features = devmap->features;
+       spin_unlock(&dasd_devmap_lock);
+       return 0;
+}
+
+
 int
 dasd_add_sysfs_files(struct ccw_device *cdev)
 {
@@ -704,8 +1080,10 @@ dasd_devmap_init(void)
        dasd_max_devindex = 0;
        for (i = 0; i < 256; i++)
                INIT_LIST_HEAD(&dasd_hashlists[i]);
-       return 0;
 
+       /* Initialize servermap structure. */
+       INIT_LIST_HEAD(&dasd_server_ssid_list);
+       return 0;
 }
 
 void