[PATCH] s390: channel path measurements
authorCornelia Huck <cornelia.huck@de.ibm.com>
Fri, 24 Mar 2006 11:15:14 +0000 (03:15 -0800)
committerLinus Torvalds <torvalds@g5.osdl.org>
Fri, 24 Mar 2006 15:33:16 +0000 (07:33 -0800)
Gather extended measurements for channel paths from the channel subsystem and
expose them to userspace via a sysfs attribute.

Signed-off-by: Cornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
Signed-off-by: Linus Torvalds <torvalds@osdl.org>
drivers/s390/cio/chsc.c
drivers/s390/cio/chsc.h
drivers/s390/cio/css.c
drivers/s390/cio/css.h

index f4183d660258108728a626b73c22798ba4699570..8b57fe62a9f9ccf6a07628e1db7b883ae448e315 100644 (file)
@@ -98,10 +98,8 @@ chsc_get_sch_desc_irq(struct subchannel *sch, void *page)
 
        ssd_area = page;
 
-       ssd_area->request = (struct chsc_header) {
-               .length = 0x0010,
-               .code   = 0x0004,
-       };
+       ssd_area->request.length = 0x0010;
+       ssd_area->request.code = 0x0004;
 
        ssd_area->ssid = sch->schid.ssid;
        ssd_area->f_sch = sch->schid.sch_no;
@@ -517,10 +515,8 @@ chsc_process_crw(void)
                struct device *dev;
                memset(sei_area, 0, sizeof(*sei_area));
                memset(&res_data, 0, sizeof(struct res_acc_data));
-               sei_area->request = (struct chsc_header) {
-                       .length = 0x0010,
-                       .code   = 0x000e,
-               };
+               sei_area->request.length = 0x0010;
+               sei_area->request.code = 0x000e;
 
                ccode = chsc(sei_area);
                if (ccode > 0)
@@ -874,6 +870,264 @@ s390_vary_chpid( __u8 chpid, int on)
        return 0;
 }
 
