diff mbox

[V3,1/3] drivers/char/tpm: Add new device driver to support IBM vTPM

Message ID 1345670263.25124.7.camel@footlong (mailing list archive)
State Not Applicable
Delegated to: Benjamin Herrenschmidt
Headers show

Commit Message

Ashley Lai Aug. 22, 2012, 9:17 p.m. UTC
This patch adds a new device driver to support IBM virtual TPM
(vTPM) for PPC64.  IBM vTPM is supported through the adjunct
partition with firmware release 740 or higher.  With vTPM
support, each lpar is able to have its own vTPM without the
physical TPM hardware.

This driver provides TPM functionalities by communicating with
the vTPM adjunct partition through Hypervisor calls (Hcalls)
and Command/Response Queue (CRQ) commands.

Signed-off-by: Ashley Lai <adlai@us.ibm.com>
---

Oopps... I forgot to remove some invalid comments.  Please use this patch for 1/3.

 drivers/char/tpm/Kconfig       |    8 +
 drivers/char/tpm/Makefile      |    1 +
 drivers/char/tpm/tpm.h         |    1 +
 drivers/char/tpm/tpm_ibmvtpm.c |  749 ++++++++++++++++++++++++++++++++++++++++
 drivers/char/tpm/tpm_ibmvtpm.h |   77 ++++
 5 files changed, 836 insertions(+), 0 deletions(-)
 create mode 100644 drivers/char/tpm/tpm_ibmvtpm.c
 create mode 100644 drivers/char/tpm/tpm_ibmvtpm.h

Comments

Kent Yoder Aug. 22, 2012, 9:42 p.m. UTC | #1
On Wed, Aug 22, 2012 at 04:17:43PM -0500, Ashley Lai wrote:
> This patch adds a new device driver to support IBM virtual TPM
> (vTPM) for PPC64.  IBM vTPM is supported through the adjunct
> partition with firmware release 740 or higher.  With vTPM
> support, each lpar is able to have its own vTPM without the
> physical TPM hardware.
> 
> This driver provides TPM functionalities by communicating with
> the vTPM adjunct partition through Hypervisor calls (Hcalls)
> and Command/Response Queue (CRQ) commands.

 Thanks Ashley, I'll include this in my next pull request to James.

Kent

