qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH] Add TPM support


From: Thomas Bleher
Subject: [Qemu-devel] [PATCH] Add TPM support
Date: Wed, 31 Oct 2007 13:06:36 +0100
User-agent: Mutt/1.5.15+20070412 (2007-04-11)

This patch adds support for an Atmel TPM chip.

Background: TPMs are rather complex chips, supporting many commands and
implementing complex crypto protocols like Direct Anonymous Attestation
(DAA). Therefore, this patch does not directly implement a TPM chip, but
instead utilizes the TPM emulator project (http://tpm-emulator.berlios.de/).
The TPM emulator can be run as a daemon, communicating through a unix domain
socket.

This patch adds a "-tpm path" parameter to qemu, where "path" is the unix
domain socket of the TPM emulator. If the parameter is given, the chip is
registered in the emulated system. Otherwise, behaviour is unchanged.

The interface presented inside qemu is that of an Atmel TPM chip, simply
because there is a Linux driver for this chip and the interface is very
simple. I do not own any TPM chip, therefore the interface was written
purely by looking at the Linux driver.

Use case: This patch makes it possible to experiment with software like IBMs
Integrity Measurement Architecture (IMA), without having an actual TPM (this
patch was developed for a demonstration involving IMA, among other things).
It should also be possible to use Microsofts BitLocker technology, although
this hasn't been tested yet.

---
 Makefile.target |    3 +
 hw/tpm.c        |  219 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 vl.c            |   12 +++
 vl.h            |    4 +
 4 files changed, 238 insertions(+), 0 deletions(-)
 create mode 100644 hw/tpm.c

diff --git a/Makefile.target b/Makefile.target
index 33dc285..3665e82 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -460,6 +460,9 @@ VL_OBJS += ne2000.o
 VL_OBJS += pcnet.o
 VL_OBJS += rtl8139.o
 
+# TPM device
+VL_OBJS += tpm.o
+
 ifeq ($(TARGET_BASE_ARCH), i386)
 # Hardware support
 VL_OBJS+= ide.o pckbd.o ps2.o vga.o $(SOUND_HW) dma.o $(AUDIODRV)
diff --git a/hw/tpm.c b/hw/tpm.c
new file mode 100644
index 0000000..eaebd46
--- /dev/null
+++ b/hw/tpm.c
@@ -0,0 +1,219 @@
+/*
+ * TPM emulation
+ * Written by Thomas Bleher <address@hidden>.
+ *
+ * This driver emulates a TPM chip. TPM chips are quite complex, and a TPM
+ * emulator already exists, therefore this driver just connects to this
+ * emulator and forwards all the data. For the TPM emulator project, see
+ * http://tpm-emulator.berlios.de/
+ *
+ * The author does not own any TPM chip himself, so the Linux Kernel driver for
+ * Atmel TPM chips was taken as a reference. The code works fine with the Linux
+ * driver, but no tests have been done on other operating systems.
+ *
+ * Some structures are copied from the Linux Kernel source code.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <unistd.h>
+#include "vl.h"
+
+#define TPM_ADDR 0x4E
+/* just choose a free port */
+#define TPM_PORT_LO 0x00
+#define TPM_PORT_HI 0x67
+#define TPM_PORT ((TPM_PORT_HI << 8) | TPM_PORT_LO)
+
+/* write status bits */
+enum tpm_atmel_write_status {
+        ATML_STATUS_ABORT = 0x01,
+        ATML_STATUS_LASTBYTE = 0x04
+};
+/* read status bits */
+enum tpm_atmel_read_status {
+        ATML_STATUS_BUSY = 0x01,
+        ATML_STATUS_DATA_AVAIL = 0x02,
+        ATML_STATUS_REWRITE = 0x04,
+        ATML_STATUS_READY = 0x08
+};
+
+typedef struct {
+    unsigned char last_init_input, last_status_input;
+    unsigned char send_data[2048], recv_data[2048];
+    unsigned int send_data_index, recv_data_pos, recv_data_length;
+    int tpm_fd;
+    struct pollfd tpm_poll;
+    int data_to_send, data_to_recv; /* boolean flags */
+} TPMState;
+
+static TPMState tpm_state;
+
+static uint32_t tpm_ioport_read_data(void *opaque, uint32_t addr)
+{
+    TPMState *s = opaque;
+    if (s->recv_data_pos >= s->recv_data_length) {
+        fprintf(stderr, "WARNING: Trying to read more data than is there!\n");
+        return 0;
+    }
+    return s->recv_data[s->recv_data_pos++];
+}
+
+static void tpm_ioport_write_data(void *opaque, uint32_t addr, uint32_t val)
+{
+    TPMState *s = opaque;
+    if (s->data_to_recv) {
+        fprintf(stderr, "WARNING: tpm received data when not supposed to! 
Discarding\n");
+        return;
+    }
+    s->send_data[s->send_data_index++] = (char) (val & 0xFF);
+    s->data_to_send = 1;
+}
+
+static uint32_t tpm_ioport_read_status(void *opaque, uint32_t addr)
+{
+    TPMState *s = opaque;
+    unsigned char status;
+    int result;
+
+    status = 0;
+/* This is a bit unclean: On the first status request we trigger sending
+ * the data to the tpm.
+ */
+    if (s->data_to_send) {
+        result = write(s->tpm_fd, s->send_data, s->send_data_index);
+        if (result < s->send_data_index) {
+            fprintf(stderr, "WARNING: Failed to write data to tpm!\n");
+            return ATML_STATUS_BUSY;
+        }
+        s->send_data_index = 0;
+        s->recv_data_pos = 0;
+        s->recv_data_length = 0;
+        s->data_to_send = 0;
+        s->data_to_recv = 1;
+    }
+    if (s->data_to_recv) {
+        if (poll(&(s->tpm_poll), 1, 0) > 0) {
+            result = read(s->tpm_fd, s->recv_data, 2048);
+            if (result < 6) { // a minimal packet is 6 bytes long
+                fprintf(stderr, "WARNING: Not enough data from tpm!\n");
+                return ATML_STATUS_BUSY;
+            }
+            s->recv_data_length = result;
+            s->recv_data_pos = 0;
+            s->data_to_recv = 0;
+        }
+    }
+
+    if (s->recv_data_length > s->recv_data_pos)
+        status |= ATML_STATUS_DATA_AVAIL;
+    if (s->data_to_recv)
+        status |= ATML_STATUS_BUSY;
+    if (!status)
+        status = ATML_STATUS_READY;
+
+    return status;
+}
+
+static void tpm_ioport_write_status(void *opaque, uint32_t addr, uint32_t val)
+{
+    TPMState *s = opaque;
+    s->last_status_input = (char) (val & 0xFF);
+    // TODO: If we receive a Cancel-message we should act on it.
+}
+
+static uint32_t tpm_ioport_read_init(void *opaque, uint32_t addr)
+{
+    TPMState *s = opaque;
+
+    switch (s->last_init_input) {
+    /* see atmel_verify_tpm11() in the linux kernel sources */
+        case 0:
+        case 1:
+            return 1;
+        case 4:
+            return 'A';
+        case 5:
+            return 'T';
+        case 6:
+            return 'M';
+        case 7:
+            return 'L';
+        case 8:
+            return TPM_PORT_LO;
+        case 9:
+            return TPM_PORT_HI;
+    }
+
+    return 0;
+}
+
+static void tpm_ioport_write_init(void *opaque, uint32_t addr, uint32_t val)
+{
+    TPMState *s = opaque;
+    s->last_init_input = (char) (val & 0xFF);
+}
+
+static int tpm_open_socket(const char* tpm_socket)
+{
+    int sock, res, len;
+    struct sockaddr_un addr;
+
+    sock = socket(PF_UNIX, SOCK_STREAM, 0);
+    if (sock < 0)
+        return -1;
+    addr.sun_family = AF_UNIX;
+    len = strlen(tpm_socket) + 1; // include terminating \0
+    if (len > sizeof(addr.sun_path)) {
+        errno = ENAMETOOLONG;
+        return -1;
+    }
+    strcpy(addr.sun_path, tpm_socket);
+    res = connect(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr_un));
+    if (res < 0)
+        return -1;
+    return sock;
+}
+
+/* should be called first, initializes all structures and connects to the 
external emulator */
+void tpm_configure(const char* tpm_socket)
+{
+    TPMState *s = &tpm_state;
+    s->last_init_input = 0;
+    s->last_status_input = 0;
+    s->send_data_index = 0;
+    s->recv_data_length = 0;
+    s->recv_data_pos = 0;
+    s->data_to_send = 0;
+    s->data_to_recv = 0;
+
+    s->tpm_fd = tpm_open_socket(tpm_socket);
+    if (s->tpm_fd == -1) {
+        perror("Failed to create connection to the TPM");
+        exit(1);
+    }
+
+    s->tpm_poll.fd = s->tpm_fd;
+    s->tpm_poll.events = POLLIN;
+}
+
+/* split of from tpm_configure() so the configuration can be called earlier */
+void tpm_register()
+{
+    TPMState *s = &tpm_state;
+    /* these are only for initialization */
+    register_ioport_write(TPM_ADDR,   1, 1, tpm_ioport_write_init, s);
+    register_ioport_read (TPM_ADDR+1, 1, 1, tpm_ioport_read_init, s);
+
+    /* real TPM interface */
+    register_ioport_write(TPM_PORT,   1, 1, tpm_ioport_write_data, s);
+    register_ioport_read (TPM_PORT,   1, 1, tpm_ioport_read_data, s);
+    register_ioport_write(TPM_PORT+1, 1, 1, tpm_ioport_write_status, s);
+    register_ioport_read (TPM_PORT+1, 1, 1, tpm_ioport_read_status, s);
+}
+
diff --git a/vl.c b/vl.c
index f9500a9..0084e63 100644
--- a/vl.c
+++ b/vl.c
@@ -218,6 +218,7 @@ int alt_grab = 0;
 unsigned int nb_prom_envs = 0;
 const char *prom_envs[MAX_PROM_ENVS];
 #endif
+int tpm_enabled = 0;
 
 #define TFR(expr) do { if ((expr) != -1) break; } while (errno == EINTR)
 
@@ -7107,6 +7108,8 @@ static void help(int exitcode)
 #endif
            "-clock          force the use of the given methods for timer 
alarm.\n"
            "                To see what timers are available use -clock help\n"
+           "-tpm path       enable TPM support inside qemu and connect to a 
tpm\n"
+           "                emulator on the given path\n"
            "\n"
            "During emulation, the following keys are useful:\n"
            "ctrl-alt-f      toggle full screen\n"
@@ -7207,6 +7210,7 @@ enum {
     QEMU_OPTION_prom_env,
     QEMU_OPTION_old_param,
     QEMU_OPTION_clock,
+    QEMU_OPTION_tpm,
 };
 
 typedef struct QEMUOption {
@@ -7313,6 +7317,7 @@ const QEMUOption qemu_options[] = {
     { "old-param", 0, QEMU_OPTION_old_param },
 #endif
     { "clock", HAS_ARG, QEMU_OPTION_clock },
+    { "tpm", HAS_ARG, QEMU_OPTION_tpm },
     { NULL },
 };
 
@@ -8097,6 +8102,10 @@ int main(int argc, char **argv)
             case QEMU_OPTION_clock:
                 configure_alarms(optarg);
                 break;
+            case QEMU_OPTION_tpm:
+                tpm_enabled = 1;
+                tpm_configure(optarg);
+                break;
             }
         }
     }
@@ -8427,6 +8436,9 @@ int main(int argc, char **argv)
         }
     }
 
+    if (tpm_enabled)
+        tpm_register();
+
     if (display_state.dpy_refresh) {
         display_state.gui_timer = qemu_new_timer(rt_clock, gui_update, 
&display_state);
         qemu_mod_timer(display_state.gui_timer, qemu_get_clock(rt_clock));
diff --git a/vl.h b/vl.h
index 6c98033..8d9f9e8 100644
--- a/vl.h
+++ b/vl.h
@@ -1716,4 +1716,8 @@ void readline_start(const char *prompt, int is_password,
 
 void kqemu_record_dump(void);
 
+/* tpm.c */
+void tpm_configure(const char* tpm_socket);
+void tpm_register();
+
 #endif /* VL_H */




reply via email to

[Prev in Thread] Current Thread [Next in Thread]