+/*
+ * Channel measurement related functions
+ */
+static ssize_t
+chp_measurement_chars_read(struct kobject *kobj, char *buf, loff_t off,
+                          size_t count)
+{
+       struct channel_path *chp;
+       unsigned int size;
+
+       chp = to_channelpath(container_of(kobj, struct device, kobj));
+       if (!chp->cmg_chars)
+               return 0;
+
+       size = sizeof(struct cmg_chars);
+
+       if (off > size)
+               return 0;
+       if (off + count > size)
+               count = size - off;
+       memcpy(buf, chp->cmg_chars + off, count);
+       return count;
+}
+
+static struct bin_attribute chp_measurement_chars_attr = {
+       .attr = {
+               .name = "measurement_chars",
+               .mode = S_IRUSR,
+               .owner = THIS_MODULE,
+       },
+       .size = sizeof(struct cmg_chars),
+       .read = chp_measurement_chars_read,
+};
+
+static void
+chp_measurement_copy_block(struct cmg_entry *buf,
+                          struct channel_subsystem *css, int chpid)
+{
+       void *area;
+       struct cmg_entry *entry, reference_buf;
+       int idx;
+
+       if (chpid < 128) {
+               area = css->cub_addr1;
+               idx = chpid;
+       } else {
+               area = css->cub_addr2;
+               idx = chpid - 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 kobject *kobj, char *buf, loff_t off, size_t count)
+{
+       struct channel_path *chp;
+       struct channel_subsystem *css;
+       unsigned int size;
+
+       chp = to_channelpath(container_of(kobj, struct device, kobj));
+       css = to_css(chp->dev.parent);
+
+       size = sizeof(struct cmg_chars);
+
+       /* Only allow single reads. */
+       if (off || count < size)
+               return 0;
+       chp_measurement_copy_block((struct cmg_entry *)buf, css, chp->id);
+       return count;
+}
+
+static struct bin_attribute chp_measurement_attr = {
+       .attr = {
+               .name = "measurement",
+               .mode = S_IRUSR,
+               .owner = THIS_MODULE,
+       },
+       .size = sizeof(struct cmg_entry),
+       .read = chp_measurement_read,
+};
+
+static void
+chsc_remove_chp_cmg_attr(struct channel_path *chp)
+{
+       sysfs_remove_bin_file(&chp->dev.kobj, &chp_measurement_chars_attr);
+       sysfs_remove_bin_file(&chp->dev.kobj, &chp_measurement_attr);
+}
+
+static int
+chsc_add_chp_cmg_attr(struct channel_path *chp)
+{
+       int ret;
+
+       ret = sysfs_create_bin_file(&chp->dev.kobj,
+                                   &chp_measurement_chars_attr);
+       if (ret)
+               return ret;
+       ret = sysfs_create_bin_file(&chp->dev.kobj, &chp_measurement_attr);
+       if (ret)
+               sysfs_remove_bin_file(&chp->dev.kobj,
+                                     &chp_measurement_chars_attr);
+       return ret;
+}
+
+static void
+chsc_remove_cmg_attr(struct channel_subsystem *css)
+{
+       int i;
+
+       for (i = 0; i <= __MAX_CHPID; i++) {
+               if (!css->chps[i])
+                       continue;
+               chsc_remove_chp_cmg_attr(css->chps[i]);
+       }
+}
+
+static int
+chsc_add_cmg_attr(struct channel_subsystem *css)
+{
+       int i, ret;
+
+       ret = 0;
+       for (i = 0; i <= __MAX_CHPID; i++) {
+               if (!css->chps[i])
+                       continue;
+               ret = chsc_add_chp_cmg_attr(css->chps[i]);
+               if (ret)
+                       goto cleanup;
+       }
+       return ret;
+cleanup:
+       for (--i; i >= 0; i--) {
+               if (!css->chps[i])
+                       continue;
+               chsc_remove_chp_cmg_attr(css->chps[i]);
+       }
+       return ret;
+}
+
+
+static int
+__chsc_do_secm(struct channel_subsystem *css, int enable, void *page)
+{
+       struct {
+               struct chsc_header request;
+               u32 operation_code : 2;
+               u32 : 30;
+               u32 key : 4;
+               u32 : 28;
+               u32 zeroes1;
+               u32 cub_addr1;
+               u32 zeroes2;
+               u32 cub_addr2;
+               u32 reserved[13];
+               struct chsc_header response;
+               u32 status : 8;
+               u32 : 4;
+               u32 fmt : 4;
+               u32 : 16;
+       } *secm_area;
+       int ret, ccode;
+
+       secm_area = page;
+       secm_area->request.length = 0x0050;
+       secm_area->request.code = 0x0016;
+
+       secm_area->key = PAGE_DEFAULT_KEY;
+       secm_area->cub_addr1 = (u64)(unsigned long)css->cub_addr1;
+       secm_area->cub_addr2 = (u64)(unsigned long)css->cub_addr2;
+
+       secm_area->operation_code = enable ? 0 : 1;
+
+       ccode = chsc(secm_area);
+       if (ccode > 0)
+               return (ccode == 3) ? -ENODEV : -EBUSY;
+
+       switch (secm_area->response.code) {
+       case 0x0001: /* Success. */
+               ret = 0;
+               break;
+       case 0x0003: /* Invalid block. */
+       case 0x0007: /* Invalid format. */
+       case 0x0008: /* Other invalid block. */
+               CIO_CRW_EVENT(2, "Error in chsc request block!\n");
+               ret = -EINVAL;
+               break;
+       case 0x0004: /* Command not provided in model. */
+               CIO_CRW_EVENT(2, "Model does not provide secm\n");
+               ret = -EOPNOTSUPP;
+               break;
+       case 0x0102: /* cub adresses incorrect */
+               CIO_CRW_EVENT(2, "Invalid addresses in chsc request block\n");
+               ret = -EINVAL;
+               break;
+       case 0x0103: /* key error */
+               CIO_CRW_EVENT(2, "Access key error in secm\n");
+               ret = -EINVAL;
+               break;
+       case 0x0105: /* error while starting */
+               CIO_CRW_EVENT(2, "Error while starting channel measurement\n");
+               ret = -EIO;
+               break;
+       default:
+               CIO_CRW_EVENT(2, "Unknown CHSC response %d\n",
+                             secm_area->response.code);
+               ret = -EIO;
+       }
+       return ret;
+}
+
+int
+chsc_secm(struct channel_subsystem *css, int enable)
+{
+       void  *secm_area;
+       int ret;
+
+       secm_area = (void *)get_zeroed_page(GFP_KERNEL |  GFP_DMA);
+       if (!secm_area)
+               return -ENOMEM;
+
+       mutex_lock(&css->mutex);
+       if (enable && !css->cm_enabled) {
+               css->cub_addr1 = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+               css->cub_addr2 = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+               if (!css->cub_addr1 || !css->cub_addr2) {
+                       free_page((unsigned long)css->cub_addr1);
+                       free_page((unsigned long)css->cub_addr2);
+                       free_page((unsigned long)secm_area);
+                       mutex_unlock(&css->mutex);
+                       return -ENOMEM;
+               }
+       }
+       ret = __chsc_do_secm(css, enable, secm_area);
+       if (!ret) {
+               css->cm_enabled = enable;
+               if (css->cm_enabled) {
+                       ret = chsc_add_cmg_attr(css);
+                       if (ret) {
+                               memset(secm_area, 0, PAGE_SIZE);
+                               __chsc_do_secm(css, 0, secm_area);
+                               css->cm_enabled = 0;
+                       }
+               } else
+                       chsc_remove_cmg_attr(css);
+       }
+       if (enable && !css->cm_enabled) {
+               free_page((unsigned long)css->cub_addr1);
+               free_page((unsigned long)css->cub_addr2);
+       }
+       mutex_unlock(&css->mutex);
+       free_page((unsigned long)secm_area);
+       return ret;
+}
+
 /*
  * Files for the channel path entries.
  */
@@ -925,9 +1179,39 @@ chp_type_show(struct device *dev, struct device_attribute *attr, char *buf)
 
 static DEVICE_ATTR(type, 0444, chp_type_show, NULL);
 
+static ssize_t
+chp_cmg_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct channel_path *chp = to_channelpath(dev);
+
+       if (!chp)
+               return 0;
+       if (chp->cmg == -1) /* channel measurements not available */
+               return sprintf(buf, "unknown\n");
+       return sprintf(buf, "%x\n", chp->cmg);
+}
+
+static DEVICE_ATTR(cmg, 0444, chp_cmg_show, NULL);
+
+static ssize_t
+chp_shared_show(struct device *dev, struct device_attribute *attr, char *buf)
+{
+       struct channel_path *chp = to_channelpath(dev);
+
+       if (!chp)
+               return 0;
+       if (chp->shared == -1) /* channel measurements not available */
+               return sprintf(buf, "unknown\n");
+       return sprintf(buf, "%x\n", chp->shared);
+}
+
+static DEVICE_ATTR(shared, 0444, chp_shared_show, NULL);
+
 static struct attribute * chp_attrs[] = {
        &dev_attr_status.attr,
        &dev_attr_type.attr,
+       &dev_attr_cmg.attr,
+       &dev_attr_shared.attr,
        NULL,
 };
 