> Signed-off-by: Ashley Lai <adlai@us.ibm.com>
> ---
> 
> Oopps... I forgot to remove some invalid comments.  Please use this patch for 1/3.
> 
>  drivers/char/tpm/Kconfig       |    8 +
>  drivers/char/tpm/Makefile      |    1 +
>  drivers/char/tpm/tpm.h         |    1 +
>  drivers/char/tpm/tpm_ibmvtpm.c |  749 ++++++++++++++++++++++++++++++++++++++++
>  drivers/char/tpm/tpm_ibmvtpm.h |   77 ++++
>  5 files changed, 836 insertions(+), 0 deletions(-)
>  create mode 100644 drivers/char/tpm/tpm_ibmvtpm.c
>  create mode 100644 drivers/char/tpm/tpm_ibmvtpm.h
> 
> diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
> index c4aac48..915875e 100644
> --- a/drivers/char/tpm/Kconfig
> +++ b/drivers/char/tpm/Kconfig
> @@ -73,4 +73,12 @@ config TCG_INFINEON
>  	  Further information on this driver and the supported hardware
>  	  can be found at http://www.trust.rub.de/projects/linux-device-driver-infineon-tpm/ 
> 
> +config TCG_IBMVTPM
> +	tristate "IBM VTPM Interface"
> +	depends on PPC64
> +	---help---
> +	  If you have IBM virtual TPM (VTPM) support say Yes and it
> +	  will be accessible from within Linux.  To compile this driver
> +	  as a module, choose M here; the module will be called tpm_ibmvtpm.
> +
>  endif # TCG_TPM
> diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
> index beac52f..547509d 100644
> --- a/drivers/char/tpm/Makefile
> +++ b/drivers/char/tpm/Makefile
> @@ -11,3 +11,4 @@ obj-$(CONFIG_TCG_TIS_I2C_INFINEON) += tpm_i2c_infineon.o
>  obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
>  obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
>  obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o
> +obj-$(CONFIG_TCG_IBMVTPM) += tpm_ibmvtpm.o
> diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
> index 645136e..870fde7 100644
> --- a/drivers/char/tpm/tpm.h
> +++ b/drivers/char/tpm/tpm.h
> @@ -100,6 +100,7 @@ struct tpm_vendor_specific {
>  	bool timeout_adjusted;
>  	unsigned long duration[3]; /* jiffies */
>  	bool duration_adjusted;
> +	void *data;
> 
>  	wait_queue_head_t read_queue;
>  	wait_queue_head_t int_queue;
> diff --git a/drivers/char/tpm/tpm_ibmvtpm.c b/drivers/char/tpm/tpm_ibmvtpm.c
> new file mode 100644
> index 0000000..efc4ab3
> --- /dev/null
> +++ b/drivers/char/tpm/tpm_ibmvtpm.c
> @@ -0,0 +1,749 @@
> +/*
> + * Copyright (C) 2012 IBM Corporation
> + *
> + * Author: Ashley Lai <adlai@us.ibm.com>
> + *
> + * Maintained by: <tpmdd-devel@lists.sourceforge.net>
> + *
> + * Device driver for TCG/TCPA TPM (trusted platform module).
> + * Specifications at www.trustedcomputinggroup.org
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.
> + *
> + */
> +
> +#include <linux/dma-mapping.h>
> +#include <linux/dmapool.h>
> +#include <linux/slab.h>
> +#include <asm/vio.h>
> +#include <asm/irq.h>
> +#include <linux/types.h>
> +#include <linux/list.h>
> +#include <linux/spinlock.h>
> +#include <linux/interrupt.h>
> +#include <linux/wait.h>
> +#include <asm/prom.h>
> +
> +#include "tpm.h"
> +#include "tpm_ibmvtpm.h"
> +
> +static const char tpm_ibmvtpm_driver_name[] = "tpm_ibmvtpm";
> +
> +static struct vio_device_id tpm_ibmvtpm_device_table[] __devinitdata = {
> +	{ "IBM,vtpm", "IBM,vtpm"},
> +	{ "", "" }
> +};
> +MODULE_DEVICE_TABLE(vio, tpm_ibmvtpm_device_table);
> +
> +DECLARE_WAIT_QUEUE_HEAD(wq);
> +
> +/**
> + * ibmvtpm_send_crq - Send a CRQ request
> + * @vdev:	vio device struct
> + * @w1:		first word
> + * @w2:		second word
> + *
> + * Return value:
> + *	0 -Sucess
> + *	Non-zero - Failure
> + */
> +static int ibmvtpm_send_crq(struct vio_dev *vdev, u64 w1, u64 w2)
> +{
> +	return plpar_hcall_norets(H_SEND_CRQ, vdev->unit_address, w1, w2);
> +}
> +
> +/**
> + * ibmvtpm_get_data - Retrieve ibm vtpm data
> + * @dev:	device struct
> + *
> + * Return value:
> + *	vtpm device struct
> + */
> +static struct ibmvtpm_dev *ibmvtpm_get_data(const struct device *dev)
> +{
> +	struct tpm_chip *chip = dev_get_drvdata(dev);
> +	if (chip)
> +		return (struct ibmvtpm_dev *)chip->vendor.data;
> +	return NULL;
> +}
> +
> +/**
> + * tpm_ibmvtpm_recv - Receive data after send
> + * @chip:	tpm chip struct
> + * @buf:	buffer to read
> + * count:	size of buffer
> + *
> + * Return value:
> + *	Number of bytes read
> + */
> +static int tpm_ibmvtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count)
> +{
> +	struct ibmvtpm_dev *ibmvtpm;
> +	u16 len;
> +
> +	ibmvtpm = (struct ibmvtpm_dev *)chip->vendor.data;
> +
> +	if (!ibmvtpm->rtce_buf) {
> +		dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n");
> +		return 0;
> +	}
> +
> +	wait_event_interruptible(wq, ibmvtpm->crq_res.len != 0);
> +
> +	if (count < ibmvtpm->crq_res.len) {
> +		dev_err(ibmvtpm->dev,
> +			"Invalid size in recv: count=%ld, crq_size=%d\n",
> +			count, ibmvtpm->crq_res.len);
> +		return -EIO;
> +	}
> +
> +	spin_lock(&ibmvtpm->rtce_lock);
> +	memcpy((void *)buf, (void *)ibmvtpm->rtce_buf, ibmvtpm->crq_res.len);
> +	memset(ibmvtpm->rtce_buf, 0, ibmvtpm->crq_res.len);
> +	ibmvtpm->crq_res.valid = 0;
> +	ibmvtpm->crq_res.msg = 0;
> +	len = ibmvtpm->crq_res.len;
> +	ibmvtpm->crq_res.len = 0;
> +	spin_unlock(&ibmvtpm->rtce_lock);
> +	return len;
> +}
> +
> +/**
> + * tpm_ibmvtpm_send - Send tpm request
> + * @chip:	tpm chip struct
> + * @buf:	buffer contains data to send
> + * count:	size of buffer
> + *
> + * Return value:
> + *	Number of bytes sent
> + */
> +static int tpm_ibmvtpm_send(struct tpm_chip *chip, u8 *buf, size_t count)
> +{
> +	struct ibmvtpm_dev *ibmvtpm;
> +	struct ibmvtpm_crq crq;
> +	u64 *word = (u64 *) &crq;
> +	int rc;
> +
> +	ibmvtpm = (struct ibmvtpm_dev *)chip->vendor.data;
> +
> +	if (!ibmvtpm->rtce_buf) {
> +		dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n");
> +		return 0;
> +	}
> +
> +	if (count > ibmvtpm->rtce_size) {
> +		dev_err(ibmvtpm->dev,
> +			"Invalid size in send: count=%ld, rtce_size=%d\n",
> +			count, ibmvtpm->rtce_size);
> +		return -EIO;
> +	}
> +
> +	spin_lock(&ibmvtpm->rtce_lock);
> +	memcpy((void *)ibmvtpm->rtce_buf, (void *)buf, count);
> +	crq.valid = (u8)IBMVTPM_VALID_CMD;
> +	crq.msg = (u8)VTPM_TPM_COMMAND;
> +	crq.len = (u16)count;
> +	crq.data = ibmvtpm->rtce_dma_handle;
> +
> +	rc = ibmvtpm_send_crq(ibmvtpm->vdev, word[0], word[1]);
> +	if (rc != H_SUCCESS) {
> +		dev_err(ibmvtpm->dev, "tpm_ibmvtpm_send failed rc=%d\n", rc);
> +		rc = 0;
> +	} else
> +		rc = count;
> +
> +	spin_unlock(&ibmvtpm->rtce_lock);
> +	return rc;
> +}
> +
> +static void tpm_ibmvtpm_cancel(struct tpm_chip *chip)
> +{
> +	return;
> +}
> +
> +static u8 tpm_ibmvtpm_status(struct tpm_chip *chip)
> +{
> +	return 0;
> +}
> +
> +/**
> + * ibmvtpm_crq_get_rtce_size - Send a CRQ request to get rtce size
> + * @ibmvtpm:	vtpm device struct
> + *
> + * Return value:
> + *	0 - Success
> + *	Non-zero - Failure
> + */
> +static int ibmvtpm_crq_get_rtce_size(struct ibmvtpm_dev *ibmvtpm)
> +{
> +	struct ibmvtpm_crq crq;
> +	u64 *buf = (u64 *) &crq;
> +	int rc;
> +
> +	crq.valid = (u8)IBMVTPM_VALID_CMD;
> +	crq.msg = (u8)VTPM_GET_RTCE_BUFFER_SIZE;
> +
> +	rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
> +	if (rc != H_SUCCESS)
> +		dev_err(ibmvtpm->dev,
> +			"ibmvtpm_crq_get_rtce_size failed rc=%d\n", rc);
> +
> +	return rc;
> +}
> +
> +/**
> + * ibmvtpm_crq_get_version - Send a CRQ request to get vtpm version
> + *			   - Note that this is vtpm version and not tpm version
> + * @ibmvtpm:	vtpm device struct
> + *
> + * Return value:
> + *	0 - Success
> + *	Non-zero - Failure
> + */
> +static int ibmvtpm_crq_get_version(struct ibmvtpm_dev *ibmvtpm)
> +{
> +	struct ibmvtpm_crq crq;
> +	u64 *buf = (u64 *) &crq;
> +	int rc;
> +
> +	crq.valid = (u8)IBMVTPM_VALID_CMD;
> +	crq.msg = (u8)VTPM_GET_VERSION;
> +
> +	rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
> +	if (rc != H_SUCCESS)
> +		dev_err(ibmvtpm->dev,
> +			"ibmvtpm_crq_get_version failed rc=%d\n", rc);
> +
> +	return rc;
> +}
> +
> +/**
> + * ibmvtpm_crq_send_init_complete - Send a CRQ initialize complete message
> + * @ibmvtpm:	vtpm device struct
> + *
> + * Return value:
> + *	0 - Success
> + *	Non-zero - Failure
> + */
> +static int ibmvtpm_crq_send_init_complete(struct ibmvtpm_dev *ibmvtpm)
> +{
> +	int rc;
> +
> +	rc = ibmvtpm_send_crq(ibmvtpm->vdev, INIT_CRQ_COMP_CMD, 0);
> +	if (rc != H_SUCCESS)
> +		dev_err(ibmvtpm->dev,
> +			"ibmvtpm_crq_send_init_complete failed rc=%d\n", rc);
> +
> +	return rc;
> +}
> +
> +/**
> + * ibmvtpm_crq_send_init - Send a CRQ initialize message
> + * @ibmvtpm:	vtpm device struct
> + *
> + * Return value:
> + *	0 - Success
> + *	Non-zero - Failure
> + */
> +static int ibmvtpm_crq_send_init(struct ibmvtpm_dev *ibmvtpm)
> +{
> +	int rc;
> +
> +	rc = ibmvtpm_send_crq(ibmvtpm->vdev, INIT_CRQ_CMD, 0);
> +	if (rc != H_SUCCESS)
> +		dev_err(ibmvtpm->dev,
> +			"ibmvtpm_crq_send_init failed rc=%d\n", rc);
> +
> +	return rc;
> +}
> +
> +/**
> + * tpm_ibmvtpm_remove - ibm vtpm remove entry point
> + * @vdev:	vio device struct
> + *
> + * Return value:
> + *	0
> + */
> +static int __devexit tpm_ibmvtpm_remove(struct vio_dev *vdev)
> +{
> +	struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(&vdev->dev);
> +	int rc = 0;
> +
> +	free_irq(vdev->irq, ibmvtpm);
> +	tasklet_kill(&ibmvtpm->tasklet);
> +
> +	do {
> +		if (rc)
> +			msleep(100);
> +		rc = plpar_hcall_norets(H_FREE_CRQ, vdev->unit_address);
> +	} while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
> +
> +	dma_unmap_single(ibmvtpm->dev, ibmvtpm->crq_dma_handle,
> +			 CRQ_RES_BUF_SIZE, DMA_BIDIRECTIONAL);
> +	free_page((unsigned long)ibmvtpm->crq_queue.crq_addr);
> +
> +	if (ibmvtpm->rtce_buf) {
> +		dma_unmap_single(ibmvtpm->dev, ibmvtpm->rtce_dma_handle,
> +				 ibmvtpm->rtce_size, DMA_BIDIRECTIONAL);
> +		kfree(ibmvtpm->rtce_buf);
> +	}
> +
> +	tpm_remove_hardware(ibmvtpm->dev);
> +
> +	kfree(ibmvtpm);
> +
> +	return 0;
> +}
> +
> +/**
> + * tpm_ibmvtpm_get_desired_dma - Get DMA size needed by this driver
> + * @vdev:	vio device struct
> + *
> + * Return value:
> + *	Number of bytes the driver needs to DMA map
> + */
> +static unsigned long tpm_ibmvtpm_get_desired_dma(struct vio_dev *vdev)
> +{
> +	struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(&vdev->dev);
> +	return CRQ_RES_BUF_SIZE + ibmvtpm->rtce_size;
> +}
> +
> +/**
> + * tpm_ibmvtpm_suspend - Suspend
> + * @dev:	device struct
> + *
> + * Return value:
> + *	0
> + */
> +static int tpm_ibmvtpm_suspend(struct device *dev)
> +{
> +	struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(dev);
> +	struct ibmvtpm_crq crq;
> +	u64 *buf = (u64 *) &crq;
> +	int rc = 0;
> +
> +	crq.valid = (u8)IBMVTPM_VALID_CMD;
> +	crq.msg = (u8)VTPM_PREPARE_TO_SUSPEND;
> +
> +	rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
> +	if (rc != H_SUCCESS)
> +		dev_err(ibmvtpm->dev,
> +			"tpm_ibmvtpm_suspend failed rc=%d\n", rc);
> +
> +	return rc;
> +}
> +
> +/**
> + * ibmvtpm_reset_crq - Reset CRQ
> + * @ibmvtpm:	ibm vtpm struct
> + *
> + * Return value:
> + *	0 - Success
> + *	Non-zero - Failure
> + */
> +static int ibmvtpm_reset_crq(struct ibmvtpm_dev *ibmvtpm)
> +{
> +	int rc = 0;
> +
> +	do {
> +		if (rc)
> +			msleep(100);
> +		rc = plpar_hcall_norets(H_FREE_CRQ,
> +					ibmvtpm->vdev->unit_address);
> +	} while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
> +
> +	memset(ibmvtpm->crq_queue.crq_addr, 0, CRQ_RES_BUF_SIZE);
> +	ibmvtpm->crq_queue.index = 0;
> +
> +	return plpar_hcall_norets(H_REG_CRQ, ibmvtpm->vdev->unit_address,
> +				  ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE);
> +}
> +
> +/**
> + * tpm_ibmvtpm_resume - Resume from suspend
> + * @dev:	device struct
> + *
> + * Return value:
> + *	0
> + */
> +static int tpm_ibmvtpm_resume(struct device *dev)
> +{
> +	struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(dev);
> +	unsigned long flags;
> +	int rc = 0;
> +
> +	do {
> +		if (rc)
> +			msleep(100);
> +		rc = plpar_hcall_norets(H_ENABLE_CRQ,
> +					ibmvtpm->vdev->unit_address);
> +	} while (rc == H_IN_PROGRESS || rc == H_BUSY || H_IS_LONG_BUSY(rc));
> +
> +	if (rc) {
> +		dev_err(dev, "Error enabling ibmvtpm rc=%d\n", rc);
> +		return rc;
> +	}
> +
> +	spin_lock_irqsave(&ibmvtpm->lock, flags);
> +	vio_disable_interrupts(ibmvtpm->vdev);
> +	tasklet_schedule(&ibmvtpm->tasklet);
> +	spin_unlock_irqrestore(&ibmvtpm->lock, flags);
> +
> +	rc = ibmvtpm_crq_send_init(ibmvtpm);
> +	if (rc)
> +		dev_err(dev, "Error send_init rc=%d\n", rc);
> +
> +	return rc;
> +}
> +
> +static const struct file_operations ibmvtpm_ops = {
> +	.owner = THIS_MODULE,
> +	.llseek = no_llseek,
> +	.open = tpm_open,
> +	.read = tpm_read,
> +	.write = tpm_write,
> +	.release = tpm_release,
> +};
> +
> +static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL);
> +static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL);
> +static DEVICE_ATTR(enabled, S_IRUGO, tpm_show_enabled, NULL);
> +static DEVICE_ATTR(active, S_IRUGO, tpm_show_active, NULL);
> +static DEVICE_ATTR(owned, S_IRUGO, tpm_show_owned, NULL);
> +static DEVICE_ATTR(temp_deactivated, S_IRUGO, tpm_show_temp_deactivated,
> +		   NULL);
> +static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps_1_2, NULL);
> +static DEVICE_ATTR(cancel, S_IWUSR | S_IWGRP, NULL, tpm_store_cancel);
> +static DEVICE_ATTR(durations, S_IRUGO, tpm_show_durations, NULL);
> +static DEVICE_ATTR(timeouts, S_IRUGO, tpm_show_timeouts, NULL);
> +
> +static struct attribute *ibmvtpm_attrs[] = {
> +	&dev_attr_pubek.attr,
> +	&dev_attr_pcrs.attr,
> +	&dev_attr_enabled.attr,
> +	&dev_attr_active.attr,
> +	&dev_attr_owned.attr,
> +	&dev_attr_temp_deactivated.attr,
> +	&dev_attr_caps.attr,
> +	&dev_attr_cancel.attr,
> +	&dev_attr_durations.attr,
> +	&dev_attr_timeouts.attr, NULL,
> +};
> +
> +static struct attribute_group ibmvtpm_attr_grp = { .attrs = ibmvtpm_attrs };
> +
> +static const struct tpm_vendor_specific tpm_ibmvtpm = {
> +	.recv = tpm_ibmvtpm_recv,
> +	.send = tpm_ibmvtpm_send,
> +	.cancel = tpm_ibmvtpm_cancel,
> +	.status = tpm_ibmvtpm_status,
> +	.req_complete_mask = 0,
> +	.req_complete_val = 0,
> +	.req_canceled = 0,
> +	.attr_group = &ibmvtpm_attr_grp,
> +	.miscdev = { .fops = &ibmvtpm_ops, },
> +};
> +
> +static const struct dev_pm_ops tpm_ibmvtpm_pm_ops = {
> +	.suspend = tpm_ibmvtpm_suspend,
> +	.resume = tpm_ibmvtpm_resume,
> +};
> +
> +/**
> + * ibmvtpm_crq_get_next - Get next responded crq
> + * @ibmvtpm	vtpm device struct
> + *
> + * Return value:
> + *	vtpm crq pointer
> + */
> +static struct ibmvtpm_crq *ibmvtpm_crq_get_next(struct ibmvtpm_dev *ibmvtpm)
> +{
> +	struct ibmvtpm_crq_queue *crq_q = &ibmvtpm->crq_queue;
> +	struct ibmvtpm_crq *crq = &crq_q->crq_addr[crq_q->index];
> +
> +	if (crq->valid & VTPM_MSG_RES) {
> +		if (++crq_q->index == crq_q->num_entry)
> +			crq_q->index = 0;
> +		rmb();
> +	} else
> +		crq = NULL;
> +	return crq;
> +}
> +
> +/**
> + * ibmvtpm_crq_process - Process responded crq
> + * @crq		crq to be processed
> + * @ibmvtpm	vtpm device struct
> + *
> + * Return value:
> + *	Nothing
> + */
> +static void ibmvtpm_crq_process(struct ibmvtpm_crq *crq,
> +				struct ibmvtpm_dev *ibmvtpm)
> +{
> +	int rc = 0;
> +
> +	switch (crq->valid) {
> +	case VALID_INIT_CRQ:
> +		switch (crq->msg) {
> +		case INIT_CRQ_RES:
> +			dev_info(ibmvtpm->dev, "CRQ initialized\n");
> +			rc = ibmvtpm_crq_send_init_complete(ibmvtpm);
> +			if (rc)
> +				dev_err(ibmvtpm->dev, "Unable to send CRQ init complete rc=%d\n", rc);
> +			return;
> +		case INIT_CRQ_COMP_RES:
> +			dev_info(ibmvtpm->dev,
> +				 "CRQ initialization completed\n");
> +			return;
> +		default:
> +			dev_err(ibmvtpm->dev, "Unknown crq message type: %d\n", crq->msg);
> +			return;
> +		}
> +		return;
> +	case IBMVTPM_VALID_CMD:
> +		switch (crq->msg) {
> +		case VTPM_GET_RTCE_BUFFER_SIZE_RES:
> +			if (crq->len <= 0) {
> +				dev_err(ibmvtpm->dev, "Invalid rtce size\n");
> +				return;
> +			}
> +			ibmvtpm->rtce_size = crq->len;
> +			ibmvtpm->rtce_buf = kmalloc(ibmvtpm->rtce_size,
> +						    GFP_KERNEL);
> +			if (!ibmvtpm->rtce_buf) {
> +				dev_err(ibmvtpm->dev, "Failed to allocate memory for rtce buffer\n");
> +				return;
> +			}
> +
> +			ibmvtpm->rtce_dma_handle = dma_map_single(ibmvtpm->dev,
> +				ibmvtpm->rtce_buf, ibmvtpm->rtce_size,
> +				DMA_BIDIRECTIONAL);
> +
> +			if (dma_mapping_error(ibmvtpm->dev,
> +					      ibmvtpm->rtce_dma_handle)) {
> +				kfree(ibmvtpm->rtce_buf);
> +				ibmvtpm->rtce_buf = NULL;
> +				dev_err(ibmvtpm->dev, "Failed to dma map rtce buffer\n");
> +			}
> +
> +			return;
> +		case VTPM_GET_VERSION_RES:
> +			ibmvtpm->vtpm_version = crq->data;
> +			return;
> +		case VTPM_TPM_COMMAND_RES:
> +			ibmvtpm->crq_res.valid = crq->valid;
> +			ibmvtpm->crq_res.msg = crq->msg;
> +			ibmvtpm->crq_res.len = crq->len;
> +			ibmvtpm->crq_res.data = crq->data;
> +			wake_up_interruptible(&wq);
> +			return;
> +		default:
> +			return;
> +		}
> +	}
> +	return;
> +}
> +
> +/**
> + * ibmvtpm_interrupt -	Interrupt handler
> + * @irq:		irq number to handle
> + * @vtpm_instance:	vtpm that received interrupt
> + *
> + * Returns:
> + *	IRQ_HANDLED
> + **/
> +static irqreturn_t ibmvtpm_interrupt(int irq, void *vtpm_instance)
> +{
> +	struct ibmvtpm_dev *ibmvtpm = (struct ibmvtpm_dev *) vtpm_instance;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&ibmvtpm->lock, flags);
> +	vio_disable_interrupts(ibmvtpm->vdev);
> +	tasklet_schedule(&ibmvtpm->tasklet);
> +	spin_unlock_irqrestore(&ibmvtpm->lock, flags);
> +
> +	return IRQ_HANDLED;
> +}
> +
> +/**
> + * ibmvtpm_tasklet - Interrupt handler tasklet
> + * @data:	ibm vtpm device struct
> + *
> + * Returns:
> + *	Nothing
> + **/
> +static void ibmvtpm_tasklet(void *data)
> +{
> +	struct ibmvtpm_dev *ibmvtpm = data;
> +	struct ibmvtpm_crq *crq;
> +	unsigned long flags;
> +
> +	spin_lock_irqsave(&ibmvtpm->lock, flags);
> +	while ((crq = ibmvtpm_crq_get_next(ibmvtpm)) != NULL) {
> +		ibmvtpm_crq_process(crq, ibmvtpm);
> +		crq->valid = 0;
> +		wmb();
> +	}
> +
> +	vio_enable_interrupts(ibmvtpm->vdev);
> +	spin_unlock_irqrestore(&ibmvtpm->lock, flags);
> +}
> +
> +/**
> + * tpm_ibmvtpm_probe - ibm vtpm initialize entry point
> + * @vio_dev:	vio device struct
> + * @id:		vio device id struct
> + *
> + * Return value:
> + *	0 - Success
> + *	Non-zero - Failure
> + */
> +static int __devinit tpm_ibmvtpm_probe(struct vio_dev *vio_dev,
> +				   const struct vio_device_id *id)
> +{
> +	struct ibmvtpm_dev *ibmvtpm;
> +	struct device *dev = &vio_dev->dev;
> +	struct ibmvtpm_crq_queue *crq_q;
> +	struct tpm_chip *chip;
> +	int rc = -ENOMEM, rc1;
> +
> +	chip = tpm_register_hardware(dev, &tpm_ibmvtpm);
> +	if (!chip) {
> +		dev_err(dev, "tpm_register_hardware failed\n");
> +		return -ENODEV;
> +	}
> +
> +	ibmvtpm = kzalloc(sizeof(struct ibmvtpm_dev), GFP_KERNEL);
> +	if (!ibmvtpm) {
> +		dev_err(dev, "kzalloc for ibmvtpm failed\n");
> +		goto cleanup;
> +	}
> +
> +	crq_q = &ibmvtpm->crq_queue;
> +	crq_q->crq_addr = (struct ibmvtpm_crq *)get_zeroed_page(GFP_KERNEL);
> +	if (!crq_q->crq_addr) {
> +		dev_err(dev, "Unable to allocate memory for crq_addr\n");
> +		goto cleanup;
> +	}
> +
> +	crq_q->num_entry = CRQ_RES_BUF_SIZE / sizeof(*crq_q->crq_addr);
> +	ibmvtpm->crq_dma_handle = dma_map_single(dev, crq_q->crq_addr,
> +						 CRQ_RES_BUF_SIZE,
> +						 DMA_BIDIRECTIONAL);
> +
> +	if (dma_mapping_error(dev, ibmvtpm->crq_dma_handle)) {
> +		dev_err(dev, "dma mapping failed\n");
> +		goto cleanup;
> +	}
> +
> +	rc = plpar_hcall_norets(H_REG_CRQ, vio_dev->unit_address,
> +				ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE);
> +	if (rc == H_RESOURCE)
> +		rc = ibmvtpm_reset_crq(ibmvtpm);
> +
> +	if (rc) {
> +		dev_err(dev, "Unable to register CRQ rc=%d\n", rc);
> +		goto reg_crq_cleanup;
> +	}
> +
> +	tasklet_init(&ibmvtpm->tasklet, (void *)ibmvtpm_tasklet,
> +		     (unsigned long)ibmvtpm);
> +
> +	rc = request_irq(vio_dev->irq, ibmvtpm_interrupt, 0,
> +			 tpm_ibmvtpm_driver_name, ibmvtpm);
> +	if (rc) {
> +		dev_err(dev, "Error %d register irq 0x%x\n", rc, vio_dev->irq);
> +		goto init_irq_cleanup;
> +	}
> +
> +	rc = vio_enable_interrupts(vio_dev);
> +	if (rc) {
> +		dev_err(dev, "Error %d enabling interrupts\n", rc);
> +		goto init_irq_cleanup;
> +	}
> +
> +	crq_q->index = 0;
> +
> +	ibmvtpm->dev = dev;
> +	ibmvtpm->vdev = vio_dev;
> +	chip->vendor.data = (void *)ibmvtpm;
> +
> +	spin_lock_init(&ibmvtpm->lock);
> +	spin_lock_init(&ibmvtpm->rtce_lock);
> +
> +	rc = ibmvtpm_crq_send_init(ibmvtpm);
> +	if (rc)
> +		goto init_irq_cleanup;
> +
> +	rc = ibmvtpm_crq_get_version(ibmvtpm);
> +	if (rc)
> +		goto init_irq_cleanup;
> +
> +	rc = ibmvtpm_crq_get_rtce_size(ibmvtpm);
> +	if (rc)
> +		goto init_irq_cleanup;
> +
> +	return rc;
> +init_irq_cleanup:
> +	tasklet_kill(&ibmvtpm->tasklet);
> +	do {
> +		rc1 = plpar_hcall_norets(H_FREE_CRQ, vio_dev->unit_address);
> +	} while (rc1 == H_BUSY || H_IS_LONG_BUSY(rc1));
> +reg_crq_cleanup:
> +	dma_unmap_single(dev, ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE,
> +			 DMA_BIDIRECTIONAL);
> +cleanup:
> +	if (ibmvtpm) {
> +		if (crq_q->crq_addr)
> +			free_page((unsigned long)crq_q->crq_addr);
> +		kfree(ibmvtpm);
> +	}
> +
> +	tpm_remove_hardware(dev);
> +
> +	return rc;
> +}
> +
> +static struct vio_driver ibmvtpm_driver = {
> +	.id_table	 = tpm_ibmvtpm_device_table,
> +	.probe		 = tpm_ibmvtpm_probe,
> +	.remove		 = tpm_ibmvtpm_remove,
> +	.get_desired_dma = tpm_ibmvtpm_get_desired_dma,
> +	.name		 = tpm_ibmvtpm_driver_name,
> +	.pm		 = &tpm_ibmvtpm_pm_ops,
> +};
> +
> +/**
> + * ibmvtpm_module_init - Initialize ibm vtpm module
> + *
> + * Return value:
> + *	0 -Success
> + *	Non-zero - Failure
> + */
> +static int __init ibmvtpm_module_init(void)
> +{
> +	return vio_register_driver(&ibmvtpm_driver);
> +}
> +
> +/**
> + * ibmvtpm_module_exit - Teardown ibm vtpm module
> + *
> + * Return value:
> + *	Nothing
> + */
> +static void __exit ibmvtpm_module_exit(void)
> +{
> +	vio_unregister_driver(&ibmvtpm_driver);
> +}
> +
> +module_init(ibmvtpm_module_init);
> +module_exit(ibmvtpm_module_exit);
> +
> +MODULE_AUTHOR("adlai@us.ibm.com");
> +MODULE_DESCRIPTION("IBM vTPM Driver");
> +MODULE_VERSION("1.0");
> +MODULE_LICENSE("GPL");
> diff --git a/drivers/char/tpm/tpm_ibmvtpm.h b/drivers/char/tpm/tpm_ibmvtpm.h
> new file mode 100644
> index 0000000..4296eb4
> --- /dev/null
> +++ b/drivers/char/tpm/tpm_ibmvtpm.h
> @@ -0,0 +1,77 @@
> +/*
> + * Copyright (C) 2012 IBM Corporation
> + *
> + * Author: Ashley Lai <adlai@us.ibm.com>
> + *
> + * Maintained by: <tpmdd-devel@lists.sourceforge.net>
> + *
> + * Device driver for TCG/TCPA TPM (trusted platform module).
> + * Specifications at www.trustedcomputinggroup.org
> + *
> + * This program is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU General Public License as
> + * published by the Free Software Foundation, version 2 of the
> + * License.
> + *
> + */
> +
> +#ifndef __TPM_IBMVTPM_H__
> +#define __TPM_IBMVTPM_H__
> +
> +/* vTPM Message Format 1 */
> +struct ibmvtpm_crq {
> +	u8 valid;
> +	u8 msg;
> +	u16 len;
> +	u32 data;
> +	u64 reserved;
> +} __attribute__((packed, aligned(8)));
> +
> +struct ibmvtpm_crq_queue {
> +	struct ibmvtpm_crq *crq_addr;
> +	u32 index;
> +	u32 num_entry;
> +};
> +
> +struct ibmvtpm_dev {
> +	struct device *dev;
> +	struct vio_dev *vdev;
> +	struct ibmvtpm_crq_queue crq_queue;
> +	dma_addr_t crq_dma_handle;
> +	spinlock_t lock;
> +	struct tasklet_struct tasklet;
> +	u32 rtce_size;
> +	void __iomem *rtce_buf;
> +	dma_addr_t rtce_dma_handle;
> +	spinlock_t rtce_lock;
> +	struct ibmvtpm_crq crq_res;
> +	u32 vtpm_version;
> +};
> +
> +#define CRQ_RES_BUF_SIZE	PAGE_SIZE
> +
> +/* Initialize CRQ */
> +#define INIT_CRQ_CMD		0xC001000000000000LL /* Init cmd */
> +#define INIT_CRQ_COMP_CMD	0xC002000000000000LL /* Init complete cmd */
> +#define INIT_CRQ_RES		0x01	/* Init respond */
> +#define INIT_CRQ_COMP_RES	0x02	/* Init complete respond */
> +#define VALID_INIT_CRQ		0xC0	/* Valid command for init crq */
> +
> +/* vTPM CRQ response is the message type | 0x80 */
> +#define VTPM_MSG_RES		0x80
> +#define IBMVTPM_VALID_CMD	0x80
> +
> +/* vTPM CRQ message types */
> +#define VTPM_GET_VERSION			0x01
> +#define VTPM_GET_VERSION_RES			(0x01 | VTPM_MSG_RES)
> +
> +#define VTPM_TPM_COMMAND			0x02
> +#define VTPM_TPM_COMMAND_RES			(0x02 | VTPM_MSG_RES)
> +
> +#define VTPM_GET_RTCE_BUFFER_SIZE		0x03
> +#define VTPM_GET_RTCE_BUFFER_SIZE_RES		(0x03 | VTPM_MSG_RES)
> +
> +#define VTPM_PREPARE_TO_SUSPEND			0x04
> +#define VTPM_PREPARE_TO_SUSPEND_RES		(0x04 | VTPM_MSG_RES)
> +
> +#endif
> -- 
> 1.7.1
> 
>
Benjamin Herrenschmidt Sept. 5, 2012, 3:40 a.m. UTC | #2
On Wed, 2012-08-22 at 16:42 -0500, Kent Yoder wrote:
> On Wed, Aug 22, 2012 at 04:17:43PM -0500, Ashley Lai wrote:
> > This patch adds a new device driver to support IBM virtual TPM
> > (vTPM) for PPC64.  IBM vTPM is supported through the adjunct
> > partition with firmware release 740 or higher.  With vTPM
> > support, each lpar is able to have its own vTPM without the
> > physical TPM hardware.
> > 
> > This driver provides TPM functionalities by communicating with
> > the vTPM adjunct partition through Hypervisor calls (Hcalls)
> > and Command/Response Queue (CRQ) commands.
> 
>  Thanks Ashley, I'll include this in my next pull request to James.

