Merge tag 's390-6.1-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
authorLinus Torvalds <torvalds@linux-foundation.org>
Sun, 9 Oct 2022 20:51:40 +0000 (13:51 -0700)
committerLinus Torvalds <torvalds@linux-foundation.org>
Sun, 9 Oct 2022 20:51:40 +0000 (13:51 -0700)
Pull s390 updates from Vasily Gorbik:

 - Make use of the IBM z16 processor activity instrumentation facility
   extension to count neural network processor assist operations: add a
   new PMU device driver so that perf can make use of this.

 - Rework memcpy_real() to avoid DAT-off mode.

 - Rework absolute lowcore access code.

 - Various small fixes and improvements all over the code.

* tag 's390-6.1-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux:
  s390/pci: remove unused bus_next field from struct zpci_dev
  s390/cio: remove unused ccw_device_force_console() declaration
  s390/pai: Add support for PAI Extension 1 NNPA counters
  s390/mm: fix no previous prototype warnings in maccess.c
  s390/mm: uninline copy_oldmem_kernel() function
  s390/mm,ptdump: add real memory copy page markers
  s390/mm: rework memcpy_real() to avoid DAT-off mode
  s390/dump: save IPL CPU registers once DAT is available
  s390/pci: convert high_memory to physical address
  s390/smp,ptdump: add absolute lowcore markers
  s390/smp: rework absolute lowcore access
  s390/smp: call smp_reinit_ipl_cpu() before scheduler is available
  s390/ptdump: add missing amode31 markers
  s390/mm: split lowcore pages with set_memory_4k()
  s390/mm: remove unused access parameter from do_fault_error()
  s390/delay: sync comment within __delay() with reality
  s390: move from strlcpy with unused retval to strscpy

1  2 
drivers/s390/block/dasd_devmap.c

@@@ -26,6 -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"
@@@ -49,7 -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 +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;
@@@ -426,7 -426,7 +426,7 @@@ dasd_add_busid(const char *bus_id, int 
        if (!devmap) {
                /* This bus_id is new. */
                new->devindex = dasd_max_devindex++;
-               strlcpy(new->bus_id, bus_id, DASD_BUS_ID_SIZE);
+               strscpy(new->bus_id, bus_id, DASD_BUS_ID_SIZE);
                new->features = features;
                new->device = NULL;
                list_add(&new->list, &dasd_hashlists[hash]);
        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) {
                        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;
  }
@@@ -594,238 -585,6 +594,238 @@@ dasd_create_device(struct ccw_device *c
  }
  
  /*
 + * 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);
@@@ -858,8 -617,6 +858,8 @@@ dasd_delete_device(struct dasd_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.
@@@ -937,7 -694,6 +937,7 @@@ void dasd_add_link_to_gendisk(struct ge
        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)
  {
@@@ -1578,6 -1334,7 +1578,6 @@@ dasd_timeout_store(struct device *dev, 
                   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));
                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;
@@@ -1924,347 -1683,6 +1924,347 @@@ dasd_path_fcs_show(struct kobject *kobj
  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,       \
@@@ -2321,9 -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,
  };