diff mbox series

[v5,4/7] tpm: Add TPM CRQ driver implementation

Message ID 20200111012155.3350198-5-stefanb@linux.ibm.com
State Superseded
Headers show
Series Add vTPM 2.0 support to SLOF | expand

Commit Message

Stefan Berger Jan. 11, 2020, 1:21 a.m. UTC
This patch adds a TPM driver for the CRQ interface as used by
the QEMU PAPR implementation.

Also add a Readme that explains the benefits and installation procedure
for the vTPM.

Signed-off-by: Stefan Berger <stefanb@linux.ibm.com>
---
 board-qemu/Makefile      |   2 +-
 include/helpers.h        |   1 +
 lib/Makefile             |   2 +-
 lib/libtpm/Makefile      |  50 +++++
 lib/libtpm/Readme        |  95 ++++++++
 lib/libtpm/tcgbios_int.h |  30 +++
 lib/libtpm/tpm_drivers.c | 466 +++++++++++++++++++++++++++++++++++++++
 lib/libtpm/tpm_drivers.h |  82 +++++++
 slof/helpers.c           |   6 +
 9 files changed, 732 insertions(+), 2 deletions(-)
 create mode 100644 lib/libtpm/Makefile
 create mode 100644 lib/libtpm/Readme
 create mode 100644 lib/libtpm/tcgbios_int.h
 create mode 100644 lib/libtpm/tpm_drivers.c
 create mode 100644 lib/libtpm/tpm_drivers.h
diff mbox series

Patch

diff --git a/board-qemu/Makefile b/board-qemu/Makefile
index 61a1367..f419202 100644
--- a/board-qemu/Makefile
+++ b/board-qemu/Makefile
@@ -15,7 +15,7 @@  BOARD_TARGETS = tools_build romfs_build stage1 subdirs
 SUBDIRS = slof
 
 COMMON_LIBS = libc libbootmsg libbases libnvram libelf libhvcall libvirtio \
-              libusb libveth libe1k libnet libbootmenu
+              libusb libveth libe1k libnet libbootmenu libtpm
 
 all: $(BOARD_TARGETS)
 	$(MAKE) boot_rom.bin
diff --git a/include/helpers.h b/include/helpers.h
index 9a06a5c..2f460d6 100644
--- a/include/helpers.h
+++ b/include/helpers.h
@@ -44,6 +44,7 @@  extern int SLOF_get_property(const char *node, const char *propname,
                              char **addr, int *len);
 extern int SLOF_get_keystroke(void);
 extern void SLOF_reset(void);
+extern unsigned long SLOF_get_vtpm_unit(void);
 
 #define offset_of(type, member) ((long) &((type *)0)->member)
 #define container_of(ptr, type, member) ({                      \
diff --git a/lib/Makefile b/lib/Makefile
index 1e8bb62..7369894 100644
--- a/lib/Makefile
+++ b/lib/Makefile
@@ -11,7 +11,7 @@ 
 # ****************************************************************************/
 
 SUBDIRS = libc libipmi libbootmsg libbases libnvram libelf libhvcall libvirtio \
-          libusb libveth libe1k libbcm libnet libbootmenu
+          libusb libveth libe1k libbcm libnet libbootmenu libtpm
 
 all:  subdirs
 
