]> git.karo-electronics.de Git - mv-sheeva.git/blobdiff - drivers/s390/cio/chsc.c
Merge branch 'master' into tk71
[mv-sheeva.git] / drivers / s390 / cio / chsc.c
index 4cbb1a6ca33c809c6fd9366f891b7c6e44ac39d9..0689fcf23a118b0b8917bd3a16199ce6539107a1 100644 (file)
@@ -2,7 +2,7 @@
  *  drivers/s390/cio/chsc.c
  *   S/390 common I/O routines -- channel subsystem call
  *
- *    Copyright IBM Corp. 1999,2008
+ *    Copyright IBM Corp. 1999,2010
  *    Author(s): Ingo Adlung (adlung@de.ibm.com)
  *              Cornelia Huck (cornelia.huck@de.ibm.com)
  *              Arnd Bergmann (arndb@de.ibm.com)
@@ -29,8 +29,8 @@
 #include "chsc.h"
 
 static void *sei_page;
-static DEFINE_SPINLOCK(siosl_lock);
-static DEFINE_SPINLOCK(sda_lock);
+static void *chsc_page;
+static DEFINE_SPINLOCK(chsc_page_lock);
 
 /**
  * chsc_error_from_response() - convert a chsc response to an error
@@ -85,17 +85,15 @@ struct chsc_ssd_area {
 
 int chsc_get_ssd_info(struct subchannel_id schid, struct chsc_ssd_info *ssd)
 {
-       unsigned long page;
        struct chsc_ssd_area *ssd_area;
        int ccode;
        int ret;
        int i;
        int mask;
 
-       page = get_zeroed_page(GFP_KERNEL | GFP_DMA);
-       if (!page)
-               return -ENOMEM;
-       ssd_area = (struct chsc_ssd_area *) page;
+       spin_lock_irq(&chsc_page_lock);
+       memset(chsc_page, 0, PAGE_SIZE);
+       ssd_area = chsc_page;
        ssd_area->request.length = 0x0010;
        ssd_area->request.code = 0x0004;
        ssd_area->ssid = schid.ssid;
@@ -106,25 +104,25 @@ int chsc_get_ssd_info(struct subchannel_id schid, struct chsc_ssd_info *ssd)
        /* Check response. */
        if (ccode > 0) {
                ret = (ccode == 3) ? -ENODEV : -EBUSY;
-               goto out_free;
+               goto out;
        }
        ret = chsc_error_from_response(ssd_area->response.code);
        if (ret != 0) {
                CIO_MSG_EVENT(2, "chsc: ssd failed for 0.%x.%04x (rc=%04x)\n",
                              schid.ssid, schid.sch_no,
                              ssd_area->response.code);
-               goto out_free;
+               goto out;
        }
        if (!ssd_area->sch_valid) {
                ret = -ENODEV;
-               goto out_free;
+               goto out;
        }
        /* Copy data */
        ret = 0;
        memset(ssd, 0, sizeof(struct chsc_ssd_info));
        if ((ssd_area->st != SUBCHANNEL_TYPE_IO) &&
            (ssd_area->st != SUBCHANNEL_TYPE_MSG))
-               goto out_free;
+               goto out;
        ssd->path_mask = ssd_area->path_mask;
        ssd->fla_valid_mask = ssd_area->fla_valid_mask;
        for (i = 0; i < 8; i++) {
@@ -136,8 +134,8 @@ int chsc_get_ssd_info(struct subchannel_id schid, struct chsc_ssd_info *ssd)
                if (ssd_area->fla_valid_mask & mask)
                        ssd->fla[i] = ssd_area->fla[i];
        }
-out_free:
-       free_page(page);
+out:
+       spin_unlock_irq(&chsc_page_lock);
        return ret;
 }
 
