[S390] cio: fix subchannel channel-path data usage

Ensure that channel-path related subchannel data is only retrieved and
used when it is valid and that it is updated when it may have changed.

Signed-off-by: Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/drivers/s390/cio/chsc.c b/drivers/s390/cio/chsc.c
index 89a130a..0841e16 100644
--- a/drivers/s390/cio/chsc.c
+++ b/drivers/s390/cio/chsc.c
@@ -26,155 +26,84 @@
 
 static void *sei_page;
 
-/* FIXME: this is _always_ called for every subchannel. shouldn't we
- *	  process more than one at a time? */
-static int
-chsc_get_sch_desc_irq(struct subchannel *sch, void *page)
+struct chsc_ssd_area {
+	struct chsc_header request;
+	u16 :10;
+	u16 ssid:2;
+	u16 :4;
+	u16 f_sch;	  /* first subchannel */
+	u16 :16;
+	u16 l_sch;	  /* last subchannel */
+	u32 :32;
+	struct chsc_header response;
+	u32 :32;
+	u8 sch_valid : 1;
+	u8 dev_valid : 1;
+	u8 st	     : 3; /* subchannel type */
+	u8 zeroes    : 3;
+	u8  unit_addr;	  /* unit address */
+	u16 devno;	  /* device number */
+	u8 path_mask;
+	u8 fla_valid_mask;
+	u16 sch;	  /* subchannel */
+	u8 chpid[8];	  /* chpids 0-7 */
+	u16 fla[8];	  /* full link addresses 0-7 */
+} __attribute__ ((packed));
+
+int chsc_get_ssd_info(struct subchannel_id schid, struct chsc_ssd_info *ssd)
 {
-	int ccode, j;
-
-	struct {
-		struct chsc_header request;
-		u16 reserved1a:10;
-		u16 ssid:2;
-		u16 reserved1b:4;
-		u16 f_sch;	  /* first subchannel */
-		u16 reserved2;
-		u16 l_sch;	  /* last subchannel */
-		u32 reserved3;
-		struct chsc_header response;
-		u32 reserved4;
-		u8 sch_valid : 1;
-		u8 dev_valid : 1;
-		u8 st	     : 3; /* subchannel type */
-		u8 zeroes    : 3;
-		u8  unit_addr;	  /* unit address */
-		u16 devno;	  /* device number */
-		u8 path_mask;
-		u8 fla_valid_mask;
-		u16 sch;	  /* subchannel */
-		u8 chpid[8];	  /* chpids 0-7 */
-		u16 fla[8];	  /* full link addresses 0-7 */
-	} __attribute__ ((packed)) *ssd_area;
-
-	ssd_area = page;
-
-	ssd_area->request.length = 0x0010;
-	ssd_area->request.code = 0x0004;
-
-	ssd_area->ssid = sch->schid.ssid;
-	ssd_area->f_sch = sch->schid.sch_no;
-	ssd_area->l_sch = sch->schid.sch_no;
-
-	ccode = chsc(ssd_area);
-	if (ccode > 0) {
-		pr_debug("chsc returned with ccode = %d\n", ccode);
-		return (ccode == 3) ? -ENODEV : -EBUSY;
-	}
-
-	switch (ssd_area->response.code) {
-	case 0x0001: /* everything ok */
-		break;
-	case 0x0002:
-		CIO_CRW_EVENT(2, "Invalid command!\n");
-		return -EINVAL;
-	case 0x0003:
-		CIO_CRW_EVENT(2, "Error in chsc request block!\n");
-		return -EINVAL;
-	case 0x0004:
-		CIO_CRW_EVENT(2, "Model does not provide ssd\n");
-		return -EOPNOTSUPP;
-	default:
-		CIO_CRW_EVENT(2, "Unknown CHSC response %d\n",
-			      ssd_area->response.code);
-		return -EIO;
-	}
-
-	/*
-	 * ssd_area->st stores the type of the detected
-	 * subchannel, with the following definitions:
-	 *
-	 * 0: I/O subchannel:	  All fields have meaning
-	 * 1: CHSC subchannel:	  Only sch_val, st and sch
-	 *			  have meaning
-	 * 2: Message subchannel: All fields except unit_addr
-	 *			  have meaning
-	 * 3: ADM subchannel:	  Only sch_val, st and sch
-	 *			  have meaning
-	 *
-	 * Other types are currently undefined.
-	 */
-	if (ssd_area->st > 3) { /* uhm, that looks strange... */
-		CIO_CRW_EVENT(0, "Strange subchannel type %d"
-			      " for sch 0.%x.%04x\n", ssd_area->st,
-			      sch->schid.ssid, sch->schid.sch_no);
-		/*
-		 * There may have been a new subchannel type defined in the
-		 * time since this code was written; since we don't know which
-		 * fields have meaning and what to do with it we just jump out
-		 */
-		return 0;
-	} else {
-		const char *type[4] = {"I/O", "chsc", "message", "ADM"};
-		CIO_CRW_EVENT(6, "ssd: sch 0.%x.%04x is %s subchannel\n",
-			      sch->schid.ssid, sch->schid.sch_no,
-			      type[ssd_area->st]);
-
-		sch->ssd_info.valid = 1;
-		sch->ssd_info.type = ssd_area->st;
-	}
-
-	if (ssd_area->st == 0 || ssd_area->st == 2) {
-		for (j = 0; j < 8; j++) {
-			if (!((0x80 >> j) & ssd_area->path_mask &
-			      ssd_area->fla_valid_mask))
-				continue;
-			sch->ssd_info.chpid[j] = ssd_area->chpid[j];
-			sch->ssd_info.fla[j]   = ssd_area->fla[j];
-		}
-	}
-	return 0;
-}
-
-int
-css_get_ssd_info(struct subchannel *sch)
-{
+	unsigned long page;
+	struct chsc_ssd_area *ssd_area;
+	int ccode;
 	int ret;
-	void *page;
+	int i;
+	int mask;
 
-	page = (void *)get_zeroed_page(GFP_KERNEL | GFP_DMA);
+	page = get_zeroed_page(GFP_KERNEL | GFP_DMA);
 	if (!page)
 		return -ENOMEM;
-	spin_lock_irq(sch->lock);
-	ret = chsc_get_sch_desc_irq(sch, page);
-	if (ret) {
-		static int cio_chsc_err_msg;
-		
-		if (!cio_chsc_err_msg) {
-			printk(KERN_ERR
-			       "chsc_get_sch_descriptions:"
-			       " Error %d while doing chsc; "
-			       "processing some machine checks may "
-			       "not work\n", ret);
-			cio_chsc_err_msg = 1;
-		}
-	}
-	spin_unlock_irq(sch->lock);
-	free_page((unsigned long)page);
-	if (!ret) {
-		int j, mask;
-		struct chp_id chpid;
+	ssd_area = (struct chsc_ssd_area *) page;
+	ssd_area->request.length = 0x0010;
+	ssd_area->request.code = 0x0004;
+	ssd_area->ssid = schid.ssid;
+	ssd_area->f_sch = schid.sch_no;
+	ssd_area->l_sch = schid.sch_no;
 
-		chp_id_init(&chpid);
-		/* Allocate channel path structures, if needed. */
-		for (j = 0; j < 8; j++) {
-			mask = 0x80 >> j;
-			chpid.id = sch->ssd_info.chpid[j];
-			if ((sch->schib.pmcw.pim & mask) &&
-			    !chp_is_registered(chpid))
-				chp_new(chpid);
-		}
+	ccode = chsc(ssd_area);
+	/* Check response. */
+	if (ccode > 0) {
+		ret = (ccode == 3) ? -ENODEV : -EBUSY;
+		goto out_free;
 	}
+	if (ssd_area->response.code != 0x0001) {
+		CIO_MSG_EVENT(2, "chsc: ssd failed for 0.%x.%04x (rc=%04x)\n",
+			      schid.ssid, schid.sch_no,
+			      ssd_area->response.code);
+		ret = -EIO;
+		goto out_free;
+	}
+	if (!ssd_area->sch_valid) {
+		ret = -ENODEV;
+		goto out_free;
+	}
+	/* Copy data */
+	ret = 0;
+	memset(ssd, 0, sizeof(struct chsc_ssd_info));
+	if ((ssd_area->st != 0) && (ssd_area->st != 2))
+		goto out_free;
+	ssd->path_mask = ssd_area->path_mask;
+	ssd->fla_valid_mask = ssd_area->fla_valid_mask;
+	for (i = 0; i < 8; i++) {
+		mask = 0x80 >> i;
+		if (ssd_area->path_mask & mask) {
+			chp_id_init(&ssd->chpid[i]);
+			ssd->chpid[i].id = ssd_area->chpid[i];
+		}
+		if (ssd_area->fla_valid_mask & mask)
+			ssd->fla[i] = ssd_area->fla[i];
+	}
+out_free:
+	free_page(page);
 	return ret;
 }
 
@@ -276,47 +205,6 @@
 			 s390_subchannel_remove_chpid);
 }
 
