Merge branch 'for-linus' of master.kernel.org:/pub/scm/linux/kernel/git/roland/infiniband
[pandora-kernel.git] / drivers / s390 / cio / chsc.c
index 2d78f0f..6f05a44 100644 (file)
@@ -93,7 +93,7 @@ chsc_get_sch_desc_irq(struct subchannel *sch, void *page)
                u16 sch;          /* subchannel */
                u8 chpid[8];      /* chpids 0-7 */
                u16 fla[8];       /* full link addresses 0-7 */
-       } *ssd_area;
+       } __attribute__ ((packed)) *ssd_area;
 
        ssd_area = page;
 
@@ -183,7 +183,7 @@ css_get_ssd_info(struct subchannel *sch)
        page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
        if (!page)
                return -ENOMEM;
-       spin_lock_irq(&sch->lock);
+       spin_lock_irq(sch->lock);
        ret = chsc_get_sch_desc_irq(sch, page);
        if (ret) {
                static int cio_chsc_err_msg;
@@ -197,7 +197,7 @@ css_get_ssd_info(struct subchannel *sch)
                        cio_chsc_err_msg = 1;
                }
        }
-       spin_unlock_irq(&sch->lock);
+       spin_unlock_irq(sch->lock);
        free_page((unsigned long)page);
        if (!ret) {
                int j, chpid, mask;
@@ -233,7 +233,7 @@ s390_subchannel_remove_chpid(struct device *dev, void *data)
        if (j >= 8)
                return 0;
 
-       spin_lock_irq(&sch->lock);
+       spin_lock_irq(sch->lock);
 
        stsch(sch->schid, &schib);
        if (!schib.pmcw.dnv)
@@ -251,6 +251,8 @@ s390_subchannel_remove_chpid(struct device *dev, void *data)
                cc = cio_clear(sch);
                if (cc == -ENODEV)
                        goto out_unreg;
+               /* Request retry of internal operation. */
+               device_set_intretry(sch);
                /* Call handler. */
                if (sch->driver && sch->driver->termination)
                        sch->driver->termination(&sch->dev);
@@ -263,10 +265,10 @@ s390_subchannel_remove_chpid(struct device *dev, void *data)
        else if (sch->lpm == mask)
                goto out_unreg;
 out_unlock:
-       spin_unlock_irq(&sch->lock);
+       spin_unlock_irq(sch->lock);
        return 0;
 out_unreg:
-       spin_unlock_irq(&sch->lock);
+       spin_unlock_irq(sch->lock);
        sch->lpm = 0;
        if (css_enqueue_subchannel_slow(sch->schid)) {
                css_clear_subchannel_slow_list();
@@ -275,7 +277,7 @@ out_unreg:
        return 0;
 }
 
-static inline void
+static void
 s390_set_chpid_offline( __u8 chpid)
 {
        char dbf_txt[15];
@@ -336,7 +338,7 @@ s390_process_res_acc_sch(struct res_acc_data *res_data, struct subchannel *sch)
        return 0x80 >> chp;
 }
 
-static inline int
+static int
 s390_process_res_acc_new_sch(struct subchannel_id schid)
 {
        struct schib schib;
@@ -376,12 +378,12 @@ __s390_process_res_acc(struct subchannel_id schid, void *data)
                /* Check if a subchannel is newly available. */
                return s390_process_res_acc_new_sch(schid);
 
-       spin_lock_irq(&sch->lock);
+       spin_lock_irq(sch->lock);
 
        chp_mask = s390_process_res_acc_sch(res_data, sch);
 
        if (chp_mask == 0) {
-               spin_unlock_irq(&sch->lock);
+               spin_unlock_irq(sch->lock);
                put_device(&sch->dev);
                return 0;
        }
@@ -395,7 +397,7 @@ __s390_process_res_acc(struct subchannel_id schid, void *data)
        else if (sch->driver && sch->driver->verify)
                sch->driver->verify(&sch->dev);
 
-       spin_unlock_irq(&sch->lock);
+       spin_unlock_irq(sch->lock);
        put_device(&sch->dev);
        return 0;
 }
@@ -442,7 +444,7 @@ __get_chpid_from_lir(void *data)
                u32 andesc[28];
                /* incident-specific information */
                u32 isinfo[28];
-       } *lir;
+       } __attribute__ ((packed)) *lir;
 
        lir = data;
        if (!(lir->iq&0x80))
@@ -459,154 +461,146 @@ __get_chpid_from_lir(void *data)
        return (u16) (lir->indesc[0]&0x000000ff);
 }
 
