From: Peter Tiedemann <ptiedem@de.ibm.com>,
      Frank Pavlic <pavlic@de.ibm.com>

cio/qeth changes:
 - Add new api to common i/o layer for qeth 1920 device support.
 - Add 1920 device support to qeth.

Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/s390/cio/chsc.c       |  109 +++++++++++++++++++++++++++++++++-
 25-akpm/drivers/s390/cio/chsc.h       |   14 ++++
 25-akpm/drivers/s390/cio/device_ops.c |   11 +++
 25-akpm/drivers/s390/net/qeth.h       |    2 
 25-akpm/drivers/s390/net/qeth_main.c  |   48 +++++++++++++-
 25-akpm/drivers/s390/net/qeth_sys.c   |   14 +++-
 25-akpm/include/asm-s390/ccwdev.h     |    1 
 7 files changed, 189 insertions(+), 10 deletions(-)

diff -puN drivers/s390/cio/chsc.c~s390-qeth-1920-device-support drivers/s390/cio/chsc.c
--- 25/drivers/s390/cio/chsc.c~s390-qeth-1920-device-support	Thu Mar 24 15:29:02 2005
+++ 25-akpm/drivers/s390/cio/chsc.c	Thu Mar 24 15:29:02 2005
@@ -1,7 +1,7 @@
 /*
  *  drivers/s390/cio/chsc.c
  *   S/390 common I/O routines -- channel subsystem call
- *   $Revision: 1.118 $
+ *   $Revision: 1.119 $
  *
  *    Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
  *			      IBM Corporation
@@ -887,6 +887,27 @@ chp_status_write(struct device *dev, con
 
 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)
@@ -897,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.
@@ -921,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) {
@@ -928,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;
@@ -940,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)
 {
diff -puN drivers/s390/cio/chsc.h~s390-qeth-1920-device-support drivers/s390/cio/chsc.h
--- 25/drivers/s390/cio/chsc.h~s390-qeth-1920-device-support	Thu Mar 24 15:29:02 2005
+++ 25-akpm/drivers/s390/cio/chsc.h	Thu Mar 24 15:29:02 2005
@@ -12,9 +12,21 @@ struct chsc_header {
 	u16 code;
 };
 
+struct channel_path_desc {
+	u8 flags;
+	u8 lsn;
+	u8 desc;
+	u8 chpid;
+	u8 swla;
+	u8 zeroes;
+	u8 chla;
+	u8 chpp;
+};
+
 struct channel_path {
 	int id;
 	int state;
+	struct channel_path_desc desc;
 	struct device dev;
 };
 
@@ -49,4 +61,6 @@ extern struct css_chsc_char css_chsc_cha
 
 extern int chsc_determine_css_characteristics(void);
 extern int css_characteristics_avail;
+
+extern void *chsc_get_chp_desc(struct subchannel*, int);
 #endif
diff -puN drivers/s390/cio/device_ops.c~s390-qeth-1920-device-support drivers/s390/cio/device_ops.c
--- 25/drivers/s390/cio/device_ops.c~s390-qeth-1920-device-support	Thu Mar 24 15:29:02 2005
+++ 25-akpm/drivers/s390/cio/device_ops.c	Thu Mar 24 15:29:02 2005
@@ -24,6 +24,7 @@
 #include "cio.h"
 #include "cio_debug.h"
 #include "css.h"
+#include "chsc.h"
 #include "device.h"
 #include "qdio.h"
 
@@ -559,6 +560,15 @@ out_unlock:
 	return ret;
 }
 
+void *
+ccw_device_get_chp_desc(struct ccw_device *cdev, int chp_no)
+{
+	struct subchannel *sch;
+
+	sch = to_subchannel(cdev->dev.parent);
+	return chsc_get_chp_desc(sch, chp_no);
+}
+
 // FIXME: these have to go:
 
 int
@@ -589,4 +599,5 @@ EXPORT_SYMBOL(read_conf_data);
 EXPORT_SYMBOL(read_dev_chars);
 EXPORT_SYMBOL(_ccw_device_get_subchannel_number);
 EXPORT_SYMBOL(_ccw_device_get_device_number);
+EXPORT_SYMBOL_GPL(ccw_device_get_chp_desc);
 EXPORT_SYMBOL_GPL(read_conf_data_lpm);
diff -puN drivers/s390/net/qeth.h~s390-qeth-1920-device-support drivers/s390/net/qeth.h
--- 25/drivers/s390/net/qeth.h~s390-qeth-1920-device-support	Thu Mar 24 15:29:02 2005
+++ 25-akpm/drivers/s390/net/qeth.h	Thu Mar 24 15:29:02 2005
@@ -24,7 +24,7 @@
 
 #include "qeth_mpc.h"
 
-#define VERSION_QETH_H 		"$Revision: 1.132 $"
+#define VERSION_QETH_H 		"$Revision: 1.135 $"
 
 #ifdef CONFIG_QETH_IPV6
 #define QETH_VERSION_IPV6 	":IPv6"
diff -puN drivers/s390/net/qeth_main.c~s390-qeth-1920-device-support drivers/s390/net/qeth_main.c
--- 25/drivers/s390/net/qeth_main.c~s390-qeth-1920-device-support	Thu Mar 24 15:29:02 2005
+++ 25-akpm/drivers/s390/net/qeth_main.c	Thu Mar 24 15:29:02 2005
@@ -1071,6 +1071,35 @@ qeth_setup_card(struct qeth_card *card)
 }
 
 static int
+is_1920_device (struct qeth_card *card)
+{
+	int single_queue = 0;
+	struct ccw_device *ccwdev;
+	struct channelPath_dsc {
+		u8 flags;
+		u8 lsn;
+		u8 desc;
+		u8 chpid;
+		u8 swla;
+		u8 zeroes;
+		u8 chla;
+		u8 chpp;
+	} *chp_dsc;
+
+	QETH_DBF_TEXT(setup, 2, "chk_1920");
+
+	ccwdev = card->data.ccwdev;
+	chp_dsc = (struct channelPath_dsc *)ccw_device_get_chp_desc(ccwdev, 0);
+	if (chp_dsc != NULL) {
+		/* CHPP field bit 6 == 1 -> single queue */
+		single_queue = ((chp_dsc->chpp & 0x02) == 0x02);
+		kfree(chp_dsc);
+	}
+	QETH_DBF_TEXT_(setup, 2, "rc:%x", single_queue);
+	return single_queue;
+}
+
+static int
 qeth_determine_card_type(struct qeth_card *card)
 {
 	int i = 0;
@@ -1081,7 +1110,14 @@ qeth_determine_card_type(struct qeth_car
 		if ((CARD_RDEV(card)->id.dev_type == known_devices[i][2]) &&
 		    (CARD_RDEV(card)->id.dev_model == known_devices[i][3])) {
 			card->info.type = known_devices[i][4];
-			card->qdio.no_out_queues = known_devices[i][8];
+			if (is_1920_device(card)) {
+				PRINT_INFO("Priority Queueing not able "
+					   "due to hardware limitations!\n");
+				card->qdio.no_out_queues = 1;
+				card->qdio.default_out_queue = 0;
+			} else {
+				card->qdio.no_out_queues = known_devices[i][8];
+			}
 			card->info.is_multicast_different = known_devices[i][9];
 			return 0;
 		}
@@ -1112,6 +1148,10 @@ qeth_probe_device(struct ccwgroup_device
 		QETH_DBF_TEXT_(setup, 2, "1err%d", -ENOMEM);
 		return -ENOMEM;
 	}
+	card->read.ccwdev  = gdev->cdev[0];
+	card->write.ccwdev = gdev->cdev[1];
+	card->data.ccwdev  = gdev->cdev[2];
+
 	if ((rc = qeth_setup_card(card))){
 		QETH_DBF_TEXT_(setup, 2, "2err%d", rc);
 		put_device(dev);
@@ -1130,9 +1170,6 @@ qeth_probe_device(struct ccwgroup_device
 		qeth_free_card(card);
 		return rc;
 	}
-	card->read.ccwdev  = gdev->cdev[0];
-	card->write.ccwdev = gdev->cdev[1];
-	card->data.ccwdev  = gdev->cdev[2];
 	if ((rc = qeth_determine_card_type(card))){
 		PRINT_WARN("%s: not a valid card type\n", __func__);
 		QETH_DBF_TEXT_(setup, 2, "3err%d", rc);
@@ -3642,8 +3679,9 @@ qeth_get_priority_queue(struct qeth_card
 			/* TODO: IPv6!!! */
 		}
 		return card->qdio.default_out_queue;
+	case 1: /* fallthrough for single-out-queue 1920-device */
 	default:
-		return 0;
+		return card->qdio.default_out_queue;
 	}
 }
 
diff -puN drivers/s390/net/qeth_sys.c~s390-qeth-1920-device-support drivers/s390/net/qeth_sys.c
--- 25/drivers/s390/net/qeth_sys.c~s390-qeth-1920-device-support	Thu Mar 24 15:29:02 2005
+++ 25-akpm/drivers/s390/net/qeth_sys.c	Thu Mar 24 15:29:02 2005
@@ -1,6 +1,6 @@
 /*
  *
- * linux/drivers/s390/net/qeth_sys.c ($Revision: 1.49 $)
+ * linux/drivers/s390/net/qeth_sys.c ($Revision: 1.51 $)
  *
  * Linux on zSeries OSA Express and HiperSockets support
  * This file contains code related to sysfs.
@@ -20,7 +20,7 @@
 #include "qeth_mpc.h"
 #include "qeth_fs.h"
 
-const char *VERSION_QETH_SYS_C = "$Revision: 1.49 $";
+const char *VERSION_QETH_SYS_C = "$Revision: 1.51 $";
 
 /*****************************************************************************/
 /*                                                                           */
@@ -249,6 +249,16 @@ qeth_dev_prioqing_store(struct device *d
 	    (card->state != CARD_STATE_RECOVER))
 		return -EPERM;
 
+	/* check if 1920 devices are supported ,
+	 * if though we have to permit priority queueing
+	 */
+	if (card->qdio.no_out_queues == 1) {
+		PRINT_WARN("Priority queueing disabled due "
+			   "to hardware limitations!\n");
+		card->qdio.do_prio_queueing = QETH_PRIOQ_DEFAULT;
+		return -EPERM;
+	}
+
 	tmp = strsep((char **) &buf, "\n");
 	if (!strcmp(tmp, "prio_queueing_prec"))
 		card->qdio.do_prio_queueing = QETH_PRIO_Q_ING_PREC;
diff -puN include/asm-s390/ccwdev.h~s390-qeth-1920-device-support include/asm-s390/ccwdev.h
--- 25/include/asm-s390/ccwdev.h~s390-qeth-1920-device-support	Thu Mar 24 15:29:02 2005
+++ 25-akpm/include/asm-s390/ccwdev.h	Thu Mar 24 15:29:02 2005
@@ -188,4 +188,5 @@ extern int _ccw_device_get_subchannel_nu
 extern struct device *s390_root_dev_register(const char *);
 extern void s390_root_dev_unregister(struct device *);
 
+extern void *ccw_device_get_chp_desc(struct ccw_device *, int);
 #endif /* _S390_CCWDEV_H_ */
_