Oh ? I was about to put it in the powerpc tree ... But yeah, I notice
there's a change to tpm.h so it probably should be at least acked by the
TPM folks. As for the subsequent patches, they are powerpc specific so I
can add them, I don't think there's a strict dependency, is there ?

Now, while having a look at the driver, I found a few things that I'm
not sure I'm happy with:

Mainly:

> > + */
> > +static int tpm_ibmvtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count)
> > +{
> > +	struct ibmvtpm_dev *ibmvtpm;
> > +	u16 len;
> > +
> > +	ibmvtpm = (struct ibmvtpm_dev *)chip->vendor.data;
> > +
> > +	if (!ibmvtpm->rtce_buf) {
> > +		dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n");
> > +		return 0;
> > +	}
> > +
> > +	wait_event_interruptible(wq, ibmvtpm->crq_res.len != 0);
> > +
> > +	if (count < ibmvtpm->crq_res.len) {

That doesn't look right. The "other side" as far as I can tell is:

> > +		case VTPM_TPM_COMMAND_RES:
> > +			ibmvtpm->crq_res.valid = crq->valid;
> > +			ibmvtpm->crq_res.msg = crq->msg;
> > +			ibmvtpm->crq_res.len = crq->len;
> > +			ibmvtpm->crq_res.data = crq->data;
> > +			wake_up_interruptible(&wq);

That looks racy to me. At the very least it should be doing:

			ibmvtpm->crq_res.data = crq->data;
			smp_wmb();
			ibmvtpm->crq_res.len = crq->len;

IE. Ensure that "len" is written last, and possibly have an
corresponding smp_rmb() after wait_event_interruptible() in the receive
case.

Also, I dislike having a global symbol called "wq". You also don't seem
to have any synchronization on access to that wq, can't you end up with
several clients trying to send messages & wait for responses getting all
mixed up ? You might need to make sure that at least you do a
wake_up_interruptible_all() to ensure you wake them all up (inefficient
but works). Unless you can track per-client ?

Or is the above TPM layer making sure only one command/response happens
at a given point in time ?

You also do an interruptible wait but don't seem to be checking for
signals (and not testing the result from wait_event_interruptible which
might be returning -ERESTARTSYS in this case).

That all sound a bit wrong to me ...
> > +static irqreturn_t ibmvtpm_interrupt(int irq, void *vtpm_instance)
> > +{
> > +	struct ibmvtpm_dev *ibmvtpm = (struct ibmvtpm_dev *) vtpm_instance;
> > +	unsigned long flags;
> > +
> > +	spin_lock_irqsave(&ibmvtpm->lock, flags);
> > +	vio_disable_interrupts(ibmvtpm->vdev);
> > +	tasklet_schedule(&ibmvtpm->tasklet);
> > +	spin_unlock_irqrestore(&ibmvtpm->lock, flags);
> > +
> > +	return IRQ_HANDLED;
> > +}

Tasklets ? We still use those things ? That's softirq iirc, do you
really need to offload ? I mean, it allows to limit the amount of time
an interrupt is disabled, but that's only useful if you assume you'll
have quite a lot of traffic here, is that the case ?

You might be actually increasing latency here in favor of throughput, is
that what you are aiming for ? Also keep in mind that
vio_disable/enable_interrupt() being an hcall, it can have significant
overhead. So again, only worth it if you're going to process a bulk of
data at a time.

Cheers,
Ben.
Kent Yoder Sept. 5, 2012, 3:46 p.m. UTC | #3
On Wed, Sep 05, 2012 at 01:40:07PM +1000, Benjamin Herrenschmidt wrote:
> On Wed, 2012-08-22 at 16:42 -0500, Kent Yoder wrote:
> > On Wed, Aug 22, 2012 at 04:17:43PM -0500, Ashley Lai wrote:
> > > This patch adds a new device driver to support IBM virtual TPM
> > > (vTPM) for PPC64.  IBM vTPM is supported through the adjunct
> > > partition with firmware release 740 or higher.  With vTPM
> > > support, each lpar is able to have its own vTPM without the
> > > physical TPM hardware.
> > > 
> > > This driver provides TPM functionalities by communicating with
> > > the vTPM adjunct partition through Hypervisor calls (Hcalls)
> > > and Command/Response Queue (CRQ) commands.
> > 
> >  Thanks Ashley, I'll include this in my next pull request to James.
> 
> Oh ? I was about to put it in the powerpc tree ... But yeah, I notice
> there's a change to tpm.h so it probably should be at least acked by the
> TPM folks. As for the subsequent patches, they are powerpc specific so I
> can add them, I don't think there's a strict dependency, is there ?

  James did accept my pull request, so these are already in
security-next...

Kent
Ashley Lai Sept. 5, 2012, 5:25 p.m. UTC | #4
Hi Ben.,

Thank you so much for the comments.  Please see my response below.

> > > + */
> > > +static int tpm_ibmvtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count)
> > > +{
> > > +	struct ibmvtpm_dev *ibmvtpm;
> > > +	u16 len;
> > > +
> > > +	ibmvtpm = (struct ibmvtpm_dev *)chip->vendor.data;
> > > +
> > > +	if (!ibmvtpm->rtce_buf) {
> > > +		dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n");
> > > +		return 0;
> > > +	}
> > > +
> > > +	wait_event_interruptible(wq, ibmvtpm->crq_res.len != 0);
> > > +
> > > +	if (count < ibmvtpm->crq_res.len) {
> 
> That doesn't look right. The "other side" as far as I can tell is:
> 
> > > +		case VTPM_TPM_COMMAND_RES:
> > > +			ibmvtpm->crq_res.valid = crq->valid;
> > > +			ibmvtpm->crq_res.msg = crq->msg;
> > > +			ibmvtpm->crq_res.len = crq->len;
> > > +			ibmvtpm->crq_res.data = crq->data;
> > > +			wake_up_interruptible(&wq);
> 
> That looks racy to me. At the very least it should be doing:
> 
> 			ibmvtpm->crq_res.data = crq->data;
> 			smp_wmb();
> 			ibmvtpm->crq_res.len = crq->len;
> 
> IE. Ensure that "len" is written last, and possibly have an
> corresponding smp_rmb() after wait_event_interruptible() in the receive
> case.

Good catch. I agreed len should be written last and adding memory
barrier is a good idea.

> 
> Also, I dislike having a global symbol called "wq". You also don't seem
> to have any synchronization on access to that wq, can't you end up with
> several clients trying to send messages & wait for responses getting all
> mixed up ? You might need to make sure that at least you do a
> wake_up_interruptible_all() to ensure you wake them all up (inefficient
> but works). Unless you can track per-client ?

The TPM layer above allows only one request at any given point in time.
Therefore we don't need to wake_up_interruptible_all().  I can move wq
to the private structure.

> 
> Or is the above TPM layer making sure only one command/response happens
> at a given point in time ?

You are right.  See above.
> 
> You also do an interruptible wait but don't seem to be checking for
> signals (and not testing the result from wait_event_interruptible which
> might be returning -ERESTARTSYS in this case).
> 
Good point.  I will check for signal in the next version.

> That all sound a bit wrong to me ...
> > > +static irqreturn_t ibmvtpm_interrupt(int irq, void *vtpm_instance)
> > > +{
> > > +	struct ibmvtpm_dev *ibmvtpm = (struct ibmvtpm_dev *) vtpm_instance;
> > > +	unsigned long flags;
> > > +
> > > +	spin_lock_irqsave(&ibmvtpm->lock, flags);
> > > +	vio_disable_interrupts(ibmvtpm->vdev);
> > > +	tasklet_schedule(&ibmvtpm->tasklet);
> > > +	spin_unlock_irqrestore(&ibmvtpm->lock, flags);
> > > +
> > > +	return IRQ_HANDLED;
> > > +}
> 
> Tasklets ? We still use those things ? That's softirq iirc, do you
> really need to offload ? I mean, it allows to limit the amount of time
> an interrupt is disabled, but that's only useful if you assume you'll
> have quite a lot of traffic here, is that the case ?
> 
> You might be actually increasing latency here in favor of throughput, is
> that what you are aiming for ? Also keep in mind that
> vio_disable/enable_interrupt() being an hcall, it can have significant
> overhead. So again, only worth it if you're going to process a bulk of
> data at a time.

My original thought was to have the bottom half process the crq data
while the top half can service another request.  There is some work in
processing crq data but not that bad. You are right the
vio_disable/enable_interrupt() can have significant overhead.  I will
remove tasklet in the next version.

> 
> Cheers,
> Ben.
> 
> 

Thanks,
--Ashley Lai
Benjamin Herrenschmidt Sept. 5, 2012, 9:11 p.m. UTC | #5
On Wed, 2012-09-05 at 10:46 -0500, Kent Yoder wrote:
> On Wed, Sep 05, 2012 at 01:40:07PM +1000, Benjamin Herrenschmidt wrote:
> > On Wed, 2012-08-22 at 16:42 -0500, Kent Yoder wrote:
> > > On Wed, Aug 22, 2012 at 04:17:43PM -0500, Ashley Lai wrote:
> > > > This patch adds a new device driver to support IBM virtual TPM
> > > > (vTPM) for PPC64.  IBM vTPM is supported through the adjunct
> > > > partition with firmware release 740 or higher.  With vTPM
> > > > support, each lpar is able to have its own vTPM without the
> > > > physical TPM hardware.
> > > > 
> > > > This driver provides TPM functionalities by communicating with
> > > > the vTPM adjunct partition through Hypervisor calls (Hcalls)
> > > > and Command/Response Queue (CRQ) commands.
> > > 
> > >  Thanks Ashley, I'll include this in my next pull request to James.
> > 
> > Oh ? I was about to put it in the powerpc tree ... But yeah, I notice
> > there's a change to tpm.h so it probably should be at least acked by the
> > TPM folks. As for the subsequent patches, they are powerpc specific so I
> > can add them, I don't think there's a strict dependency, is there ?
> 
>   James did accept my pull request, so these are already in
> security-next...

For the driver itself, it's not a big issue (though I did found issue
while reviewing it so it will need another round of updates). For the
code that changes arch/powerpc, especially prom_init.c, that stuff must
at the very least be acked by me (or the acting powerpc person if I'm
away) if it's going to go via a different tree.

Cheers,
Ben.
Kent Yoder Sept. 7, 2012, 5:38 p.m. UTC | #6
> >   James did accept my pull request, so these are already in
> > security-next...
> 
> For the driver itself, it's not a big issue (though I did found issue
> while reviewing it so it will need another round of updates). For the
> code that changes arch/powerpc, especially prom_init.c, that stuff must
> at the very least be acked by me (or the acting powerpc person if I'm
> away) if it's going to go via a different tree.

  Sorry about that.  Hopefully there won't be any changes there and we
can amend with your ack.

  As for the driver updates, I'd hate to see everyone else's code in the
pull request get delayed yet again.  James, will it be ok to apply the
update on top of security-next?

Thanks,
Kent

> Cheers,
> Ben.
>
James Morris Sept. 12, 2012, 3:16 a.m. UTC | #7
On Fri, 7 Sep 2012, Kent Yoder wrote:

> > >   James did accept my pull request, so these are already in
> > > security-next...
> > 
> > For the driver itself, it's not a big issue (though I did found issue
> > while reviewing it so it will need another round of updates). For the
> > code that changes arch/powerpc, especially prom_init.c, that stuff must
> > at the very least be acked by me (or the acting powerpc person if I'm
> > away) if it's going to go via a different tree.
> 
>   Sorry about that.  Hopefully there won't be any changes there and we
> can amend with your ack.
> 
>   As for the driver updates, I'd hate to see everyone else's code in the
> pull request get delayed yet again.  James, will it be ok to apply the
> update on top of security-next?