@@ -497,6 +495,7 @@ __s390_vary_chpid_on(struct subchannel_id schid, void *data)
  */
 int chsc_chp_vary(struct chp_id chpid, int on)
 {
+       struct channel_path *chp = chpid_to_chp(chpid);
        struct chp_link link;
 
        memset(&link, 0, sizeof(struct chp_link));
@@ -506,11 +505,12 @@ int chsc_chp_vary(struct chp_id chpid, int on)
        /*
         * Redo PathVerification on the devices the chpid connects to
         */
-
-       if (on)
+       if (on) {
+               /* Try to update the channel path descritor. */
+               chsc_determine_base_channel_path_desc(chpid, &chp->desc);
                for_each_subchannel_staged(s390_subchannel_vary_chpid_on,
                                           __s390_vary_chpid_on, &link);
-       else
+       else
                for_each_subchannel_staged(s390_subchannel_vary_chpid_off,
                                           NULL, &link);
 
@@ -552,7 +552,7 @@ cleanup:
        return ret;
 }
 
-int __chsc_do_secm(struct channel_subsystem *css, int enable, void *page)
+int __chsc_do_secm(struct channel_subsystem *css, int enable)
 {
        struct {
                struct chsc_header request;
@@ -573,7 +573,9 @@ int __chsc_do_secm(struct channel_subsystem *css, int enable, void *page)
        } __attribute__ ((packed)) *secm_area;
        int ret, ccode;
 
-       secm_area = page;
+       spin_lock_irq(&chsc_page_lock);
+       memset(chsc_page, 0, PAGE_SIZE);
+       secm_area = chsc_page;
        secm_area->request.length = 0x0050;
        secm_area->request.code = 0x0016;
 
@@ -584,8 +586,10 @@ int __chsc_do_secm(struct channel_subsystem *css, int enable, void *page)
        secm_area->operation_code = enable ? 0 : 1;
 
        ccode = chsc(secm_area);
-       if (ccode > 0)
-               return (ccode == 3) ? -ENODEV : -EBUSY;
+       if (ccode > 0) {
+               ret = (ccode == 3) ? -ENODEV : -EBUSY;
+               goto out;
+       }
 
        switch (secm_area->response.code) {
        case 0x0102:
@@ -598,37 +602,32 @@ int __chsc_do_secm(struct channel_subsystem *css, int enable, void *page)
        if (ret != 0)
                CIO_CRW_EVENT(2, "chsc: secm failed (rc=%04x)\n",
                              secm_area->response.code);
+out:
+       spin_unlock_irq(&chsc_page_lock);
        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;
-
        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);
                        return -ENOMEM;
                }
        }
-       ret = __chsc_do_secm(css, enable, secm_area);
+       ret = __chsc_do_secm(css, enable);
        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);
+                               __chsc_do_secm(css, 0);
                                css->cm_enabled = 0;
                        }
                } else
@@ -638,44 +637,24 @@ chsc_secm(struct channel_subsystem *css, int enable)
                free_page((unsigned long)css->cub_addr1);
                free_page((unsigned long)css->cub_addr2);
        }