@@ -966,10 +1250,8 @@ chsc_determine_channel_path_description(int chpid,
        if (!scpd_area)
                return -ENOMEM;
 
-       scpd_area->request = (struct chsc_header) {
-               .length = 0x0010,
-               .code   = 0x0002,
-       };
+       scpd_area->request.length = 0x0010;
+       scpd_area->request.code = 0x0002;
 
        scpd_area->first_chpid = chpid;
        scpd_area->last_chpid = chpid;
@@ -1006,6 +1288,111 @@ out:
        return ret;
 }
 
+static void
+chsc_initialize_cmg_chars(struct channel_path *chp, u8 cmcv,
+                         struct cmg_chars *chars)
+{
+       switch (chp->cmg) {
+       case 2:
+       case 3:
+               chp->cmg_chars = kmalloc(sizeof(struct cmg_chars),
+                                        GFP_KERNEL);
+               if (chp->cmg_chars) {
+                       int i, mask;
+                       struct cmg_chars *cmg_chars;
+
+                       cmg_chars = chp->cmg_chars;
+                       for (i = 0; i < NR_MEASUREMENT_CHARS; i++) {
+                               mask = 0x80 >> (i + 3);
+                               if (cmcv & mask)
+                                       cmg_chars->values[i] = chars->values[i];
+                               else
+                                       cmg_chars->values[i] = 0;
+                       }
+               }
+               break;
+       default:
+               /* No cmg-dependent data. */
+               break;
+       }
+}
+
+static int
+chsc_get_channel_measurement_chars(struct channel_path *chp)
+{
+       int ccode, ret;
+
+       struct {
+               struct chsc_header request;
+               u32 : 24;
+               u32 first_chpid : 8;
+               u32 : 24;
+               u32 last_chpid : 8;
+               u32 zeroes1;
+               struct chsc_header response;
+               u32 zeroes2;
+               u32 not_valid : 1;
+               u32 shared : 1;
+               u32 : 22;
+               u32 chpid : 8;
+               u32 cmcv : 5;
+               u32 : 11;
+               u32 cmgq : 8;
+               u32 cmg : 8;
+               u32 zeroes3;
+               u32 data[NR_MEASUREMENT_CHARS];
+       } *scmc_area;
+
+       scmc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+       if (!scmc_area)
+               return -ENOMEM;
+
+       scmc_area->request.length = 0x0010;
+       scmc_area->request.code = 0x0022;
+
+       scmc_area->first_chpid = chp->id;
+       scmc_area->last_chpid = chp->id;
+
+       ccode = chsc(scmc_area);
+       if (ccode > 0) {
+               ret = (ccode == 3) ? -ENODEV : -EBUSY;
+               goto out;
+       }
+
+       switch (scmc_area->response.code) {
+       case 0x0001: /* Success. */
+               if (!scmc_area->not_valid) {
+                       chp->cmg = scmc_area->cmg;
+                       chp->shared = scmc_area->shared;
+                       chsc_initialize_cmg_chars(chp, scmc_area->cmcv,
+                                                 (struct cmg_chars *)
+                                                 &scmc_area->data);
+               } else {
+                       chp->cmg = -1;
+                       chp->shared = -1;
+               }
+               ret = 0;
+               break;
+       case 0x0003: /* Invalid block. */
+       case 0x0007: /* Invalid format. */
+       case 0x0008: /* Invalid bit combination. */
+               CIO_CRW_EVENT(2, "Error in chsc request block!\n");
+               ret = -EINVAL;
+               break;
+       case 0x0004: /* Command not provided. */
+               CIO_CRW_EVENT(2, "Model does not provide scmc\n");
+               ret = -EOPNOTSUPP;
+               break;
+       default:
+               CIO_CRW_EVENT(2, "Unknown CHSC response %d\n",
+                             scmc_area->response.code);
+               ret = -EIO;
+       }
+out:
+       free_page((unsigned long)scmc_area);
+       return ret;
+}
+
 /*
  * Entries for chpids on the system bus.
  * This replaces /proc/chpids.
@@ -1034,6 +1421,22 @@ new_channel_path(int chpid)
        ret = chsc_determine_channel_path_description(chpid, &chp->desc);
        if (ret)
                goto out_free;
+       /* Get channel-measurement characteristics. */
+       if (css_characteristics_avail && css_chsc_characteristics.scmc
+           && css_chsc_characteristics.secm) {
+               ret = chsc_get_channel_measurement_chars(chp);
+               if (ret)
+                       goto out_free;
+       } else {
+               static int msg_done;
+
+               if (!msg_done) {
+                       printk(KERN_WARNING "cio: Channel measurements not "
+                              "available, continuing.\n");
+                       msg_done = 1;
+               }
+               chp->cmg = -1;
+       }
 
        /* make it known to the system */
        ret = device_register(&chp->dev);