-int
-chsc_process_crw(void)
+struct chsc_sei_area {
+       struct chsc_header request;
+       u32 reserved1;
+       u32 reserved2;
+       u32 reserved3;
+       struct chsc_header response;
+       u32 reserved4;
+       u8  flags;
+       u8  vf;         /* validity flags */
+       u8  rs;         /* reporting source */
+       u8  cc;         /* content code */
+       u16 fla;        /* full link address */
+       u16 rsid;       /* reporting source id */
+       u32 reserved5;
+       u32 reserved6;
+       u8 ccdf[4096 - 16 - 24];        /* content-code dependent field */
+       /* ccdf has to be big enough for a link-incident record */
+} __attribute__ ((packed));
+
+static int chsc_process_sei_link_incident(struct chsc_sei_area *sei_area)
+{
+       int chpid;
+
+       CIO_CRW_EVENT(4, "chsc: link incident (rs=%02x, rs_id=%04x)\n",
+                     sei_area->rs, sei_area->rsid);
+       if (sei_area->rs != 4)
+               return 0;
+       chpid = __get_chpid_from_lir(sei_area->ccdf);
+       if (chpid < 0)
+               CIO_CRW_EVENT(4, "chsc: link incident - invalid LIR\n");
+       else
+               s390_set_chpid_offline(chpid);
+
+       return 0;
+}
+
+static int chsc_process_sei_res_acc(struct chsc_sei_area *sei_area)
 {
-       int chpid, ret;
        struct res_acc_data res_data;
-       struct {
-               struct chsc_header request;
-               u32 reserved1;
-               u32 reserved2;
-               u32 reserved3;
-               struct chsc_header response;
-               u32 reserved4;
-               u8  flags;
-               u8  vf;         /* validity flags */
-               u8  rs;         /* reporting source */
-               u8  cc;         /* content code */
-               u16 fla;        /* full link address */
-               u16 rsid;       /* reporting source id */
-               u32 reserved5;
-               u32 reserved6;
-               u32 ccdf[96];   /* content-code dependent field */
-               /* ccdf has to be big enough for a link-incident record */
-       } *sei_area;
+       struct device *dev;
+       int status;
+       int rc;
+
+       CIO_CRW_EVENT(4, "chsc: resource accessibility event (rs=%02x, "
+                     "rs_id=%04x)\n", sei_area->rs, sei_area->rsid);
+       if (sei_area->rs != 4)
+               return 0;
+       /* allocate a new channel path structure, if needed */
+       status = get_chp_status(sei_area->rsid);
+       if (status < 0)
+               new_channel_path(sei_area->rsid);
+       else if (!status)
+               return 0;
+       dev = get_device(&css[0]->chps[sei_area->rsid]->dev);
+       memset(&res_data, 0, sizeof(struct res_acc_data));
+       res_data.chp = to_channelpath(dev);
+       if ((sei_area->vf & 0xc0) != 0) {
+               res_data.fla = sei_area->fla;
+               if ((sei_area->vf & 0xc0) == 0xc0)
+                       /* full link address */
+                       res_data.fla_mask = 0xffff;
+               else
+                       /* link address */
+                       res_data.fla_mask = 0xff00;
+       }
+       rc = s390_process_res_acc(&res_data);
+       put_device(dev);
+
+       return rc;
+}
+
+static int chsc_process_sei(struct chsc_sei_area *sei_area)
+{
+       int rc;
+
+       /* Check if we might have lost some information. */
+       if (sei_area->flags & 0x40)
+               CIO_CRW_EVENT(2, "chsc: event overflow\n");
+       /* which kind of information was stored? */
+       rc = 0;
+       switch (sei_area->cc) {
+       case 1: /* link incident*/
+               rc = chsc_process_sei_link_incident(sei_area);
+               break;
+       case 2: /* i/o resource accessibiliy */
+               rc = chsc_process_sei_res_acc(sei_area);
+               break;
+       default: /* other stuff */
+               CIO_CRW_EVENT(4, "chsc: unhandled sei content code %d\n",
+                             sei_area->cc);
+               break;
+       }
+
+       return rc;
+}
+
+int chsc_process_crw(void)
+{
+       struct chsc_sei_area *sei_area;
+       int ret;
+       int rc;
 
        if (!sei_page)
                return 0;
-       /*
-        * build the chsc request block for store event information
-        * and do the call
-        * This function is only called by the machine check handler thread,
-        * so we don't need locking for the sei_page.
-        */
+       /* Access to sei_page is serialized through machine check handler
+        * thread, so no need for locking. */
        sei_area = sei_page;
 
        CIO_TRACE_EVENT( 2, "prcss");
        ret = 0;
        do {
-               int ccode, status;
-               struct device *dev;
                memset(sei_area, 0, sizeof(*sei_area));
-               memset(&res_data, 0, sizeof(struct res_acc_data));
                sei_area->request.length = 0x0010;
                sei_area->request.code = 0x000e;
+               if (chsc(sei_area))
+                       break;
 
-               ccode = chsc(sei_area);
-               if (ccode > 0)
-                       return 0;
-
-               switch (sei_area->response.code) {
-                       /* for debug purposes, check for problems */
-               case 0x0001:
-                       CIO_CRW_EVENT(4, "chsc_process_crw: event information "
-                                       "successfully stored\n");
-                       break; /* everything ok */
-               case 0x0002:
-                       CIO_CRW_EVENT(2,
-                                     "chsc_process_crw: invalid command!\n");
-                       return 0;
-               case 0x0003:
-                       CIO_CRW_EVENT(2, "chsc_process_crw: error in chsc "
-                                     "request block!\n");
-                       return 0;
-               case 0x0005:
-                       CIO_CRW_EVENT(2, "chsc_process_crw: no event "
-                                     "information stored\n");
-                       return 0;
-               default:
-                       CIO_CRW_EVENT(2, "chsc_process_crw: chsc response %d\n",
+               if (sei_area->response.code == 0x0001) {
+                       CIO_CRW_EVENT(4, "chsc: sei successful\n");
+                       rc = chsc_process_sei(sei_area);
+                       if (rc)
+                               ret = rc;
+               } else {
+                       CIO_CRW_EVENT(2, "chsc: sei failed (rc=%04x)\n",
                                      sei_area->response.code);
-                       return 0;
-               }
-
-               /* Check if we might have lost some information. */
-               if (sei_area->flags & 0x40)
-                       CIO_CRW_EVENT(2, "chsc_process_crw: Event information "
-                                      "has been lost due to overflow!\n");
-
-               if (sei_area->rs != 4) {
-                       CIO_CRW_EVENT(2, "chsc_process_crw: reporting source "
-                                     "(%04X) isn't a chpid!\n",
-                                     sei_area->rsid);
-                       continue;
-               }
-
-               /* which kind of information was stored? */
-               switch (sei_area->cc) {
-               case 1: /* link incident*/
-                       CIO_CRW_EVENT(4, "chsc_process_crw: "
-                                     "channel subsystem reports link incident,"
-                                     " reporting source is chpid %x\n",
-                                     sei_area->rsid);
-                       chpid = __get_chpid_from_lir(sei_area->ccdf);
-                       if (chpid < 0)
-                               CIO_CRW_EVENT(4, "%s: Invalid LIR, skipping\n",
-                                             __FUNCTION__);
-                       else
-                               s390_set_chpid_offline(chpid);
-                       break;
-                       
-               case 2: /* i/o resource accessibiliy */
-                       CIO_CRW_EVENT(4, "chsc_process_crw: "
-                                     "channel subsystem reports some I/O "
-                                     "devices may have become accessible\n");
-                       pr_debug("Data received after sei: \n");
-                       pr_debug("Validity flags: %x\n", sei_area->vf);
-                       
-                       /* allocate a new channel path structure, if needed */
-                       status = get_chp_status(sei_area->rsid);
-                       if (status < 0)
-                               new_channel_path(sei_area->rsid);
-                       else if (!status)
-                               break;
-                       dev = get_device(&css[0]->chps[sei_area->rsid]->dev);
-                       res_data.chp = to_channelpath(dev);
-                       pr_debug("chpid: %x", sei_area->rsid);
-                       if ((sei_area->vf & 0xc0) != 0) {
-                               res_data.fla = sei_area->fla;
-                               if ((sei_area->vf & 0xc0) == 0xc0) {
-                                       pr_debug(" full link addr: %x",
-                                                sei_area->fla);
-                                       res_data.fla_mask = 0xffff;
-                               } else {
-                                       pr_debug(" link addr: %x",
-                                                sei_area->fla);
-                                       res_data.fla_mask = 0xff00;
-                               }
-                       }
-                       ret = s390_process_res_acc(&res_data);
-                       pr_debug("\n\n");
-                       put_device(dev);
-                       break;
-                       
-               default: /* other stuff */
-                       CIO_CRW_EVENT(4, "chsc_process_crw: event %d\n",
-                                     sei_area->cc);
+                       ret = 0;
                        break;
                }
        } while (sei_area->flags & 0x80);
+
        return ret;
 }
 
-static inline int
+static int
 __chp_add_new_sch(struct subchannel_id schid)
 {
        struct schib schib;
        int ret;
 
-       if (stsch(schid, &schib))
+       if (stsch_err(schid, &schib))
                /* We're through */
                return need_rescan ? -EAGAIN : -ENXIO;
 
@@ -633,21 +627,21 @@ __chp_add(struct subchannel_id schid, void *data)
        if (!sch)
                /* Check if the subchannel is now available. */
                return __chp_add_new_sch(schid);
-       spin_lock_irq(&sch->lock);
+       spin_lock_irq(sch->lock);
        for (i=0; i<8; i++) {
                mask = 0x80 >> i;
                if ((sch->schib.pmcw.pim & mask) &&
                    (sch->schib.pmcw.chpid[i] == chp->id)) {
                        if (stsch(sch->schid, &sch->schib) != 0) {
                                /* Endgame. */
-                               spin_unlock_irq(&sch->lock);
+                               spin_unlock_irq(sch->lock);
                                return -ENXIO;
                        }
                        break;
                }
        }
        if (i==8) {
-               spin_unlock_irq(&sch->lock);
+               spin_unlock_irq(sch->lock);
                return 0;
        }
        sch->lpm = ((sch->schib.pmcw.pim &
@@ -658,7 +652,7 @@ __chp_add(struct subchannel_id schid, void *data)
        if (sch->driver && sch->driver->verify)
                sch->driver->verify(&sch->dev);
 
-       spin_unlock_irq(&sch->lock);
+       spin_unlock_irq(sch->lock);
        put_device(&sch->dev);
        return 0;
 }
@@ -707,13 +701,10 @@ chp_process_crw(int chpid, int on)
        return chp_add(chpid);
 }
 
-static inline int check_for_io_on_path(struct subchannel *sch, int index)
+static int check_for_io_on_path(struct subchannel *sch, int index)
 {
        int cc;
 
-       if (!device_is_online(sch))
-               /* cio could be doing I/O. */
-               return 0;
        cc = stsch(sch->schid, &sch->schib);
        if (cc)
                return 0;
@@ -722,7 +713,27 @@ static inline int check_for_io_on_path(struct subchannel *sch, int index)
        return 0;
 }
 
-static inline void
+static void terminate_internal_io(struct subchannel *sch)
+{
+       if (cio_clear(sch)) {
+               /* Recheck device in case clear failed. */
+               sch->lpm = 0;
+               if (device_trigger_verify(sch) != 0) {
+                       if(css_enqueue_subchannel_slow(sch->schid)) {
+                               css_clear_subchannel_slow_list();
+                               need_rescan = 1;
+                       }
+               }
+               return;
+       }
+       /* Request retry of internal operation. */
+       device_set_intretry(sch);
+       /* Call handler. */
+       if (sch->driver && sch->driver->termination)
+               sch->driver->termination(&sch->dev);
+}
+
+static void
 __s390_subchannel_vary_chpid(struct subchannel *sch, __u8 chpid, int on)
 {
        int chp, old_lpm;
@@ -731,7 +742,7 @@ __s390_subchannel_vary_chpid(struct subchannel *sch, __u8 chpid, int on)
        if (!sch->ssd_info.valid)
                return;
        
-       spin_lock_irqsave(&sch->lock, flags);
+       spin_lock_irqsave(sch->lock, flags);
        old_lpm = sch->lpm;
        for (chp = 0; chp < 8; chp++) {
                if (sch->ssd_info.chpid[chp] != chpid)
@@ -744,23 +755,29 @@ __s390_subchannel_vary_chpid(struct subchannel *sch, __u8 chpid, int on)
                                device_trigger_reprobe(sch);
                        else if (sch->driver && sch->driver->verify)
                                sch->driver->verify(&sch->dev);
-               } else {
-                       sch->opm &= ~(0x80 >> chp);
-                       sch->lpm &= ~(0x80 >> chp);
-                       if (check_for_io_on_path(sch, chp))
+                       break;
+               }
+               sch->opm &= ~(0x80 >> chp);
+               sch->lpm &= ~(0x80 >> chp);
+               if (check_for_io_on_path(sch, chp)) {
+                       if (device_is_online(sch))
                                /* Path verification is done after killing. */
                                device_kill_io(sch);
-                       else if (!sch->lpm) {
+                       else
+                               /* Kill and retry internal I/O. */
+                               terminate_internal_io(sch);
+               } else if (!sch->lpm) {
+                       if (device_trigger_verify(sch) != 0) {
                                if (css_enqueue_subchannel_slow(sch->schid)) {
                                        css_clear_subchannel_slow_list();
                                        need_rescan = 1;
                                }
-                       } else if (sch->driver && sch->driver->verify)
-                               sch->driver->verify(&sch->dev);
-               }
+                       }
+               } else if (sch->driver && sch->driver->verify)
+                       sch->driver->verify(&sch->dev);
                break;
        }
-       spin_unlock_irqrestore(&sch->lock, flags);
+       spin_unlock_irqrestore(sch->lock, flags);
 }
 
 static int
@@ -942,8 +959,8 @@ static struct bin_attribute chp_measurement_attr = {
 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);
+       device_remove_bin_file(&chp->dev, &chp_measurement_chars_attr);
+       device_remove_bin_file(&chp->dev, &chp_measurement_attr);
 }
 
 static int
