[S390] ap: toleration support for ap device type 10

Add toleration support for ap devices with device type 10.

Signed-off-by: Holger Dengler <hd@linux.vnet.ibm.com>
Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com>
diff --git a/drivers/s390/crypto/ap_bus.c b/drivers/s390/crypto/ap_bus.c
index 16e4a25..f8134a4 100644
--- a/drivers/s390/crypto/ap_bus.c
+++ b/drivers/s390/crypto/ap_bus.c
@@ -6,6 +6,7 @@
  *	      Martin Schwidefsky <schwidefsky@de.ibm.com>
  *	      Ralph Wuerthner <rwuerthn@de.ibm.com>
  *	      Felix Beck <felix.beck@de.ibm.com>
+ *	      Holger Dengler <hd@linux.vnet.ibm.com>
  *
  * Adjunct processor bus.
  *
@@ -222,29 +223,75 @@
 }
 #endif
 
-static inline struct ap_queue_status __ap_4096_commands_available(ap_qid_t qid,
-								  int *support)
+#ifdef CONFIG_64BIT
+static inline struct ap_queue_status
+__ap_query_functions(ap_qid_t qid, unsigned int *functions)
 {
 	register unsigned long reg0 asm ("0") = 0UL | qid | (1UL << 23);
-	register struct ap_queue_status reg1 asm ("1");
-	register unsigned long reg2 asm ("2") = 0UL;
+	register struct ap_queue_status reg1 asm ("1") = AP_QUEUE_STATUS_INVALID;
+	register unsigned long reg2 asm ("2");
 
 	asm volatile(
 		".long 0xb2af0000\n"
-		"0: la    %1,0\n"
-		"1:\n"
-		EX_TABLE(0b, 1b)
-		: "+d" (reg0), "=d" (reg1), "=d" (reg2)
+		"0:\n"
+		EX_TABLE(0b, 0b)
+		: "+d" (reg0), "+d" (reg1), "=d" (reg2)
 		:
 		: "cc");
 
-	if (reg2 & 0x6000000000000000ULL)
-		*support = 1;
-	else
-		*support = 0;
-
+	*functions = (unsigned int)(reg2 >> 32);
 	return reg1;
 }
+#endif
+
+/**
+ * ap_query_functions(): Query supported functions.
+ * @qid: The AP queue number
+ * @functions: Pointer to functions field.
+ *
+ * Returns
+ *   0	     on success.
+ *   -ENODEV  if queue not valid.
+ *   -EBUSY   if device busy.
+ *   -EINVAL  if query function is not supported
+ */
+static int ap_query_functions(ap_qid_t qid, unsigned int *functions)
+{
+#ifdef CONFIG_64BIT
+	struct ap_queue_status status;
+	int i;
+	status = __ap_query_functions(qid, functions);
+
+	for (i = 0; i < AP_MAX_RESET; i++) {
+		if (ap_queue_status_invalid_test(&status))
+			return -ENODEV;
+
+		switch (status.response_code) {
+		case AP_RESPONSE_NORMAL:
+			return 0;
+		case AP_RESPONSE_RESET_IN_PROGRESS:
+		case AP_RESPONSE_BUSY:
+			break;
+		case AP_RESPONSE_Q_NOT_AVAIL:
+		case AP_RESPONSE_DECONFIGURED:
+		case AP_RESPONSE_CHECKSTOPPED:
+		case AP_RESPONSE_INVALID_ADDRESS:
+			return -ENODEV;
+		case AP_RESPONSE_OTHERWISE_CHANGED:
+			break;
+		default:
+			break;
+		}
+		if (i < AP_MAX_RESET - 1) {
+			udelay(5);
+			status = __ap_query_functions(qid, functions);
+		}
+	}
+	return -EBUSY;
+#else
+	return -EINVAL;
+#endif
+}
 
 /**
  * ap_4096_commands_availablen(): Check for availability of 4096 bit RSA
@@ -255,33 +302,13 @@
  */
 int ap_4096_commands_available(ap_qid_t qid)
 {
-	struct ap_queue_status status;
-	int i, support = 0;
-	status = __ap_4096_commands_available(qid, &support);
+	unsigned int functions;
 
-	for (i = 0; i < AP_MAX_RESET; i++) {
-		switch (status.response_code) {
-		case AP_RESPONSE_NORMAL:
-			return support;
-		case AP_RESPONSE_RESET_IN_PROGRESS:
-		case AP_RESPONSE_BUSY:
-			break;
-		case AP_RESPONSE_Q_NOT_AVAIL:
-		case AP_RESPONSE_DECONFIGURED:
-		case AP_RESPONSE_CHECKSTOPPED:
-		case AP_RESPONSE_INVALID_ADDRESS:
-			return 0;
-		case AP_RESPONSE_OTHERWISE_CHANGED:
-			break;
-		default:
-			break;
-		}
-		if (i < AP_MAX_RESET - 1) {
-			udelay(5);
-			status = __ap_4096_commands_available(qid, &support);
-		}
-	}
-	return support;
+	if (ap_query_functions(qid, &functions))
+		return 0;
+
+	return test_ap_facility(functions, 1) &&
+	       test_ap_facility(functions, 2);
 }
 EXPORT_SYMBOL(ap_4096_commands_available);
 