@@ -1046,8 +1449,19 @@ new_channel_path(int chpid)
        if (ret) {
                device_unregister(&chp->dev);
                goto out_free;
-       } else
-               css[0]->chps[chpid] = chp;
+       }
+       mutex_lock(&css[0]->mutex);
+       if (css[0]->cm_enabled) {
+               ret = chsc_add_chp_cmg_attr(chp);
+               if (ret) {
+                       sysfs_remove_group(&chp->dev.kobj, &chp_attr_group);
+                       device_unregister(&chp->dev);
+                       mutex_unlock(&css[0]->mutex);
+                       goto out_free;
+               }
+       }
+       css[0]->chps[chpid] = chp;
+       mutex_unlock(&css[0]->mutex);
        return ret;
 out_free:
        kfree(chp);
@@ -1103,10 +1517,8 @@ chsc_enable_facility(int operation_code)
        sda_area = (void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
        if (!sda_area)
                return -ENOMEM;
-       sda_area->request = (struct chsc_header) {
-               .length = 0x0400,
-               .code = 0x0031,
-       };
+       sda_area->request.length = 0x0400;
+       sda_area->request.code = 0x0031;
        sda_area->operation_code = operation_code;
 
        ret = chsc(sda_area);
@@ -1161,10 +1573,8 @@ chsc_determine_css_characteristics(void)
                return -ENOMEM;
        }
 
