Merge tag 's390-6.10-1' of git://git.kernel.org/pub/scm/linux/kernel/git/s390/linux
[sfrench/cifs-2.6.git] / drivers / s390 / cio / chp.c
index 675d7ed82356a9494d7df785494bd7d2ce8e2ec6..a07bbecba61cd491826f67fed8f88a9e1b27520e 100644 (file)
@@ -127,10 +127,9 @@ static int s390_vary_chpid(struct chp_id chpid, int on)
 /*
  * Channel measurement related functions
  */
-static ssize_t chp_measurement_chars_read(struct file *filp,
-                                         struct kobject *kobj,
-                                         struct bin_attribute *bin_attr,
-                                         char *buf, loff_t off, size_t count)
+static ssize_t measurement_chars_read(struct file *filp, struct kobject *kobj,
+                                     struct bin_attribute *bin_attr,
+                                     char *buf, loff_t off, size_t count)
 {
        struct channel_path *chp;
        struct device *device;
@@ -143,87 +142,79 @@ static ssize_t chp_measurement_chars_read(struct file *filp,
        return memory_read_from_buffer(buf, count, &off, &chp->cmg_chars,
                                       sizeof(chp->cmg_chars));
 }
+static BIN_ATTR_ADMIN_RO(measurement_chars, sizeof(struct cmg_chars));
 
-static const struct bin_attribute chp_measurement_chars_attr = {
-       .attr = {
-               .name = "measurement_chars",
-               .mode = S_IRUSR,
-       },
-       .size = sizeof(struct cmg_chars),
-       .read = chp_measurement_chars_read,
-};
-
-static void chp_measurement_copy_block(struct cmg_entry *buf,
-                                      struct channel_subsystem *css,
-                                      struct chp_id chpid)
-{
-       void *area;
-       struct cmg_entry *entry, reference_buf;
-       int idx;
-
-       if (chpid.id < 128) {
-               area = css->cub_addr1;
-               idx = chpid.id;
-       } else {
-               area = css->cub_addr2;
-               idx = chpid.id - 128;
-       }
-       entry = area + (idx * sizeof(struct cmg_entry));
-       do {
-               memcpy(buf, entry, sizeof(*entry));
-               memcpy(&reference_buf, entry, sizeof(*entry));
-       } while (reference_buf.values[0] != buf->values[0]);
-}
-
-static ssize_t chp_measurement_read(struct file *filp, struct kobject *kobj,
-                                   struct bin_attribute *bin_attr,
-                                   char *buf, loff_t off, size_t count)
+static ssize_t chp_measurement_copy_block(void *buf, loff_t off, size_t count,
+                                         struct kobject *kobj, bool extended)
 {
        struct channel_path *chp;
        struct channel_subsystem *css;
        struct device *device;
        unsigned int size;
+       void *area, *entry;
+       int id, idx;
 
        device = kobj_to_dev(kobj);
        chp = to_channelpath(device);
        css = to_css(chp->dev.parent);
+       id = chp->chpid.id;
 
-       size = sizeof(struct cmg_entry);
+       if (extended) {
+               /* Check if extended measurement data is available. */
+               if (!chp->extended)
+                       return 0;
+
+               size = sizeof(struct cmg_ext_entry);
+               area = css->ecub[id / CSS_ECUES_PER_PAGE];
+               idx = id % CSS_ECUES_PER_PAGE;
+       } else {
+               size = sizeof(struct cmg_entry);
+               area = css->cub[id / CSS_CUES_PER_PAGE];
+               idx = id % CSS_CUES_PER_PAGE;
+       }
+       entry = area + (idx * size);
 
        /* Only allow single reads. */
        if (off || count < size)
                return 0;
-       chp_measurement_copy_block((struct cmg_entry *)buf, css, chp->chpid);
-       count = size;
-       return count;
+
+       memcpy(buf, entry, size);
+
+       return size;
 }
 
-static const struct bin_attribute chp_measurement_attr = {
-       .attr = {
-               .name = "measurement",
-               .mode = S_IRUSR,
-       },
-       .size = sizeof(struct cmg_entry),
-       .read = chp_measurement_read,
+static ssize_t measurement_read(struct file *filp, struct kobject *kobj,
+                               struct bin_attribute *bin_attr,
+                               char *buf, loff_t off, size_t count)
+{
+       return chp_measurement_copy_block(buf, off, count, kobj, false);
+}
+static BIN_ATTR_ADMIN_RO(measurement, sizeof(struct cmg_entry));
+
+static ssize_t ext_measurement_read(struct file *filp, struct kobject *kobj,
+                                   struct bin_attribute *bin_attr,
+                                   char *buf, loff_t off, size_t count)
+{
+       return chp_measurement_copy_block(buf, off, count, kobj, true);
+}
+static BIN_ATTR_ADMIN_RO(ext_measurement, sizeof(struct cmg_ext_entry));
+
+static struct bin_attribute *measurement_attrs[] = {
+       &bin_attr_measurement_chars,
+       &bin_attr_measurement,
+       &bin_attr_ext_measurement,
+       NULL,
 };
+BIN_ATTRIBUTE_GROUPS(measurement);
 
 void chp_remove_cmg_attr(struct channel_path *chp)
 {
-       device_remove_bin_file(&chp->dev, &chp_measurement_chars_attr);
-       device_remove_bin_file(&chp->dev, &chp_measurement_attr);
+       device_remove_groups(&chp->dev, measurement_groups);
 }
 
 int chp_add_cmg_attr(struct channel_path *chp)
 {
-       int ret;
-
-       ret = device_create_bin_file(&chp->dev, &chp_measurement_chars_attr);
-       if (ret)
-               return ret;
-       ret = device_create_bin_file(&chp->dev, &chp_measurement_attr);
-       if (ret)
-               device_remove_bin_file(&chp->dev, &chp_measurement_chars_attr);
-       return ret;
+       return device_add_groups(&chp->dev, measurement_groups);
 }
 
 /*
@@ -401,6 +392,35 @@ static ssize_t chp_esc_show(struct device *dev,
 }
 static DEVICE_ATTR(esc, 0444, chp_esc_show, NULL);
 
+static char apply_max_suffix(unsigned long *value, unsigned long base)
+{
+       static char suffixes[] = { 0, 'K', 'M', 'G', 'T' };
+       int i;
+
+       for (i = 0; i < ARRAY_SIZE(suffixes) - 1; i++) {
+               if (*value < base || *value % base != 0)
+                       break;
+               *value /= base;
+       }
+
+       return suffixes[i];
+}
+
+static ssize_t speed_bps_show(struct device *dev,
+                             struct device_attribute *attr, char *buf)
+{
+       struct channel_path *chp = to_channelpath(dev);
+       unsigned long speed = chp->speed;
+       char suffix;
+
+       suffix = apply_max_suffix(&speed, 1000);
+
+       return suffix ? sysfs_emit(buf, "%lu%c\n", speed, suffix) :
+                       sysfs_emit(buf, "%lu\n", speed);
+}
+
+static DEVICE_ATTR_RO(speed_bps);
+
 static ssize_t util_string_read(struct file *filp, struct kobject *kobj,
                                struct bin_attribute *attr, char *buf,
                                loff_t off, size_t count)
@@ -432,6 +452,7 @@ static struct attribute *chp_attrs[] = {
        &dev_attr_chid.attr,
        &dev_attr_chid_external.attr,
        &dev_attr_esc.attr,
+       &dev_attr_speed_bps.attr,
        NULL,
 };
 static struct attribute_group chp_attr_group = {