A separate thread is used for I/O to the host TPM device because the Linux TPM driver does not allow non-blocking I/O.
Signed-off-by: Andreas Niederl <andreas.nied...@iaik.tugraz.at> --- Makefile.objs | 5 +- hw/tpm_backend.c | 1 + hw/tpm_host_backend.c | 282 +++++++++++++++++++++++++++++++++++++++++++++++++ hw/tpm_int.h | 3 + 4 files changed, 290 insertions(+), 1 deletions(-) create mode 100644 hw/tpm_host_backend.c diff --git a/Makefile.objs b/Makefile.objs index 6c78453..55fd6b5 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -284,7 +284,10 @@ hw-obj-$(CONFIG_VIRTFS) += virtio-9p-local.o virtio-9p-xattr.o hw-obj-$(CONFIG_VIRTFS) += virtio-9p-xattr-user.o virtio-9p-posix-acl.o # TPM passthrough device -hw-obj-$(CONFIG_TPM) += tpm_tis.o tpm_backend.o +hw-obj-$(CONFIG_TPM) += tpm_tis.o tpm_backend.o tpm_host_backend.o +ifndef CONFIG_THREAD +common-obj-$(CONFIG_TPM) += qemu-thread.o +endif ###################################################################### # libdis diff --git a/hw/tpm_backend.c b/hw/tpm_backend.c index b87c089..41dbfd4 100644 --- a/hw/tpm_backend.c +++ b/hw/tpm_backend.c @@ -40,6 +40,7 @@ typedef struct { } TPMDriverTable; static const TPMDriverTable driver_table[] = { + { .name = "host", .open = qemu_tpm_host_open }, }; int qemu_tpm_add(QemuOpts *opts) { diff --git a/hw/tpm_host_backend.c b/hw/tpm_host_backend.c new file mode 100644 index 0000000..4ae9deb --- /dev/null +++ b/hw/tpm_host_backend.c @@ -0,0 +1,282 @@ +/* + * tpm_host_backend.c - TPM host passthrough backend driver + * + * Copyright (C) 2011 IAIK, Graz University of Technology + * + * Author: Andreas Niederl <andreas.nied...@iaik.tugraz.at> + * + * A custom thread is used for asynchronous I/O to the host TPM device + * because the Linux TPM driver does not allow non-blocking I/O. + * + * 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 <errno.h> +#include <signal.h> + +#include "qemu-common.h" +#include "qemu-thread.h" + +#include "hw/tpm_int.h" + + +typedef struct { + QemuThread id; + QemuMutex lock; + QemuCond send_command; +} TPMThread; + +#define STATUS_DONE (1 << 1) +#define STATUS_IN_PROGRESS (1 << 0) +#define STATUS_IDLE 0 + +typedef struct { + TPMDriver common; + + TPMThread thread; + + uint8_t send_status; + uint8_t recv_status; + + int32_t send_len; + int32_t recv_len; + + int fd; +} TPMHostDriver; + +static int tpm_host_send(TPMDriver *drv, uint8_t locty, uint32_t len) +{ + TPMHostDriver *hdrv = DO_UPCAST(TPMHostDriver, common, drv); + int n = 0; + + drv->locty = locty; + + qemu_mutex_lock(&hdrv->thread.lock); + switch (hdrv->send_status) { + case STATUS_IN_PROGRESS: + break; + case STATUS_IDLE: + hdrv->send_len = len; + hdrv->recv_len = TPM_MAX_PKT; + /* asynchronous send */ + n = 1; + qemu_cond_signal( &hdrv->thread.send_command); + break; + case STATUS_DONE: + n = hdrv->send_len; + hdrv->send_status = STATUS_IDLE; + break; + default: + n = -1; + fprintf(stderr, + "tpm host backend: internal error on send status %d\n", + hdrv->send_status); + break; + } + qemu_mutex_unlock(&hdrv->thread.lock); + + return n; +} + +static int tpm_host_recv(TPMDriver *drv, uint8_t locty, uint32_t len) +{ + TPMHostDriver *hdrv = DO_UPCAST(TPMHostDriver, common, drv); + int n = 0; + + drv->locty = locty; + + qemu_mutex_lock(&hdrv->thread.lock); + switch (hdrv->recv_status) { + case STATUS_IN_PROGRESS: + break; + case STATUS_IDLE: + break; + case STATUS_DONE: + hdrv->recv_status = STATUS_IDLE; + n = hdrv->recv_len; + break; + default: + n = -1; + fprintf(stderr, + "tpm host backend: internal error on recv status %d\n", + hdrv->recv_status); + break; + } + qemu_mutex_unlock(&hdrv->thread.lock); + + return n; +} + + +/* borrowed from qemu-char.c */ +static int unix_write(int fd, const uint8_t *buf, uint32_t len) +{ + int ret, len1; + + len1 = len; + while (len1 > 0) { + ret = write(fd, buf, len1); + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -1; + } else if (ret == 0) { + break; + } else { + buf += ret; + len1 -= ret; + } + } + return len - len1; +} + +static int unix_read(int fd, uint8_t *buf, uint32_t len) +{ + int ret, len1; + uint8_t *buf1; + + len1 = len; + buf1 = buf; + while ((len1 > 0) && (ret = read(fd, buf1, len1)) != 0) { + if (ret < 0) { + if (errno != EINTR && errno != EAGAIN) + return -1; + } else { + buf1 += ret; + len1 -= ret; + } + } + return len - len1; +} + +static void die2(int err, const char *what) +{ + fprintf(stderr, "%s failed: %s\n", what, strerror(err)); + abort(); +} + +static void die(const char *what) +{ + die2(errno, what); +} + +static void *tpm_host_thread(void *opaque) +{ + TPMHostDriver *drv = opaque; + TPMDriver *s = &drv->common; + sigset_t set; + uint32_t tpm_ret; + int ret; + + /* block all signals */ + if (sigfillset(&set)) { + die("sigfillset"); + } + if (sigprocmask(SIG_BLOCK, &set, NULL)) { + die("sigprocmask"); + } + + qemu_mutex_lock(&drv->thread.lock); + while (1) { + qemu_cond_wait(&drv->thread.send_command, &drv->thread.lock); + drv->send_status = STATUS_IN_PROGRESS; + qemu_mutex_unlock(&drv->thread.lock); + + DSHOW_BUFF(s->buf, "To TPM"); + + ret = unix_write(drv->fd, s->buf, drv->send_len); + + qemu_mutex_lock(&drv->thread.lock); + drv->send_len = ret; + drv->send_status = STATUS_DONE; + + if (ret < 0) { + fprintf(stderr, "Error: while transmitting data to host tpm" + ": %s (%i)\n", + strerror(errno), errno); + continue; + } + + drv->recv_status = STATUS_IN_PROGRESS; + qemu_mutex_unlock(&drv->thread.lock); + + ret = unix_read(drv->fd, s->buf, drv->recv_len); + + qemu_mutex_lock(&drv->thread.lock); + drv->recv_len = ret; + drv->recv_status = STATUS_DONE; + drv->send_status = STATUS_IDLE; + + if (ret < 0) { + fprintf(stderr, "Error: while reading data from host tpm" + ": %s (%i)\n", + strerror(errno), errno); + continue; + } + + DSHOW_BUFF(s->buf, "From TPM"); + + tpm_ret = (s->buf[8])*256 + s->buf[9]; + if (tpm_ret) { + DPRINTF("tpm command failed with error %d\n", tpm_ret); + } else { + DPRINTF("tpm command succeeded\n"); + } + } + + return NULL; +} + + +TPMDriver *qemu_tpm_host_open(QemuOpts *opts) +{ + TPMDriver *drv = NULL; + TPMHostDriver *hdrv = NULL; + TPMThread *thread = NULL; + char *path = NULL; + int fd = -1; + + hdrv = qemu_mallocz(sizeof(TPMHostDriver)); + memset(hdrv, 0, sizeof(TPMHostDriver)); + drv = &hdrv->common; + + /* methods */ + drv->send = tpm_host_send; + drv->recv = tpm_host_recv; + + /* file open */ + if (qemu_opt_get(opts, "path") == NULL) { + fprintf(stderr, "tpm: No path specified.\n"); + goto fail; + } + + path = qemu_strdup(qemu_opt_get(opts, "path")); + fd = open(path, O_RDWR); + if (fd < 0) { + fprintf(stderr, "Cannot open %s: %s (%i)\n", + path, strerror(errno), errno); + goto fail; + } + hdrv->fd = fd; + + thread = &hdrv->thread; + qemu_mutex_init(&thread->lock); + qemu_cond_init( &thread->send_command); + + qemu_thread_create(&thread->id, &tpm_host_thread, hdrv); + + return drv; + +fail: + if (fd >= 0) { + close(fd); + } + qemu_free(hdrv); + return NULL; +} + + diff --git a/hw/tpm_int.h b/hw/tpm_int.h index 7869a81..71a71f4 100644 --- a/hw/tpm_int.h +++ b/hw/tpm_int.h @@ -36,6 +36,9 @@ struct TPMDriver { TPMDriver *tpm_get_driver(const char *id); +TPMDriver *qemu_tpm_host_open(QemuOpts *opts); + + #define TPM_MAX_PKT 4096 #define TPM_MAX_PATH 4096 -- 1.7.4.1