-       free_page((unsigned long)secm_area);
        return ret;
 }
 
 int chsc_determine_channel_path_desc(struct chp_id chpid, int fmt, int rfmt,
-                                    int c, int m,
-                                    struct chsc_response_struct *resp)
+                                    int c, int m, void *page)
 {
+       struct chsc_scpd *scpd_area;
        int ccode, ret;
 
-       struct {
-               struct chsc_header request;
-               u32 : 2;
-               u32 m : 1;
-               u32 c : 1;
-               u32 fmt : 4;
-               u32 cssid : 8;
-               u32 : 4;
-               u32 rfmt : 4;
-               u32 first_chpid : 8;
-               u32 : 24;
-               u32 last_chpid : 8;
-               u32 zeroes1;
-               struct chsc_header response;
-               u8 data[PAGE_SIZE - 20];
-       } __attribute__ ((packed)) *scpd_area;
-
        if ((rfmt == 1) && !css_general_characteristics.fcs)
                return -EINVAL;
        if ((rfmt == 2) && !css_general_characteristics.cib)
                return -EINVAL;
-       scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
-       if (!scpd_area)
-               return -ENOMEM;
 
+       memset(page, 0, PAGE_SIZE);
+       scpd_area = page;
        scpd_area->request.length = 0x0010;
        scpd_area->request.code = 0x0002;
-
        scpd_area->cssid = chpid.cssid;
        scpd_area->first_chpid = chpid.id;
        scpd_area->last_chpid = chpid.id;
@@ -685,20 +664,13 @@ int chsc_determine_channel_path_desc(struct chp_id chpid, int fmt, int rfmt,
        scpd_area->rfmt = rfmt;
 
        ccode = chsc(scpd_area);
-       if (ccode > 0) {
-               ret = (ccode == 3) ? -ENODEV : -EBUSY;
-               goto out;
-       }
+       if (ccode > 0)
+               return (ccode == 3) ? -ENODEV : -EBUSY;
 
        ret = chsc_error_from_response(scpd_area->response.code);
-       if (ret == 0)
-               /* Success. */
-               memcpy(resp, &scpd_area->response, scpd_area->response.length);
-       else
+       if (ret)
                CIO_CRW_EVENT(2, "chsc: scpd failed (rc=%04x)\n",
                              scpd_area->response.code);
-out:
-       free_page((unsigned long)scpd_area);
        return ret;
 }
 EXPORT_SYMBOL_GPL(chsc_determine_channel_path_desc);
@@ -707,17 +679,38 @@ int chsc_determine_base_channel_path_desc(struct chp_id chpid,
                                          struct channel_path_desc *desc)
 {
        struct chsc_response_struct *chsc_resp;
+       struct chsc_scpd *scpd_area;
+       unsigned long flags;
        int ret;
 
-       chsc_resp = kzalloc(sizeof(*chsc_resp), GFP_KERNEL);
-       if (!chsc_resp)
-               return -ENOMEM;
-       ret = chsc_determine_channel_path_desc(chpid, 0, 0, 0, 0, chsc_resp);
+       spin_lock_irqsave(&chsc_page_lock, flags);
+       scpd_area = chsc_page;
+       ret = chsc_determine_channel_path_desc(chpid, 0, 0, 0, 0, scpd_area);
        if (ret)
-               goto out_free;
+               goto out;
+       chsc_resp = (void *)&scpd_area->response;
        memcpy(desc, &chsc_resp->data, sizeof(*desc));
-out_free:
-       kfree(chsc_resp);
+out:
+       spin_unlock_irqrestore(&chsc_page_lock, flags);
+       return ret;
+}
+
+int chsc_determine_fmt1_channel_path_desc(struct chp_id chpid,
+                                         struct channel_path_desc_fmt1 *desc)
+{
+       struct chsc_response_struct *chsc_resp;
+       struct chsc_scpd *scpd_area;
+       int ret;
+
+       spin_lock_irq(&chsc_page_lock);
+       scpd_area = chsc_page;
+       ret = chsc_determine_channel_path_desc(chpid, 0, 0, 1, 0, scpd_area);
+       if (ret)
+               goto out;
+       chsc_resp = (void *)&scpd_area->response;
+       memcpy(desc, &chsc_resp->data, sizeof(*desc));
+out:
+       spin_unlock_irq(&chsc_page_lock);
        return ret;
 }
 
@@ -725,33 +718,22 @@ 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;
+       struct cmg_chars *cmg_chars;
+       int i, mask;
+
+       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;
        }
 }
 
 int chsc_get_channel_measurement_chars(struct channel_path *chp)
 {
+       struct cmg_chars *cmg_chars;
        int ccode, ret;
 
        struct {
@@ -775,13 +757,16 @@ int chsc_get_channel_measurement_chars(struct channel_path *chp)
                u32 data[NR_MEASUREMENT_CHARS];
        } __attribute__ ((packed)) *scmc_area;
 
-       scmc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
-       if (!scmc_area)
+       chp->cmg_chars = NULL;
+       cmg_chars = kmalloc(sizeof(*cmg_chars), GFP_KERNEL);
+       if (!cmg_chars)
                return -ENOMEM;
 
+       spin_lock_irq(&chsc_page_lock);
+       memset(chsc_page, 0, PAGE_SIZE);
+       scmc_area = chsc_page;
        scmc_area->request.length = 0x0010;
        scmc_area->request.code = 0x0022;
-
        scmc_area->first_chpid = chp->chpid.id;
        scmc_area->last_chpid = chp->chpid.id;
 
@@ -792,53 +777,65 @@ int chsc_get_channel_measurement_chars(struct channel_path *chp)
        }
 
        ret = chsc_error_from_response(scmc_area->response.code);