-       scsc_area->request = (struct chsc_header) {
-               .length = 0x0010,
-               .code   = 0x0010,
-       };
+       scsc_area->request.length = 0x0010;
+       scsc_area->request.code = 0x0010;
 
        result = chsc(scsc_area);
        if (result) {
index 3e75095f35d03e70f21bb43304d9fa537258c8fa..a259245780ae7465d1be49fe442c076d32324585 100644 (file)
@@ -12,6 +12,16 @@ struct chsc_header {
        u16 code;
 };
 
+#define NR_MEASUREMENT_CHARS 5
+struct cmg_chars {
+       u32 values[NR_MEASUREMENT_CHARS];
+};
+
+#define NR_MEASUREMENT_ENTRIES 8
+struct cmg_entry {
+       u32 values[NR_MEASUREMENT_ENTRIES];
+};
+
 struct channel_path_desc {
        u8 flags;
        u8 lsn;
@@ -27,6 +37,10 @@ struct channel_path {
        int id;
        int state;
        struct channel_path_desc desc;
+       /* Channel-measurement related stuff: */
+       int cmg;
+       int shared;
+       void *cmg_chars;
        struct device dev;
 };
 
@@ -52,7 +66,11 @@ struct css_general_char {
 
 struct css_chsc_char {
        u64 res;
-       u64 : 43;
+       u64 : 20;
+       u32 secm : 1; /* bit 84 */
+       u32 : 1;
+       u32 scmc : 1; /* bit 86 */
+       u32 : 20;
        u32 scssc : 1;  /* bit 107 */
        u32 scsscf : 1; /* bit 108 */
        u32 : 19;
@@ -67,6 +85,8 @@ extern int css_characteristics_avail;
 extern void *chsc_get_chp_desc(struct subchannel*, int);
 
 extern int chsc_enable_facility(int);
+struct channel_subsystem;
+extern int chsc_secm(struct channel_subsystem *, int);
 
 #define to_channelpath(device) container_of(device, struct channel_path, dev)
 
index 3c77d65960dbf65e2ed259b25ee4022174d45ce5..5b304dbacaeb3a7559605a4b2bdc1a4488248608 100644 (file)
@@ -452,15 +452,50 @@ channel_subsystem_release(struct device *dev)
        struct channel_subsystem *css;
 
        css = to_css(dev);
+       mutex_destroy(&css->mutex);
        kfree(css);
 }
 
+static ssize_t
+css_cm_enable_show(struct device *dev, struct device_attribute *attr,
+                  char *buf)
+{
+       struct channel_subsystem *css = to_css(dev);
+
+       if (!css)
+               return 0;
+       return sprintf(buf, "%x\n", css->cm_enabled);
+}
+
+static ssize_t
+css_cm_enable_store(struct device *dev, struct device_attribute *attr,
+                   const char *buf, size_t count)
+{
+       struct channel_subsystem *css = to_css(dev);
+       int ret;
+
+       switch (buf[0]) {
+       case '0':
+               ret = css->cm_enabled ? chsc_secm(css, 0) : 0;
+               break;
+       case '1':
+               ret = css->cm_enabled ? 0 : chsc_secm(css, 1);
+               break;
+       default:
+               ret = -EINVAL;
+       }
+       return ret < 0 ? ret : count;
+}
+
+static DEVICE_ATTR(cm_enable, 0644, css_cm_enable_show, css_cm_enable_store);
+
 static inline void __init
 setup_css(int nr)
 {
        u32 tod_high;
 
        memset(css[nr], 0, sizeof(struct channel_subsystem));
+       mutex_init(&css[nr]->mutex);
        css[nr]->valid = 1;
        css[nr]->cssid = nr;
        sprintf(css[nr]->device.bus_id, "css%x", nr);
@@ -507,6 +542,9 @@ init_channel_subsystem (void)
                ret = device_register(&css[i]->device);
                if (ret)
                        goto out_free;
+               if (css_characteristics_avail && css_chsc_characteristics.secm)
+                       device_create_file(&css[i]->device,
+                                          &dev_attr_cm_enable);
        }
        css_init_done = 1;
 
@@ -519,6 +557,9 @@ out_free:
 out_unregister:
        while (i > 0) {
                i--;
+               if (css_characteristics_avail && css_chsc_characteristics.secm)
+                       device_remove_file(&css[i]->device,
+                                          &dev_attr_cm_enable);
                device_unregister(&css[i]->device);
        }
 out_bus:
index b6375861cb3766188d85240e823828abca0e526c..74a257b23383abb94c8d1e011e896e97a50c1265 100644 (file)
@@ -1,6 +1,7 @@
 #ifndef _CSS_H
 #define _CSS_H
 
+#include <linux/mutex.h>
 #include <linux/wait.h>
 #include <linux/workqueue.h>
 
@@ -150,6 +151,11 @@ struct channel_subsystem {
        struct channel_path *chps[__MAX_CHPID + 1];
        struct device device;
        struct pgid global_pgid;
+       struct mutex mutex;
+       /* channel measurement related */
+       int cm_enabled;
+       void *cub_addr1;
+       void *cub_addr2;
 };
 #define to_css(dev) container_of(dev, struct channel_subsystem, device)