I guess?
diff mbox

Patch

diff --git a/drivers/char/tpm/Kconfig b/drivers/char/tpm/Kconfig
index c4aac48..915875e 100644
--- a/drivers/char/tpm/Kconfig
+++ b/drivers/char/tpm/Kconfig
@@ -73,4 +73,12 @@  config TCG_INFINEON
 	  Further information on this driver and the supported hardware
 	  can be found at http://www.trust.rub.de/projects/linux-device-driver-infineon-tpm/ 

+config TCG_IBMVTPM
+	tristate "IBM VTPM Interface"
+	depends on PPC64
+	---help---
+	  If you have IBM virtual TPM (VTPM) support say Yes and it
+	  will be accessible from within Linux.  To compile this driver
+	  as a module, choose M here; the module will be called tpm_ibmvtpm.
+
 endif # TCG_TPM
diff --git a/drivers/char/tpm/Makefile b/drivers/char/tpm/Makefile
index beac52f..547509d 100644
--- a/drivers/char/tpm/Makefile
+++ b/drivers/char/tpm/Makefile
@@ -11,3 +11,4 @@  obj-$(CONFIG_TCG_TIS_I2C_INFINEON) += tpm_i2c_infineon.o
 obj-$(CONFIG_TCG_NSC) += tpm_nsc.o
 obj-$(CONFIG_TCG_ATMEL) += tpm_atmel.o
 obj-$(CONFIG_TCG_INFINEON) += tpm_infineon.o