-       if (ret == 0) {
-               /* 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;
-               }
-       } else {
+       if (ret) {
                CIO_CRW_EVENT(2, "chsc: scmc failed (rc=%04x)\n",
                              scmc_area->response.code);
+               goto out;
+       }
+       if (scmc_area->not_valid) {
+               chp->cmg = -1;
+               chp->shared = -1;
+               goto out;
+       }
+       chp->cmg = scmc_area->cmg;
+       chp->shared = scmc_area->shared;
+       if (chp->cmg != 2 && chp->cmg != 3) {
+               /* No cmg-dependent data. */
+               goto out;
        }
+       chp->cmg_chars = cmg_chars;
+       chsc_initialize_cmg_chars(chp, scmc_area->cmcv,
+                                 (struct cmg_chars *) &scmc_area->data);
 out:
-       free_page((unsigned long)scmc_area);
+       spin_unlock_irq(&chsc_page_lock);
+       if (!chp->cmg_chars)
+               kfree(cmg_chars);
+
        return ret;
 }
 
-int __init chsc_alloc_sei_area(void)
+int __init chsc_init(void)
 {
        int ret;
 
        sei_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
-       if (!sei_page) {
-               CIO_MSG_EVENT(0, "Can't allocate page for processing of "
-                             "chsc machine checks!\n");
-               return -ENOMEM;
+       chsc_page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+       if (!sei_page || !chsc_page) {
+               ret = -ENOMEM;
+               goto out_err;
        }
        ret = crw_register_handler(CRW_RSC_CSS, chsc_process_crw);
        if (ret)
-               kfree(sei_page);
+               goto out_err;
+       return ret;
+out_err:
+       free_page((unsigned long)chsc_page);
+       free_page((unsigned long)sei_page);
        return ret;
 }
 
-void __init chsc_free_sei_area(void)
+void __init chsc_init_cleanup(void)
 {
        crw_unregister_handler(CRW_RSC_CSS);
-       kfree(sei_page);
+       free_page((unsigned long)chsc_page);
+       free_page((unsigned long)sei_page);
 }
 
 int chsc_enable_facility(int operation_code)
 {
+       unsigned long flags;
        int ret;
-       static struct {
+       struct {
                struct chsc_header request;
                u8 reserved1:4;
                u8 format:4;
@@ -851,32 +848,33 @@ int chsc_enable_facility(int operation_code)
                u32 reserved5:4;
                u32 format2:4;
                u32 reserved6:24;
-       } __attribute__ ((packed, aligned(4096))) sda_area;
+       } __attribute__ ((packed)) *sda_area;
 
-       spin_lock(&sda_lock);
-       memset(&sda_area, 0, sizeof(sda_area));
-       sda_area.request.length = 0x0400;
-       sda_area.request.code = 0x0031;
-       sda_area.operation_code = operation_code;
+       spin_lock_irqsave(&chsc_page_lock, flags);
+       memset(chsc_page, 0, PAGE_SIZE);
+       sda_area = chsc_page;
+       sda_area->request.length = 0x0400;
+       sda_area->request.code = 0x0031;
+       sda_area->operation_code = operation_code;
 
-       ret = chsc(&sda_area);
+       ret = chsc(sda_area);
        if (ret > 0) {
                ret = (ret == 3) ? -ENODEV : -EBUSY;
                goto out;
        }
 
-       switch (sda_area.response.code) {
+       switch (sda_area->response.code) {
        case 0x0101:
                ret = -EOPNOTSUPP;
                break;
        default:
-               ret = chsc_error_from_response(sda_area.response.code);
+               ret = chsc_error_from_response(sda_area->response.code);
        }
        if (ret != 0)
                CIO_CRW_EVENT(2, "chsc: sda (oc=%x) failed (rc=%04x)\n",
-                             operation_code, sda_area.response.code);
- out:
-       spin_unlock(&sda_lock);
+                             operation_code, sda_area->response.code);
+out:
+       spin_unlock_irqrestore(&chsc_page_lock, flags);
        return ret;
 }
 
@@ -895,13 +893,12 @@ chsc_determine_css_characteristics(void)
                struct chsc_header response;
                u32 reserved4;
                u32 general_char[510];
-               u32 chsc_char[518];
+               u32 chsc_char[508];
        } __attribute__ ((packed)) *scsc_area;
 