@@ -951,14 +968,12 @@ chsc_add_chp_cmg_attr(struct channel_path *chp)
 {
        int ret;
 
-       ret = sysfs_create_bin_file(&chp->dev.kobj,
-                                   &chp_measurement_chars_attr);
+       ret = device_create_bin_file(&chp->dev, &chp_measurement_chars_attr);
        if (ret)
                return ret;
-       ret = sysfs_create_bin_file(&chp->dev.kobj, &chp_measurement_attr);
+       ret = device_create_bin_file(&chp->dev, &chp_measurement_attr);
        if (ret)
-               sysfs_remove_bin_file(&chp->dev.kobj,
-                                     &chp_measurement_chars_attr);
+               device_remove_bin_file(&chp->dev, &chp_measurement_chars_attr);
        return ret;
 }
 
@@ -1017,7 +1032,7 @@ __chsc_do_secm(struct channel_subsystem *css, int enable, void *page)
                u32 : 4;
                u32 fmt : 4;
                u32 : 16;
-       } *secm_area;
+       } __attribute__ ((packed)) *secm_area;
        int ret, ccode;
 
        secm_area = page;
@@ -1228,7 +1243,7 @@ chsc_determine_channel_path_description(int chpid,
                struct chsc_header response;
                u32 zeroes2;
                struct channel_path_desc desc;
-       } *scpd_area;
+       } __attribute__ ((packed)) *scpd_area;
 
        scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
        if (!scpd_area)
