summaryrefslogtreecommitdiff
path: root/drivers/s390/crypto/zcrypt_pcixcc.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/s390/crypto/zcrypt_pcixcc.c')
-rw-r--r--drivers/s390/crypto/zcrypt_pcixcc.c263
1 files changed, 250 insertions, 13 deletions
diff --git a/drivers/s390/crypto/zcrypt_pcixcc.c b/drivers/s390/crypto/zcrypt_pcixcc.c
index 6064cf58be43..2da8b9381407 100644
--- a/drivers/s390/crypto/zcrypt_pcixcc.c
+++ b/drivers/s390/crypto/zcrypt_pcixcc.c
@@ -1,7 +1,7 @@
/*
* linux/drivers/s390/crypto/zcrypt_pcixcc.c
*
- * zcrypt 2.0.0
+ * zcrypt 2.1.0
*
* Copyright (C) 2001, 2006 IBM Corporation
* Author(s): Robert Burroughs
@@ -60,6 +60,15 @@
#define PCIXCC_CLEANUP_TIME (15*HZ)
+#define CEIL4(x) ((((x)+3)/4)*4)
+
+struct response_type {
+ struct completion work;
+ int type;
+};
+#define PCIXCC_RESPONSE_TYPE_ICA 0
+#define PCIXCC_RESPONSE_TYPE_XCRB 1
+
static struct ap_device_id zcrypt_pcixcc_ids[] = {
{ AP_DEVICE(AP_DEVICE_TYPE_PCIXCC) },
{ AP_DEVICE(AP_DEVICE_TYPE_CEX2C) },
@@ -244,6 +253,108 @@ static int ICACRT_msg_to_type6CRT_msgX(struct zcrypt_device *zdev,
}
/**
+ * Convert a XCRB message to a type6 CPRB message.
+ *
+ * @zdev: crypto device pointer
+ * @ap_msg: pointer to AP message
+ * @xcRB: pointer to user input data
+ *
+ * Returns 0 on success or -EFAULT.
+ */
+struct type86_fmt2_msg {
+ struct type86_hdr hdr;
+ struct type86_fmt2_ext fmt2;
+} __attribute__((packed));
+
+static int XCRB_msg_to_type6CPRB_msgX(struct zcrypt_device *zdev,
+ struct ap_message *ap_msg,
+ struct ica_xcRB *xcRB)
+{
+ static struct type6_hdr static_type6_hdrX = {
+ .type = 0x06,
+ .offset1 = 0x00000058,
+ };
+ struct {
+ struct type6_hdr hdr;
+ struct ica_CPRBX cprbx;
+ } __attribute__((packed)) *msg = ap_msg->message;
+
+ int rcblen = CEIL4(xcRB->request_control_blk_length);
+ int replylen;
+ char *req_data = ap_msg->message + sizeof(struct type6_hdr) + rcblen;
+ char *function_code;
+
+ /* length checks */
+ ap_msg->length = sizeof(struct type6_hdr) +
+ CEIL4(xcRB->request_control_blk_length) +
+ xcRB->request_data_length;
+ if (ap_msg->length > PCIXCC_MAX_XCRB_MESSAGE_SIZE) {
+ PRINTK("Combined message is too large (%ld/%d/%d).\n",
+ sizeof(struct type6_hdr),
+ xcRB->request_control_blk_length,
+ xcRB->request_data_length);
+ return -EFAULT;
+ }
+ if (CEIL4(xcRB->reply_control_blk_length) >
+ PCIXCC_MAX_XCRB_REPLY_SIZE) {
+ PDEBUG("Reply CPRB length is too large (%d).\n",
+ xcRB->request_control_blk_length);
+ return -EFAULT;
+ }
+ if (CEIL4(xcRB->reply_data_length) > PCIXCC_MAX_XCRB_DATA_SIZE) {
+ PDEBUG("Reply data block length is too large (%d).\n",
+ xcRB->reply_data_length);
+ return -EFAULT;
+ }
+ replylen = CEIL4(xcRB->reply_control_blk_length) +
+ CEIL4(xcRB->reply_data_length) +
+ sizeof(struct type86_fmt2_msg);
+ if (replylen > PCIXCC_MAX_XCRB_RESPONSE_SIZE) {
+ PDEBUG("Reply CPRB + data block > PCIXCC_MAX_XCRB_RESPONSE_SIZE"
+ " (%d/%d/%d).\n",
+ sizeof(struct type86_fmt2_msg),
+ xcRB->reply_control_blk_length,
+ xcRB->reply_data_length);
+ xcRB->reply_control_blk_length = PCIXCC_MAX_XCRB_RESPONSE_SIZE -
+ (sizeof(struct type86_fmt2_msg) +
+ CEIL4(xcRB->reply_data_length));
+ PDEBUG("Capping Reply CPRB length at %d\n",
+ xcRB->reply_control_blk_length);
+ }
+
+ /* prepare type6 header */
+ msg->hdr = static_type6_hdrX;
+ memcpy(msg->hdr.agent_id , &(xcRB->agent_ID), sizeof(xcRB->agent_ID));
+ msg->hdr.ToCardLen1 = xcRB->request_control_blk_length;
+ if (xcRB->request_data_length) {
+ msg->hdr.offset2 = msg->hdr.offset1 + rcblen;
+ msg->hdr.ToCardLen2 = xcRB->request_data_length;
+ }
+ msg->hdr.FromCardLen1 = xcRB->reply_control_blk_length;
+ msg->hdr.FromCardLen2 = xcRB->reply_data_length;
+
+ /* prepare CPRB */
+ if (copy_from_user(&(msg->cprbx), xcRB->request_control_blk_addr,
+ xcRB->request_control_blk_length))
+ return -EFAULT;
+ if (msg->cprbx.cprb_len + sizeof(msg->hdr.function_code) >
+ xcRB->request_control_blk_length) {
+ PDEBUG("cprb_len too large (%d/%d)\n", msg->cprbx.cprb_len,
+ xcRB->request_control_blk_length);
+ return -EFAULT;
+ }
+ function_code = ((unsigned char *)&msg->cprbx) + msg->cprbx.cprb_len;
+ memcpy(msg->hdr.function_code, function_code, sizeof(msg->hdr.function_code));
+
+ /* copy data block */
+ if (xcRB->request_data_length &&
+ copy_from_user(req_data, xcRB->request_data_address,
+ xcRB->request_data_length))
+ return -EFAULT;
+ return 0;
+}
+
+/**
* Copy results from a type 86 ICA reply message back to user space.
*
* @zdev: crypto device pointer
@@ -363,6 +474,37 @@ static int convert_type86_ica(struct zcrypt_device *zdev,
return 0;
}
+/**
+ * Copy results from a type 86 XCRB reply message back to user space.
+ *
+ * @zdev: crypto device pointer
+ * @reply: reply AP message.
+ * @xcRB: pointer to XCRB
+ *
+ * Returns 0 on success or -EINVAL, -EFAULT, -EAGAIN in case of an error.
+ */
+static int convert_type86_xcrb(struct zcrypt_device *zdev,
+ struct ap_message *reply,
+ struct ica_xcRB *xcRB)
+{
+ struct type86_fmt2_msg *msg = reply->message;
+ char *data = reply->message;
+
+ /* Copy CPRB to user */
+ if (copy_to_user(xcRB->reply_control_blk_addr,
+ data + msg->fmt2.offset1, msg->fmt2.count1))
+ return -EFAULT;
+ xcRB->reply_control_blk_length = msg->fmt2.count1;
+
+ /* Copy data buffer to user */
+ if (msg->fmt2.count2)
+ if (copy_to_user(xcRB->reply_data_addr,
+ data + msg->fmt2.offset2, msg->fmt2.count2))
+ return -EFAULT;
+ xcRB->reply_data_length = msg->fmt2.count2;
+ return 0;
+}
+
static int convert_response_ica(struct zcrypt_device *zdev,
struct ap_message *reply,
char __user *outputdata,
@@ -391,6 +533,36 @@ static int convert_response_ica(struct zcrypt_device *zdev,
}
}
+static int convert_response_xcrb(struct zcrypt_device *zdev,
+ struct ap_message *reply,
+ struct ica_xcRB *xcRB)
+{
+ struct type86x_reply *msg = reply->message;
+
+ /* Response type byte is the second byte in the response. */
+ switch (((unsigned char *) reply->message)[1]) {
+ case TYPE82_RSP_CODE:
+ case TYPE88_RSP_CODE:
+ xcRB->status = 0x0008044DL; /* HDD_InvalidParm */
+ return convert_error(zdev, reply);
+ case TYPE86_RSP_CODE:
+ if (msg->hdr.reply_code) {
+ memcpy(&(xcRB->status), msg->fmt2.apfs, sizeof(u32));
+ return convert_error(zdev, reply);
+ }
+ if (msg->cprbx.cprb_ver_id == 0x02)
+ return convert_type86_xcrb(zdev, reply, xcRB);
+ /* no break, incorrect cprb version is an unknown response */
+ default: /* Unknown response type, this should NEVER EVER happen */
+ PRINTK("Unrecognized Message Header: %08x%08x\n",
+ *(unsigned int *) reply->message,
+ *(unsigned int *) (reply->message+4));
+ xcRB->status = 0x0008044DL; /* HDD_InvalidParm */
+ zdev->online = 0;
+ return -EAGAIN; /* repeat the request on a different device. */
+ }
+}
+
/**
* This function is called from the AP bus code after a crypto request
* "msg" has finished with the reply message "reply".
@@ -407,6 +579,8 @@ static void zcrypt_pcixcc_receive(struct ap_device *ap_dev,
.type = TYPE82_RSP_CODE,
.reply_code = REP82_ERROR_MACHINE_FAILURE,
};
+ struct response_type *resp_type =
+ (struct response_type *) msg->private;
struct type86x_reply *t86r = reply->message;
int length;
@@ -415,12 +589,27 @@ static void zcrypt_pcixcc_receive(struct ap_device *ap_dev,
memcpy(msg->message, &error_reply, sizeof(error_reply));
else if (t86r->hdr.type == TYPE86_RSP_CODE &&
t86r->cprbx.cprb_ver_id == 0x02) {
- length = sizeof(struct type86x_reply) + t86r->length - 2;
- length = min(PCIXCC_MAX_ICA_RESPONSE_SIZE, length);
- memcpy(msg->message, reply->message, length);
+ switch (resp_type->type) {
+ case PCIXCC_RESPONSE_TYPE_ICA:
+ length = sizeof(struct type86x_reply)
+ + t86r->length - 2;
+ length = min(PCIXCC_MAX_ICA_RESPONSE_SIZE, length);
+ memcpy(msg->message, reply->message, length);
+ break;
+ case PCIXCC_RESPONSE_TYPE_XCRB:
+ length = t86r->fmt2.offset2 + t86r->fmt2.count2;
+ length = min(PCIXCC_MAX_XCRB_RESPONSE_SIZE, length);
+ memcpy(msg->message, reply->message, length);
+ break;
+ default:
+ PRINTK("Invalid internal response type: %i\n",
+ resp_type->type);
+ memcpy(msg->message, &error_reply,
+ sizeof error_reply);
+ }
} else
memcpy(msg->message, reply->message, sizeof error_reply);
- complete((struct completion *) msg->private);
+ complete(&(resp_type->work));
}
static atomic_t zcrypt_step = ATOMIC_INIT(0);
@@ -436,7 +625,9 @@ static long zcrypt_pcixcc_modexpo(struct zcrypt_device *zdev,
struct ica_rsa_modexpo *mex)
{
struct ap_message ap_msg;
- struct completion work;
+ struct response_type resp_type = {
+ .type = PCIXCC_RESPONSE_TYPE_ICA,
+ };
int rc;
ap_msg.message = (void *) get_zeroed_page(GFP_KERNEL);
@@ -444,14 +635,14 @@ static long zcrypt_pcixcc_modexpo(struct zcrypt_device *zdev,
return -ENOMEM;
ap_msg.psmid = (((unsigned long long) current->pid) << 32) +
atomic_inc_return(&zcrypt_step);
- ap_msg.private = &work;
+ ap_msg.private = &resp_type;
rc = ICAMEX_msg_to_type6MEX_msgX(zdev, &ap_msg, mex);
if (rc)
goto out_free;
- init_completion(&work);
+ init_completion(&resp_type.work);
ap_queue_message(zdev->ap_dev, &ap_msg);
rc = wait_for_completion_interruptible_timeout(
- &work, PCIXCC_CLEANUP_TIME);
+ &resp_type.work, PCIXCC_CLEANUP_TIME);
if (rc > 0)
rc = convert_response_ica(zdev, &ap_msg, mex->outputdata,
mex->outputdatalength);
@@ -478,7 +669,9 @@ static long zcrypt_pcixcc_modexpo_crt(struct zcrypt_device *zdev,
struct ica_rsa_modexpo_crt *crt)
{
struct ap_message ap_msg;
- struct completion work;
+ struct response_type resp_type = {
+ .type = PCIXCC_RESPONSE_TYPE_ICA,
+ };
int rc;
ap_msg.message = (void *) get_zeroed_page(GFP_KERNEL);
@@ -486,14 +679,14 @@ static long zcrypt_pcixcc_modexpo_crt(struct zcrypt_device *zdev,
return -ENOMEM;
ap_msg.psmid = (((unsigned long long) current->pid) << 32) +
atomic_inc_return(&zcrypt_step);
- ap_msg.private = &work;
+ ap_msg.private = &resp_type;
rc = ICACRT_msg_to_type6CRT_msgX(zdev, &ap_msg, crt);
if (rc)
goto out_free;
- init_completion(&work);
+ init_completion(&resp_type.work);
ap_queue_message(zdev->ap_dev, &ap_msg);
rc = wait_for_completion_interruptible_timeout(
- &work, PCIXCC_CLEANUP_TIME);
+ &resp_type.work, PCIXCC_CLEANUP_TIME);
if (rc > 0)
rc = convert_response_ica(zdev, &ap_msg, crt->outputdata,
crt->outputdatalength);
@@ -510,11 +703,55 @@ out_free:
}
/**
+ * The request distributor calls this function if it picked the PCIXCC/CEX2C
+ * device to handle a send_cprb request.
+ * @zdev: pointer to zcrypt_device structure that identifies the
+ * PCIXCC/CEX2C device to the request distributor
+ * @xcRB: pointer to the send_cprb request buffer
+ */
+long zcrypt_pcixcc_send_cprb(struct zcrypt_device *zdev, struct ica_xcRB *xcRB)
+{
+ struct ap_message ap_msg;
+ struct response_type resp_type = {
+ .type = PCIXCC_RESPONSE_TYPE_XCRB,
+ };
+ int rc;
+
+ ap_msg.message = (void *) kmalloc(PCIXCC_MAX_XCRB_MESSAGE_SIZE, GFP_KERNEL);
+ if (!ap_msg.message)
+ return -ENOMEM;
+ ap_msg.psmid = (((unsigned long long) current->pid) << 32) +
+ atomic_inc_return(&zcrypt_step);
+ ap_msg.private = &resp_type;
+ rc = XCRB_msg_to_type6CPRB_msgX(zdev, &ap_msg, xcRB);
+ if (rc)
+ goto out_free;
+ init_completion(&resp_type.work);
+ ap_queue_message(zdev->ap_dev, &ap_msg);
+ rc = wait_for_completion_interruptible_timeout(
+ &resp_type.work, PCIXCC_CLEANUP_TIME);
+ if (rc > 0)
+ rc = convert_response_xcrb(zdev, &ap_msg, xcRB);
+ else {
+ /* Signal pending or message timed out. */
+ ap_cancel_message(zdev->ap_dev, &ap_msg);
+ if (rc == 0)
+ /* Message timed out. */
+ rc = -ETIME;
+ }
+out_free:
+ memset(ap_msg.message, 0x0, ap_msg.length);
+ kfree(ap_msg.message);
+ return rc;
+}
+
+/**
* The crypto operations for a PCIXCC/CEX2C card.
*/
static struct zcrypt_ops zcrypt_pcixcc_ops = {
.rsa_modexpo = zcrypt_pcixcc_modexpo,
.rsa_modexpo_crt = zcrypt_pcixcc_modexpo_crt,
+ .send_cprb = zcrypt_pcixcc_send_cprb,
};
/**