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 | 239 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ vl.c | 12 ++ vl.h | 4 4 files changed, 258 insertions(+) --- 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) --- /dev/null +++ b/hw/tpm.c @@ -0,0 +1,240 @@ +/* + * TPM emulation + * Copyright (c) 2007 Thomas Bleher + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +/* + * 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. + * + * The two enums below were copied from the Linux Kernel source code. Other than + * that, no code was re-used. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#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); +} + --- 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 */