+obj-$(CONFIG_TCG_IBMVTPM) += tpm_ibmvtpm.o
diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h
index 645136e..870fde7 100644
--- a/drivers/char/tpm/tpm.h
+++ b/drivers/char/tpm/tpm.h
@@ -100,6 +100,7 @@  struct tpm_vendor_specific {
 	bool timeout_adjusted;
 	unsigned long duration[3]; /* jiffies */
 	bool duration_adjusted;
+	void *data;
 
 	wait_queue_head_t read_queue;
 	wait_queue_head_t int_queue;
diff --git a/drivers/char/tpm/tpm_ibmvtpm.c b/drivers/char/tpm/tpm_ibmvtpm.c
new file mode 100644
index 0000000..efc4ab3
--- /dev/null
+++ b/drivers/char/tpm/tpm_ibmvtpm.c
@@ -0,0 +1,749 @@ 
+/*
+ * Copyright (C) 2012 IBM Corporation
+ *
+ * Author: Ashley Lai <adlai@us.ibm.com>
+ *
+ * Maintained by: <tpmdd-devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ */
+
+#include <linux/dma-mapping.h>
+#include <linux/dmapool.h>
+#include <linux/slab.h>
+#include <asm/vio.h>
+#include <asm/irq.h>
+#include <linux/types.h>
+#include <linux/list.h>
+#include <linux/spinlock.h>
+#include <linux/interrupt.h>
+#include <linux/wait.h>
+#include <asm/prom.h>
+
+#include "tpm.h"
+#include "tpm_ibmvtpm.h"
+
+static const char tpm_ibmvtpm_driver_name[] = "tpm_ibmvtpm";
+
+static struct vio_device_id tpm_ibmvtpm_device_table[] __devinitdata = {
+	{ "IBM,vtpm", "IBM,vtpm"},
+	{ "", "" }
+};
+MODULE_DEVICE_TABLE(vio, tpm_ibmvtpm_device_table);
+
+DECLARE_WAIT_QUEUE_HEAD(wq);
+
+/**
+ * ibmvtpm_send_crq - Send a CRQ request
+ * @vdev:	vio device struct
+ * @w1:		first word
+ * @w2:		second word
+ *
+ * Return value:
+ *	0 -Sucess
+ *	Non-zero - Failure
+ */
+static int ibmvtpm_send_crq(struct vio_dev *vdev, u64 w1, u64 w2)
+{
+	return plpar_hcall_norets(H_SEND_CRQ, vdev->unit_address, w1, w2);
+}
+
+/**
+ * ibmvtpm_get_data - Retrieve ibm vtpm data
+ * @dev:	device struct
+ *
+ * Return value:
+ *	vtpm device struct
+ */
+static struct ibmvtpm_dev *ibmvtpm_get_data(const struct device *dev)
+{
+	struct tpm_chip *chip = dev_get_drvdata(dev);
+	if (chip)
+		return (struct ibmvtpm_dev *)chip->vendor.data;
+	return NULL;
+}
+
+/**
+ * tpm_ibmvtpm_recv - Receive data after send
+ * @chip:	tpm chip struct
+ * @buf:	buffer to read
+ * count:	size of buffer
+ *
+ * Return value:
+ *	Number of bytes read
+ */
+static int tpm_ibmvtpm_recv(struct tpm_chip *chip, u8 *buf, size_t count)
+{
+	struct ibmvtpm_dev *ibmvtpm;
+	u16 len;
+
+	ibmvtpm = (struct ibmvtpm_dev *)chip->vendor.data;
+
+	if (!ibmvtpm->rtce_buf) {
+		dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n");
+		return 0;
+	}
+
+	wait_event_interruptible(wq, ibmvtpm->crq_res.len != 0);
+
+	if (count < ibmvtpm->crq_res.len) {
+		dev_err(ibmvtpm->dev,
+			"Invalid size in recv: count=%ld, crq_size=%d\n",
+			count, ibmvtpm->crq_res.len);
+		return -EIO;
+	}
+
+	spin_lock(&ibmvtpm->rtce_lock);
+	memcpy((void *)buf, (void *)ibmvtpm->rtce_buf, ibmvtpm->crq_res.len);
+	memset(ibmvtpm->rtce_buf, 0, ibmvtpm->crq_res.len);
+	ibmvtpm->crq_res.valid = 0;
+	ibmvtpm->crq_res.msg = 0;
+	len = ibmvtpm->crq_res.len;
+	ibmvtpm->crq_res.len = 0;
+	spin_unlock(&ibmvtpm->rtce_lock);
+	return len;
+}
+
+/**
+ * tpm_ibmvtpm_send - Send tpm request
+ * @chip:	tpm chip struct
+ * @buf:	buffer contains data to send
+ * count:	size of buffer
+ *
+ * Return value:
+ *	Number of bytes sent
+ */
+static int tpm_ibmvtpm_send(struct tpm_chip *chip, u8 *buf, size_t count)
+{
+	struct ibmvtpm_dev *ibmvtpm;
+	struct ibmvtpm_crq crq;
+	u64 *word = (u64 *) &crq;
+	int rc;
+
+	ibmvtpm = (struct ibmvtpm_dev *)chip->vendor.data;
+
+	if (!ibmvtpm->rtce_buf) {
+		dev_err(ibmvtpm->dev, "ibmvtpm device is not ready\n");
+		return 0;
+	}
+
+	if (count > ibmvtpm->rtce_size) {
+		dev_err(ibmvtpm->dev,
+			"Invalid size in send: count=%ld, rtce_size=%d\n",
+			count, ibmvtpm->rtce_size);
+		return -EIO;
+	}
+
+	spin_lock(&ibmvtpm->rtce_lock);
+	memcpy((void *)ibmvtpm->rtce_buf, (void *)buf, count);
+	crq.valid = (u8)IBMVTPM_VALID_CMD;
+	crq.msg = (u8)VTPM_TPM_COMMAND;
+	crq.len = (u16)count;
+	crq.data = ibmvtpm->rtce_dma_handle;
+
+	rc = ibmvtpm_send_crq(ibmvtpm->vdev, word[0], word[1]);
+	if (rc != H_SUCCESS) {
+		dev_err(ibmvtpm->dev, "tpm_ibmvtpm_send failed rc=%d\n", rc);
+		rc = 0;
+	} else
+		rc = count;
+
+	spin_unlock(&ibmvtpm->rtce_lock);
+	return rc;
+}
+
+static void tpm_ibmvtpm_cancel(struct tpm_chip *chip)
+{
+	return;
+}
+
+static u8 tpm_ibmvtpm_status(struct tpm_chip *chip)
+{
+	return 0;
+}
+
+/**
+ * ibmvtpm_crq_get_rtce_size - Send a CRQ request to get rtce size
+ * @ibmvtpm:	vtpm device struct
+ *
+ * Return value:
+ *	0 - Success
+ *	Non-zero - Failure
+ */
+static int ibmvtpm_crq_get_rtce_size(struct ibmvtpm_dev *ibmvtpm)
+{
+	struct ibmvtpm_crq crq;
+	u64 *buf = (u64 *) &crq;
+	int rc;
+
+	crq.valid = (u8)IBMVTPM_VALID_CMD;
+	crq.msg = (u8)VTPM_GET_RTCE_BUFFER_SIZE;
+
+	rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
+	if (rc != H_SUCCESS)
+		dev_err(ibmvtpm->dev,
+			"ibmvtpm_crq_get_rtce_size failed rc=%d\n", rc);
+
+	return rc;
+}
+
+/**
+ * ibmvtpm_crq_get_version - Send a CRQ request to get vtpm version
+ *			   - Note that this is vtpm version and not tpm version
+ * @ibmvtpm:	vtpm device struct
+ *
+ * Return value:
+ *	0 - Success
+ *	Non-zero - Failure
+ */
+static int ibmvtpm_crq_get_version(struct ibmvtpm_dev *ibmvtpm)
+{
+	struct ibmvtpm_crq crq;
+	u64 *buf = (u64 *) &crq;
+	int rc;
+
+	crq.valid = (u8)IBMVTPM_VALID_CMD;
+	crq.msg = (u8)VTPM_GET_VERSION;
+
+	rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
+	if (rc != H_SUCCESS)
+		dev_err(ibmvtpm->dev,
+			"ibmvtpm_crq_get_version failed rc=%d\n", rc);
+
+	return rc;
+}
+
+/**
+ * ibmvtpm_crq_send_init_complete - Send a CRQ initialize complete message
+ * @ibmvtpm:	vtpm device struct
+ *
+ * Return value:
+ *	0 - Success
+ *	Non-zero - Failure
+ */
+static int ibmvtpm_crq_send_init_complete(struct ibmvtpm_dev *ibmvtpm)
+{
+	int rc;
+
+	rc = ibmvtpm_send_crq(ibmvtpm->vdev, INIT_CRQ_COMP_CMD, 0);
+	if (rc != H_SUCCESS)
+		dev_err(ibmvtpm->dev,
+			"ibmvtpm_crq_send_init_complete failed rc=%d\n", rc);
+
+	return rc;
+}
+
+/**
+ * ibmvtpm_crq_send_init - Send a CRQ initialize message
+ * @ibmvtpm:	vtpm device struct
+ *
+ * Return value:
+ *	0 - Success
+ *	Non-zero - Failure
+ */
+static int ibmvtpm_crq_send_init(struct ibmvtpm_dev *ibmvtpm)
+{
+	int rc;
+
+	rc = ibmvtpm_send_crq(ibmvtpm->vdev, INIT_CRQ_CMD, 0);
+	if (rc != H_SUCCESS)
+		dev_err(ibmvtpm->dev,
+			"ibmvtpm_crq_send_init failed rc=%d\n", rc);
+
+	return rc;
+}
+
+/**
+ * tpm_ibmvtpm_remove - ibm vtpm remove entry point
+ * @vdev:	vio device struct
+ *
+ * Return value:
+ *	0
+ */
+static int __devexit tpm_ibmvtpm_remove(struct vio_dev *vdev)
+{
+	struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(&vdev->dev);
+	int rc = 0;
+
+	free_irq(vdev->irq, ibmvtpm);
+	tasklet_kill(&ibmvtpm->tasklet);
+
+	do {
+		if (rc)
+			msleep(100);
+		rc = plpar_hcall_norets(H_FREE_CRQ, vdev->unit_address);
+	} while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
+
+	dma_unmap_single(ibmvtpm->dev, ibmvtpm->crq_dma_handle,
+			 CRQ_RES_BUF_SIZE, DMA_BIDIRECTIONAL);
+	free_page((unsigned long)ibmvtpm->crq_queue.crq_addr);
+
+	if (ibmvtpm->rtce_buf) {
+		dma_unmap_single(ibmvtpm->dev, ibmvtpm->rtce_dma_handle,
+				 ibmvtpm->rtce_size, DMA_BIDIRECTIONAL);
+		kfree(ibmvtpm->rtce_buf);
+	}
+
+	tpm_remove_hardware(ibmvtpm->dev);
+
+	kfree(ibmvtpm);
+
+	return 0;
+}
+
+/**
+ * tpm_ibmvtpm_get_desired_dma - Get DMA size needed by this driver
+ * @vdev:	vio device struct
+ *
+ * Return value:
+ *	Number of bytes the driver needs to DMA map
+ */
+static unsigned long tpm_ibmvtpm_get_desired_dma(struct vio_dev *vdev)
+{
+	struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(&vdev->dev);
+	return CRQ_RES_BUF_SIZE + ibmvtpm->rtce_size;
+}
+
+/**
+ * tpm_ibmvtpm_suspend - Suspend
+ * @dev:	device struct
+ *
+ * Return value:
+ *	0
+ */
+static int tpm_ibmvtpm_suspend(struct device *dev)
+{
+	struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(dev);
+	struct ibmvtpm_crq crq;
+	u64 *buf = (u64 *) &crq;
+	int rc = 0;
+
+	crq.valid = (u8)IBMVTPM_VALID_CMD;
+	crq.msg = (u8)VTPM_PREPARE_TO_SUSPEND;
+
+	rc = ibmvtpm_send_crq(ibmvtpm->vdev, buf[0], buf[1]);
+	if (rc != H_SUCCESS)
+		dev_err(ibmvtpm->dev,
+			"tpm_ibmvtpm_suspend failed rc=%d\n", rc);
+
+	return rc;
+}
+
+/**
+ * ibmvtpm_reset_crq - Reset CRQ
+ * @ibmvtpm:	ibm vtpm struct
+ *
+ * Return value:
+ *	0 - Success
+ *	Non-zero - Failure
+ */
+static int ibmvtpm_reset_crq(struct ibmvtpm_dev *ibmvtpm)
+{
+	int rc = 0;
+
+	do {
+		if (rc)
+			msleep(100);
+		rc = plpar_hcall_norets(H_FREE_CRQ,
+					ibmvtpm->vdev->unit_address);
+	} while (rc == H_BUSY || H_IS_LONG_BUSY(rc));
+
+	memset(ibmvtpm->crq_queue.crq_addr, 0, CRQ_RES_BUF_SIZE);
+	ibmvtpm->crq_queue.index = 0;
+
+	return plpar_hcall_norets(H_REG_CRQ, ibmvtpm->vdev->unit_address,
+				  ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE);
+}
+
+/**
+ * tpm_ibmvtpm_resume - Resume from suspend
+ * @dev:	device struct
+ *
+ * Return value:
+ *	0
+ */
+static int tpm_ibmvtpm_resume(struct device *dev)
+{
+	struct ibmvtpm_dev *ibmvtpm = ibmvtpm_get_data(dev);
+	unsigned long flags;
+	int rc = 0;
+
+	do {
+		if (rc)
+			msleep(100);
+		rc = plpar_hcall_norets(H_ENABLE_CRQ,
+					ibmvtpm->vdev->unit_address);
+	} while (rc == H_IN_PROGRESS || rc == H_BUSY || H_IS_LONG_BUSY(rc));
+
+	if (rc) {
+		dev_err(dev, "Error enabling ibmvtpm rc=%d\n", rc);
+		return rc;
+	}
+
+	spin_lock_irqsave(&ibmvtpm->lock, flags);
+	vio_disable_interrupts(ibmvtpm->vdev);
+	tasklet_schedule(&ibmvtpm->tasklet);
+	spin_unlock_irqrestore(&ibmvtpm->lock, flags);
+
+	rc = ibmvtpm_crq_send_init(ibmvtpm);
+	if (rc)
+		dev_err(dev, "Error send_init rc=%d\n", rc);
+
+	return rc;
+}
+
+static const struct file_operations ibmvtpm_ops = {
+	.owner = THIS_MODULE,
+	.llseek = no_llseek,
+	.open = tpm_open,
+	.read = tpm_read,
+	.write = tpm_write,
+	.release = tpm_release,
+};
+
+static DEVICE_ATTR(pubek, S_IRUGO, tpm_show_pubek, NULL);
+static DEVICE_ATTR(pcrs, S_IRUGO, tpm_show_pcrs, NULL);
+static DEVICE_ATTR(enabled, S_IRUGO, tpm_show_enabled, NULL);
+static DEVICE_ATTR(active, S_IRUGO, tpm_show_active, NULL);
+static DEVICE_ATTR(owned, S_IRUGO, tpm_show_owned, NULL);
+static DEVICE_ATTR(temp_deactivated, S_IRUGO, tpm_show_temp_deactivated,
+		   NULL);
+static DEVICE_ATTR(caps, S_IRUGO, tpm_show_caps_1_2, NULL);
+static DEVICE_ATTR(cancel, S_IWUSR | S_IWGRP, NULL, tpm_store_cancel);
+static DEVICE_ATTR(durations, S_IRUGO, tpm_show_durations, NULL);
+static DEVICE_ATTR(timeouts, S_IRUGO, tpm_show_timeouts, NULL);
+
+static struct attribute *ibmvtpm_attrs[] = {
+	&dev_attr_pubek.attr,
+	&dev_attr_pcrs.attr,
+	&dev_attr_enabled.attr,
+	&dev_attr_active.attr,
+	&dev_attr_owned.attr,
+	&dev_attr_temp_deactivated.attr,
+	&dev_attr_caps.attr,
+	&dev_attr_cancel.attr,
+	&dev_attr_durations.attr,
+	&dev_attr_timeouts.attr, NULL,
+};
+
+static struct attribute_group ibmvtpm_attr_grp = { .attrs = ibmvtpm_attrs };
+
+static const struct tpm_vendor_specific tpm_ibmvtpm = {
+	.recv = tpm_ibmvtpm_recv,
+	.send = tpm_ibmvtpm_send,
+	.cancel = tpm_ibmvtpm_cancel,
+	.status = tpm_ibmvtpm_status,
+	.req_complete_mask = 0,
+	.req_complete_val = 0,
+	.req_canceled = 0,
+	.attr_group = &ibmvtpm_attr_grp,
+	.miscdev = { .fops = &ibmvtpm_ops, },
+};
+
+static const struct dev_pm_ops tpm_ibmvtpm_pm_ops = {
+	.suspend = tpm_ibmvtpm_suspend,
+	.resume = tpm_ibmvtpm_resume,
+};
+
+/**
+ * ibmvtpm_crq_get_next - Get next responded crq
+ * @ibmvtpm	vtpm device struct
+ *
+ * Return value:
+ *	vtpm crq pointer
+ */
+static struct ibmvtpm_crq *ibmvtpm_crq_get_next(struct ibmvtpm_dev *ibmvtpm)
+{
+	struct ibmvtpm_crq_queue *crq_q = &ibmvtpm->crq_queue;
+	struct ibmvtpm_crq *crq = &crq_q->crq_addr[crq_q->index];
+
+	if (crq->valid & VTPM_MSG_RES) {
+		if (++crq_q->index == crq_q->num_entry)
+			crq_q->index = 0;
+		rmb();
+	} else
+		crq = NULL;
+	return crq;
+}
+
+/**
+ * ibmvtpm_crq_process - Process responded crq
+ * @crq		crq to be processed
+ * @ibmvtpm	vtpm device struct
+ *
+ * Return value:
+ *	Nothing
+ */
+static void ibmvtpm_crq_process(struct ibmvtpm_crq *crq,
+				struct ibmvtpm_dev *ibmvtpm)
+{
+	int rc = 0;
+
+	switch (crq->valid) {
+	case VALID_INIT_CRQ:
+		switch (crq->msg) {
+		case INIT_CRQ_RES:
+			dev_info(ibmvtpm->dev, "CRQ initialized\n");
+			rc = ibmvtpm_crq_send_init_complete(ibmvtpm);
+			if (rc)
+				dev_err(ibmvtpm->dev, "Unable to send CRQ init complete rc=%d\n", rc);
+			return;
+		case INIT_CRQ_COMP_RES:
+			dev_info(ibmvtpm->dev,
+				 "CRQ initialization completed\n");
+			return;
+		default:
+			dev_err(ibmvtpm->dev, "Unknown crq message type: %d\n", crq->msg);
+			return;
+		}
+		return;
+	case IBMVTPM_VALID_CMD:
+		switch (crq->msg) {
+		case VTPM_GET_RTCE_BUFFER_SIZE_RES:
+			if (crq->len <= 0) {
+				dev_err(ibmvtpm->dev, "Invalid rtce size\n");
+				return;
+			}
+			ibmvtpm->rtce_size = crq->len;
+			ibmvtpm->rtce_buf = kmalloc(ibmvtpm->rtce_size,
+						    GFP_KERNEL);
+			if (!ibmvtpm->rtce_buf) {
+				dev_err(ibmvtpm->dev, "Failed to allocate memory for rtce buffer\n");
+				return;
+			}
+
+			ibmvtpm->rtce_dma_handle = dma_map_single(ibmvtpm->dev,
+				ibmvtpm->rtce_buf, ibmvtpm->rtce_size,
+				DMA_BIDIRECTIONAL);
+
+			if (dma_mapping_error(ibmvtpm->dev,
+					      ibmvtpm->rtce_dma_handle)) {
+				kfree(ibmvtpm->rtce_buf);
+				ibmvtpm->rtce_buf = NULL;
+				dev_err(ibmvtpm->dev, "Failed to dma map rtce buffer\n");
+			}
+
+			return;
+		case VTPM_GET_VERSION_RES:
+			ibmvtpm->vtpm_version = crq->data;
+			return;
+		case VTPM_TPM_COMMAND_RES:
+			ibmvtpm->crq_res.valid = crq->valid;
+			ibmvtpm->crq_res.msg = crq->msg;
+			ibmvtpm->crq_res.len = crq->len;
+			ibmvtpm->crq_res.data = crq->data;
+			wake_up_interruptible(&wq);
+			return;
+		default:
+			return;
+		}
+	}
+	return;
+}
+
+/**
+ * ibmvtpm_interrupt -	Interrupt handler
+ * @irq:		irq number to handle
+ * @vtpm_instance:	vtpm that received interrupt
+ *
+ * Returns:
+ *	IRQ_HANDLED
+ **/
+static irqreturn_t ibmvtpm_interrupt(int irq, void *vtpm_instance)
+{
+	struct ibmvtpm_dev *ibmvtpm = (struct ibmvtpm_dev *) vtpm_instance;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ibmvtpm->lock, flags);
+	vio_disable_interrupts(ibmvtpm->vdev);
+	tasklet_schedule(&ibmvtpm->tasklet);
+	spin_unlock_irqrestore(&ibmvtpm->lock, flags);
+
+	return IRQ_HANDLED;
+}
+
+/**
+ * ibmvtpm_tasklet - Interrupt handler tasklet
+ * @data:	ibm vtpm device struct
+ *
+ * Returns:
+ *	Nothing
+ **/
+static void ibmvtpm_tasklet(void *data)
+{
+	struct ibmvtpm_dev *ibmvtpm = data;
+	struct ibmvtpm_crq *crq;
+	unsigned long flags;
+
+	spin_lock_irqsave(&ibmvtpm->lock, flags);
+	while ((crq = ibmvtpm_crq_get_next(ibmvtpm)) != NULL) {
+		ibmvtpm_crq_process(crq, ibmvtpm);
+		crq->valid = 0;
+		wmb();
+	}
+
+	vio_enable_interrupts(ibmvtpm->vdev);
+	spin_unlock_irqrestore(&ibmvtpm->lock, flags);
+}
+
+/**
+ * tpm_ibmvtpm_probe - ibm vtpm initialize entry point
+ * @vio_dev:	vio device struct
+ * @id:		vio device id struct
+ *
+ * Return value:
+ *	0 - Success
+ *	Non-zero - Failure
+ */
+static int __devinit tpm_ibmvtpm_probe(struct vio_dev *vio_dev,
+				   const struct vio_device_id *id)
+{
+	struct ibmvtpm_dev *ibmvtpm;
+	struct device *dev = &vio_dev->dev;
+	struct ibmvtpm_crq_queue *crq_q;
+	struct tpm_chip *chip;
+	int rc = -ENOMEM, rc1;
+
+	chip = tpm_register_hardware(dev, &tpm_ibmvtpm);
+	if (!chip) {
+		dev_err(dev, "tpm_register_hardware failed\n");
+		return -ENODEV;
+	}
+
+	ibmvtpm = kzalloc(sizeof(struct ibmvtpm_dev), GFP_KERNEL);
+	if (!ibmvtpm) {
+		dev_err(dev, "kzalloc for ibmvtpm failed\n");
+		goto cleanup;
+	}
+
+	crq_q = &ibmvtpm->crq_queue;
+	crq_q->crq_addr = (struct ibmvtpm_crq *)get_zeroed_page(GFP_KERNEL);
+	if (!crq_q->crq_addr) {
+		dev_err(dev, "Unable to allocate memory for crq_addr\n");
+		goto cleanup;
+	}
+
+	crq_q->num_entry = CRQ_RES_BUF_SIZE / sizeof(*crq_q->crq_addr);
+	ibmvtpm->crq_dma_handle = dma_map_single(dev, crq_q->crq_addr,
+						 CRQ_RES_BUF_SIZE,
+						 DMA_BIDIRECTIONAL);
+
+	if (dma_mapping_error(dev, ibmvtpm->crq_dma_handle)) {
+		dev_err(dev, "dma mapping failed\n");
+		goto cleanup;
+	}
+
+	rc = plpar_hcall_norets(H_REG_CRQ, vio_dev->unit_address,
+				ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE);
+	if (rc == H_RESOURCE)
+		rc = ibmvtpm_reset_crq(ibmvtpm);
+
+	if (rc) {
+		dev_err(dev, "Unable to register CRQ rc=%d\n", rc);
+		goto reg_crq_cleanup;
+	}
+
+	tasklet_init(&ibmvtpm->tasklet, (void *)ibmvtpm_tasklet,
+		     (unsigned long)ibmvtpm);
+
+	rc = request_irq(vio_dev->irq, ibmvtpm_interrupt, 0,
+			 tpm_ibmvtpm_driver_name, ibmvtpm);
+	if (rc) {
+		dev_err(dev, "Error %d register irq 0x%x\n", rc, vio_dev->irq);
+		goto init_irq_cleanup;
+	}
+
+	rc = vio_enable_interrupts(vio_dev);
+	if (rc) {
+		dev_err(dev, "Error %d enabling interrupts\n", rc);
+		goto init_irq_cleanup;
+	}
+
+	crq_q->index = 0;
+
+	ibmvtpm->dev = dev;
+	ibmvtpm->vdev = vio_dev;
+	chip->vendor.data = (void *)ibmvtpm;
+
+	spin_lock_init(&ibmvtpm->lock);
+	spin_lock_init(&ibmvtpm->rtce_lock);
+
+	rc = ibmvtpm_crq_send_init(ibmvtpm);
+	if (rc)
+		goto init_irq_cleanup;
+
+	rc = ibmvtpm_crq_get_version(ibmvtpm);
+	if (rc)
+		goto init_irq_cleanup;
+
+	rc = ibmvtpm_crq_get_rtce_size(ibmvtpm);
+	if (rc)
+		goto init_irq_cleanup;
+
+	return rc;
+init_irq_cleanup:
+	tasklet_kill(&ibmvtpm->tasklet);
+	do {
+		rc1 = plpar_hcall_norets(H_FREE_CRQ, vio_dev->unit_address);
+	} while (rc1 == H_BUSY || H_IS_LONG_BUSY(rc1));
+reg_crq_cleanup:
+	dma_unmap_single(dev, ibmvtpm->crq_dma_handle, CRQ_RES_BUF_SIZE,
+			 DMA_BIDIRECTIONAL);
+cleanup:
+	if (ibmvtpm) {
+		if (crq_q->crq_addr)
+			free_page((unsigned long)crq_q->crq_addr);
+		kfree(ibmvtpm);
+	}
+
+	tpm_remove_hardware(dev);
+
+	return rc;
+}
+
+static struct vio_driver ibmvtpm_driver = {
+	.id_table	 = tpm_ibmvtpm_device_table,
+	.probe		 = tpm_ibmvtpm_probe,
+	.remove		 = tpm_ibmvtpm_remove,
+	.get_desired_dma = tpm_ibmvtpm_get_desired_dma,
+	.name		 = tpm_ibmvtpm_driver_name,
+	.pm		 = &tpm_ibmvtpm_pm_ops,
+};
+
+/**
+ * ibmvtpm_module_init - Initialize ibm vtpm module
+ *
+ * Return value:
+ *	0 -Success
+ *	Non-zero - Failure
+ */
+static int __init ibmvtpm_module_init(void)
+{
+	return vio_register_driver(&ibmvtpm_driver);
+}
+
+/**
+ * ibmvtpm_module_exit - Teardown ibm vtpm module
+ *
+ * Return value:
+ *	Nothing
+ */
+static void __exit ibmvtpm_module_exit(void)
+{
+	vio_unregister_driver(&ibmvtpm_driver);
+}
+
+module_init(ibmvtpm_module_init);
+module_exit(ibmvtpm_module_exit);
+
+MODULE_AUTHOR("adlai@us.ibm.com");
+MODULE_DESCRIPTION("IBM vTPM Driver");
+MODULE_VERSION("1.0");
+MODULE_LICENSE("GPL");
diff --git a/drivers/char/tpm/tpm_ibmvtpm.h b/drivers/char/tpm/tpm_ibmvtpm.h
new file mode 100644
index 0000000..4296eb4
--- /dev/null
+++ b/drivers/char/tpm/tpm_ibmvtpm.h
@@ -0,0 +1,77 @@ 
+/*
+ * Copyright (C) 2012 IBM Corporation
+ *
+ * Author: Ashley Lai <adlai@us.ibm.com>
+ *
+ * Maintained by: <tpmdd-devel@lists.sourceforge.net>
+ *
+ * Device driver for TCG/TCPA TPM (trusted platform module).
+ * Specifications at www.trustedcomputinggroup.org
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation, version 2 of the
+ * License.
+ *
+ */
+
+#ifndef __TPM_IBMVTPM_H__
+#define __TPM_IBMVTPM_H__
+
+/* vTPM Message Format 1 */
+struct ibmvtpm_crq {
+	u8 valid;
+	u8 msg;
+	u16 len;
+	u32 data;
+	u64 reserved;
+} __attribute__((packed, aligned(8)));
+
+struct ibmvtpm_crq_queue {
+	struct ibmvtpm_crq *crq_addr;
+	u32 index;
+	u32 num_entry;
+};
+
+struct ibmvtpm_dev {
+	struct device *dev;
+	struct vio_dev *vdev;
+	struct ibmvtpm_crq_queue crq_queue;
+	dma_addr_t crq_dma_handle;
+	spinlock_t lock;
+	struct tasklet_struct tasklet;
+	u32 rtce_size;
+	void __iomem *rtce_buf;
+	dma_addr_t rtce_dma_handle;
+	spinlock_t rtce_lock;
+	struct ibmvtpm_crq crq_res;
+	u32 vtpm_version;
+};
+
+#define CRQ_RES_BUF_SIZE	PAGE_SIZE
+
+/* Initialize CRQ */
+#define INIT_CRQ_CMD		0xC001000000000000LL /* Init cmd */
+#define INIT_CRQ_COMP_CMD	0xC002000000000000LL /* Init complete cmd */
+#define INIT_CRQ_RES		0x01	/* Init respond */
+#define INIT_CRQ_COMP_RES	0x02	/* Init complete respond */
+#define VALID_INIT_CRQ		0xC0	/* Valid command for init crq */
+
+/* vTPM CRQ response is the message type | 0x80 */
+#define VTPM_MSG_RES		0x80
+#define IBMVTPM_VALID_CMD	0x80
+
+/* vTPM CRQ message types */
+#define VTPM_GET_VERSION			0x01
+#define VTPM_GET_VERSION_RES			(0x01 | VTPM_MSG_RES)
+
+#define VTPM_TPM_COMMAND			0x02
+#define VTPM_TPM_COMMAND_RES			(0x02 | VTPM_MSG_RES)
+
+#define VTPM_GET_RTCE_BUFFER_SIZE		0x03
+#define VTPM_GET_RTCE_BUFFER_SIZE_RES		(0x03 | VTPM_MSG_RES)
+
+#define VTPM_PREPARE_TO_SUSPEND			0x04
+#define VTPM_PREPARE_TO_SUSPEND_RES		(0x04 | VTPM_MSG_RES)
+
+#endif