-       scsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
-       if (!scsc_area)
-               return -ENOMEM;
-
+       spin_lock_irq(&chsc_page_lock);
+       memset(chsc_page, 0, PAGE_SIZE);
+       scsc_area = chsc_page;
        scsc_area->request.length = 0x0010;
        scsc_area->request.code = 0x0010;
 
@@ -921,7 +918,7 @@ chsc_determine_css_characteristics(void)
                CIO_CRW_EVENT(2, "chsc: scsc failed (rc=%04x)\n",
                              scsc_area->response.code);
 exit:
-       free_page ((unsigned long) scsc_area);
+       spin_unlock_irq(&chsc_page_lock);
        return result;
 }
 
@@ -976,29 +973,29 @@ int chsc_sstpi(void *page, void *result, size_t size)
        return (rr->response.code == 0x0001) ? 0 : -EIO;
 }
 
-static struct {
-       struct chsc_header request;
-       u32 word1;
-       struct subchannel_id sid;
-       u32 word3;
-       struct chsc_header response;
-       u32 word[11];
-} __attribute__ ((packed)) siosl_area __attribute__ ((__aligned__(PAGE_SIZE)));
-
 int chsc_siosl(struct subchannel_id schid)
 {
+       struct {
+               struct chsc_header request;
+               u32 word1;
+               struct subchannel_id sid;
+               u32 word3;
+               struct chsc_header response;
+               u32 word[11];
+       } __attribute__ ((packed)) *siosl_area;
        unsigned long flags;
        int ccode;
        int rc;
 
-       spin_lock_irqsave(&siosl_lock, flags);
-       memset(&siosl_area, 0, sizeof(siosl_area));
-       siosl_area.request.length = 0x0010;
-       siosl_area.request.code = 0x0046;
-       siosl_area.word1 = 0x80000000;
-       siosl_area.sid = schid;
+       spin_lock_irqsave(&chsc_page_lock, flags);
+       memset(chsc_page, 0, PAGE_SIZE);
+       siosl_area = chsc_page;
+       siosl_area->request.length = 0x0010;
+       siosl_area->request.code = 0x0046;
+       siosl_area->word1 = 0x80000000;
+       siosl_area->sid = schid;
 
-       ccode = chsc(&siosl_area);
+       ccode = chsc(siosl_area);
        if (ccode > 0) {
                if (ccode == 3)
                        rc = -ENODEV;
@@ -1008,17 +1005,16 @@ int chsc_siosl(struct subchannel_id schid)
                              schid.ssid, schid.sch_no, ccode);
                goto out;
        }
-       rc = chsc_error_from_response(siosl_area.response.code);
+       rc = chsc_error_from_response(siosl_area->response.code);
        if (rc)
                CIO_MSG_EVENT(2, "chsc: siosl failed for 0.%x.%04x (rc=%04x)\n",
                              schid.ssid, schid.sch_no,
-                             siosl_area.response.code);
+                             siosl_area->response.code);
        else
                CIO_MSG_EVENT(4, "chsc: siosl succeeded for 0.%x.%04x\n",
                              schid.ssid, schid.sch_no);
 out:
-       spin_unlock_irqrestore(&siosl_lock, flags);
-
+       spin_unlock_irqrestore(&chsc_page_lock, flags);
        return rc;
 }
 EXPORT_SYMBOL_GPL(chsc_siosl);