@@ -1325,7 +1340,7 @@ chsc_get_channel_measurement_chars(struct channel_path *chp)
                u32 cmg : 8;
                u32 zeroes3;
                u32 data[NR_MEASUREMENT_CHARS];
-       } *scmc_area;
+       } __attribute__ ((packed)) *scmc_area;
 
        scmc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
        if (!scmc_area)
@@ -1465,41 +1480,6 @@ chsc_get_chp_desc(struct subchannel *sch, int chp_no)
        return desc;
 }
 
-static int reset_channel_path(struct channel_path *chp)
-{
-       int cc;
-
-       cc = rchp(chp->id);
-       switch (cc) {
-       case 0:
-               return 0;
-       case 2:
-               return -EBUSY;
-       default:
-               return -ENODEV;
-       }
-}
-
-static void reset_channel_paths_css(struct channel_subsystem *css)
-{
-       int i;
-
-       for (i = 0; i <= __MAX_CHPID; i++) {
-               if (css->chps[i])
-                       reset_channel_path(css->chps[i]);
-       }
-}
-
-void cio_reset_channel_paths(void)
-{
-       int i;
-
-       for (i = 0; i <= __MAX_CSSID; i++) {
-               if (css[i] && css[i]->valid)
-                       reset_channel_paths_css(css[i]);
-       }
-}
-
 static int __init
 chsc_alloc_sei_area(void)
 {
@@ -1527,7 +1507,7 @@ chsc_enable_facility(int operation_code)
                u32 reserved5:4;
                u32 format2:4;
                u32 reserved6:24;
-       } *sda_area;
+       } __attribute__ ((packed)) *sda_area;
 
        sda_area = (void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
        if (!sda_area)
@@ -1579,7 +1559,7 @@ chsc_determine_css_characteristics(void)
                u32 reserved4;
                u32 general_char[510];
                u32 chsc_char[518];
-       } *scsc_area;
+       } __attribute__ ((packed)) *scsc_area;
 
        scsc_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
        if (!scsc_area) {