vserver 2.0 rc7
[linux-2.6.git] / drivers / s390 / cio / chsc.c
index de60cfd..b35fe12 100644 (file)
@@ -1,7 +1,7 @@
 /*
  *  drivers/s390/cio/chsc.c
  *   S/390 common I/O routines -- channel subsystem call
- *   $Revision: 1.115 $
+ *   $Revision: 1.119 $
  *
  *    Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
  *                           IBM Corporation
@@ -423,12 +423,12 @@ s390_process_res_acc (u8 chpid, __u16 fla, u32 fla_mask)
                             sch->schib.pmcw.pam &
                             sch->schib.pmcw.pom)
                            | chp_mask) & sch->opm;
-               spin_unlock_irq(&sch->lock);
                if (!old_lpm && sch->lpm)
                        device_trigger_reprobe(sch);
                else if (sch->driver && sch->driver->verify)
                        sch->driver->verify(&sch->dev);
 
+               spin_unlock_irq(&sch->lock);
                put_device(&sch->dev);
                if (fla_mask != 0)
                        break;
@@ -703,6 +703,9 @@ __check_for_io_and_kill(struct subchannel *sch, int index)
 {
        int cc;
 
+       if (!device_is_online(sch))
+               /* cio could be doing I/O. */
+               return 0;
        cc = stsch(sch->irq, &sch->schib);
        if (cc)
                return 0;
@@ -717,10 +720,12 @@ static inline void
 __s390_subchannel_vary_chpid(struct subchannel *sch, __u8 chpid, int on)
 {
        int chp, old_lpm;
+       unsigned long flags;
 
        if (!sch->ssd_info.valid)
                return;
        
+       spin_lock_irqsave(&sch->lock, flags);
        old_lpm = sch->lpm;
        for (chp = 0; chp < 8; chp++) {
                if (sch->ssd_info.chpid[chp] != chpid)
@@ -751,6 +756,7 @@ __s390_subchannel_vary_chpid(struct subchannel *sch, __u8 chpid, int on)
                }
                break;
        }
+       spin_unlock_irqrestore(&sch->lock, flags);
 }
 
 static int
@@ -881,6 +887,27 @@ chp_status_write(struct device *dev, const char *buf, size_t count)
 
 static DEVICE_ATTR(status, 0644, chp_status_show, chp_status_write);
 
+static ssize_t
+chp_type_show(struct device *dev, char *buf)
+{
+       struct channel_path *chp = container_of(dev, struct channel_path, dev);
+
+       if (!chp)
+               return 0;
+       return sprintf(buf, "%x\n", chp->desc.desc);
+}
+
+static DEVICE_ATTR(type, 0444, chp_type_show, NULL);
+
+static struct attribute * chp_attrs[] = {
+       &dev_attr_status.attr,
+       &dev_attr_type.attr,
+       NULL,
+};
+
+static struct attribute_group chp_attr_group = {
+       .attrs = chp_attrs,
+};
 
 static void
 chp_release(struct device *dev)
@@ -891,6 +918,68 @@ chp_release(struct device *dev)
        kfree(cp);
 }
 
+static int
+chsc_determine_channel_path_description(int chpid,
+                                       struct channel_path_desc *desc)
+{
+       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;
+               struct channel_path_desc desc;
+       } *scpd_area;
+
+       scpd_area = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+       if (!scpd_area)
+               return -ENOMEM;
+
+       scpd_area->request = (struct chsc_header) {
+               .length = 0x0010,
+               .code   = 0x0002,
+       };
+
+       scpd_area->first_chpid = chpid;
+       scpd_area->last_chpid = chpid;
+
+       ccode = chsc(scpd_area);
+       if (ccode > 0) {
+               ret = (ccode == 3) ? -ENODEV : -EBUSY;
+               goto out;
+       }
+
+       switch (scpd_area->response.code) {
+       case 0x0001: /* Success. */
+               memcpy(desc, &scpd_area->desc,
+                      sizeof(struct channel_path_desc));
+               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 scpd\n");
+               ret = -EOPNOTSUPP;
+               break;
+       default:
+               CIO_CRW_EVENT(2, "Unknown CHSC response %d\n",
+                             scpd_area->response.code);
+               ret = -EIO;
+       }
+out:
+       free_page((unsigned long)scpd_area);
+       return ret;
+}
+
 /*
  * Entries for chpids on the system bus.
  * This replaces /proc/chpids.
@@ -915,6 +1004,11 @@ new_channel_path(int chpid)
        };
        snprintf(chp->dev.bus_id, BUS_ID_SIZE, "chp0.%x", chpid);
 
+       /* Obtain channel path description and fill it in. */
+       ret = chsc_determine_channel_path_description(chpid, &chp->desc);
+       if (ret)
+               goto out_free;
+
        /* make it known to the system */
        ret = device_register(&chp->dev);
        if (ret) {
@@ -922,7 +1016,7 @@ new_channel_path(int chpid)
                       __func__, chpid);
                goto out_free;
        }
-       ret = device_create_file(&chp->dev, &dev_attr_status);
+       ret = sysfs_create_group(&chp->dev.kobj, &chp_attr_group);
        if (ret) {
                device_unregister(&chp->dev);
                goto out_free;
@@ -934,6 +1028,23 @@ out_free:
        return ret;
 }
 
+void *
+chsc_get_chp_desc(struct subchannel *sch, int chp_no)
+{
+       struct channel_path *chp;
+       struct channel_path_desc *desc;
+
+       chp = chps[sch->schib.pmcw.chpid[chp_no]];
+       if (!chp)
+               return NULL;
+       desc = kmalloc(sizeof(struct channel_path_desc), GFP_KERNEL);
+       if (!desc)
+               return NULL;
+       memcpy(desc, &chp->desc, sizeof(struct channel_path_desc));
+       return desc;
+}
+
+
 static int __init
 chsc_alloc_sei_area(void)
 {