diff --git a/lib/libtpm/Makefile b/lib/libtpm/Makefile
new file mode 100644
index 0000000..ff19e1c
--- /dev/null
+++ b/lib/libtpm/Makefile
@@ -0,0 +1,50 @@ 
+# *****************************************************************************
+# * Copyright (c) 2015 IBM Corporation
+# * All rights reserved.
+# * This program and the accompanying materials
+# * are made available under the terms of the BSD License
+# * which accompanies this distribution, and is available at
+# * http://www.opensource.org/licenses/bsd-license.php
+# *
+# * Contributors:
+# *     IBM Corporation - initial implementation
+# ****************************************************************************/
+
+TOPCMNDIR ?= ../..
+
+CPPFLAGS = -I../libc/include $(CPUARCHDEF) -I$(INCLBRDDIR) \
+	   -I$(INCLCMNDIR) -I$(INCLCMNDIR)/$(CPUARCH) -I$(SLOFCMNDIR)
+CPPFLAGS += -I../libhvcall
+
+LDFLAGS = -nostdlib
+
+TARGET = ../libtpm.a
+
+
+all: $(TARGET)
+
+SRCS = tpm_drivers.c
+
+OBJS = $(SRCS:%.c=%.o)
+
+$(TARGET): $(OBJS)
+	$(AR) -rc $@ $(OBJS)
+	$(RANLIB) $@
+
+clean:
+	$(RM) $(TARGET) $(OBJS)
+
+distclean: clean
+	$(RM) Makefile.dep
+
+
+# Rules for creating the dependency file:
+depend:
+	$(RM) Makefile.dep
+	$(MAKE) Makefile.dep
+
+Makefile.dep: Makefile
+	$(CC) -M $(CPPFLAGS) $(CFLAGS) $(SRCS) $(SRCSS) > Makefile.dep
+
+# Include dependency file if available:
+-include Makefile.dep
diff --git a/lib/libtpm/Readme b/lib/libtpm/Readme
new file mode 100644
index 0000000..50f5e95
--- /dev/null
+++ b/lib/libtpm/Readme
@@ -0,0 +1,95 @@ 
+This directory hosts (v)TPM related code.
+
+Background:
+-----------
+
+A TPM is a crypto chip that is found in many systems. Besides it offering
+a secure key store, among other functionality, it is also used to implement
+'trusted boot'. This is realized by code in the firmware measuring parts of the
+firmware's code and data as well as system data, such as the boot block, and
+logging these measurements and storing (extending) them in the TPM's platform
+configuration register (PCR).
+
+The benefits of having a TPM (or vTPM) in a system are:
+
+- enablement of trusted boot; this allow us to eventually extend the chain of
+  trust from the hypervisor to the guests
+- enablement of attestation so that one can verify what software is running on
+  a machine (OpenPTS, OpenAttestation)
+- provides TPM functionality to VMs, which includes a standardized mechanism
+  to store keys and other blobs (Linux trusted keys, GNU TLS's TPM extensions)
+
+
+QEMU/KVM + SLOF support:
+------------------------
+
+To enable a vTPM with QEMU, the following steps need to be followed
+
+- build a recent version of libtpms
+
+  #> git clone https://github.com/stefanberger/libtpms
+  #> cd libtpms
+  #> ./autogen.sh --prefix=/usr --with-tpm2 --with-openssl
+
+  The following step may require to install dependencies
+
+  #> make
+  #> make check
+  #> make install
+
+- build swtpm
+
+  #> git clone https://github.com/stefanberger/swtpm
+  #> cd swtpm
+  #> ./autogen.sh --prefix=/usr --with-openssl
+
+  The following step may require to install dependencies
+
+  #> ./configure --prefix=/usr --with-openssl
+  #> make
+  #> make check
+  #> make install
+
+- build QEMU with vTPM support:
+
+  #> git clone https://github.com/stefanberger/qemu-tpm
+  #> cd qemu-tpm
+
+  The PPC64 patches are currently in the tpm-next+spapr.v8 branch
+  #> git checkout origin/tpm-next+spapr.v8 -b tpm-next+spapr.v8
+
+  The following step may require to install dependencies
+
+  #> ./configure --prefix=/usr --enable-kvm --target-list="ppc64-softmmu"
+  #> make
+  #> make install
+
+To start a QEMU VM with an attached vTPM (swtpm), run the following commands
+as 'root'. The following will setup the vTPM so that its state will be stored
+in /tmp/mytpm1. A unique directory for each VM instance with attached vTPM
+should be provided. Whenever QEMU is started, the swtpm has to be started
+before it. The './boot_rom.bin' represents SLOF with vTPM extensions built-in.
+
+  #> mkdir -p /tmp/mytpm1
+  #> swtpm socket --tpm2 --tpmstate dir=/tmp \
+       --ctrl type=unixio,path=/tmp/mytpm1/ctrl.sock
+
+  In another terminal:
+
+  #> qemu-system-ppc64 \
+      -enable-kvm \
+      -boot menu=on \
+      --chardev socket,id=chrtpm,path=/tmp/mytpm1/ctrl.sock \
+      -tpmdev emulator,id=tpm0,chardev=chrtpm \
+      -device tpm-spapr,tpmdev=tpm0 \
+      -vnc 0.0.0.0:2 \
+      [...]
+
+     Add hard disk and other parameters as needed.
+
+Notes:
+  - The Linux kernel in the VM must have the tpm_ibmvtpm module available
+    or built-in.
+
+  - 'swtpm_ioctl --unix /tmp/ctrl.sock -s' can be used to gracefully shut
+    down the vTPM.
diff --git a/lib/libtpm/tcgbios_int.h b/lib/libtpm/tcgbios_int.h
new file mode 100644
index 0000000..835bd36
--- /dev/null
+++ b/lib/libtpm/tcgbios_int.h
@@ -0,0 +1,30 @@ 
+/*****************************************************************************
+ * Copyright (c) 2015-2020 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials
+ * are made available under the terms of the BSD License
+ * which accompanies this distribution, and is available at
+ * http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Contributors:
+ *     IBM Corporation - initial implementation
+ *****************************************************************************/
+
+#ifndef TCGBIOS_INT_H
+#define TCGBIOS_INT_H
+
+#include <stdint.h>
+
+struct tpm_req_header {
+	uint16_t tag;
+	uint32_t totlen;
+	uint32_t ordinal;
+} __attribute__((packed));
+
+struct tpm_rsp_header {
+	uint16_t tag;
+	uint32_t totlen;
+	uint32_t errcode;
+} __attribute__((packed));
+
+#endif /* TCGBIOS_INT_H */
diff --git a/lib/libtpm/tpm_drivers.c b/lib/libtpm/tpm_drivers.c
new file mode 100644
index 0000000..c1335d7
--- /dev/null
+++ b/lib/libtpm/tpm_drivers.c
@@ -0,0 +1,466 @@ 
+/*****************************************************************************
+ * Copyright (c) 2015-2020 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials
+ * are made available under the terms of the BSD License
+ * which accompanies this distribution, and is available at
+ * http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Contributors:
+ *     IBM Corporation - initial implementation
+ *****************************************************************************/
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+
+#include "string.h"
+#include "helpers.h"
+#include "byteorder.h"
+#include "tcgbios_int.h"
+#include "tpm_drivers.h"
+#include "libhvcall.h"
+#include "paflof.h"
+
+#undef PAPR_VTPM_DEBUG
+//#define PAPR_VTPM_DEBUG
+#ifdef PAPR_VTPM_DEBUG
+#define dprintf(_x ...) do { printf("VTPM CRQ: " _x); } while(0)
+#else
+#define dprintf(_x ...)
+#endif
+
+#define MIN(a, b) ((a) > (b) ? (b) : (a))
+
+/* layout of the command request queue for vTPM */
+struct crq {
+	uint8_t valid;
+	uint8_t msg;
+	uint16_t len;
+	uint32_t data;
+	uint64_t reserved;
+} __attribute__((packed));
+
+#define PAPR_VTPM_INIT_CRQ_COMMAND      0xC0
+#define PAPR_VTPM_VALID_COMMAND         0x80
+#define PAPR_VTPM_MSG_RESULT            0x80
+
+/* crq.msg request types when crq.valid = PAPR_VTPM_INIT_CRQ_COMMAND */
+#define PAPR_VTPM_INIT_CRQ_RESULT       0x1
+
+/* crq.msg request types when crq.valid = PAPR_VTPM_VALID_COMMAND */
+#define PAPR_VTPM_GET_VERSION           0x1
+#define PAPR_VTPM_TPM_COMMAND           0x2
+#define PAPR_VTPM_GET_RTCE_BUFFER_SIZE  0x3
+
+#define TPM2_DEFAULT_DURATION_SHORT       750000 /* us */
+#define TPM2_DEFAULT_DURATION_MEDIUM     2000000 /* us */
+#define TPM2_DEFAULT_DURATION_LONG       2000000 /* us */
+
+static const uint32_t tpm2_durations[3] = {
+	TPM2_DEFAULT_DURATION_SHORT,
+	TPM2_DEFAULT_DURATION_MEDIUM,
+	TPM2_DEFAULT_DURATION_LONG,
+};
+
+#define QUEUE_SIZE 4096
+
+/* state of the PAPR CRQ VTPM driver */
+static struct spapr_vtpm_driver_state {
+	/* whether it driver been initialized */
+	bool initialized;
+
+	/* unit number */
+	unsigned long unit;
+
+	/* CRQ queue address and size */
+	unsigned char *qaddr;
+	unsigned long qsize;
+
+	/* current q_entry */
+	unsigned int curr_q_entry;
+
+	/* current response CRQ */
+	struct crq *response;
+
+	/* power firmware defined state and error code */
+	vtpm_drv_state driver_state;
+	vtpm_drv_error driver_error;
+
+	/* size of buffer supported by hypervisor */
+	unsigned int buffer_size;
+
+        /* buffer for commands and responses */
+        char *buffer;
+
+	/* version of the TPM we talk to -- from CRQ message */
+	uint32_t tpm_version;
+} spapr_vtpm = {
+	.qsize = QUEUE_SIZE,
+	.driver_state = VTPM_DRV_STATE_INVALID,
+	.driver_error = VTPM_DRV_ERROR_NO_FAILURE,
+};
+
+static void vtpm_drv_state_set(vtpm_drv_state s, vtpm_drv_error e)
+{
+	spapr_vtpm.driver_state = s;
+	spapr_vtpm.driver_error = e;
+}
+
+static vtpm_drv_error vtpm_drv_error_get(void)
+{
+	return spapr_vtpm.driver_error;
+}
+
+static struct crq* get_crq(void *qaddr, unsigned long q_entry)
+{
+	return &((struct crq *)qaddr)[q_entry];
+}
+
+/*
+ * Get the crq where the response will be found. This
+ * function will clear the CRQ's valid field and advance
+ * the entry counter to the next entry.
+ */
+static struct crq *get_response_crq(void)
+{
+	struct crq *crq;
+
+	dprintf("curr_q_entry = %d\n", spapr_vtpm.curr_q_entry);
+
+	crq = get_crq(spapr_vtpm.qaddr, spapr_vtpm.curr_q_entry);
+	memset(crq, 0, sizeof(*crq));
+
+	spapr_vtpm.curr_q_entry += 1;
+	if (spapr_vtpm.curr_q_entry == (spapr_vtpm.qsize / sizeof(struct crq)))
+		spapr_vtpm.curr_q_entry = 0;
+
+	return crq;
+}
+
+/*
+ * Send a message via CRQ and wait for the response
+ */
+static bool spapr_send_crq_and_wait(unsigned long unit,
+				    struct crq *crq,
+				    struct crq **response,
+				    unsigned timeout,
+				    vtpm_drv_state state1,
+				    vtpm_drv_state state2)
+{
+	long rc;
+	unsigned i;
+
+	*response = get_response_crq();
+
+	vtpm_drv_state_set(state1, VTPM_DRV_ERROR_NO_FAILURE);
+
+	rc = hv_send_crq(unit, (uint64_t *)crq);
+	if (rc != H_SUCCESS) {
+		vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
+				   VTPM_DRV_ERROR_TPM_CRQ_ERROR);
+		return false;
+	}
+
+	vtpm_drv_state_set(state2, VTPM_DRV_ERROR_NO_FAILURE);
+
+	for (i = 0; i < timeout; i += 1000) {
+		if (((*response)->valid & PAPR_VTPM_MSG_RESULT))
+			return true;
+		SLOF_usleep(1000);
+	}
+
+	vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
+			   VTPM_DRV_ERROR_WAIT_TIMEOUT);
+
+	dprintf("Received no response from CRQ\n");
+	return false;
+}
+
+/*
+ * Get parameters from the CRQ
+ */
+static bool spapr_vtpm_get_params(void)
+{
+	struct crq crq, *response;
+	static bool completed = false; /* only once */
+
+	if (completed)
+		return true;
+
+	/* get the TPM version */
+	crq.valid = PAPR_VTPM_VALID_COMMAND;
+	crq.msg = PAPR_VTPM_GET_VERSION;
+
+	if (!spapr_send_crq_and_wait(spapr_vtpm.unit, &crq, &response, 10,
+				     VTPM_DRV_STATE_SEND_GET_VERSION,
+				     VTPM_DRV_STATE_WAIT_VERSION)) {
+		printf("%s: Failure getting TPM version from CRQ\n", __func__);
+		return false;
+	}
+
+	vtpm_drv_state_set(VTPM_DRV_STATE_CHECK_VERSION,
+			   VTPM_DRV_ERROR_NO_FAILURE);
+
+	spapr_vtpm.tpm_version = be32_to_cpu(response->data);
+	dprintf("TPM backend version: %d\n", spapr_vtpm.tpm_version);
+
+	/* get the TPM's buffer size */
+	crq.valid = PAPR_VTPM_VALID_COMMAND;
+	crq.msg = PAPR_VTPM_GET_RTCE_BUFFER_SIZE;
+
+	if (!spapr_send_crq_and_wait(spapr_vtpm.unit, &crq, &response, 10,
+				     VTPM_DRV_STATE_SEND_BUFSIZE_REQ,
+				     VTPM_DRV_STATE_WAIT_BUFSIZE)) {
+		printf("%s: Failure getting RTCE buffer size from CRQ\n",
+		       __func__);
+		return false;
+	}
+
+	vtpm_drv_state_set(VTPM_DRV_STATE_ALLOC_RTCE_BUF,
+			   VTPM_DRV_ERROR_NO_FAILURE);
+
+	dprintf("RTCE buffer size: %u\n", be16_to_cpu(response->len));
+	spapr_vtpm.buffer_size = be16_to_cpu(response->len);
+	if (spapr_vtpm.buffer_size < 1024) {
+		printf("%s: RTCE buffer size of %u bytes is too small. "
+		       "Minimum is 1024 bytes.\n", __func__,
+		       spapr_vtpm.buffer_size);
+		vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
+				   VTPM_DRV_ERROR_BAD_RTCE_SIZE);
+		return false;
+	}
+	spapr_vtpm.buffer = SLOF_alloc_mem(spapr_vtpm.buffer_size);
+	if (!spapr_vtpm.buffer) {
+		printf("%s: Could not allocate buffer of size %u.\n",
+		       __func__, spapr_vtpm.buffer_size);
+		vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
+				   VTPM_DRV_ERROR_BAD_RTCE_SIZE);
+		return false;
+	}
+
+	completed = true;
+
+	return true;
+}
+
+static bool spapr_vtpm_activate(void)
+{
+	long rc;
+	struct crq crq, *response;
+
+	if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
+		printf("%s: CRQ: In failure mode\n", __func__);
+		return false;
+	}
+
+	vtpm_drv_state_set(VTPM_DRV_STATE_REG_CRQ,
+			   VTPM_DRV_ERROR_NO_FAILURE);
+
+	rc = hv_reg_crq(spapr_vtpm.unit, (unsigned long)spapr_vtpm.qaddr,
+			spapr_vtpm.qsize);
+	if (rc != H_SUCCESS) {
+		vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
+				   VTPM_DRV_ERROR_UNEXPECTED_REG_ERROR);
+		printf("%s: CRQ registration failed\n", __func__);
+		return false;
+	}
+
+	/* we always start with curr_q_entry 0 */
+	spapr_vtpm.curr_q_entry = 0;
+
+	if (!spapr_vtpm.initialized) {
+
+		crq.valid = PAPR_VTPM_INIT_CRQ_COMMAND;
+		crq.msg = PAPR_VTPM_INIT_CRQ_RESULT;
+
+		if (!spapr_send_crq_and_wait(spapr_vtpm.unit,
+					     &crq,
+					     &response,
+					     10,
+					     VTPM_DRV_STATE_SEND_INIT,
+					     VTPM_DRV_STATE_WAIT_INIT_COMP)) {
+			printf("%s: Initializing CRQ failed\n", __func__);
+			goto err_exit;
+		}
+		dprintf("Successfully initialized CRQ\n");
+
+		spapr_vtpm.initialized = true;
+	}
+
+	if (spapr_vtpm_get_params())
+		return true;
+
+err_exit:
+	hv_free_crq(spapr_vtpm.unit);
+	spapr_vtpm.unit = 0;
+
+	return false;
+}
+
+void spapr_vtpm_finalize(void)
+{
+	if (spapr_vtpm.unit)
+		hv_free_crq(spapr_vtpm.unit);
+}
+
+/*
+ * Check whether we have a CRQ underneath us; if we do, the CRQ will
+ * be left open.
+ */
+static bool spapr_vtpm_probe(void)
+{
+	if (!spapr_vtpm.qaddr) {
+		spapr_vtpm.qaddr = SLOF_alloc_mem(spapr_vtpm.qsize);
+		if (!spapr_vtpm.qaddr) {
+			printf("%s: Unable to allocate memory\n", __func__);
+			return false;
+		}
+		memset(spapr_vtpm.qaddr, 0, spapr_vtpm.qsize);
+
+		dprintf("getting FORTH vtpm-unit\n");
+		spapr_vtpm.unit = SLOF_get_vtpm_unit();
+		if (!spapr_vtpm.unit) {
+			printf("%s: Could not get valid vtpm-unit\n", __func__);
+			return false;
+		}
+	}
+
+	dprintf("vtpm_unit = %lx, buffer = %p\n",
+		spapr_vtpm.unit, spapr_vtpm.qaddr);
+
+	if (!spapr_vtpm_activate())
+		return false;
+
+	return true;
+}
+
+static bool spapr_vtpm_senddata(const uint8_t *const data, uint32_t len)
+{
+	struct crq crq;
+	long rc;
+
+	if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
+		printf("%s: VTPM CRQ: In failure mode\n", __func__);
+		return false;
+	}
+
+	if (len > spapr_vtpm.buffer_size) {
+		printf("%s: VTPM CRQ: Send buffer too large: %u > %u\n",
+		       __func__, len, spapr_vtpm.buffer_size);
+		return false;
+	}
+
+	spapr_vtpm.response = get_response_crq();
+	spapr_vtpm.response->data = (uint64_t)spapr_vtpm.buffer;
+	/* response CRQ has been set and valid field cleared */
+
+	crq.valid = PAPR_VTPM_VALID_COMMAND;
+	crq.msg = PAPR_VTPM_TPM_COMMAND;
+	crq.len = cpu_to_be16(len);
+	crq.data = (uint64_t)spapr_vtpm.buffer;
+	memcpy(spapr_vtpm.buffer, data, MIN(len, spapr_vtpm.buffer_size));
+
+	vtpm_drv_state_set(VTPM_DRV_STATE_SEND_TPM_CMD,
+			   VTPM_DRV_ERROR_NO_FAILURE);
+
+	rc = hv_send_crq(spapr_vtpm.unit, (uint64_t *)&crq);
+
+	if (rc == H_SUCCESS) {
+		vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_TPM_RSP,
+				   VTPM_DRV_ERROR_NO_FAILURE);
+	} else {
+		vtpm_drv_state_set(VTPM_DRV_STATE_WAIT_INIT,
+				   VTPM_DRV_ERROR_UNEXPECTED_SEND_ERROR);
+	}
+
+	return (rc == H_SUCCESS);
+}
+
+static bool spapr_vtpm_waitresponseready(enum tpm_duration_type to_t)
+{
+	uint32_t timeout = tpm2_durations[to_t];
+	int i;
+
+	if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
+		printf("%s: VTPM CRQ: In failure mode\n", __func__);
+		return false;
+	}
+
+	/* response CRQ has been set */
+
+	for (i = 0; i < timeout; i += 1000) {
+		if (spapr_vtpm.response->valid & PAPR_VTPM_MSG_RESULT) {
+			/* TPM responded: move to Send tpm-cmd state */
+			vtpm_drv_state_set(VTPM_DRV_STATE_SEND_TPM_CMD,
+					   VTPM_DRV_ERROR_NO_FAILURE);
+			dprintf("Received response to TPM command\n");
+			return true;
+		}
+		SLOF_usleep(1000);
+	}
+
+	vtpm_drv_state_set(VTPM_DRV_STATE_FAILURE,
+			   VTPM_DRV_ERROR_WAIT_TIMEOUT);
+
+	dprintf("Received NO response to TPM command");
+
+	return false;
+}
+
+static bool spapr_vtpm_readresponse(uint8_t *buffer, uint32_t *len)
+{
+	uint32_t length;
+
+	if (vtpm_drv_error_get() != VTPM_DRV_ERROR_NO_FAILURE) {
+		printf("%s: VTPM CRQ: In failure mode\n", __func__);
+		return false;
+	}
+
+	/* response CRQ has been set */
+	length = MIN(*len, be32_to_cpu(spapr_vtpm.response->len));
+
+	memcpy(buffer, (void *)(uint64_t)spapr_vtpm.response->data, length);
+
+	dprintf("Length of copied response: %d\n", length);
+
+	spapr_vtpm.response = NULL;
+	*len = length;
+
+	return true;
+}
+
+vtpm_drv_error spapr_vtpm_get_error(void)
+{
+	return vtpm_drv_error_get();
+}
+
+void spapr_vtpm_set_error(vtpm_drv_error errcode)
+{
+	spapr_vtpm.driver_error = errcode;
+}
+
+/**** higher layer interface ****/
+
+bool spapr_is_vtpm_present(void)
+{
+	bool rc = false;
+
+	if (spapr_vtpm_probe()) {
+		rc = true;
+	}
+
+	return rc;
+}
+
+int tpmhw_transmit(uint8_t locty, struct tpm_req_header *req,
+                   void *respbuffer, uint32_t *respbufferlen,
+                   enum tpm_duration_type to_t)
+{
+	if (!spapr_vtpm_senddata((uint8_t *)req, be32_to_cpu(req->totlen)) ||
+	    !spapr_vtpm_waitresponseready(to_t) ||
+	    !spapr_vtpm_readresponse(respbuffer, respbufferlen) ||
+	    *respbufferlen < sizeof(struct tpm_rsp_header))
+		return -1;
+	return 0;
+}
diff --git a/lib/libtpm/tpm_drivers.h b/lib/libtpm/tpm_drivers.h
new file mode 100644
index 0000000..1de1532
--- /dev/null
+++ b/lib/libtpm/tpm_drivers.h
@@ -0,0 +1,82 @@ 
+/*****************************************************************************
+ * Copyright (c) 2015-2020 IBM Corporation
+ * All rights reserved.
+ * This program and the accompanying materials
+ * are made available under the terms of the BSD License
+ * which accompanies this distribution, and is available at
+ * http://www.opensource.org/licenses/bsd-license.php
+ *
+ * Contributors:
+ *     IBM Corporation - initial implementation
+ *****************************************************************************/
+
+#ifndef TPM_DRIVERS_H
+#define TPM_DRIVERS_H
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <unistd.h>
+
+#include "tcgbios_int.h"
+
+enum tpm_duration_type {
+	TPM_DURATION_TYPE_SHORT = 0,
+	TPM_DURATION_TYPE_MEDIUM,
+	TPM_DURATION_TYPE_LONG,
+};
+
+/* firmware driver states */
+typedef enum {
+	VTPM_DRV_STATE_INVALID = 0,
+	VTPM_DRV_STATE_INIT_CALLED = 1,
+	VTPM_DRV_STATE_REG_CRQ = 2,
+	VTPM_DRV_STATE_WAIT_INIT = 3,
+	VTPM_DRV_STATE_SEND_INIT = 4,
+	VTPM_DRV_STATE_FAILURE = 5,
+	VTPM_DRV_STATE_WAIT_INIT_COMP = 6,
+	VTPM_DRV_STATE_SEND_INIT_COMP = 7,
+	VTPM_DRV_STATE_SEND_GET_VERSION = 8,
+	VTPM_DRV_STATE_WAIT_VERSION = 9,
+	VTPM_DRV_STATE_CHECK_VERSION = 10,
+	VTPM_DRV_STATE_SEND_BUFSIZE_REQ = 11,
+	VTPM_DRV_STATE_WAIT_BUFSIZE = 12,
+	VTPM_DRV_STATE_ALLOC_RTCE_BUF = 13,
+	VTPM_DRV_STATE_SEND_TPM_CMD = 14,
+	VTPM_DRV_STATE_WAIT_TPM_RSP = 15,
+} vtpm_drv_state;
+
+/* firmware driver errors */
+typedef enum {
+	VTPM_DRV_ERROR_NO_FAILURE = -1,
+	VTPM_DRV_ERROR_NOT_FOUND_TIMEOUT = 0,
+	VTPM_DRV_ERROR_UNEXPECTED_REG_ERROR = 1,
+	VTPM_DRV_ERROR_PARTNER_FAILED = 2,
+	VTPM_DRV_ERROR_UNEXPECTED_TSP_ERROR = 3,
+	VTPM_DRV_ERROR_TPM_PROTOCOL_ERROR = 4,
+	VTPM_DRV_ERROR_WAIT_TIMEOUT = 5,
+	VTPM_DRV_ERROR_UNEXPECTED_SEND_ERROR = 6,
+	VTPM_DRV_ERROR_CRQ_OPEN_FAIL = 7,
+	VTPM_DRV_ERROR_BAD_STATE = 8,
+	VTPM_DRV_ERROR_TPM_FAIL = 9,
+	VTPM_DRV_ERROR_TPM_CRQ_ERROR = 10,
+	VTPM_DRV_ERROR_BAD_VERSION = 11,
+	VTPM_DRV_ERROR_BAD_RTCE_SIZE = 12,
+	VTPM_DRV_ERROR_SML_FAILURE = 13,
+	VTPM_DRV_ERROR_SML_HANDED_OVER = 14,
+} vtpm_drv_error;
+
+/* the max. buffer size by the external TPM is 4k */
+#define PAPR_VTPM_MAX_BUFFER_SIZE       4096
+
+/* exported functions */
+bool spapr_is_vtpm_present(void);
+void spapr_vtpm_finalize(void);
+vtpm_drv_error spapr_vtpm_get_error(void);
+void spapr_vtpm_set_error(vtpm_drv_error errcode);
+
+struct tpm_req_header;
+int tpmhw_transmit(uint8_t locty, struct tpm_req_header *req,
+                   void *respbuffer, uint32_t *respbufferlen,
+                   enum tpm_duration_type to_t);
+
+#endif /* TPM_DRIVERS_H */
diff --git a/slof/helpers.c b/slof/helpers.c
index 9d37bc3..64023b2 100644
--- a/slof/helpers.c
+++ b/slof/helpers.c
@@ -235,3 +235,9 @@  void SLOF_reset(void)
 {
 	forth_eval("reset-all");
 }
+
+unsigned long SLOF_get_vtpm_unit(void)
+{
+	forth_eval("vtpm-unit");
+	return forth_pop();
+}