-struct res_acc_data {
-	struct chp_id chpid;
-	u32 fla_mask;
-	u16 fla;
-};
-
-static int s390_process_res_acc_sch(struct res_acc_data *res_data,
-				    struct subchannel *sch)
-{
-	int found;
-	int chp;
-	int ccode;
-
-	found = 0;
-	for (chp = 0; chp <= 7; chp++)
-		/*
-		 * check if chpid is in information updated by ssd
-		 */
-		if (sch->ssd_info.valid &&
-		    sch->ssd_info.chpid[chp] == res_data->chpid.id &&
-		    (sch->ssd_info.fla[chp] & res_data->fla_mask)
-		    == res_data->fla) {
-			found = 1;
-			break;
-		}
-
-	if (found == 0)
-		return 0;
-
-	/*
-	 * Do a stsch to update our subchannel structure with the
-	 * new path information and eventually check for logically
-	 * offline chpids.
-	 */
-	ccode = stsch(sch->schid, &sch->schib);
-	if (ccode > 0)
-		return 0;
-
-	return 0x80 >> chp;
-}
-
 static int
 s390_process_res_acc_new_sch(struct subchannel_id schid)
 {
@@ -338,6 +226,32 @@
 	return 0;
 }
 
+struct res_acc_data {
+	struct chp_id chpid;
+	u32 fla_mask;
+	u16 fla;
+};
+
+static int get_res_chpid_mask(struct chsc_ssd_info *ssd,
+			      struct res_acc_data *data)
+{
+	int i;
+	int mask;
+
+	for (i = 0; i < 8; i++) {
+		mask = 0x80 >> i;
+		if (!(ssd->path_mask & mask))
+			continue;
+		if (!chp_id_is_equal(&ssd->chpid[i], &data->chpid))
+			continue;
+		if ((ssd->fla_valid_mask & mask) &&
+		    ((ssd->fla[i] & data->fla_mask) != data->fla))
+			continue;
+		return mask;
+	}
+	return 0;
+}
+
 static int
 __s390_process_res_acc(struct subchannel_id schid, void *data)
 {
@@ -352,14 +266,11 @@
 		return s390_process_res_acc_new_sch(schid);
 
 	spin_lock_irq(sch->lock);
-
-	chp_mask = s390_process_res_acc_sch(res_data, sch);
-
-	if (chp_mask == 0) {
-		spin_unlock_irq(sch->lock);
-		put_device(&sch->dev);
-		return 0;
-	}
+	chp_mask = get_res_chpid_mask(&sch->ssd_info, res_data);
+	if (chp_mask == 0)
+		goto out;
+	if (stsch(sch->schid, &sch->schib))
+		goto out;
 	old_lpm = sch->lpm;
 	sch->lpm = ((sch->schib.pmcw.pim &
 		     sch->schib.pmcw.pam &
@@ -369,13 +280,12 @@
 		device_trigger_reprobe(sch);
 	else if (sch->driver && sch->driver->verify)
 		sch->driver->verify(&sch->dev);
-
+out:
 	spin_unlock_irq(sch->lock);
 	put_device(&sch->dev);
 	return 0;
 }
 
-
 static void s390_process_res_acc (struct res_acc_data *res_data)
 {
 	char dbf_txt[15];
@@ -661,29 +571,30 @@
 					 struct chp_id chpid, int on)
 {
 	int chp, old_lpm;
+	int mask;
 	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.id)
+		mask = 0x80 >> chp;
+		if (!(sch->ssd_info.path_mask & mask))
+			continue;
+		if (!chp_id_is_equal(&sch->ssd_info.chpid[chp], &chpid))
 			continue;
 
 		if (on) {
-			sch->opm |= (0x80 >> chp);
-			sch->lpm |= (0x80 >> chp);
+			sch->opm |= mask;
+			sch->lpm |= mask;
 			if (!old_lpm)
 				device_trigger_reprobe(sch);
 			else if (sch->driver && sch->driver->verify)
 				sch->driver->verify(&sch->dev);
 			break;
 		}
-		sch->opm &= ~(0x80 >> chp);
-		sch->lpm &= ~(0x80 >> chp);
-		if (check_for_io_on_path(sch, (0x80 >> chp))) {
+		sch->opm &= ~mask;
+		sch->lpm &= ~mask;
+		if (check_for_io_on_path(sch, mask)) {
 			if (device_is_online(sch))
 				/* Path verification is done after killing. */
 				device_kill_io(sch);