@@ -1135,6 +1162,7 @@
 	struct device *dev;
 	ap_qid_t qid;
 	int queue_depth, device_type;
+	unsigned int device_functions;
 	int rc, i;
 
 	if (ap_select_domain() != 0)
@@ -1183,14 +1211,30 @@
 		INIT_LIST_HEAD(&ap_dev->list);
 		setup_timer(&ap_dev->timeout, ap_request_timeout,
 			    (unsigned long) ap_dev);
-		if (device_type == 0) {
+		switch (device_type) {
+		case 0:
 			if (ap_probe_device_type(ap_dev)) {
 				kfree(ap_dev);
 				continue;
 			}
-		}
-		else
+			break;
+		case 10:
+			if (ap_query_functions(qid, &device_functions)) {
+				kfree(ap_dev);
+				continue;
+			}
+			if (test_ap_facility(device_functions, 3))
+				ap_dev->device_type = AP_DEVICE_TYPE_CEX3C;
+			else if (test_ap_facility(device_functions, 4))
+				ap_dev->device_type = AP_DEVICE_TYPE_CEX3A;
+			else {
+				kfree(ap_dev);
+				continue;
+			}
+			break;
+		default:
 			ap_dev->device_type = device_type;
+		}
 
 		ap_dev->device.bus = &ap_bus_type;
 		ap_dev->device.parent = ap_root_device;
diff --git a/drivers/s390/crypto/ap_bus.h b/drivers/s390/crypto/ap_bus.h
index 08b9738..d960a63 100644
--- a/drivers/s390/crypto/ap_bus.h
+++ b/drivers/s390/crypto/ap_bus.h
@@ -6,6 +6,7 @@
  *	      Martin Schwidefsky <schwidefsky@de.ibm.com>
  *	      Ralph Wuerthner <rwuerthn@de.ibm.com>
  *	      Felix Beck <felix.beck@de.ibm.com>
+ *	      Holger Dengler <hd@linux.vnet.ibm.com>
  *
  * Adjunct processor bus header file.
  *
@@ -72,7 +73,26 @@
 	unsigned int int_enabled	: 1;
 	unsigned int response_code	: 8;
 	unsigned int pad2		: 16;
-};
+} __packed;
+
+#define AP_QUEUE_STATUS_INVALID \
+		{ 1, 1, 1, 0xF, 1, 0xFF, 0xFFFF }
+
+static inline
+int ap_queue_status_invalid_test(struct ap_queue_status *status)
+{
+	struct ap_queue_status invalid = AP_QUEUE_STATUS_INVALID;
+	return !(memcmp(status, &invalid, sizeof(struct ap_queue_status)));
+}
+
+#define MAX_AP_FACILITY 31
+
+static inline int test_ap_facility(unsigned int function, unsigned int nr)
+{
+	if (nr > MAX_AP_FACILITY)
+		return 0;
+	return function & (unsigned int)(0x80000000 >> nr);
+}
 
 #define AP_RESPONSE_NORMAL		0x00
 #define AP_RESPONSE_Q_NOT_AVAIL		0x01