* [Qemu-devel] [PATCH V20 1/8] Support for TPM command line options
2013-01-18 16:02 [Qemu-devel] [PATCH V20 0/8] Qemu Trusted Platform Module (TPM) integration Stefan Berger
@ 2013-01-18 16:02 ` Stefan Berger
2013-02-01 15:33 ` Corey Bryant
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 2/8] Add TPM (frontend) hardware interface (TPM TIS) to QEMU Stefan Berger
` (6 subsequent siblings)
7 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2013-01-18 16:02 UTC (permalink / raw)
To: stefanb, qemu-devel, anthony; +Cc: andreas.niederl, mst
This patch adds support for TPM command line options.
The command line options supported here are
./qemu-... -tpmdev passthrough,path=<path to TPM device>,id=<id>
-device tpm-tis,tpmdev=<id>
and
./qemu-... -tpmdev ?
where the latter works similar to -soundhw ? and shows a list of
available TPM backends (for example 'passthrough').
Using the type parameter, the backend is chosen, i.e., 'passthrough' for the
passthrough driver. The interpretation of the other parameters along
with determining whether enough parameters were provided is pushed into
the backend driver, which needs to implement the interface function
'create' and return a TPMDriver structure if the VM can be started or 'NULL'
if not enough or bad parameters were provided.
Monitor support for 'info tpm' has been added. It for example prints the
following:
(qemu) info tpm
TPM devices:
tpm0: model=tpm-tis
\ tpm0: type=passthrough,path=/dev/tpm0
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
hmp-commands.hx | 2 +
hmp.c | 30 ++++++++
hmp.h | 1 +
hw/tpm_tis.h | 78 ++++++++++++++++++++
monitor.c | 8 ++
qapi-schema.json | 32 ++++++++
qemu-config.c | 20 +++++
qemu-options.hx | 33 +++++++++
qmp-commands.hx | 5 ++
tpm.c | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
tpm.h | 84 +++++++++++++++++++++
vl.c | 17 +++++
12 files changed, 527 insertions(+)
create mode 100644 hw/tpm_tis.h
create mode 100644 tpm.c
create mode 100644 tpm.h
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 010b8c9..b3a1005 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -1570,6 +1570,8 @@ show device tree
show qdev device model list
@item info roms
show roms
+@item info tpm
+show the TPM device
@end table
ETEXI
diff --git a/hmp.c b/hmp.c
index 180ba2b..8d63d03 100644
--- a/hmp.c
+++ b/hmp.c
@@ -628,6 +628,36 @@ void hmp_info_block_jobs(Monitor *mon)
}
}
+void hmp_info_tpm(Monitor *mon)
+{
+ TPMInfoList *info_list, *info;
+ Error *err = NULL;
+ unsigned int c = 0;
+
+ info_list = qmp_query_tpm(&err);
+ if (err) {
+ monitor_printf(mon, "TPM device not supported\n");
+ error_free(err);
+ return;
+ }
+
+ monitor_printf(mon, "TPM device:\n");
+
+ for (info = info_list; info; info = info->next) {
+ TPMInfo *ti = info->value;
+ monitor_printf(mon, " tpm%d: model=%s\n",
+ c, ti->model);
+ monitor_printf(mon, " \\ %s: type=%s%s%s%s%s\n",
+ ti->id, ti->type,
+ ti->has_path ? ",path=" : "",
+ ti->has_path ? ti->path : "",
+ ti->has_cancel_path ? ",cancel_path=" : "",
+ ti->has_cancel_path ? ti->cancel_path : "");
+ c++;
+ }
+ qapi_free_TPMInfoList(info_list);
+}
+
void hmp_quit(Monitor *mon, const QDict *qdict)
{
monitor_suspend(mon);
diff --git a/hmp.h b/hmp.h
index 0ab03be..df43f7d 100644
--- a/hmp.h
+++ b/hmp.h
@@ -36,6 +36,7 @@ void hmp_info_spice(Monitor *mon);
void hmp_info_balloon(Monitor *mon);
void hmp_info_pci(Monitor *mon);
void hmp_info_block_jobs(Monitor *mon);
+void hmp_info_tpm(Monitor *mon);
void hmp_quit(Monitor *mon, const QDict *qdict);
void hmp_stop(Monitor *mon, const QDict *qdict);
void hmp_system_reset(Monitor *mon, const QDict *qdict);
diff --git a/hw/tpm_tis.h b/hw/tpm_tis.h
new file mode 100644
index 0000000..7f6dcb9
--- /dev/null
+++ b/hw/tpm_tis.h
@@ -0,0 +1,78 @@
+/*
+ * tpm_tis.c - QEMU's TPM TIS interface emulator
+ *
+ * Copyright (C) 2006,2010,2011 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ * David Safford <safford@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Implementation of the TIS interface according to specs found at
+ * http://www.trustedcomputiggroup.org
+ *
+ */
+#ifndef HW_TPM_TIS_H
+#define HW_TPM_TIS_H
+
+#include "isa.h"
+#include "qemu-common.h"
+
+#define TPM_TIS_ADDR_BASE 0xFED40000
+
+#define TPM_TIS_NUM_LOCALITIES 5 /* per spec */
+#define TPM_TIS_LOCALITY_SHIFT 12
+#define TPM_TIS_NO_LOCALITY 0xff
+
+#define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
+
+#define TPM_TIS_IRQ 5
+
+#define TPM_TIS_BUFFER_MAX 4096
+
+
+typedef struct TPMSizedBuffer {
+ uint32_t size;
+ uint8_t *buffer;
+} TPMSizedBuffer;
+
+typedef enum {
+ TPM_TIS_STATE_IDLE = 0,
+ TPM_TIS_STATE_READY,
+ TPM_TIS_STATE_COMPLETION,
+ TPM_TIS_STATE_EXECUTION,
+ TPM_TIS_STATE_RECEPTION,
+} TPMTISState;
+
+/* locality data -- all fields are persisted */
+typedef struct TPMLocality {
+ TPMTISState state;
+ uint8_t access;
+ uint8_t sts;
+ uint32_t inte;
+ uint32_t ints;
+
+ uint16_t w_offset;
+ uint16_t r_offset;
+ TPMSizedBuffer w_buffer;
+ TPMSizedBuffer r_buffer;
+} TPMLocality;
+
+typedef struct TPMTISEmuState {
+ QEMUBH *bh;
+ uint32_t offset;
+ uint8_t buf[TPM_TIS_BUFFER_MAX];
+
+ uint8_t active_locty;
+ uint8_t aborting_locty;
+ uint8_t next_locty;
+
+ TPMLocality loc[TPM_TIS_NUM_LOCALITIES];
+
+ qemu_irq irq;
+ uint32_t irq_num;
+} TPMTISEmuState;
+
+#endif /* HW_TPM_TIS_H */
diff --git a/monitor.c b/monitor.c
index c0e32d6..558346c 100644
--- a/monitor.c
+++ b/monitor.c
@@ -47,6 +47,7 @@
#include "migration.h"
#include "kvm.h"
#include "acl.h"
+#include "tpm.h"
#include "qint.h"
#include "qfloat.h"
#include "qlist.h"
@@ -2736,6 +2737,13 @@ static mon_cmd_t info_cmds[] = {
.mhandler.info = do_trace_print_events,
},
{
+ .name = "tpm",
+ .args_type = "",
+ .params = "",
+ .help = "show the TPM device",
+ .mhandler.info = hmp_info_tpm,
+ },
+ {
.name = NULL,
},
};
diff --git a/qapi-schema.json b/qapi-schema.json
index 5dfa052..82f753b 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3017,3 +3017,35 @@
# Since: 1.3.0
##
{ 'command': 'nbd-server-stop' }
+
+##
+# @TPMInfo:
+#
+# Information about the TPM
+#
+# @model: The TPM frontend model, i.e., tpm-tis
+#
+# @id: The ID of the TPM
+#
+# @type: The type of TPM backend, i.e., passthrough
+#
+# @path: #optional Path to the TPM backend device
+#
+# @cancel_path: #optional Path to TPM backend device's cancel sysfs entry
+#
+# Since: 1.5.0
+##
+{ 'type': 'TPMInfo',
+ 'data': {'model': 'str', 'id': 'str', 'type': 'str', '*path': 'str',
+ '*cancel_path': 'str' } }
+
+##
+# @query-tpm
+#
+# Return information about the TPM device.
+#
+# Returns: @TPMInfo on success
+#
+# Since: 1.5.0
+##
+{ 'command': 'query-tpm', 'returns': ['TPMInfo'] }
diff --git a/qemu-config.c b/qemu-config.c
index 10d1ba4..59f605a 100644
--- a/qemu-config.c
+++ b/qemu-config.c
@@ -691,6 +691,25 @@ static QemuOptsList qemu_object_opts = {
},
};
+static QemuOptsList qemu_tpmdev_opts = {
+ .name = "tpmdev",
+ .implied_opt_name = "type",
+ .head = QTAILQ_HEAD_INITIALIZER(qemu_tpmdev_opts.head),
+ .desc = {
+ {
+ .name = "type",
+ .type = QEMU_OPT_STRING,
+ .help = "Type of TPM backend",
+ },
+ {
+ .name = "path",
+ .type = QEMU_OPT_STRING,
+ .help = "Persistent storage for TPM state",
+ },
+ { /* end of list */ }
+ },
+};
+
static QemuOptsList *vm_config_groups[32] = {
&qemu_drive_opts,
&qemu_chardev_opts,
@@ -709,6 +728,7 @@ static QemuOptsList *vm_config_groups[32] = {
&qemu_sandbox_opts,
&qemu_add_fd_opts,
&qemu_object_opts,
+ &qemu_tpmdev_opts,
NULL,
};
diff --git a/qemu-options.hx b/qemu-options.hx
index de43b1b..27a968e 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2158,6 +2158,39 @@ ETEXI
DEFHEADING()
+#ifdef CONFIG_TPM
+DEFHEADING(TPM device options:)
+
+DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
+ "-tpmdev [<type>],id=str[,option][,option][,...]\n",
+ QEMU_ARCH_ALL)
+STEXI
+
+The general form of a TPM device option is:
+@table @option
+
+@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
+@findex -tpmdev
+Backend type must be:
+
+The specific backend type will determine the applicable options.
+The @code{-tpmdev} options requires a @code{-device} option.
+
+Options to each backend are described below.
+
+Use ? to print all available TPM backend types.
+@example
+qemu -tpmdev ?
+@end example
+
+@end table
+
+ETEXI
+
+DEFHEADING()
+
+#endif
+
DEFHEADING(Linux/Multiboot boot specific:)
STEXI
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 5c692d0..915e042 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -2654,3 +2654,8 @@ EQMP
.args_type = "",
.mhandler.cmd_new = qmp_marshal_input_query_target,
},
+ {
+ .name = "query-tpm",
+ .args_type = "",
+ .mhandler.cmd_new = qmp_marshal_input_query_tpm,
+ },
diff --git a/tpm.c b/tpm.c
new file mode 100644
index 0000000..c9356b6
--- /dev/null
+++ b/tpm.c
@@ -0,0 +1,217 @@
+/*
+ * TPM configuration
+ *
+ * Copyright (C) 2011 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Based on net.c
+ */
+#include "config-host.h"
+
+#include "monitor.h"
+#include "qerror.h"
+#include "tpm.h"
+#include "qmp-commands.h"
+
+static QLIST_HEAD(, TPMBackend) tpm_backends =
+ QLIST_HEAD_INITIALIZER(tpm_backends);
+
+#ifdef CONFIG_TPM
+
+static const TPMDriverOps *be_drivers[] = {
+ NULL,
+};
+
+const TPMDriverOps *tpm_get_backend_driver(const char *type)
+{
+ int i;
+
+ for (i = 0; be_drivers[i] != NULL; i++) {
+ if (!strcmp(be_drivers[i]->type, type)) {
+ break;
+ }
+ }
+
+ return be_drivers[i];
+}
+
+/*
+ * Walk the list of available TPM backend drivers and display them on the
+ * screen.
+ */
+void tpm_display_backend_drivers(void)
+{
+ int i;
+
+ fprintf(stderr, "Supported TPM types (choose only one):\n");
+
+ for (i = 0; be_drivers[i] != NULL; i++) {
+ fprintf(stderr, "%12s %s\n",
+ be_drivers[i]->type, be_drivers[i]->desc());
+ }
+ fprintf(stderr, "\n");
+}
+
+/*
+ * Find the TPM with the given Id
+ */
+TPMBackend *qemu_find_tpm(const char *id)
+{
+ TPMBackend *drv;
+
+ QLIST_FOREACH(drv, &tpm_backends, list) {
+ if (!strcmp(drv->id, id)) {
+ return drv;
+ }
+ }
+
+ return NULL;
+}
+
+static int configure_tpm(QemuOpts *opts)
+{
+ const char *value;
+ const char *id;
+ const TPMDriverOps *be;
+ TPMBackend *drv;
+
+ if (!QLIST_EMPTY(&tpm_backends)) {
+ error_report("Only one TPM is allowed.\n");
+ return 1;
+ }
+
+ id = qemu_opts_id(opts);
+ if (id == NULL) {
+ qerror_report(QERR_MISSING_PARAMETER, "id");
+ return 1;
+ }
+
+ value = qemu_opt_get(opts, "type");
+ if (!value) {
+ qerror_report(QERR_MISSING_PARAMETER, "type");
+ tpm_display_backend_drivers();
+ return 1;
+ }
+
+ be = tpm_get_backend_driver(value);
+ if (be == NULL) {
+ qerror_report(QERR_INVALID_PARAMETER_VALUE, "type",
+ "a TPM backend type");
+ tpm_display_backend_drivers();
+ return 1;
+ }
+
+ drv = be->create(opts, id);
+ if (!drv) {
+ return 1;
+ }
+
+ QLIST_INSERT_HEAD(&tpm_backends, drv, list);
+
+ return 0;
+}
+
+static int tpm_init_tpmdev(QemuOpts *opts, void *dummy)
+{
+ return configure_tpm(opts);
+}
+
+/*
+ * Walk the list of TPM backend drivers that are in use and call their
+ * destroy function to have them cleaned up.
+ */
+void tpm_cleanup(void)
+{
+ TPMBackend *drv, *next;
+
+ QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) {
+ QLIST_REMOVE(drv, list);
+ drv->ops->destroy(drv);
+ }
+}
+
+/*
+ * Initialize the TPM. Process the tpmdev command line options describing the
+ * TPM backend.
+ */
+int tpm_init(void)
+{
+ if (qemu_opts_foreach(qemu_find_opts("tpmdev"),
+ tpm_init_tpmdev, NULL, 1) != 0) {
+ return -1;
+ }
+
+ atexit(tpm_cleanup);
+
+ return 0;
+}
+
+/*
+ * Parse the TPM configuration options.
+ * It is possible to pass an option '-tpmdev none' to not activate any TPM.
+ * To display all available TPM backends the user may use '-tpmdev ?'
+ */
+int tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
+{
+ QemuOpts *opts;
+
+ if (*optarg == '?') {
+ tpm_display_backend_drivers();
+ return -1;
+ }
+ opts = qemu_opts_parse(opts_list, optarg, 1);
+ if (!opts) {
+ return -1;
+ }
+ return 0;
+}
+
+#endif /* CONFIG_TPM */
+
+static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv)
+{
+ TPMInfo *res = g_new0(TPMInfo, 1);
+
+ res->model = g_strdup(drv->fe_model);
+ res->id = g_strdup(drv->id);
+ if (drv->path) {
+ res->path = g_strdup(drv->path);
+ res->has_path = true;
+ }
+ if (drv->cancel_path) {
+ res->cancel_path = g_strdup(drv->cancel_path);
+ res->has_cancel_path = true;
+ }
+ res->type = g_strdup(drv->ops->type);
+
+ return res;
+}
+
+/*
+ * Walk the list of active TPM backends and collect information about them
+ * following the schema description in qapi-schema.json.
+ */
+TPMInfoList *qmp_query_tpm(Error **errp)
+{
+ TPMBackend *drv;
+ TPMInfoList *info, *head = NULL, *cur_item = NULL;
+
+ QLIST_FOREACH(drv, &tpm_backends, list) {
+ info = g_new0(TPMInfoList, 1);
+ info->value = qmp_query_tpm_inst(drv);
+
+ if (!cur_item) {
+ head = cur_item = info;
+ } else {
+ cur_item->next = info;
+ cur_item = info;
+ }
+ }
+
+ return head;
+}
diff --git a/tpm.h b/tpm.h
new file mode 100644
index 0000000..8943b61
--- /dev/null
+++ b/tpm.h
@@ -0,0 +1,84 @@
+/*
+ * TPM configuration
+ *
+ * Copyright (C) 2011 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ */
+#ifndef QEMU_TPM_H
+#define QEMU_TPM_H
+
+#include "memory.h"
+#include "hw/tpm_tis.h"
+
+struct TPMDriverOps;
+typedef struct TPMDriverOps TPMDriverOps;
+
+typedef struct TPMBackend {
+ char *id;
+ const char *fe_model;
+ char *path;
+ char *cancel_path;
+ const TPMDriverOps *ops;
+
+ QLIST_ENTRY(TPMBackend) list;
+} TPMBackend;
+
+/* overall state of the TPM interface */
+typedef struct TPMState {
+ ISADevice busdev;
+ MemoryRegion mmio;
+
+ union {
+ TPMTISEmuState tis;
+ } s;
+
+ uint8_t locty_number;
+ TPMLocality *locty_data;
+
+ char *backend;
+ TPMBackend *be_driver;
+} TPMState;
+
+typedef void (TPMRecvDataCB)(TPMState *, uint8_t locty);
+
+struct TPMDriverOps {
+ const char *type;
+ /* get a descriptive text of the backend to display to the user */
+ const char *(*desc)(void);
+
+ TPMBackend *(*create)(QemuOpts *opts, const char *id);
+ void (*destroy)(TPMBackend *t);
+
+ /* initialize the backend */
+ int (*init)(TPMBackend *t, TPMState *s, TPMRecvDataCB *datacb);
+ /* start up the TPM on the backend */
+ int (*startup_tpm)(TPMBackend *t);
+ /* returns true if nothing will ever answer TPM requests */
+ bool (*had_startup_error)(TPMBackend *t);
+
+ size_t (*realloc_buffer)(TPMSizedBuffer *sb);
+
+ void (*deliver_request)(TPMBackend *t);
+
+ void (*reset)(TPMBackend *t);
+
+ void (*cancel_cmd)(TPMBackend *t);
+
+ bool (*get_tpm_established_flag)(TPMBackend *t);
+};
+
+#define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
+
+int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
+int tpm_init(void);
+void tpm_cleanup(void);
+TPMBackend *qemu_find_tpm(const char *id);
+void tpm_display_backend_drivers(void);
+const TPMDriverOps *tpm_get_backend_driver(const char *id);
+
+#endif /* QEMU_TPM_H */
diff --git a/vl.c b/vl.c
index a3ab384..2cbbc0b 100644
--- a/vl.c
+++ b/vl.c
@@ -139,6 +139,7 @@ int main(int argc, char **argv)
#include "blockdev.h"
#include "hw/block-common.h"
#include "block-migration.h"
+#include "tpm.h"
#include "dma.h"
#include "audio/audio.h"
#include "migration.h"
@@ -2938,6 +2939,13 @@ int main(int argc, char **argv, char **envp)
}
break;
}
+#ifdef CONFIG_TPM
+ case QEMU_OPTION_tpmdev:
+ if (tpm_config_parse(qemu_find_opts("tpmdev"), optarg) < 0) {
+ exit(1);
+ }
+ break;
+#endif
case QEMU_OPTION_mempath:
mem_path = optarg;
break;
@@ -3749,6 +3757,12 @@ int main(int argc, char **argv, char **envp)
exit(1);
}
+#ifdef CONFIG_TPM
+ if (tpm_init() < 0) {
+ exit(1);
+ }
+#endif
+
/* init the bluetooth world */
if (foreach_device_config(DEV_BT, bt_parse))
exit(1);
@@ -4001,6 +4015,9 @@ int main(int argc, char **argv, char **envp)
pause_all_vcpus();
net_cleanup();
res_free();
+#ifdef CONFIG_TPM
+ tpm_cleanup();
+#endif
return 0;
}
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [Qemu-devel] [PATCH V20 1/8] Support for TPM command line options
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 1/8] Support for TPM command line options Stefan Berger
@ 2013-02-01 15:33 ` Corey Bryant
0 siblings, 0 replies; 21+ messages in thread
From: Corey Bryant @ 2013-02-01 15:33 UTC (permalink / raw)
To: Stefan Berger, Luiz Capitulino
Cc: Joel Schopp, mst, qemu-devel, anthony, andreas.niederl
Thanks for the fixes since the last version. I have a few comments
below. Apologies for not catching some of these the first time through.
On 01/18/2013 11:02 AM, Stefan Berger wrote:
> This patch adds support for TPM command line options.
> The command line options supported here are
>
> ./qemu-... -tpmdev passthrough,path=<path to TPM device>,id=<id>
> -device tpm-tis,tpmdev=<id>
>
> and
>
> ./qemu-... -tpmdev ?
>
> where the latter works similar to -soundhw ? and shows a list of
> available TPM backends (for example 'passthrough').
>
> Using the type parameter, the backend is chosen, i.e., 'passthrough' for the
> passthrough driver. The interpretation of the other parameters along
> with determining whether enough parameters were provided is pushed into
> the backend driver, which needs to implement the interface function
> 'create' and return a TPMDriver structure if the VM can be started or 'NULL'
> if not enough or bad parameters were provided.
>
> Monitor support for 'info tpm' has been added. It for example prints the
> following:
>
> (qemu) info tpm
> TPM devices:
> tpm0: model=tpm-tis
> \ tpm0: type=passthrough,path=/dev/tpm0
>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> ---
> hmp-commands.hx | 2 +
> hmp.c | 30 ++++++++
> hmp.h | 1 +
> hw/tpm_tis.h | 78 ++++++++++++++++++++
> monitor.c | 8 ++
> qapi-schema.json | 32 ++++++++
> qemu-config.c | 20 +++++
> qemu-options.hx | 33 +++++++++
> qmp-commands.hx | 5 ++
> tpm.c | 217 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
> tpm.h | 84 +++++++++++++++++++++
> vl.c | 17 +++++
> 12 files changed, 527 insertions(+)
> create mode 100644 hw/tpm_tis.h
> create mode 100644 tpm.c
> create mode 100644 tpm.h
>
> diff --git a/hmp-commands.hx b/hmp-commands.hx
> index 010b8c9..b3a1005 100644
> --- a/hmp-commands.hx
> +++ b/hmp-commands.hx
> @@ -1570,6 +1570,8 @@ show device tree
> show qdev device model list
> @item info roms
> show roms
> +@item info tpm
> +show the TPM device
> @end table
> ETEXI
>
> diff --git a/hmp.c b/hmp.c
> index 180ba2b..8d63d03 100644
> --- a/hmp.c
> +++ b/hmp.c
> @@ -628,6 +628,36 @@ void hmp_info_block_jobs(Monitor *mon)
> }
> }
>
> +void hmp_info_tpm(Monitor *mon)
> +{
> + TPMInfoList *info_list, *info;
> + Error *err = NULL;
> + unsigned int c = 0;
> +
> + info_list = qmp_query_tpm(&err);
> + if (err) {
> + monitor_printf(mon, "TPM device not supported\n");
> + error_free(err);
> + return;
> + }
> +
> + monitor_printf(mon, "TPM device:\n");
> +
> + for (info = info_list; info; info = info->next) {
> + TPMInfo *ti = info->value;
> + monitor_printf(mon, " tpm%d: model=%s\n",
> + c, ti->model);
> + monitor_printf(mon, " \\ %s: type=%s%s%s%s%s\n",
> + ti->id, ti->type,
> + ti->has_path ? ",path=" : "",
> + ti->has_path ? ti->path : "",
> + ti->has_cancel_path ? ",cancel_path=" : "",
> + ti->has_cancel_path ? ti->cancel_path : "");
Does this cause spacing issues if path is not specified and cancel_path is?
> + c++;
> + }
> + qapi_free_TPMInfoList(info_list);
> +}
> +
> void hmp_quit(Monitor *mon, const QDict *qdict)
> {
> monitor_suspend(mon);
> diff --git a/hmp.h b/hmp.h
> index 0ab03be..df43f7d 100644
> --- a/hmp.h
> +++ b/hmp.h
> @@ -36,6 +36,7 @@ void hmp_info_spice(Monitor *mon);
> void hmp_info_balloon(Monitor *mon);
> void hmp_info_pci(Monitor *mon);
> void hmp_info_block_jobs(Monitor *mon);
> +void hmp_info_tpm(Monitor *mon);
> void hmp_quit(Monitor *mon, const QDict *qdict);
> void hmp_stop(Monitor *mon, const QDict *qdict);
> void hmp_system_reset(Monitor *mon, const QDict *qdict);
> diff --git a/hw/tpm_tis.h b/hw/tpm_tis.h
> new file mode 100644
> index 0000000..7f6dcb9
> --- /dev/null
> +++ b/hw/tpm_tis.h
> @@ -0,0 +1,78 @@
> +/*
> + * tpm_tis.c - QEMU's TPM TIS interface emulator
Should me point out somewhere that this is based on the v1.2 TIS?
> + *
> + * Copyright (C) 2006,2010,2011 IBM Corporation
Should this be updated to 2013? That's a global comment throughout the
patch series.
> + *
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + * David Safford <safford@us.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + *
> + * Implementation of the TIS interface according to specs found at
> + * http://www.trustedcomputiggroup.org
> + *
> + */
> +#ifndef HW_TPM_TIS_H
> +#define HW_TPM_TIS_H
> +
> +#include "isa.h"
> +#include "qemu-common.h"
> +
> +#define TPM_TIS_ADDR_BASE 0xFED40000
> +
> +#define TPM_TIS_NUM_LOCALITIES 5 /* per spec */
> +#define TPM_TIS_LOCALITY_SHIFT 12
> +#define TPM_TIS_NO_LOCALITY 0xff
> +
> +#define TPM_TIS_IS_VALID_LOCTY(x) ((x) < TPM_TIS_NUM_LOCALITIES)
> +
> +#define TPM_TIS_IRQ 5
> +
> +#define TPM_TIS_BUFFER_MAX 4096
> +
> +
> +typedef struct TPMSizedBuffer {
> + uint32_t size;
> + uint8_t *buffer;
> +} TPMSizedBuffer;
> +
> +typedef enum {
> + TPM_TIS_STATE_IDLE = 0,
> + TPM_TIS_STATE_READY,
> + TPM_TIS_STATE_COMPLETION,
> + TPM_TIS_STATE_EXECUTION,
> + TPM_TIS_STATE_RECEPTION,
Just a nit comment. TPM_TIS_STATE_COMPLETION and
TPM_TIS_STATE_RECEPTION could be swapped and they'd be in logical order.
Same goes for any switch statements.
> +} TPMTISState;
> +
> +/* locality data -- all fields are persisted */
> +typedef struct TPMLocality {
> + TPMTISState state;
> + uint8_t access;
> + uint8_t sts;
> + uint32_t inte;
> + uint32_t ints;
> +
> + uint16_t w_offset;
> + uint16_t r_offset;
> + TPMSizedBuffer w_buffer;
> + TPMSizedBuffer r_buffer;
> +} TPMLocality;
> +
> +typedef struct TPMTISEmuState {
> + QEMUBH *bh;
> + uint32_t offset;
> + uint8_t buf[TPM_TIS_BUFFER_MAX];
> +
> + uint8_t active_locty;
> + uint8_t aborting_locty;
> + uint8_t next_locty;
> +
> + TPMLocality loc[TPM_TIS_NUM_LOCALITIES];
> +
> + qemu_irq irq;
> + uint32_t irq_num;
> +} TPMTISEmuState;
> +
> +#endif /* HW_TPM_TIS_H */
> diff --git a/monitor.c b/monitor.c
> index c0e32d6..558346c 100644
> --- a/monitor.c
> +++ b/monitor.c
> @@ -47,6 +47,7 @@
> #include "migration.h"
> #include "kvm.h"
> #include "acl.h"
> +#include "tpm.h"
> #include "qint.h"
> #include "qfloat.h"
> #include "qlist.h"
> @@ -2736,6 +2737,13 @@ static mon_cmd_t info_cmds[] = {
> .mhandler.info = do_trace_print_events,
> },
> {
> + .name = "tpm",
> + .args_type = "",
> + .params = "",
> + .help = "show the TPM device",
> + .mhandler.info = hmp_info_tpm,
> + },
> + {
> .name = NULL,
> },
> };
> diff --git a/qapi-schema.json b/qapi-schema.json
> index 5dfa052..82f753b 100644
> --- a/qapi-schema.json
> +++ b/qapi-schema.json
> @@ -3017,3 +3017,35 @@
> # Since: 1.3.0
> ##
> { 'command': 'nbd-server-stop' }
> +
> +##
> +# @TPMInfo:
> +#
> +# Information about the TPM
> +#
> +# @model: The TPM frontend model, i.e., tpm-tis
> +#
> +# @id: The ID of the TPM
> +#
> +# @type: The type of TPM backend, i.e., passthrough
> +#
> +# @path: #optional Path to the TPM backend device
> +#
> +# @cancel_path: #optional Path to TPM backend device's cancel sysfs entry
> +#
> +# Since: 1.5.0
> +##
> +{ 'type': 'TPMInfo',
> + 'data': {'model': 'str', 'id': 'str', 'type': 'str', '*path': 'str',
> + '*cancel_path': 'str' } }
> +
It might be preferred that you break the monitor support into it's own
patch, and logically it would make more sense to introduce these backend
specific members after the backend patch. But those are just nit comments.
More importantly, I have a question (probably for Luiz) regarding future
modification of the monitor command. In the future, if a new vTPM
backend is introduced (e.g. an emulated software vTPM), can we add new
optional members to the end of this TPMInfo? For example, could we
modify it to this in the future?
{ 'type': 'TPMInfo',
'data': {'model': 'str', 'id': 'str', 'type': 'str', '*path': 'str',
'*cancel_path': 'str', '*new_emulated_1': 'str',
'*new_emulated_2': 'str' } }
And would it make more sense to have something like this?
{ 'type': 'TPMInfoPassthrough',
'data': {'*path': 'str', '*cancel_path': 'str'}
{ 'type': 'TPMInfoEmulated',
'data': {'*new_emulated_1': 'str', '*new_emulated_2': 'str' }}
{ 'type': 'TPMInfo',
'data': {'model': 'str', 'id': 'str', 'type': 'str',
'*vtpm_passthrough': 'TPMInfoPassthrough',
'*vtpm_emulated': 'TPMInfoEmulated' } }
> +##
> +# @query-tpm
> +#
> +# Return information about the TPM device.
> +#
> +# Returns: @TPMInfo on success
> +#
> +# Since: 1.5.0
> +##
> +{ 'command': 'query-tpm', 'returns': ['TPMInfo'] }
> diff --git a/qemu-config.c b/qemu-config.c
> index 10d1ba4..59f605a 100644
> --- a/qemu-config.c
> +++ b/qemu-config.c
> @@ -691,6 +691,25 @@ static QemuOptsList qemu_object_opts = {
> },
> };
>
> +static QemuOptsList qemu_tpmdev_opts = {
> + .name = "tpmdev",
> + .implied_opt_name = "type",
> + .head = QTAILQ_HEAD_INITIALIZER(qemu_tpmdev_opts.head),
> + .desc = {
> + {
> + .name = "type",
> + .type = QEMU_OPT_STRING,
> + .help = "Type of TPM backend",
> + },
> + {
> + .name = "path",
> + .type = QEMU_OPT_STRING,
> + .help = "Persistent storage for TPM state",
Is this help message correct? Shouldn't it say it's the path to the
host TPM device?
> + },
> + { /* end of list */ }
> + },
> +};
> +
> static QemuOptsList *vm_config_groups[32] = {
> &qemu_drive_opts,
> &qemu_chardev_opts,
> @@ -709,6 +728,7 @@ static QemuOptsList *vm_config_groups[32] = {
> &qemu_sandbox_opts,
> &qemu_add_fd_opts,
> &qemu_object_opts,
> + &qemu_tpmdev_opts,
> NULL,
> };
>
> diff --git a/qemu-options.hx b/qemu-options.hx
> index de43b1b..27a968e 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -2158,6 +2158,39 @@ ETEXI
>
> DEFHEADING()
>
> +#ifdef CONFIG_TPM
> +DEFHEADING(TPM device options:)
> +
> +DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
> + "-tpmdev [<type>],id=str[,option][,option][,...]\n",
> + QEMU_ARCH_ALL)
> +STEXI
> +
> +The general form of a TPM device option is:
> +@table @option
> +
> +@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
> +@findex -tpmdev
> +Backend type must be:
> +
> +The specific backend type will determine the applicable options.
> +The @code{-tpmdev} options requires a @code{-device} option.
> +
> +Options to each backend are described below.
> +
> +Use ? to print all available TPM backend types.
> +@example
> +qemu -tpmdev ?
> +@end example
> +
> +@end table
> +
> +ETEXI
> +
> +DEFHEADING()
> +
> +#endif
> +
> DEFHEADING(Linux/Multiboot boot specific:)
> STEXI
>
> diff --git a/qmp-commands.hx b/qmp-commands.hx
> index 5c692d0..915e042 100644
> --- a/qmp-commands.hx
> +++ b/qmp-commands.hx
> @@ -2654,3 +2654,8 @@ EQMP
> .args_type = "",
> .mhandler.cmd_new = qmp_marshal_input_query_target,
> },
> + {
> + .name = "query-tpm",
> + .args_type = "",
> + .mhandler.cmd_new = qmp_marshal_input_query_tpm,
> + },
> diff --git a/tpm.c b/tpm.c
> new file mode 100644
> index 0000000..c9356b6
> --- /dev/null
> +++ b/tpm.c
> @@ -0,0 +1,217 @@
> +/*
> + * TPM configuration
> + *
> + * Copyright (C) 2011 IBM Corporation
> + *
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + *
> + * Based on net.c
> + */
> +#include "config-host.h"
> +
> +#include "monitor.h"
> +#include "qerror.h"
> +#include "tpm.h"
> +#include "qmp-commands.h"
> +
> +static QLIST_HEAD(, TPMBackend) tpm_backends =
> + QLIST_HEAD_INITIALIZER(tpm_backends);
> +
> +#ifdef CONFIG_TPM
> +
> +static const TPMDriverOps *be_drivers[] = {
> + NULL,
> +};
> +
> +const TPMDriverOps *tpm_get_backend_driver(const char *type)
> +{
> + int i;
> +
> + for (i = 0; be_drivers[i] != NULL; i++) {
> + if (!strcmp(be_drivers[i]->type, type)) {
> + break;
> + }
> + }
> +
> + return be_drivers[i];
> +}
> +
> +/*
> + * Walk the list of available TPM backend drivers and display them on the
> + * screen.
> + */
> +void tpm_display_backend_drivers(void)
> +{
> + int i;
> +
> + fprintf(stderr, "Supported TPM types (choose only one):\n");
> +
> + for (i = 0; be_drivers[i] != NULL; i++) {
> + fprintf(stderr, "%12s %s\n",
> + be_drivers[i]->type, be_drivers[i]->desc());
> + }
> + fprintf(stderr, "\n");
> +}
> +
> +/*
> + * Find the TPM with the given Id
> + */
> +TPMBackend *qemu_find_tpm(const char *id)
> +{
> + TPMBackend *drv;
> +
> + QLIST_FOREACH(drv, &tpm_backends, list) {
> + if (!strcmp(drv->id, id)) {
> + return drv;
> + }
> + }
> +
> + return NULL;
> +}
> +
> +static int configure_tpm(QemuOpts *opts)
> +{
> + const char *value;
> + const char *id;
> + const TPMDriverOps *be;
> + TPMBackend *drv;
> +
> + if (!QLIST_EMPTY(&tpm_backends)) {
> + error_report("Only one TPM is allowed.\n");
> + return 1;
> + }
> +
> + id = qemu_opts_id(opts);
> + if (id == NULL) {
> + qerror_report(QERR_MISSING_PARAMETER, "id");
> + return 1;
> + }
> +
> + value = qemu_opt_get(opts, "type");
> + if (!value) {
> + qerror_report(QERR_MISSING_PARAMETER, "type");
> + tpm_display_backend_drivers();
> + return 1;
> + }
> +
> + be = tpm_get_backend_driver(value);
> + if (be == NULL) {
> + qerror_report(QERR_INVALID_PARAMETER_VALUE, "type",
> + "a TPM backend type");
> + tpm_display_backend_drivers();
> + return 1;
> + }
> +
> + drv = be->create(opts, id);
> + if (!drv) {
> + return 1;
> + }
> +
> + QLIST_INSERT_HEAD(&tpm_backends, drv, list);
> +
> + return 0;
> +}
> +
> +static int tpm_init_tpmdev(QemuOpts *opts, void *dummy)
> +{
> + return configure_tpm(opts);
> +}
> +
> +/*
> + * Walk the list of TPM backend drivers that are in use and call their
> + * destroy function to have them cleaned up.
> + */
> +void tpm_cleanup(void)
> +{
> + TPMBackend *drv, *next;
> +
> + QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) {
> + QLIST_REMOVE(drv, list);
> + drv->ops->destroy(drv);
> + }
> +}
> +
> +/*
> + * Initialize the TPM. Process the tpmdev command line options describing the
> + * TPM backend.
> + */
> +int tpm_init(void)
> +{
> + if (qemu_opts_foreach(qemu_find_opts("tpmdev"),
> + tpm_init_tpmdev, NULL, 1) != 0) {
> + return -1;
> + }
> +
> + atexit(tpm_cleanup);
> +
> + return 0;
> +}
> +
> +/*
> + * Parse the TPM configuration options.
> + * It is possible to pass an option '-tpmdev none' to not activate any TPM.
Is '-tpmdev none' still applicable?
> + * To display all available TPM backends the user may use '-tpmdev ?'
> + */
> +int tpm_config_parse(QemuOptsList *opts_list, const char *optarg)
> +{
> + QemuOpts *opts;
> +
> + if (*optarg == '?') {
> + tpm_display_backend_drivers();
> + return -1;
> + }
> + opts = qemu_opts_parse(opts_list, optarg, 1);
> + if (!opts) {
> + return -1;
> + }
> + return 0;
> +}
> +
> +#endif /* CONFIG_TPM */
> +
> +static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv)
> +{
> + TPMInfo *res = g_new0(TPMInfo, 1);
> +
> + res->model = g_strdup(drv->fe_model);
> + res->id = g_strdup(drv->id);
> + if (drv->path) {
> + res->path = g_strdup(drv->path);
> + res->has_path = true;
> + }
> + if (drv->cancel_path) {
> + res->cancel_path = g_strdup(drv->cancel_path);
> + res->has_cancel_path = true;
> + }
> + res->type = g_strdup(drv->ops->type);
> +
> + return res;
> +}
> +
> +/*
> + * Walk the list of active TPM backends and collect information about them
> + * following the schema description in qapi-schema.json.
> + */
> +TPMInfoList *qmp_query_tpm(Error **errp)
> +{
> + TPMBackend *drv;
> + TPMInfoList *info, *head = NULL, *cur_item = NULL;
> +
> + QLIST_FOREACH(drv, &tpm_backends, list) {
> + info = g_new0(TPMInfoList, 1);
> + info->value = qmp_query_tpm_inst(drv);
> +
> + if (!cur_item) {
> + head = cur_item = info;
> + } else {
> + cur_item->next = info;
> + cur_item = info;
> + }
> + }
> +
> + return head;
> +}
> diff --git a/tpm.h b/tpm.h
> new file mode 100644
> index 0000000..8943b61
> --- /dev/null
> +++ b/tpm.h
> @@ -0,0 +1,84 @@
> +/*
> + * TPM configuration
> + *
> + * Copyright (C) 2011 IBM Corporation
> + *
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + */
> +#ifndef QEMU_TPM_H
> +#define QEMU_TPM_H
> +
> +#include "memory.h"
> +#include "hw/tpm_tis.h"
> +
> +struct TPMDriverOps;
> +typedef struct TPMDriverOps TPMDriverOps;
> +
> +typedef struct TPMBackend {
> + char *id;
> + const char *fe_model;
> + char *path;
> + char *cancel_path;
> + const TPMDriverOps *ops;
> +
> + QLIST_ENTRY(TPMBackend) list;
> +} TPMBackend;
> +
> +/* overall state of the TPM interface */
> +typedef struct TPMState {
> + ISADevice busdev;
> + MemoryRegion mmio;
> +
> + union {
> + TPMTISEmuState tis;
> + } s;
> +
> + uint8_t locty_number;
> + TPMLocality *locty_data;
> +
> + char *backend;
> + TPMBackend *be_driver;
> +} TPMState;
> +
> +typedef void (TPMRecvDataCB)(TPMState *, uint8_t locty);
> +
> +struct TPMDriverOps {
> + const char *type;
> + /* get a descriptive text of the backend to display to the user */
> + const char *(*desc)(void);
> +
> + TPMBackend *(*create)(QemuOpts *opts, const char *id);
> + void (*destroy)(TPMBackend *t);
> +
> + /* initialize the backend */
> + int (*init)(TPMBackend *t, TPMState *s, TPMRecvDataCB *datacb);
> + /* start up the TPM on the backend */
> + int (*startup_tpm)(TPMBackend *t);
> + /* returns true if nothing will ever answer TPM requests */
> + bool (*had_startup_error)(TPMBackend *t);
> +
> + size_t (*realloc_buffer)(TPMSizedBuffer *sb);
> +
> + void (*deliver_request)(TPMBackend *t);
> +
> + void (*reset)(TPMBackend *t);
> +
> + void (*cancel_cmd)(TPMBackend *t);
> +
> + bool (*get_tpm_established_flag)(TPMBackend *t);
> +};
> +
> +#define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
TPM_DEFAULT_DEVICE_MODEL doesn't appear to be used anywhere.
> +
> +int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
> +int tpm_init(void);
> +void tpm_cleanup(void);
> +TPMBackend *qemu_find_tpm(const char *id);
> +void tpm_display_backend_drivers(void);
> +const TPMDriverOps *tpm_get_backend_driver(const char *id);
> +
> +#endif /* QEMU_TPM_H */
> diff --git a/vl.c b/vl.c
> index a3ab384..2cbbc0b 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -139,6 +139,7 @@ int main(int argc, char **argv)
> #include "blockdev.h"
> #include "hw/block-common.h"
> #include "block-migration.h"
> +#include "tpm.h"
> #include "dma.h"
> #include "audio/audio.h"
> #include "migration.h"
> @@ -2938,6 +2939,13 @@ int main(int argc, char **argv, char **envp)
> }
> break;
> }
> +#ifdef CONFIG_TPM
> + case QEMU_OPTION_tpmdev:
> + if (tpm_config_parse(qemu_find_opts("tpmdev"), optarg) < 0) {
> + exit(1);
> + }
> + break;
> +#endif
> case QEMU_OPTION_mempath:
> mem_path = optarg;
> break;
> @@ -3749,6 +3757,12 @@ int main(int argc, char **argv, char **envp)
> exit(1);
> }
>
> +#ifdef CONFIG_TPM
> + if (tpm_init() < 0) {
> + exit(1);
> + }
> +#endif
> +
> /* init the bluetooth world */
> if (foreach_device_config(DEV_BT, bt_parse))
> exit(1);
> @@ -4001,6 +4015,9 @@ int main(int argc, char **argv, char **envp)
> pause_all_vcpus();
> net_cleanup();
> res_free();
> +#ifdef CONFIG_TPM
> + tpm_cleanup();
> +#endif
>
> return 0;
> }
>
--
Regards,
Corey Bryant
^ permalink raw reply [flat|nested] 21+ messages in thread
* [Qemu-devel] [PATCH V20 2/8] Add TPM (frontend) hardware interface (TPM TIS) to QEMU
2013-01-18 16:02 [Qemu-devel] [PATCH V20 0/8] Qemu Trusted Platform Module (TPM) integration Stefan Berger
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 1/8] Support for TPM command line options Stefan Berger
@ 2013-01-18 16:02 ` Stefan Berger
2013-02-01 17:02 ` Corey Bryant
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 3/8] Add a debug register Stefan Berger
` (5 subsequent siblings)
7 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2013-01-18 16:02 UTC (permalink / raw)
To: stefanb, qemu-devel, anthony; +Cc: andreas.niederl, mst
This patch adds the main code of the TPM frontend driver, the TPM TIS
interface, to QEMU. The code is largely based on the previous implementation
for Xen but has been significantly extended to meet the standard's
requirements, such as the support for changing of localities and all the
functionality of the available flags.
Communication with the backend (i.e., for Xen or the libtpms-based one)
is cleanly separated through an interface which the backend driver needs
to implement.
The TPM TIS driver's backend was previously chosen in the code added
to arch_init. The frontend holds a pointer to the chosen backend (interface).
Communication with the backend is largely based on signals and conditions.
Whenever the frontend has collected a complete packet, it will signal
the backend, which then starts processing the command. Once the result
has been returned, the backend invokes a callback function
(tpm_tis_receive_cb()).
The one tricky part is support for VM suspend while the TPM is processing
a command. In this case the frontend driver is waiting for the backend
to return the result of the last command before shutting down. It waits
on a condition for a signal from the backend, which is delivered in
tpm_tis_receive_cb().
Testing the proper functioning of the different flags and localities
cannot be done from user space when running in Linux for example, since
access to the address space of the TPM TIS interface is not possible. Also
the Linux driver itself does not exercise all functionality. So, for
testing there is a fairly extensive test suite as part of the SeaBIOS patches
since from within the BIOS one can have full access to all the TPM's registers.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
hw/tpm_tis.c | 857 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 857 insertions(+)
create mode 100644 hw/tpm_tis.c
diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c
new file mode 100644
index 0000000..3ed813d
--- /dev/null
+++ b/hw/tpm_tis.c
@@ -0,0 +1,857 @@
+/*
+ * tpm_tis.c - QEMU's TPM TIS interface emulator
+ *
+ * Copyright (C) 2006,2010,2011 IBM Corporation
+ *
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ * David Safford <safford@us.ibm.com>
+ *
+ * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or later.
+ * See the COPYING file in the top-level directory.
+ *
+ * Implementation of the TIS interface according to specs found at
+ * http://www.trustedcomputiggroup.org.
+ * In the developers menu choose the PC Client section then find the TIS
+ * specification.
+ */
+
+#include "tpm.h"
+#include "block.h"
+#include "exec-memory.h"
+#include "hw/hw.h"
+#include "hw/pc.h"
+#include "hw/pci_ids.h"
+#include "hw/tpm_tis.h"
+#include "qemu-error.h"
+#include "qemu-common.h"
+
+/*#define DEBUG_TIS */
+
+#ifdef DEBUG_TIS
+#define dprintf(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define dprintf(fmt, ...) \
+ do { } while (0)
+#endif
+
+/* whether the STS interrupt is supported */
+#define RAISE_STS_IRQ
+
+/* tis registers */
+#define TPM_TIS_REG_ACCESS 0x00
+#define TPM_TIS_REG_INT_ENABLE 0x08
+#define TPM_TIS_REG_INT_VECTOR 0x0c
+#define TPM_TIS_REG_INT_STATUS 0x10
+#define TPM_TIS_REG_INTF_CAPABILITY 0x14
+#define TPM_TIS_REG_STS 0x18
+#define TPM_TIS_REG_DATA_FIFO 0x24
+#define TPM_TIS_REG_DID_VID 0xf00
+#define TPM_TIS_REG_RID 0xf04
+
+#define TPM_TIS_STS_VALID (1 << 7)
+#define TPM_TIS_STS_COMMAND_READY (1 << 6)
+#define TPM_TIS_STS_TPM_GO (1 << 5)
+#define TPM_TIS_STS_DATA_AVAILABLE (1 << 4)
+#define TPM_TIS_STS_EXPECT (1 << 3)
+#define TPM_TIS_STS_RESPONSE_RETRY (1 << 1)
+
+#define TPM_TIS_BURST_COUNT_SHIFT 8
+#define TPM_TIS_BURST_COUNT(X) \
+ ((X) << TPM_TIS_BURST_COUNT_SHIFT)
+
+#define TPM_TIS_ACCESS_TPM_REG_VALID_STS (1 << 7)
+#define TPM_TIS_ACCESS_ACTIVE_LOCALITY (1 << 5)
+#define TPM_TIS_ACCESS_BEEN_SEIZED (1 << 4)
+#define TPM_TIS_ACCESS_SEIZE (1 << 3)
+#define TPM_TIS_ACCESS_PENDING_REQUEST (1 << 2)
+#define TPM_TIS_ACCESS_REQUEST_USE (1 << 1)
+#define TPM_TIS_ACCESS_TPM_ESTABLISHMENT (1 << 0)
+
+#define TPM_TIS_INT_ENABLED (1 << 31)
+#define TPM_TIS_INT_DATA_AVAILABLE (1 << 0)
+#define TPM_TIS_INT_STS_VALID (1 << 1)
+#define TPM_TIS_INT_LOCALITY_CHANGED (1 << 2)
+#define TPM_TIS_INT_COMMAND_READY (1 << 7)
+
+#define TPM_TIS_INT_POLARITY_MASK (3 << 3)
+#define TPM_TIS_INT_POLARITY_LOW_LEVEL (1 << 3)
+
+#ifndef RAISE_STS_IRQ
+
+#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \
+ TPM_TIS_INT_DATA_AVAILABLE | \
+ TPM_TIS_INT_COMMAND_READY)
+
+#else
+
+#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \
+ TPM_TIS_INT_DATA_AVAILABLE | \
+ TPM_TIS_INT_STS_VALID | \
+ TPM_TIS_INT_COMMAND_READY)
+
+#endif
+
+#define TPM_TIS_CAP_INTERRUPT_LOW_LEVEL (1 << 4) /* support is mandatory */
+#define TPM_TIS_CAPABILITIES_SUPPORTED (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \
+ TPM_TIS_INTERRUPTS_SUPPORTED)
+
+#define TPM_TIS_TPM_DID 0x0001
+#define TPM_TIS_TPM_VID PCI_VENDOR_ID_IBM
+#define TPM_TIS_TPM_RID 0x0001
+
+#define TPM_TIS_NO_DATA_BYTE 0xff
+
+/* utility functions */
+
+static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
+{
+ return (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7);
+}
+
+static uint32_t tpm_tis_get_size_from_buffer(const TPMSizedBuffer *sb)
+{
+ return be32_to_cpu(*(uint32_t *)&sb->buffer[2]);
+}
+
+static void tpm_tis_show_buffer(const TPMSizedBuffer *sb, const char *string)
+{
+#ifdef DEBUG_TIS
+ uint32_t len, i;
+
+ len = tpm_tis_get_size_from_buffer(sb);
+ dprintf("tpm_tis: %s length = %d\n", string, len);
+ for (i = 0; i < len; i++) {
+ if (i && !(i % 16)) {
+ dprintf("\n");
+ }
+ dprintf("%.2X ", sb->buffer[i]);
+ }
+ dprintf("\n");
+#endif
+}
+
+/*
+ * Send a request to the TPM.
+ */
+static void tpm_tis_tpm_send(TPMState *s, uint8_t locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+
+ tpm_tis_show_buffer(&tis->loc[locty].w_buffer, "tpm_tis: To TPM");
+
+ s->locty_number = locty;
+ s->locty_data = &tis->loc[locty];
+
+ /*
+ * w_offset serves as length indicator for length of data;
+ * it's reset when the response comes back
+ */
+ tis->loc[locty].state = TPM_TIS_STATE_EXECUTION;
+
+ s->be_driver->ops->deliver_request(s->be_driver);
+}
+
+/* raise an interrupt if allowed */
+static void tpm_tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+
+ if (!TPM_TIS_IS_VALID_LOCTY(locty)) {
+ return;
+ }
+
+ if ((tis->loc[locty].inte & TPM_TIS_INT_ENABLED) &&
+ (tis->loc[locty].inte & irqmask)) {
+ dprintf("tpm_tis: Raising IRQ for flag %08x\n", irqmask);
+ qemu_irq_raise(s->s.tis.irq);
+ tis->loc[locty].ints |= irqmask;
+ }
+}
+
+static uint32_t tpm_tis_check_request_use_except(TPMState *s, uint8_t locty)
+{
+ uint8_t l;
+
+ for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ if (l == locty) {
+ continue;
+ }
+ if ((s->s.tis.loc[l].access & TPM_TIS_ACCESS_REQUEST_USE)) {
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+static void tpm_tis_new_active_locality(TPMState *s, uint8_t new_active_locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+ bool change = (s->s.tis.active_locty != new_active_locty);
+ bool is_seize;
+ uint8_t mask;
+
+ if (change && TPM_TIS_IS_VALID_LOCTY(s->s.tis.active_locty)) {
+ is_seize = TPM_TIS_IS_VALID_LOCTY(new_active_locty) &&
+ tis->loc[new_active_locty].access & TPM_TIS_ACCESS_SEIZE;
+
+ if (is_seize) {
+ mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ } else {
+ mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY|
+ TPM_TIS_ACCESS_REQUEST_USE);
+ }
+ /* reset flags on the old active locality */
+ tis->loc[s->s.tis.active_locty].access &= mask;
+
+ if (is_seize) {
+ tis->loc[tis->active_locty].access |= TPM_TIS_ACCESS_BEEN_SEIZED;
+ }
+ }
+
+ tis->active_locty = new_active_locty;
+
+ dprintf("tpm_tis: Active locality is now %d\n", s->s.tis.active_locty);
+
+ if (TPM_TIS_IS_VALID_LOCTY(new_active_locty)) {
+ /* set flags on the new active locality */
+ tis->loc[new_active_locty].access |= TPM_TIS_ACCESS_ACTIVE_LOCALITY;
+ tis->loc[new_active_locty].access &= ~(TPM_TIS_ACCESS_REQUEST_USE |
+ TPM_TIS_ACCESS_SEIZE);
+ }
+
+ if (change) {
+ tpm_tis_raise_irq(s, tis->active_locty, TPM_TIS_INT_LOCALITY_CHANGED);
+ }
+}
+
+/* abort -- this function switches the locality */
+static void tpm_tis_abort(TPMState *s, uint8_t locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+
+ tis->loc[locty].r_offset = 0;
+ tis->loc[locty].w_offset = 0;
+
+ dprintf("tpm_tis: tis_abort: new active locality is %d\n", tis->next_locty);
+
+ /*
+ * Need to react differently depending on who's aborting now and
+ * which locality will become active afterwards.
+ */
+ if (tis->aborting_locty == tis->next_locty) {
+ tis->loc[tis->aborting_locty].state = TPM_TIS_STATE_READY;
+ tis->loc[tis->aborting_locty].sts = TPM_TIS_STS_COMMAND_READY;
+ tpm_tis_raise_irq(s, tis->aborting_locty, TPM_TIS_INT_COMMAND_READY);
+ }
+
+ /* locality after abort is another one than the current one */
+ tpm_tis_new_active_locality(s, tis->next_locty);
+
+ tis->next_locty = TPM_TIS_NO_LOCALITY;
+ /* nobody's aborting a command anymore */
+ tis->aborting_locty = TPM_TIS_NO_LOCALITY;
+}
+
+/* prepare aborting current command */
+static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+ uint8_t busy_locty;
+
+ tis->aborting_locty = locty;
+ tis->next_locty = newlocty; /* locality after successful abort */
+
+ /*
+ * only abort a command using an interrupt if currently executing
+ * a command AND if there's a valid connection to the vTPM.
+ */
+ for (busy_locty = 0; busy_locty < TPM_TIS_NUM_LOCALITIES; busy_locty++) {
+ if (tis->loc[busy_locty].state == TPM_TIS_STATE_EXECUTION) {
+ /*
+ * request the backend to cancel. Some backends may not
+ * support it
+ */
+ s->be_driver->ops->cancel_cmd(s->be_driver);
+ return;
+ }
+ }
+
+ tpm_tis_abort(s, locty);
+}
+
+static void tpm_tis_receive_bh(void *opaque)
+{
+ TPMState *s = opaque;
+ TPMTISEmuState *tis = &s->s.tis;
+ uint8_t locty = s->locty_number;
+
+ tis->loc[locty].sts = TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE;
+ tis->loc[locty].state = TPM_TIS_STATE_COMPLETION;
+ tis->loc[locty].r_offset = 0;
+ tis->loc[locty].w_offset = 0;
+
+ if (TPM_TIS_IS_VALID_LOCTY(tis->next_locty)) {
+ tpm_tis_abort(s, locty);
+ }
+
+#ifndef RAISE_STS_IRQ
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_DATA_AVAILABLE);
+#else
+ tpm_tis_raise_irq(s, locty,
+ TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID);
+#endif
+}
+
+/*
+ * Callback from the TPM to indicate that the response was received.
+ */
+static void tpm_tis_receive_cb(TPMState *s, uint8_t locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+
+ assert(s->locty_number == locty);
+
+ qemu_bh_schedule(tis->bh);
+}
+
+/*
+ * Read a byte of response data
+ */
+static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty)
+{
+ TPMTISEmuState *tis = &s->s.tis;
+ uint32_t ret = TPM_TIS_NO_DATA_BYTE;
+ uint16_t len;
+
+ if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
+ len = tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer);
+
+ ret = tis->loc[locty].r_buffer.buffer[tis->loc[locty].r_offset++];
+ if (tis->loc[locty].r_offset >= len) {
+ /* got last byte */
+ tis->loc[locty].sts = TPM_TIS_STS_VALID;
+#ifdef RAISE_STS_IRQ
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
+#endif
+ }
+ dprintf("tpm_tis: tpm_tis_data_read byte 0x%02x [%d]\n",
+ ret, tis->loc[locty].r_offset-1);
+ }
+
+ return ret;
+}
+
+/*
+ * Read a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
+ unsigned size)
+{
+ TPMState *s = opaque;
+ TPMTISEmuState *tis = &s->s.tis;
+ uint16_t offset = addr & 0xffc;
+ uint8_t shift = (addr & 0x3) * 8;
+ uint32_t val = 0xffffffff;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ uint32_t avail;
+
+ if (s->be_driver->ops->had_startup_error(s->be_driver)) {
+ return val;
+ }
+
+ switch (offset) {
+ case TPM_TIS_REG_ACCESS:
+ /* never show the SEIZE flag even though we use it internally */
+ val = tis->loc[locty].access & ~TPM_TIS_ACCESS_SEIZE;
+ /* the pending flag is always calculated */
+ if (tpm_tis_check_request_use_except(s, locty)) {
+ val |= TPM_TIS_ACCESS_PENDING_REQUEST;
+ }
+ val |= !s->be_driver->ops->get_tpm_established_flag(s->be_driver);
+ break;
+ case TPM_TIS_REG_INT_ENABLE:
+ val = tis->loc[locty].inte;
+ break;
+ case TPM_TIS_REG_INT_VECTOR:
+ val = tis->irq_num;
+ break;
+ case TPM_TIS_REG_INT_STATUS:
+ val = tis->loc[locty].ints;
+ break;
+ case TPM_TIS_REG_INTF_CAPABILITY:
+ val = TPM_TIS_CAPABILITIES_SUPPORTED;
+ break;
+ case TPM_TIS_REG_STS:
+ if (tis->active_locty == locty) {
+ if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
+ val = TPM_TIS_BURST_COUNT(
+ tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer)
+ - tis->loc[locty].r_offset) | tis->loc[locty].sts;
+ } else {
+ avail = tis->loc[locty].w_buffer.size
+ - tis->loc[locty].w_offset;
+ /*
+ * byte-sized reads should not return 0x00 for 0x100
+ * available bytes.
+ */
+ if (size == 1 && avail > 0xff) {
+ avail = 0xff;
+ }
+ val = TPM_TIS_BURST_COUNT(avail) | tis->loc[locty].sts;
+ }
+ }
+ break;
+ case TPM_TIS_REG_DATA_FIFO:
+ if (tis->active_locty == locty) {
+ switch (tis->loc[locty].state) {
+ case TPM_TIS_STATE_COMPLETION:
+ val = tpm_tis_data_read(s, locty);
+ break;
+ default:
+ val = TPM_TIS_NO_DATA_BYTE;
+ break;
+ }
+ }
+ break;
+ case TPM_TIS_REG_DID_VID:
+ val = (TPM_TIS_TPM_DID << 16) | TPM_TIS_TPM_VID;
+ break;
+ case TPM_TIS_REG_RID:
+ val = TPM_TIS_TPM_RID;
+ break;
+ }
+
+ if (shift) {
+ val >>= shift;
+ }
+
+ dprintf("tpm_tis: read.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val);
+
+ return val;
+}
+
+/*
+ * Write a value to a register of the TIS interface
+ * See specs pages 33-63 for description of the registers
+ */
+static void tpm_tis_mmio_write_intern(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size,
+ bool hw_access)
+{
+ TPMState *s = opaque;
+ TPMTISEmuState *tis = &s->s.tis;
+ uint16_t off = addr & 0xfff;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ uint8_t active_locty, l;
+ int c, set_new_locty = 1;
+ uint16_t len;
+
+ dprintf("tpm_tis: write.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val);
+
+ if (locty == 4 && !hw_access) {
+ dprintf("tpm_tis: Access to locality 4 only allowed from hardware\n");
+ return;
+ }
+
+ if (s->be_driver->ops->had_startup_error(s->be_driver)) {
+ return;
+ }
+
+ switch (off) {
+ case TPM_TIS_REG_ACCESS:
+
+ if ((val & TPM_TIS_ACCESS_SEIZE)) {
+ val &= ~(TPM_TIS_ACCESS_REQUEST_USE |
+ TPM_TIS_ACCESS_ACTIVE_LOCALITY);
+ }
+
+ active_locty = tis->active_locty;
+
+ if ((val & TPM_TIS_ACCESS_ACTIVE_LOCALITY)) {
+ /* give up locality if currently owned */
+ if (tis->active_locty == locty) {
+ dprintf("tpm_tis: Releasing locality %d\n", locty);
+
+ uint8_t newlocty = TPM_TIS_NO_LOCALITY;
+ /* anybody wants the locality ? */
+ for (c = TPM_TIS_NUM_LOCALITIES - 1; c >= 0; c--) {
+ if ((tis->loc[c].access & TPM_TIS_ACCESS_REQUEST_USE)) {
+ dprintf("tpm_tis: Locality %d requests use.\n", c);
+ newlocty = c;
+ break;
+ }
+ }
+ dprintf("tpm_tis: TPM_TIS_ACCESS_ACTIVE_LOCALITY: "
+ "Next active locality: %d\n",
+ newlocty);
+
+ if (TPM_TIS_IS_VALID_LOCTY(newlocty)) {
+ set_new_locty = 0;
+ tpm_tis_prep_abort(s, locty, newlocty);
+ } else {
+ active_locty = TPM_TIS_NO_LOCALITY;
+ }
+ } else {
+ /* not currently the owner; clear a pending request */
+ tis->loc[locty].access &= ~TPM_TIS_ACCESS_REQUEST_USE;
+ }
+ }
+
+ if ((val & TPM_TIS_ACCESS_BEEN_SEIZED)) {
+ tis->loc[locty].access &= ~TPM_TIS_ACCESS_BEEN_SEIZED;
+ }
+
+ if ((val & TPM_TIS_ACCESS_SEIZE)) {
+ /*
+ * allow seize if a locality is active and the requesting
+ * locality is higher than the one that's active
+ * OR
+ * allow seize for requesting locality if no locality is
+ * active
+ */
+ while ((TPM_TIS_IS_VALID_LOCTY(tis->active_locty) &&
+ locty > tis->active_locty) ||
+ !TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) {
+ bool higher_seize = FALSE;
+
+ /* already a pending SEIZE ? */
+ if ((tis->loc[locty].access & TPM_TIS_ACCESS_SEIZE)) {
+ break;
+ }
+
+ /* check for ongoing seize by a higher locality */
+ for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES; l++) {
+ if ((tis->loc[l].access & TPM_TIS_ACCESS_SEIZE)) {
+ higher_seize = TRUE;
+ break;
+ }
+ }
+
+ if (higher_seize) {
+ break;
+ }
+
+ /* cancel any seize by a lower locality */
+ for (l = 0; l < locty - 1; l++) {
+ tis->loc[l].access &= ~TPM_TIS_ACCESS_SEIZE;
+ }
+
+ tis->loc[locty].access |= TPM_TIS_ACCESS_SEIZE;
+ dprintf("tpm_tis: TPM_TIS_ACCESS_SEIZE: "
+ "Locality %d seized from locality %d\n",
+ locty, tis->active_locty);
+ dprintf("tpm_tis: TPM_TIS_ACCESS_SEIZE: Initiating abort.\n");
+ set_new_locty = 0;
+ tpm_tis_prep_abort(s, tis->active_locty, locty);
+ break;
+ }
+ }
+
+ if ((val & TPM_TIS_ACCESS_REQUEST_USE)) {
+ if (tis->active_locty != locty) {
+ if (TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) {
+ tis->loc[locty].access |= TPM_TIS_ACCESS_REQUEST_USE;
+ } else {
+ /* no locality active -> make this one active now */
+ active_locty = locty;
+ }
+ }
+ }
+
+ if (set_new_locty) {
+ tpm_tis_new_active_locality(s, active_locty);
+ }
+
+ break;
+ case TPM_TIS_REG_INT_ENABLE:
+ if (tis->active_locty != locty) {
+ break;
+ }
+
+ tis->loc[locty].inte = (val & (TPM_TIS_INT_ENABLED |
+ TPM_TIS_INT_POLARITY_MASK |
+ TPM_TIS_INTERRUPTS_SUPPORTED));
+ break;
+ case TPM_TIS_REG_INT_VECTOR:
+ /* hard wired -- ignore */
+ break;
+ case TPM_TIS_REG_INT_STATUS:
+ if (tis->active_locty != locty) {
+ break;
+ }
+
+ /* clearing of interrupt flags */
+ if (((val & TPM_TIS_INTERRUPTS_SUPPORTED)) &&
+ (tis->loc[locty].ints & TPM_TIS_INTERRUPTS_SUPPORTED)) {
+ tis->loc[locty].ints &= ~val;
+ if (tis->loc[locty].ints == 0) {
+ qemu_irq_lower(tis->irq);
+ dprintf("tpm_tis: Lowering IRQ\n");
+ }
+ }
+ tis->loc[locty].ints &= ~(val & TPM_TIS_INTERRUPTS_SUPPORTED);
+ break;
+ case TPM_TIS_REG_STS:
+ if (tis->active_locty != locty) {
+ break;
+ }
+
+ val &= (TPM_TIS_STS_COMMAND_READY | TPM_TIS_STS_TPM_GO |
+ TPM_TIS_STS_RESPONSE_RETRY);
+
+ if (val == TPM_TIS_STS_COMMAND_READY) {
+ switch (tis->loc[locty].state) {
+
+ case TPM_TIS_STATE_READY:
+ tis->loc[locty].w_offset = 0;
+ tis->loc[locty].r_offset = 0;
+ break;
+
+ case TPM_TIS_STATE_IDLE:
+ tis->loc[locty].sts = TPM_TIS_STS_COMMAND_READY;
+ tis->loc[locty].state = TPM_TIS_STATE_READY;
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
+ break;
+
+ case TPM_TIS_STATE_EXECUTION:
+ case TPM_TIS_STATE_RECEPTION:
+ /* abort currently running command */
+ dprintf("tpm_tis: %s: Initiating abort.\n",
+ __func__);
+ tpm_tis_prep_abort(s, locty, locty);
+ break;
+
+ case TPM_TIS_STATE_COMPLETION:
+ tis->loc[locty].w_offset = 0;
+ tis->loc[locty].r_offset = 0;
+ /* shortcut to ready state with C/R set */
+ tis->loc[locty].state = TPM_TIS_STATE_READY;
+ if (!(tis->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) {
+ tis->loc[locty].sts = TPM_TIS_STS_COMMAND_READY;
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
+ }
+ tis->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE);
+ break;
+
+ }
+ } else if (val == TPM_TIS_STS_TPM_GO) {
+ switch (tis->loc[locty].state) {
+ case TPM_TIS_STATE_RECEPTION:
+ if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT) == 0) {
+ tpm_tis_tpm_send(s, locty);
+ }
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ } else if (val == TPM_TIS_STS_RESPONSE_RETRY) {
+ switch (tis->loc[locty].state) {
+ case TPM_TIS_STATE_COMPLETION:
+ tis->loc[locty].r_offset = 0;
+ tis->loc[locty].sts = TPM_TIS_STS_VALID |
+ TPM_TIS_STS_DATA_AVAILABLE;
+ break;
+ default:
+ /* ignore */
+ break;
+ }
+ }
+ break;
+ case TPM_TIS_REG_DATA_FIFO:
+ /* data fifo */
+ if (tis->active_locty != locty) {
+ break;
+ }
+
+ if (tis->loc[locty].state == TPM_TIS_STATE_IDLE ||
+ tis->loc[locty].state == TPM_TIS_STATE_EXECUTION ||
+ tis->loc[locty].state == TPM_TIS_STATE_COMPLETION) {
+ /* drop the byte */
+ } else {
+ dprintf("tpm_tis: Byte to send to TPM: %02x\n", (uint8_t)val);
+ if (tis->loc[locty].state == TPM_TIS_STATE_READY) {
+ tis->loc[locty].state = TPM_TIS_STATE_RECEPTION;
+ tis->loc[locty].sts = TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID;
+ }
+
+ if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
+ if (tis->loc[locty].w_offset < tis->loc[locty].w_buffer.size) {
+ tis->loc[locty].w_buffer.
+ buffer[tis->loc[locty].w_offset++] = (uint8_t)val;
+ } else {
+ tis->loc[locty].sts = TPM_TIS_STS_VALID;
+ }
+ }
+
+ /* check for complete packet */
+ if (tis->loc[locty].w_offset > 5 &&
+ (tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
+ /* we have a packet length - see if we have all of it */
+#ifdef RAISE_STS_IRQ
+ bool needIrq = !(tis->loc[locty].sts & TPM_TIS_STS_VALID);
+#endif
+ len = tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
+ if (len > tis->loc[locty].w_offset) {
+ tis->loc[locty].sts = TPM_TIS_STS_EXPECT |
+ TPM_TIS_STS_VALID;
+ } else {
+ /* packet complete */
+ tis->loc[locty].sts = TPM_TIS_STS_VALID;
+ }
+#ifdef RAISE_STS_IRQ
+ if (needIrq) {
+ tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
+ }
+#endif
+ }
+ }
+ break;
+ }
+}
+
+static void tpm_tis_mmio_write(void *opaque, hwaddr addr,
+ uint64_t val, unsigned size)
+{
+ return tpm_tis_mmio_write_intern(opaque, addr, val, size, false);
+}
+
+static const MemoryRegionOps tpm_tis_memory_ops = {
+ .read = tpm_tis_mmio_read,
+ .write = tpm_tis_mmio_write,
+ .endianness = DEVICE_LITTLE_ENDIAN,
+ .valid = {
+ .min_access_size = 1,
+ .max_access_size = 4,
+ },
+};
+
+static int tpm_tis_do_startup_tpm(TPMState *s)
+{
+ return s->be_driver->ops->startup_tpm(s->be_driver);
+}
+
+/*
+ * This function is called when the machine starts, resets or due to
+ * S3 resume.
+ */
+static void tpm_tis_reset(DeviceState *d)
+{
+ TPMState *s = DO_UPCAST(TPMState, busdev.qdev, d);
+ TPMTISEmuState *tis = &s->s.tis;
+ int c;
+
+ s->be_driver->ops->reset(s->be_driver);
+
+ tis->active_locty = TPM_TIS_NO_LOCALITY;
+ tis->next_locty = TPM_TIS_NO_LOCALITY;
+ tis->aborting_locty = TPM_TIS_NO_LOCALITY;
+
+ for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) {
+ tis->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS;
+ tis->loc[c].sts = 0;
+ tis->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL;
+ tis->loc[c].ints = 0;
+ tis->loc[c].state = TPM_TIS_STATE_IDLE;
+
+ tis->loc[c].w_offset = 0;
+ s->be_driver->ops->realloc_buffer(&tis->loc[c].w_buffer);
+ tis->loc[c].r_offset = 0;
+ s->be_driver->ops->realloc_buffer(&tis->loc[c].r_buffer);
+ }
+
+ tpm_tis_do_startup_tpm(s);
+}
+
+static int tpm_tis_init(ISADevice *dev)
+{
+ TPMState *s = DO_UPCAST(TPMState, busdev, dev);
+ TPMTISEmuState *tis = &s->s.tis;
+ int rc;
+
+ s->be_driver = qemu_find_tpm(s->backend);
+ if (!s->be_driver) {
+ error_report("tpm_tis: backend driver with id %s could not be "
+ "found.n\n", s->backend);
+ goto err_exit;
+ }
+
+ s->be_driver->fe_model = "tpm-tis";
+
+ if (s->be_driver->ops->init(s->be_driver, s, tpm_tis_receive_cb)) {
+ goto err_exit;
+ }
+
+ tis->bh = qemu_bh_new(tpm_tis_receive_bh, s);
+
+ if (tis->irq_num > 15) {
+ error_report("IRQ %d for TPM TIS is outside valid range of 0 to 15.\n",
+ tis->irq_num);
+ goto err_exit;
+ }
+
+ isa_init_irq(dev, &tis->irq, tis->irq_num);
+
+ memory_region_init_io(&s->mmio, &tpm_tis_memory_ops, s, "tpm-tis-mmio",
+ TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
+ memory_region_add_subregion(get_system_memory(), TPM_TIS_ADDR_BASE,
+ &s->mmio);
+
+ rc = tpm_tis_do_startup_tpm(s);
+ if (rc != 0) {
+ goto err_destroy_memory;
+ }
+
+ return 0;
+
+ err_destroy_memory:
+ memory_region_del_subregion(get_system_memory(), &s->mmio);
+ memory_region_destroy(&s->mmio);
+
+ err_exit:
+ return -1;
+}
+
+static const VMStateDescription vmstate_tpm_tis = {
+ .name = "tpm",
+ .unmigratable = 1,
+};
+
+static Property tpm_tis_properties[] = {
+ DEFINE_PROP_UINT32("irq", TPMState,
+ s.tis.irq_num, TPM_TIS_IRQ),
+ DEFINE_PROP_STRING("tpmdev", TPMState, backend),
+ DEFINE_PROP_END_OF_LIST(),
+};
+
+static void tpm_tis_class_initfn(ObjectClass *klass, void *data)
+{
+ DeviceClass *dc = DEVICE_CLASS(klass);
+ ISADeviceClass *ic = ISA_DEVICE_CLASS(klass);
+
+ ic->init = tpm_tis_init;
+
+ dc->props = tpm_tis_properties;
+ dc->reset = tpm_tis_reset;
+ dc->vmsd = &vmstate_tpm_tis;
+}
+
+static TypeInfo tpm_tis_info = {
+ .name = "tpm-tis",
+ .parent = TYPE_ISA_DEVICE,
+ .class_init = tpm_tis_class_initfn,
+ .instance_size = sizeof(TPMState),
+};
+
+static void tpm_tis_register(void)
+{
+ type_register_static(&tpm_tis_info);
+}
+
+type_init(tpm_tis_register)
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [Qemu-devel] [PATCH V20 2/8] Add TPM (frontend) hardware interface (TPM TIS) to QEMU
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 2/8] Add TPM (frontend) hardware interface (TPM TIS) to QEMU Stefan Berger
@ 2013-02-01 17:02 ` Corey Bryant
0 siblings, 0 replies; 21+ messages in thread
From: Corey Bryant @ 2013-02-01 17:02 UTC (permalink / raw)
To: Stefan Berger; +Cc: mst, qemu-devel, anthony, andreas.niederl
On 01/18/2013 11:02 AM, Stefan Berger wrote:
> This patch adds the main code of the TPM frontend driver, the TPM TIS
> interface, to QEMU. The code is largely based on the previous implementation
> for Xen but has been significantly extended to meet the standard's
> requirements, such as the support for changing of localities and all the
> functionality of the available flags.
>
> Communication with the backend (i.e., for Xen or the libtpms-based one)
> is cleanly separated through an interface which the backend driver needs
> to implement.
>
> The TPM TIS driver's backend was previously chosen in the code added
> to arch_init. The frontend holds a pointer to the chosen backend (interface).
>
> Communication with the backend is largely based on signals and conditions.
> Whenever the frontend has collected a complete packet, it will signal
> the backend, which then starts processing the command. Once the result
> has been returned, the backend invokes a callback function
> (tpm_tis_receive_cb()).
>
> The one tricky part is support for VM suspend while the TPM is processing
> a command. In this case the frontend driver is waiting for the backend
> to return the result of the last command before shutting down. It waits
> on a condition for a signal from the backend, which is delivered in
> tpm_tis_receive_cb().
>
> Testing the proper functioning of the different flags and localities
> cannot be done from user space when running in Linux for example, since
> access to the address space of the TPM TIS interface is not possible. Also
> the Linux driver itself does not exercise all functionality. So, for
> testing there is a fairly extensive test suite as part of the SeaBIOS patches
> since from within the BIOS one can have full access to all the TPM's registers.
>
>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> ---
> hw/tpm_tis.c | 857 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 857 insertions(+)
> create mode 100644 hw/tpm_tis.c
>
> diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c
> new file mode 100644
> index 0000000..3ed813d
> --- /dev/null
> +++ b/hw/tpm_tis.c
> @@ -0,0 +1,857 @@
> +/*
> + * tpm_tis.c - QEMU's TPM TIS interface emulator
> + *
> + * Copyright (C) 2006,2010,2011 IBM Corporation
> + *
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + * David Safford <safford@us.ibm.com>
> + *
> + * Xen 4 support: Andrease Niederl <andreas.niederl@iaik.tugraz.at>
> + *
> + * This work is licensed under the terms of the GNU GPL, version 2 or later.
> + * See the COPYING file in the top-level directory.
> + *
> + * Implementation of the TIS interface according to specs found at
> + * http://www.trustedcomputiggroup.org.
> + * In the developers menu choose the PC Client section then find the TIS
> + * specification.
> + */
> +
> +#include "tpm.h"
> +#include "block.h"
> +#include "exec-memory.h"
> +#include "hw/hw.h"
> +#include "hw/pc.h"
> +#include "hw/pci_ids.h"
> +#include "hw/tpm_tis.h"
> +#include "qemu-error.h"
> +#include "qemu-common.h"
> +
> +/*#define DEBUG_TIS */
> +
> +#ifdef DEBUG_TIS
> +#define dprintf(fmt, ...) \
> + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
> +#else
> +#define dprintf(fmt, ...) \
> + do { } while (0)
> +#endif
> +
> +/* whether the STS interrupt is supported */
> +#define RAISE_STS_IRQ
> +
> +/* tis registers */
> +#define TPM_TIS_REG_ACCESS 0x00
> +#define TPM_TIS_REG_INT_ENABLE 0x08
> +#define TPM_TIS_REG_INT_VECTOR 0x0c
> +#define TPM_TIS_REG_INT_STATUS 0x10
> +#define TPM_TIS_REG_INTF_CAPABILITY 0x14
> +#define TPM_TIS_REG_STS 0x18
> +#define TPM_TIS_REG_DATA_FIFO 0x24
> +#define TPM_TIS_REG_DID_VID 0xf00
> +#define TPM_TIS_REG_RID 0xf04
> +
> +#define TPM_TIS_STS_VALID (1 << 7)
> +#define TPM_TIS_STS_COMMAND_READY (1 << 6)
> +#define TPM_TIS_STS_TPM_GO (1 << 5)
> +#define TPM_TIS_STS_DATA_AVAILABLE (1 << 4)
> +#define TPM_TIS_STS_EXPECT (1 << 3)
> +#define TPM_TIS_STS_RESPONSE_RETRY (1 << 1)
> +
> +#define TPM_TIS_BURST_COUNT_SHIFT 8
> +#define TPM_TIS_BURST_COUNT(X) \
> + ((X) << TPM_TIS_BURST_COUNT_SHIFT)
> +
> +#define TPM_TIS_ACCESS_TPM_REG_VALID_STS (1 << 7)
> +#define TPM_TIS_ACCESS_ACTIVE_LOCALITY (1 << 5)
> +#define TPM_TIS_ACCESS_BEEN_SEIZED (1 << 4)
> +#define TPM_TIS_ACCESS_SEIZE (1 << 3)
> +#define TPM_TIS_ACCESS_PENDING_REQUEST (1 << 2)
> +#define TPM_TIS_ACCESS_REQUEST_USE (1 << 1)
> +#define TPM_TIS_ACCESS_TPM_ESTABLISHMENT (1 << 0)
> +
> +#define TPM_TIS_INT_ENABLED (1 << 31)
> +#define TPM_TIS_INT_DATA_AVAILABLE (1 << 0)
> +#define TPM_TIS_INT_STS_VALID (1 << 1)
> +#define TPM_TIS_INT_LOCALITY_CHANGED (1 << 2)
> +#define TPM_TIS_INT_COMMAND_READY (1 << 7)
> +
> +#define TPM_TIS_INT_POLARITY_MASK (3 << 3)
> +#define TPM_TIS_INT_POLARITY_LOW_LEVEL (1 << 3)
> +
> +#ifndef RAISE_STS_IRQ
> +
> +#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \
> + TPM_TIS_INT_DATA_AVAILABLE | \
> + TPM_TIS_INT_COMMAND_READY)
> +
> +#else
> +
> +#define TPM_TIS_INTERRUPTS_SUPPORTED (TPM_TIS_INT_LOCALITY_CHANGED | \
> + TPM_TIS_INT_DATA_AVAILABLE | \
> + TPM_TIS_INT_STS_VALID | \
> + TPM_TIS_INT_COMMAND_READY)
> +
> +#endif
> +
> +#define TPM_TIS_CAP_INTERRUPT_LOW_LEVEL (1 << 4) /* support is mandatory */
> +#define TPM_TIS_CAPABILITIES_SUPPORTED (TPM_TIS_CAP_INTERRUPT_LOW_LEVEL | \
> + TPM_TIS_INTERRUPTS_SUPPORTED)
> +
> +#define TPM_TIS_TPM_DID 0x0001
> +#define TPM_TIS_TPM_VID PCI_VENDOR_ID_IBM
> +#define TPM_TIS_TPM_RID 0x0001
> +
> +#define TPM_TIS_NO_DATA_BYTE 0xff
> +
> +/* utility functions */
> +
> +static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
> +{
> + return (uint8_t)((addr >> TPM_TIS_LOCALITY_SHIFT) & 0x7);
> +}
> +
> +static uint32_t tpm_tis_get_size_from_buffer(const TPMSizedBuffer *sb)
> +{
> + return be32_to_cpu(*(uint32_t *)&sb->buffer[2]);
> +}
> +
> +static void tpm_tis_show_buffer(const TPMSizedBuffer *sb, const char *string)
> +{
> +#ifdef DEBUG_TIS
> + uint32_t len, i;
> +
> + len = tpm_tis_get_size_from_buffer(sb);
> + dprintf("tpm_tis: %s length = %d\n", string, len);
> + for (i = 0; i < len; i++) {
> + if (i && !(i % 16)) {
> + dprintf("\n");
> + }
> + dprintf("%.2X ", sb->buffer[i]);
> + }
> + dprintf("\n");
> +#endif
> +}
> +
> +/*
> + * Send a request to the TPM.
> + */
> +static void tpm_tis_tpm_send(TPMState *s, uint8_t locty)
> +{
> + TPMTISEmuState *tis = &s->s.tis;
> +
> + tpm_tis_show_buffer(&tis->loc[locty].w_buffer, "tpm_tis: To TPM");
> +
> + s->locty_number = locty;
> + s->locty_data = &tis->loc[locty];
> +
> + /*
> + * w_offset serves as length indicator for length of data;
> + * it's reset when the response comes back
> + */
> + tis->loc[locty].state = TPM_TIS_STATE_EXECUTION;
> +
> + s->be_driver->ops->deliver_request(s->be_driver);
> +}
> +
> +/* raise an interrupt if allowed */
> +static void tpm_tis_raise_irq(TPMState *s, uint8_t locty, uint32_t irqmask)
> +{
> + TPMTISEmuState *tis = &s->s.tis;
> +
> + if (!TPM_TIS_IS_VALID_LOCTY(locty)) {
> + return;
> + }
> +
> + if ((tis->loc[locty].inte & TPM_TIS_INT_ENABLED) &&
> + (tis->loc[locty].inte & irqmask)) {
> + dprintf("tpm_tis: Raising IRQ for flag %08x\n", irqmask);
> + qemu_irq_raise(s->s.tis.irq);
> + tis->loc[locty].ints |= irqmask;
> + }
> +}
> +
> +static uint32_t tpm_tis_check_request_use_except(TPMState *s, uint8_t locty)
> +{
> + uint8_t l;
> +
> + for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
> + if (l == locty) {
> + continue;
> + }
> + if ((s->s.tis.loc[l].access & TPM_TIS_ACCESS_REQUEST_USE)) {
> + return 1;
> + }
> + }
> +
> + return 0;
> +}
> +
> +static void tpm_tis_new_active_locality(TPMState *s, uint8_t new_active_locty)
> +{
> + TPMTISEmuState *tis = &s->s.tis;
> + bool change = (s->s.tis.active_locty != new_active_locty);
> + bool is_seize;
> + uint8_t mask;
> +
> + if (change && TPM_TIS_IS_VALID_LOCTY(s->s.tis.active_locty)) {
> + is_seize = TPM_TIS_IS_VALID_LOCTY(new_active_locty) &&
> + tis->loc[new_active_locty].access & TPM_TIS_ACCESS_SEIZE;
> +
> + if (is_seize) {
> + mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY);
> + } else {
> + mask = ~(TPM_TIS_ACCESS_ACTIVE_LOCALITY|
> + TPM_TIS_ACCESS_REQUEST_USE);
> + }
> + /* reset flags on the old active locality */
> + tis->loc[s->s.tis.active_locty].access &= mask;
> +
> + if (is_seize) {
> + tis->loc[tis->active_locty].access |= TPM_TIS_ACCESS_BEEN_SEIZED;
> + }
> + }
> +
> + tis->active_locty = new_active_locty;
> +
> + dprintf("tpm_tis: Active locality is now %d\n", s->s.tis.active_locty);
> +
> + if (TPM_TIS_IS_VALID_LOCTY(new_active_locty)) {
> + /* set flags on the new active locality */
> + tis->loc[new_active_locty].access |= TPM_TIS_ACCESS_ACTIVE_LOCALITY;
> + tis->loc[new_active_locty].access &= ~(TPM_TIS_ACCESS_REQUEST_USE |
> + TPM_TIS_ACCESS_SEIZE);
> + }
> +
> + if (change) {
> + tpm_tis_raise_irq(s, tis->active_locty, TPM_TIS_INT_LOCALITY_CHANGED);
> + }
> +}
> +
> +/* abort -- this function switches the locality */
> +static void tpm_tis_abort(TPMState *s, uint8_t locty)
> +{
> + TPMTISEmuState *tis = &s->s.tis;
> +
> + tis->loc[locty].r_offset = 0;
> + tis->loc[locty].w_offset = 0;
> +
> + dprintf("tpm_tis: tis_abort: new active locality is %d\n", tis->next_locty);
> +
> + /*
> + * Need to react differently depending on who's aborting now and
> + * which locality will become active afterwards.
> + */
> + if (tis->aborting_locty == tis->next_locty) {
> + tis->loc[tis->aborting_locty].state = TPM_TIS_STATE_READY;
> + tis->loc[tis->aborting_locty].sts = TPM_TIS_STS_COMMAND_READY;
> + tpm_tis_raise_irq(s, tis->aborting_locty, TPM_TIS_INT_COMMAND_READY);
> + }
> +
> + /* locality after abort is another one than the current one */
> + tpm_tis_new_active_locality(s, tis->next_locty);
> +
> + tis->next_locty = TPM_TIS_NO_LOCALITY;
> + /* nobody's aborting a command anymore */
> + tis->aborting_locty = TPM_TIS_NO_LOCALITY;
> +}
> +
> +/* prepare aborting current command */
> +static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
> +{
> + TPMTISEmuState *tis = &s->s.tis;
> + uint8_t busy_locty;
> +
> + tis->aborting_locty = locty;
> + tis->next_locty = newlocty; /* locality after successful abort */
> +
> + /*
> + * only abort a command using an interrupt if currently executing
> + * a command AND if there's a valid connection to the vTPM.
> + */
> + for (busy_locty = 0; busy_locty < TPM_TIS_NUM_LOCALITIES; busy_locty++) {
> + if (tis->loc[busy_locty].state == TPM_TIS_STATE_EXECUTION) {
> + /*
> + * request the backend to cancel. Some backends may not
> + * support it
> + */
> + s->be_driver->ops->cancel_cmd(s->be_driver);
> + return;
> + }
> + }
> +
> + tpm_tis_abort(s, locty);
> +}
> +
> +static void tpm_tis_receive_bh(void *opaque)
> +{
> + TPMState *s = opaque;
> + TPMTISEmuState *tis = &s->s.tis;
> + uint8_t locty = s->locty_number;
> +
> + tis->loc[locty].sts = TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE;
> + tis->loc[locty].state = TPM_TIS_STATE_COMPLETION;
> + tis->loc[locty].r_offset = 0;
> + tis->loc[locty].w_offset = 0;
> +
> + if (TPM_TIS_IS_VALID_LOCTY(tis->next_locty)) {
> + tpm_tis_abort(s, locty);
> + }
> +
> +#ifndef RAISE_STS_IRQ
> + tpm_tis_raise_irq(s, locty, TPM_TIS_INT_DATA_AVAILABLE);
> +#else
> + tpm_tis_raise_irq(s, locty,
> + TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID);
> +#endif
> +}
> +
> +/*
> + * Callback from the TPM to indicate that the response was received.
> + */
> +static void tpm_tis_receive_cb(TPMState *s, uint8_t locty)
> +{
> + TPMTISEmuState *tis = &s->s.tis;
> +
> + assert(s->locty_number == locty);
> +
> + qemu_bh_schedule(tis->bh);
> +}
> +
> +/*
> + * Read a byte of response data
> + */
> +static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty)
> +{
> + TPMTISEmuState *tis = &s->s.tis;
> + uint32_t ret = TPM_TIS_NO_DATA_BYTE;
> + uint16_t len;
> +
> + if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
> + len = tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer);
> +
> + ret = tis->loc[locty].r_buffer.buffer[tis->loc[locty].r_offset++];
> + if (tis->loc[locty].r_offset >= len) {
> + /* got last byte */
> + tis->loc[locty].sts = TPM_TIS_STS_VALID;
> +#ifdef RAISE_STS_IRQ
> + tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
> +#endif
> + }
> + dprintf("tpm_tis: tpm_tis_data_read byte 0x%02x [%d]\n",
> + ret, tis->loc[locty].r_offset-1);
> + }
> +
> + return ret;
> +}
> +
> +/*
> + * Read a register of the TIS interface
> + * See specs pages 33-63 for description of the registers
> + */
> +static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
> + unsigned size)
> +{
> + TPMState *s = opaque;
> + TPMTISEmuState *tis = &s->s.tis;
> + uint16_t offset = addr & 0xffc;
> + uint8_t shift = (addr & 0x3) * 8;
> + uint32_t val = 0xffffffff;
> + uint8_t locty = tpm_tis_locality_from_addr(addr);
> + uint32_t avail;
> +
> + if (s->be_driver->ops->had_startup_error(s->be_driver)) {
> + return val;
> + }
> +
> + switch (offset) {
> + case TPM_TIS_REG_ACCESS:
> + /* never show the SEIZE flag even though we use it internally */
> + val = tis->loc[locty].access & ~TPM_TIS_ACCESS_SEIZE;
> + /* the pending flag is always calculated */
> + if (tpm_tis_check_request_use_except(s, locty)) {
> + val |= TPM_TIS_ACCESS_PENDING_REQUEST;
> + }
> + val |= !s->be_driver->ops->get_tpm_established_flag(s->be_driver);
> + break;
> + case TPM_TIS_REG_INT_ENABLE:
> + val = tis->loc[locty].inte;
> + break;
> + case TPM_TIS_REG_INT_VECTOR:
> + val = tis->irq_num;
> + break;
> + case TPM_TIS_REG_INT_STATUS:
> + val = tis->loc[locty].ints;
> + break;
> + case TPM_TIS_REG_INTF_CAPABILITY:
> + val = TPM_TIS_CAPABILITIES_SUPPORTED;
> + break;
> + case TPM_TIS_REG_STS:
> + if (tis->active_locty == locty) {
> + if ((tis->loc[locty].sts & TPM_TIS_STS_DATA_AVAILABLE)) {
> + val = TPM_TIS_BURST_COUNT(
> + tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer)
> + - tis->loc[locty].r_offset) | tis->loc[locty].sts;
> + } else {
> + avail = tis->loc[locty].w_buffer.size
> + - tis->loc[locty].w_offset;
> + /*
> + * byte-sized reads should not return 0x00 for 0x100
> + * available bytes.
> + */
> + if (size == 1 && avail > 0xff) {
> + avail = 0xff;
> + }
> + val = TPM_TIS_BURST_COUNT(avail) | tis->loc[locty].sts;
> + }
> + }
> + break;
> + case TPM_TIS_REG_DATA_FIFO:
> + if (tis->active_locty == locty) {
> + switch (tis->loc[locty].state) {
> + case TPM_TIS_STATE_COMPLETION:
> + val = tpm_tis_data_read(s, locty);
> + break;
> + default:
> + val = TPM_TIS_NO_DATA_BYTE;
> + break;
> + }
> + }
> + break;
> + case TPM_TIS_REG_DID_VID:
> + val = (TPM_TIS_TPM_DID << 16) | TPM_TIS_TPM_VID;
> + break;
> + case TPM_TIS_REG_RID:
> + val = TPM_TIS_TPM_RID;
> + break;
> + }
> +
> + if (shift) {
> + val >>= shift;
> + }
> +
> + dprintf("tpm_tis: read.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val);
> +
> + return val;
> +}
> +
> +/*
> + * Write a value to a register of the TIS interface
> + * See specs pages 33-63 for description of the registers
> + */
> +static void tpm_tis_mmio_write_intern(void *opaque, hwaddr addr,
> + uint64_t val, unsigned size,
> + bool hw_access)
> +{
> + TPMState *s = opaque;
> + TPMTISEmuState *tis = &s->s.tis;
> + uint16_t off = addr & 0xfff;
> + uint8_t locty = tpm_tis_locality_from_addr(addr);
> + uint8_t active_locty, l;
> + int c, set_new_locty = 1;
> + uint16_t len;
> +
> + dprintf("tpm_tis: write.%u(%08x) = %08x\n", size, (int)addr, (uint32_t)val);
> +
> + if (locty == 4 && !hw_access) {
> + dprintf("tpm_tis: Access to locality 4 only allowed from hardware\n");
> + return;
> + }
> +
> + if (s->be_driver->ops->had_startup_error(s->be_driver)) {
> + return;
> + }
> +
> + switch (off) {
> + case TPM_TIS_REG_ACCESS:
> +
> + if ((val & TPM_TIS_ACCESS_SEIZE)) {
> + val &= ~(TPM_TIS_ACCESS_REQUEST_USE |
> + TPM_TIS_ACCESS_ACTIVE_LOCALITY);
> + }
> +
> + active_locty = tis->active_locty;
> +
> + if ((val & TPM_TIS_ACCESS_ACTIVE_LOCALITY)) {
> + /* give up locality if currently owned */
> + if (tis->active_locty == locty) {
> + dprintf("tpm_tis: Releasing locality %d\n", locty);
> +
> + uint8_t newlocty = TPM_TIS_NO_LOCALITY;
> + /* anybody wants the locality ? */
> + for (c = TPM_TIS_NUM_LOCALITIES - 1; c >= 0; c--) {
> + if ((tis->loc[c].access & TPM_TIS_ACCESS_REQUEST_USE)) {
> + dprintf("tpm_tis: Locality %d requests use.\n", c);
> + newlocty = c;
> + break;
> + }
> + }
> + dprintf("tpm_tis: TPM_TIS_ACCESS_ACTIVE_LOCALITY: "
> + "Next active locality: %d\n",
> + newlocty);
> +
> + if (TPM_TIS_IS_VALID_LOCTY(newlocty)) {
> + set_new_locty = 0;
> + tpm_tis_prep_abort(s, locty, newlocty);
> + } else {
> + active_locty = TPM_TIS_NO_LOCALITY;
> + }
> + } else {
> + /* not currently the owner; clear a pending request */
> + tis->loc[locty].access &= ~TPM_TIS_ACCESS_REQUEST_USE;
> + }
> + }
> +
> + if ((val & TPM_TIS_ACCESS_BEEN_SEIZED)) {
> + tis->loc[locty].access &= ~TPM_TIS_ACCESS_BEEN_SEIZED;
> + }
> +
> + if ((val & TPM_TIS_ACCESS_SEIZE)) {
> + /*
> + * allow seize if a locality is active and the requesting
> + * locality is higher than the one that's active
> + * OR
> + * allow seize for requesting locality if no locality is
> + * active
> + */
> + while ((TPM_TIS_IS_VALID_LOCTY(tis->active_locty) &&
> + locty > tis->active_locty) ||
> + !TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) {
> + bool higher_seize = FALSE;
> +
> + /* already a pending SEIZE ? */
> + if ((tis->loc[locty].access & TPM_TIS_ACCESS_SEIZE)) {
> + break;
> + }
> +
> + /* check for ongoing seize by a higher locality */
> + for (l = locty + 1; l < TPM_TIS_NUM_LOCALITIES; l++) {
> + if ((tis->loc[l].access & TPM_TIS_ACCESS_SEIZE)) {
> + higher_seize = TRUE;
> + break;
> + }
> + }
> +
> + if (higher_seize) {
> + break;
> + }
> +
> + /* cancel any seize by a lower locality */
> + for (l = 0; l < locty - 1; l++) {
> + tis->loc[l].access &= ~TPM_TIS_ACCESS_SEIZE;
> + }
> +
> + tis->loc[locty].access |= TPM_TIS_ACCESS_SEIZE;
> + dprintf("tpm_tis: TPM_TIS_ACCESS_SEIZE: "
> + "Locality %d seized from locality %d\n",
> + locty, tis->active_locty);
> + dprintf("tpm_tis: TPM_TIS_ACCESS_SEIZE: Initiating abort.\n");
> + set_new_locty = 0;
> + tpm_tis_prep_abort(s, tis->active_locty, locty);
> + break;
> + }
> + }
> +
> + if ((val & TPM_TIS_ACCESS_REQUEST_USE)) {
> + if (tis->active_locty != locty) {
> + if (TPM_TIS_IS_VALID_LOCTY(tis->active_locty)) {
> + tis->loc[locty].access |= TPM_TIS_ACCESS_REQUEST_USE;
> + } else {
> + /* no locality active -> make this one active now */
> + active_locty = locty;
> + }
> + }
> + }
> +
> + if (set_new_locty) {
> + tpm_tis_new_active_locality(s, active_locty);
> + }
> +
> + break;
> + case TPM_TIS_REG_INT_ENABLE:
> + if (tis->active_locty != locty) {
> + break;
> + }
> +
> + tis->loc[locty].inte = (val & (TPM_TIS_INT_ENABLED |
> + TPM_TIS_INT_POLARITY_MASK |
> + TPM_TIS_INTERRUPTS_SUPPORTED));
> + break;
> + case TPM_TIS_REG_INT_VECTOR:
> + /* hard wired -- ignore */
> + break;
> + case TPM_TIS_REG_INT_STATUS:
> + if (tis->active_locty != locty) {
> + break;
> + }
> +
> + /* clearing of interrupt flags */
> + if (((val & TPM_TIS_INTERRUPTS_SUPPORTED)) &&
> + (tis->loc[locty].ints & TPM_TIS_INTERRUPTS_SUPPORTED)) {
> + tis->loc[locty].ints &= ~val;
> + if (tis->loc[locty].ints == 0) {
> + qemu_irq_lower(tis->irq);
> + dprintf("tpm_tis: Lowering IRQ\n");
> + }
> + }
> + tis->loc[locty].ints &= ~(val & TPM_TIS_INTERRUPTS_SUPPORTED);
> + break;
> + case TPM_TIS_REG_STS:
> + if (tis->active_locty != locty) {
> + break;
> + }
> +
> + val &= (TPM_TIS_STS_COMMAND_READY | TPM_TIS_STS_TPM_GO |
> + TPM_TIS_STS_RESPONSE_RETRY);
> +
> + if (val == TPM_TIS_STS_COMMAND_READY) {
> + switch (tis->loc[locty].state) {
> +
> + case TPM_TIS_STATE_READY:
> + tis->loc[locty].w_offset = 0;
> + tis->loc[locty].r_offset = 0;
> + break;
> +
> + case TPM_TIS_STATE_IDLE:
> + tis->loc[locty].sts = TPM_TIS_STS_COMMAND_READY;
> + tis->loc[locty].state = TPM_TIS_STATE_READY;
> + tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
> + break;
> +
> + case TPM_TIS_STATE_EXECUTION:
> + case TPM_TIS_STATE_RECEPTION:
> + /* abort currently running command */
> + dprintf("tpm_tis: %s: Initiating abort.\n",
> + __func__);
> + tpm_tis_prep_abort(s, locty, locty);
> + break;
> +
> + case TPM_TIS_STATE_COMPLETION:
> + tis->loc[locty].w_offset = 0;
> + tis->loc[locty].r_offset = 0;
> + /* shortcut to ready state with C/R set */
> + tis->loc[locty].state = TPM_TIS_STATE_READY;
> + if (!(tis->loc[locty].sts & TPM_TIS_STS_COMMAND_READY)) {
> + tis->loc[locty].sts = TPM_TIS_STS_COMMAND_READY;
> + tpm_tis_raise_irq(s, locty, TPM_TIS_INT_COMMAND_READY);
> + }
> + tis->loc[locty].sts &= ~(TPM_TIS_STS_DATA_AVAILABLE);
> + break;
> +
> + }
> + } else if (val == TPM_TIS_STS_TPM_GO) {
> + switch (tis->loc[locty].state) {
> + case TPM_TIS_STATE_RECEPTION:
> + if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT) == 0) {
> + tpm_tis_tpm_send(s, locty);
> + }
> + break;
> + default:
> + /* ignore */
> + break;
> + }
> + } else if (val == TPM_TIS_STS_RESPONSE_RETRY) {
> + switch (tis->loc[locty].state) {
> + case TPM_TIS_STATE_COMPLETION:
> + tis->loc[locty].r_offset = 0;
> + tis->loc[locty].sts = TPM_TIS_STS_VALID |
> + TPM_TIS_STS_DATA_AVAILABLE;
> + break;
> + default:
> + /* ignore */
> + break;
> + }
> + }
> + break;
> + case TPM_TIS_REG_DATA_FIFO:
> + /* data fifo */
> + if (tis->active_locty != locty) {
> + break;
> + }
> +
> + if (tis->loc[locty].state == TPM_TIS_STATE_IDLE ||
> + tis->loc[locty].state == TPM_TIS_STATE_EXECUTION ||
> + tis->loc[locty].state == TPM_TIS_STATE_COMPLETION) {
> + /* drop the byte */
> + } else {
> + dprintf("tpm_tis: Byte to send to TPM: %02x\n", (uint8_t)val);
> + if (tis->loc[locty].state == TPM_TIS_STATE_READY) {
> + tis->loc[locty].state = TPM_TIS_STATE_RECEPTION;
> + tis->loc[locty].sts = TPM_TIS_STS_EXPECT | TPM_TIS_STS_VALID;
> + }
> +
> + if ((tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
> + if (tis->loc[locty].w_offset < tis->loc[locty].w_buffer.size) {
> + tis->loc[locty].w_buffer.
> + buffer[tis->loc[locty].w_offset++] = (uint8_t)val;
> + } else {
> + tis->loc[locty].sts = TPM_TIS_STS_VALID;
> + }
> + }
> +
> + /* check for complete packet */
> + if (tis->loc[locty].w_offset > 5 &&
> + (tis->loc[locty].sts & TPM_TIS_STS_EXPECT)) {
> + /* we have a packet length - see if we have all of it */
> +#ifdef RAISE_STS_IRQ
> + bool needIrq = !(tis->loc[locty].sts & TPM_TIS_STS_VALID);
> +#endif
> + len = tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
> + if (len > tis->loc[locty].w_offset) {
> + tis->loc[locty].sts = TPM_TIS_STS_EXPECT |
> + TPM_TIS_STS_VALID;
> + } else {
> + /* packet complete */
> + tis->loc[locty].sts = TPM_TIS_STS_VALID;
> + }
> +#ifdef RAISE_STS_IRQ
> + if (needIrq) {
> + tpm_tis_raise_irq(s, locty, TPM_TIS_INT_STS_VALID);
> + }
> +#endif
> + }
> + }
> + break;
> + }
> +}
> +
> +static void tpm_tis_mmio_write(void *opaque, hwaddr addr,
> + uint64_t val, unsigned size)
> +{
> + return tpm_tis_mmio_write_intern(opaque, addr, val, size, false);
> +}
> +
> +static const MemoryRegionOps tpm_tis_memory_ops = {
> + .read = tpm_tis_mmio_read,
> + .write = tpm_tis_mmio_write,
> + .endianness = DEVICE_LITTLE_ENDIAN,
> + .valid = {
> + .min_access_size = 1,
> + .max_access_size = 4,
> + },
> +};
> +
> +static int tpm_tis_do_startup_tpm(TPMState *s)
> +{
> + return s->be_driver->ops->startup_tpm(s->be_driver);
> +}
> +
> +/*
> + * This function is called when the machine starts, resets or due to
> + * S3 resume.
> + */
> +static void tpm_tis_reset(DeviceState *d)
> +{
> + TPMState *s = DO_UPCAST(TPMState, busdev.qdev, d);
> + TPMTISEmuState *tis = &s->s.tis;
> + int c;
> +
> + s->be_driver->ops->reset(s->be_driver);
> +
> + tis->active_locty = TPM_TIS_NO_LOCALITY;
> + tis->next_locty = TPM_TIS_NO_LOCALITY;
> + tis->aborting_locty = TPM_TIS_NO_LOCALITY;
> +
> + for (c = 0; c < TPM_TIS_NUM_LOCALITIES; c++) {
> + tis->loc[c].access = TPM_TIS_ACCESS_TPM_REG_VALID_STS;
> + tis->loc[c].sts = 0;
> + tis->loc[c].inte = TPM_TIS_INT_POLARITY_LOW_LEVEL;
> + tis->loc[c].ints = 0;
> + tis->loc[c].state = TPM_TIS_STATE_IDLE;
> +
> + tis->loc[c].w_offset = 0;
> + s->be_driver->ops->realloc_buffer(&tis->loc[c].w_buffer);
> + tis->loc[c].r_offset = 0;
> + s->be_driver->ops->realloc_buffer(&tis->loc[c].r_buffer);
> + }
> +
> + tpm_tis_do_startup_tpm(s);
> +}
> +
> +static int tpm_tis_init(ISADevice *dev)
> +{
> + TPMState *s = DO_UPCAST(TPMState, busdev, dev);
> + TPMTISEmuState *tis = &s->s.tis;
> + int rc;
> +
> + s->be_driver = qemu_find_tpm(s->backend);
> + if (!s->be_driver) {
> + error_report("tpm_tis: backend driver with id %s could not be "
> + "found.n\n", s->backend);
> + goto err_exit;
> + }
> +
> + s->be_driver->fe_model = "tpm-tis";
> +
> + if (s->be_driver->ops->init(s->be_driver, s, tpm_tis_receive_cb)) {
> + goto err_exit;
> + }
> +
> + tis->bh = qemu_bh_new(tpm_tis_receive_bh, s);
> +
> + if (tis->irq_num > 15) {
> + error_report("IRQ %d for TPM TIS is outside valid range of 0 to 15.\n",
> + tis->irq_num);
> + goto err_exit;
> + }
> +
> + isa_init_irq(dev, &tis->irq, tis->irq_num);
> +
> + memory_region_init_io(&s->mmio, &tpm_tis_memory_ops, s, "tpm-tis-mmio",
> + TPM_TIS_NUM_LOCALITIES << TPM_TIS_LOCALITY_SHIFT);
> + memory_region_add_subregion(get_system_memory(), TPM_TIS_ADDR_BASE,
> + &s->mmio);
> +
> + rc = tpm_tis_do_startup_tpm(s);
> + if (rc != 0) {
> + goto err_destroy_memory;
> + }
> +
> + return 0;
> +
> + err_destroy_memory:
> + memory_region_del_subregion(get_system_memory(), &s->mmio);
> + memory_region_destroy(&s->mmio);
> +
> + err_exit:
> + return -1;
> +}
> +
> +static const VMStateDescription vmstate_tpm_tis = {
> + .name = "tpm",
> + .unmigratable = 1,
> +};
> +
> +static Property tpm_tis_properties[] = {
> + DEFINE_PROP_UINT32("irq", TPMState,
> + s.tis.irq_num, TPM_TIS_IRQ),
> + DEFINE_PROP_STRING("tpmdev", TPMState, backend),
> + DEFINE_PROP_END_OF_LIST(),
> +};
> +
> +static void tpm_tis_class_initfn(ObjectClass *klass, void *data)
> +{
> + DeviceClass *dc = DEVICE_CLASS(klass);
> + ISADeviceClass *ic = ISA_DEVICE_CLASS(klass);
> +
> + ic->init = tpm_tis_init;
> +
> + dc->props = tpm_tis_properties;
> + dc->reset = tpm_tis_reset;
> + dc->vmsd = &vmstate_tpm_tis;
> +}
> +
> +static TypeInfo tpm_tis_info = {
> + .name = "tpm-tis",
> + .parent = TYPE_ISA_DEVICE,
> + .class_init = tpm_tis_class_initfn,
> + .instance_size = sizeof(TPMState),
> +};
> +
> +static void tpm_tis_register(void)
> +{
> + type_register_static(&tpm_tis_info);
> +}
> +
> +type_init(tpm_tis_register)
>
Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
--
Regards,
Corey Bryant
^ permalink raw reply [flat|nested] 21+ messages in thread
* [Qemu-devel] [PATCH V20 3/8] Add a debug register
2013-01-18 16:02 [Qemu-devel] [PATCH V20 0/8] Qemu Trusted Platform Module (TPM) integration Stefan Berger
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 1/8] Support for TPM command line options Stefan Berger
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 2/8] Add TPM (frontend) hardware interface (TPM TIS) to QEMU Stefan Berger
@ 2013-01-18 16:02 ` Stefan Berger
2013-02-01 17:07 ` Corey Bryant
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 4/8] Build the TPM frontend code Stefan Berger
` (4 subsequent siblings)
7 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2013-01-18 16:02 UTC (permalink / raw)
To: stefanb, qemu-devel, anthony; +Cc: andreas.niederl, mst
This patch uses the possibility to add a vendor-specific register and
adds a debug register useful for dumping the TIS's internal state. This
register is only active in a debug build (#define DEBUG_TIS).
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
hw/tpm_tis.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 70 insertions(+)
diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c
index 3ed813d..f71192e 100644
--- a/hw/tpm_tis.c
+++ b/hw/tpm_tis.c
@@ -52,6 +52,9 @@
#define TPM_TIS_REG_DID_VID 0xf00
#define TPM_TIS_REG_RID 0xf04
+/* vendor-specific registers */
+#define TPM_TIS_REG_DEBUG 0xf90
+
#define TPM_TIS_STS_VALID (1 << 7)
#define TPM_TIS_STS_COMMAND_READY (1 << 6)
#define TPM_TIS_STS_TPM_GO (1 << 5)
@@ -105,6 +108,11 @@
#define TPM_TIS_NO_DATA_BYTE 0xff
+/* local prototypes */
+
+static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
+ unsigned size);
+
/* utility functions */
static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
@@ -346,6 +354,63 @@ static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty)
return ret;
}
+#ifdef DEBUG_TIS
+static void tpm_tis_dump_state(void *opaque, hwaddr addr)
+{
+ static const unsigned regs[] = {
+ TPM_TIS_REG_ACCESS,
+ TPM_TIS_REG_INT_ENABLE,
+ TPM_TIS_REG_INT_VECTOR,
+ TPM_TIS_REG_INT_STATUS,
+ TPM_TIS_REG_INTF_CAPABILITY,
+ TPM_TIS_REG_STS,
+ TPM_TIS_REG_DID_VID,
+ TPM_TIS_REG_RID,
+ 0xfff};
+ int idx;
+ uint8_t locty = tpm_tis_locality_from_addr(addr);
+ hwaddr base = addr & ~0xfff;
+ TPMState *s = opaque;
+ TPMTISEmuState *tis = &s->s.tis;
+
+ dprintf("tpm_tis: active locality : %d\n"
+ "tpm_tis: state of locality %d : %d\n"
+ "tpm_tis: register dump:\n",
+ tis->active_locty,
+ locty, tis->loc[locty].state);
+
+ for (idx = 0; regs[idx] != 0xfff; idx++) {
+ dprintf("tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
+ (uint32_t)tpm_tis_mmio_read(opaque, base + regs[idx], 4));
+ }
+
+ dprintf("tpm_tis: read offset : %d\n"
+ "tpm_tis: result buffer : ",
+ tis->loc[locty].r_offset);
+ for (idx = 0;
+ idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer);
+ idx++) {
+ dprintf("%c%02x%s",
+ tis->loc[locty].r_offset == idx ? '>' : ' ',
+ tis->loc[locty].r_buffer.buffer[idx],
+ ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
+ }
+ dprintf("\n"
+ "tpm_tis: write offset : %d\n"
+ "tpm_tis: request buffer: ",
+ tis->loc[locty].w_offset);
+ for (idx = 0;
+ idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
+ idx++) {
+ dprintf("%c%02x%s",
+ tis->loc[locty].w_offset == idx ? '>' : ' ',
+ tis->loc[locty].w_buffer.buffer[idx],
+ ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
+ }
+ dprintf("\n");
+}
+#endif
+
/*
* Read a register of the TIS interface
* See specs pages 33-63 for description of the registers
@@ -425,6 +490,11 @@ static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
case TPM_TIS_REG_RID:
val = TPM_TIS_TPM_RID;
break;
+#ifdef DEBUG_TIS
+ case TPM_TIS_REG_DEBUG:
+ tpm_tis_dump_state(opaque, addr);
+ break;
+#endif
}
if (shift) {
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [Qemu-devel] [PATCH V20 3/8] Add a debug register
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 3/8] Add a debug register Stefan Berger
@ 2013-02-01 17:07 ` Corey Bryant
0 siblings, 0 replies; 21+ messages in thread
From: Corey Bryant @ 2013-02-01 17:07 UTC (permalink / raw)
To: Stefan Berger; +Cc: mst, qemu-devel, anthony, andreas.niederl
On 01/18/2013 11:02 AM, Stefan Berger wrote:
> This patch uses the possibility to add a vendor-specific register and
> adds a debug register useful for dumping the TIS's internal state. This
> register is only active in a debug build (#define DEBUG_TIS).
>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> ---
> hw/tpm_tis.c | 70 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
> 1 file changed, 70 insertions(+)
>
> diff --git a/hw/tpm_tis.c b/hw/tpm_tis.c
> index 3ed813d..f71192e 100644
> --- a/hw/tpm_tis.c
> +++ b/hw/tpm_tis.c
> @@ -52,6 +52,9 @@
> #define TPM_TIS_REG_DID_VID 0xf00
> #define TPM_TIS_REG_RID 0xf04
>
> +/* vendor-specific registers */
> +#define TPM_TIS_REG_DEBUG 0xf90
> +
> #define TPM_TIS_STS_VALID (1 << 7)
> #define TPM_TIS_STS_COMMAND_READY (1 << 6)
> #define TPM_TIS_STS_TPM_GO (1 << 5)
> @@ -105,6 +108,11 @@
>
> #define TPM_TIS_NO_DATA_BYTE 0xff
>
> +/* local prototypes */
> +
> +static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
> + unsigned size);
> +
> /* utility functions */
>
> static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
> @@ -346,6 +354,63 @@ static uint32_t tpm_tis_data_read(TPMState *s, uint8_t locty)
> return ret;
> }
>
> +#ifdef DEBUG_TIS
> +static void tpm_tis_dump_state(void *opaque, hwaddr addr)
> +{
> + static const unsigned regs[] = {
> + TPM_TIS_REG_ACCESS,
> + TPM_TIS_REG_INT_ENABLE,
> + TPM_TIS_REG_INT_VECTOR,
> + TPM_TIS_REG_INT_STATUS,
> + TPM_TIS_REG_INTF_CAPABILITY,
> + TPM_TIS_REG_STS,
> + TPM_TIS_REG_DID_VID,
> + TPM_TIS_REG_RID,
> + 0xfff};
> + int idx;
> + uint8_t locty = tpm_tis_locality_from_addr(addr);
> + hwaddr base = addr & ~0xfff;
> + TPMState *s = opaque;
> + TPMTISEmuState *tis = &s->s.tis;
> +
> + dprintf("tpm_tis: active locality : %d\n"
> + "tpm_tis: state of locality %d : %d\n"
> + "tpm_tis: register dump:\n",
> + tis->active_locty,
> + locty, tis->loc[locty].state);
> +
> + for (idx = 0; regs[idx] != 0xfff; idx++) {
> + dprintf("tpm_tis: 0x%04x : 0x%08x\n", regs[idx],
> + (uint32_t)tpm_tis_mmio_read(opaque, base + regs[idx], 4));
> + }
> +
> + dprintf("tpm_tis: read offset : %d\n"
> + "tpm_tis: result buffer : ",
> + tis->loc[locty].r_offset);
> + for (idx = 0;
> + idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].r_buffer);
> + idx++) {
> + dprintf("%c%02x%s",
> + tis->loc[locty].r_offset == idx ? '>' : ' ',
> + tis->loc[locty].r_buffer.buffer[idx],
> + ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
> + }
> + dprintf("\n"
> + "tpm_tis: write offset : %d\n"
> + "tpm_tis: request buffer: ",
> + tis->loc[locty].w_offset);
> + for (idx = 0;
> + idx < tpm_tis_get_size_from_buffer(&tis->loc[locty].w_buffer);
> + idx++) {
> + dprintf("%c%02x%s",
> + tis->loc[locty].w_offset == idx ? '>' : ' ',
> + tis->loc[locty].w_buffer.buffer[idx],
> + ((idx & 0xf) == 0xf) ? "\ntpm_tis: " : "");
> + }
> + dprintf("\n");
> +}
> +#endif
> +
> /*
> * Read a register of the TIS interface
> * See specs pages 33-63 for description of the registers
> @@ -425,6 +490,11 @@ static uint64_t tpm_tis_mmio_read(void *opaque, hwaddr addr,
> case TPM_TIS_REG_RID:
> val = TPM_TIS_TPM_RID;
> break;
> +#ifdef DEBUG_TIS
> + case TPM_TIS_REG_DEBUG:
> + tpm_tis_dump_state(opaque, addr);
> + break;
> +#endif
> }
>
> if (shift) {
>
Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
--
Regards,
Corey Bryant
^ permalink raw reply [flat|nested] 21+ messages in thread
* [Qemu-devel] [PATCH V20 4/8] Build the TPM frontend code
2013-01-18 16:02 [Qemu-devel] [PATCH V20 0/8] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (2 preceding siblings ...)
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 3/8] Add a debug register Stefan Berger
@ 2013-01-18 16:02 ` Stefan Berger
2013-02-01 17:08 ` Corey Bryant
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 5/8] Add a TPM Passthrough backend driver implementation Stefan Berger
` (3 subsequent siblings)
7 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2013-01-18 16:02 UTC (permalink / raw)
To: stefanb, qemu-devel, anthony; +Cc: andreas.niederl, mst
Build the TPM frontend code that has been added so far.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
Makefile.objs | 1 +
configure | 11 +++++++++++
hw/Makefile.objs | 3 +++
3 files changed, 15 insertions(+)
diff --git a/Makefile.objs b/Makefile.objs
index 3c7abca..637669d 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -101,6 +101,7 @@ common-obj-$(CONFIG_POSIX) += compatfd.o
common-obj-y += qemu-timer.o qemu-timer-common.o
common-obj-y += qtest.o
common-obj-y += vl.o
+common-obj-y += tpm.o
common-obj-$(CONFIG_SLIRP) += slirp/
diff --git a/configure b/configure
index 994f731..a6f8c4c 100755
--- a/configure
+++ b/configure
@@ -223,6 +223,7 @@ libiscsi=""
coroutine=""
seccomp=""
glusterfs=""
+tpm="no"
# parse CC options first
for opt do
@@ -871,6 +872,8 @@ for opt do
;;
--enable-glusterfs) glusterfs="yes"
;;
+ --enable-tpm) tpm="yes"
+ ;;
*) echo "ERROR: unknown option $opt"; show_help="yes"
;;
esac
@@ -1119,6 +1122,7 @@ echo " --with-coroutine=BACKEND coroutine backend. Supported options:"
echo " gthread, ucontext, sigaltstack, windows"
echo " --enable-glusterfs enable GlusterFS backend"
echo " --disable-glusterfs disable GlusterFS backend"
+echo " --enable-tpm enable TPM support"
echo ""
echo "NOTE: The object files are built at the place where configure is launched"
exit 1
@@ -3250,6 +3254,7 @@ echo "build guest agent $guest_agent"
echo "seccomp support $seccomp"
echo "coroutine backend $coroutine_backend"
echo "GlusterFS support $glusterfs"
+echo "TPM support $tpm"
if test "$sdl_too_old" = "yes"; then
echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -4149,6 +4154,12 @@ if test "$gprof" = "yes" ; then
fi
fi
+if test "$tpm" = "yes"; then
+ if test "$target_softmmu" = "yes" ; then
+ echo "CONFIG_TPM=y" >> $config_host_mak
+ fi
+fi
+
if test "$ARCH" = "tci"; then
linker_script=""
else
diff --git a/hw/Makefile.objs b/hw/Makefile.objs
index d581d8d..15eb567 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -141,6 +141,9 @@ common-obj-$(CONFIG_MIPSNET) += mipsnet.o
common-obj-y += null-machine.o
+# TPM
+common-obj-$(CONFIG_TPM) += tpm_tis.o
+
# Sound
sound-obj-y =
sound-obj-$(CONFIG_SB16) += sb16.o
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [Qemu-devel] [PATCH V20 4/8] Build the TPM frontend code
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 4/8] Build the TPM frontend code Stefan Berger
@ 2013-02-01 17:08 ` Corey Bryant
0 siblings, 0 replies; 21+ messages in thread
From: Corey Bryant @ 2013-02-01 17:08 UTC (permalink / raw)
To: Stefan Berger; +Cc: mst, qemu-devel, anthony, andreas.niederl
On 01/18/2013 11:02 AM, Stefan Berger wrote:
> Build the TPM frontend code that has been added so far.
>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> ---
> Makefile.objs | 1 +
> configure | 11 +++++++++++
> hw/Makefile.objs | 3 +++
> 3 files changed, 15 insertions(+)
>
> diff --git a/Makefile.objs b/Makefile.objs
> index 3c7abca..637669d 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -101,6 +101,7 @@ common-obj-$(CONFIG_POSIX) += compatfd.o
> common-obj-y += qemu-timer.o qemu-timer-common.o
> common-obj-y += qtest.o
> common-obj-y += vl.o
> +common-obj-y += tpm.o
>
> common-obj-$(CONFIG_SLIRP) += slirp/
>
> diff --git a/configure b/configure
> index 994f731..a6f8c4c 100755
> --- a/configure
> +++ b/configure
> @@ -223,6 +223,7 @@ libiscsi=""
> coroutine=""
> seccomp=""
> glusterfs=""
> +tpm="no"
>
> # parse CC options first
> for opt do
> @@ -871,6 +872,8 @@ for opt do
> ;;
> --enable-glusterfs) glusterfs="yes"
> ;;
> + --enable-tpm) tpm="yes"
> + ;;
> *) echo "ERROR: unknown option $opt"; show_help="yes"
> ;;
> esac
> @@ -1119,6 +1122,7 @@ echo " --with-coroutine=BACKEND coroutine backend. Supported options:"
> echo " gthread, ucontext, sigaltstack, windows"
> echo " --enable-glusterfs enable GlusterFS backend"
> echo " --disable-glusterfs disable GlusterFS backend"
> +echo " --enable-tpm enable TPM support"
> echo ""
> echo "NOTE: The object files are built at the place where configure is launched"
> exit 1
> @@ -3250,6 +3254,7 @@ echo "build guest agent $guest_agent"
> echo "seccomp support $seccomp"
> echo "coroutine backend $coroutine_backend"
> echo "GlusterFS support $glusterfs"
> +echo "TPM support $tpm"
>
> if test "$sdl_too_old" = "yes"; then
> echo "-> Your SDL version is too old - please upgrade to have SDL support"
> @@ -4149,6 +4154,12 @@ if test "$gprof" = "yes" ; then
> fi
> fi
>
> +if test "$tpm" = "yes"; then
> + if test "$target_softmmu" = "yes" ; then
> + echo "CONFIG_TPM=y" >> $config_host_mak
> + fi
> +fi
> +
> if test "$ARCH" = "tci"; then
> linker_script=""
> else
> diff --git a/hw/Makefile.objs b/hw/Makefile.objs
> index d581d8d..15eb567 100644
> --- a/hw/Makefile.objs
> +++ b/hw/Makefile.objs
> @@ -141,6 +141,9 @@ common-obj-$(CONFIG_MIPSNET) += mipsnet.o
>
> common-obj-y += null-machine.o
>
> +# TPM
> +common-obj-$(CONFIG_TPM) += tpm_tis.o
> +
> # Sound
> sound-obj-y =
> sound-obj-$(CONFIG_SB16) += sb16.o
>
Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
--
Regards,
Corey Bryant
^ permalink raw reply [flat|nested] 21+ messages in thread
* [Qemu-devel] [PATCH V20 5/8] Add a TPM Passthrough backend driver implementation
2013-01-18 16:02 [Qemu-devel] [PATCH V20 0/8] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (3 preceding siblings ...)
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 4/8] Build the TPM frontend code Stefan Berger
@ 2013-01-18 16:02 ` Stefan Berger
2013-01-19 9:18 ` Blue Swirl
2013-02-01 19:03 ` Corey Bryant
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 6/8] Add support for cancelling of a TPM command Stefan Berger
` (2 subsequent siblings)
7 siblings, 2 replies; 21+ messages in thread
From: Stefan Berger @ 2013-01-18 16:02 UTC (permalink / raw)
To: stefanb, qemu-devel, anthony; +Cc: andreas.niederl, mst
>From Andreas Niederl's original posting with adaptations where necessary:
This patch is based of off version 9 of Stefan Berger's patch series
"QEMU Trusted Platform Module (TPM) integration"
and adds a new backend driver for it.
This patch adds a passthrough backend driver for passing commands sent to the
emulated TPM device directly to a TPM device opened on the host machine.
Thus it is possible to use a hardware TPM device in a system running on QEMU,
providing the ability to access a TPM in a special state (e.g. after a Trusted
Boot).
This functionality is being used in the acTvSM Trusted Virtualization Platform
which is available on [1].
Usage example:
qemu-system-x86_64 -tpmdev passthrough,id=tpm0,path=/dev/tpm0 \
-device tpm-tis,tpmdev=tpm0 \
-cdrom test.iso -boot d
Some notes about the host TPM:
The TPM needs to be enabled and activated. If that's not the case one
has to go through the BIOS/UEFI and enable and activate that TPM for TPM
commands to work as expected.
It may be necessary to boot the kernel using tpm_tis.force=1 in the boot
command line or 'modprobe tpm_tis force=1' in case of using it as a module.
Regards,
Andreas Niederl, Stefan Berger
[1] http://trustedjava.sourceforge.net/
Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
configure | 3 +
hw/Makefile.objs | 3 +-
hw/tpm_backend.c | 58 ++++++++
hw/tpm_backend.h | 43 ++++++
hw/tpm_passthrough.c | 378 +++++++++++++++++++++++++++++++++++++++++++++++++++
qemu-char.c | 24 ++++
qemu-options.hx | 38 +++++-
qemu_socket.h | 1 +
tpm.c | 17 +++
tpm.h | 33 +++++
vl.c | 2 +
11 files changed, 598 insertions(+), 2 deletions(-)
create mode 100644 hw/tpm_backend.c
create mode 100644 hw/tpm_backend.h
create mode 100644 hw/tpm_passthrough.c
diff --git a/configure b/configure
index a6f8c4c..73fc146 100755
--- a/configure
+++ b/configure
@@ -4156,6 +4156,9 @@ fi
if test "$tpm" = "yes"; then
if test "$target_softmmu" = "yes" ; then
+ if test "$linux" = "yes" ; then
+ echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_host_mak
+ fi
echo "CONFIG_TPM=y" >> $config_host_mak
fi
fi
diff --git a/hw/Makefile.objs b/hw/Makefile.objs
index 15eb567..a90c535 100644
--- a/hw/Makefile.objs
+++ b/hw/Makefile.objs
@@ -142,7 +142,8 @@ common-obj-$(CONFIG_MIPSNET) += mipsnet.o
common-obj-y += null-machine.o
# TPM
-common-obj-$(CONFIG_TPM) += tpm_tis.o
+common-obj-$(CONFIG_TPM) += tpm_tis.o tpm_backend.o
+common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
# Sound
sound-obj-y =
diff --git a/hw/tpm_backend.c b/hw/tpm_backend.c
new file mode 100644
index 0000000..4cf0809
--- /dev/null
+++ b/hw/tpm_backend.c
@@ -0,0 +1,58 @@
+/*
+ * common TPM backend driver functions
+ *
+ * Copyright (c) 2012 IBM Corporation
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "tpm.h"
+#include "qemu-thread.h"
+#include "hw/tpm_backend.h"
+
+void tpm_backend_thread_deliver_request(TPMBackendThread *tbt)
+{
+ g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_PROCESS_CMD, NULL);
+}
+
+void tpm_backend_thread_create(TPMBackendThread *tbt,
+ GFunc func, gpointer user_data)
+{
+ if (!tbt->pool) {
+ tbt->pool = g_thread_pool_new(func, user_data, 1, TRUE, NULL);
+ g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_INIT, NULL);
+ }
+}
+
+void tpm_backend_thread_end(TPMBackendThread *tbt)
+{
+ if (tbt->pool) {
+ g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_END, NULL);
+ g_thread_pool_free(tbt->pool, FALSE, TRUE);
+ tbt->pool = NULL;
+ }
+}
+
+void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt,
+ GFunc func, gpointer user_data)
+{
+ if (!tbt->pool) {
+ tpm_backend_thread_create(tbt, func, user_data);
+ } else {
+ g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_TPM_RESET,
+ NULL);
+ }
+}
diff --git a/hw/tpm_backend.h b/hw/tpm_backend.h
new file mode 100644
index 0000000..f5fe198
--- /dev/null
+++ b/hw/tpm_backend.h
@@ -0,0 +1,43 @@
+/*
+ * common TPM backend driver functions
+ *
+ * Copyright (c) 2012 IBM Corporation
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#ifndef HW_TPM_BACKEND_H
+#define HW_TPM_BACKEND_H
+
+typedef struct TPMBackendThread {
+ GThreadPool *pool;
+} TPMBackendThread;
+
+void tpm_backend_thread_deliver_request(TPMBackendThread *tbt);
+void tpm_backend_thread_create(TPMBackendThread *tbt,
+ GFunc func, gpointer user_data);
+void tpm_backend_thread_end(TPMBackendThread *tbt);
+void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt,
+ GFunc func, gpointer user_data);
+
+typedef enum TPMBackendCmd {
+ TPM_BACKEND_CMD_INIT = 1,
+ TPM_BACKEND_CMD_PROCESS_CMD,
+ TPM_BACKEND_CMD_END,
+ TPM_BACKEND_CMD_TPM_RESET,
+} TPMBackendCmd;
+
+#endif /* HW_TPM_BACKEND_H */
diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
new file mode 100644
index 0000000..f9e6923
--- /dev/null
+++ b/hw/tpm_passthrough.c
@@ -0,0 +1,378 @@
+/*
+ * passthrough TPM driver
+ *
+ * Copyright (c) 2010, 2011 IBM Corporation
+ * Authors:
+ * Stefan Berger <stefanb@us.ibm.com>
+ *
+ * Copyright (C) 2011 IAIK, Graz University of Technology
+ * Author: Andreas Niederl
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>
+ */
+
+#include "qemu-common.h"
+#include "qemu-error.h"
+#include "qemu_socket.h"
+#include "tpm.h"
+#include "hw/hw.h"
+#include "hw/tpm_tis.h"
+#include "hw/tpm_backend.h"
+#include "hw/pc.h"
+
+/* #define DEBUG_TPM */
+
+#ifdef DEBUG_TPM
+#define dprintf(fmt, ...) \
+ do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
+#else
+#define dprintf(fmt, ...) \
+ do { } while (0)
+#endif
+
+/* data structures */
+
+typedef struct TPMPassthruThreadParams {
+ TPMState *tpm_state;
+
+ TPMRecvDataCB *recv_data_callback;
+ TPMBackend *tb;
+} TPMPassthruThreadParams;
+
+struct TPMPassthruState {
+ TPMBackendThread tbt;
+
+ TPMPassthruThreadParams tpm_thread_params;
+
+ char *tpm_dev;
+ int tpm_fd;
+ bool had_startup_error;
+};
+
+#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
+
+static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
+{
+ return send_all(fd, buf, len);
+}
+
+static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
+{
+ return recv_all(fd, buf, len, true);
+}
+
+static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
+{
+ struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)buf;
+
+ return be32_to_cpu(resp->len);
+}
+
+static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
+ const uint8_t *in, uint32_t in_len,
+ uint8_t *out, uint32_t out_len)
+{
+ int ret;
+
+ ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
+ if (ret != in_len) {
+ error_report("tpm_passthrough: error while transmitting data "
+ "to TPM: %s (%i)\n",
+ strerror(errno), errno);
+ goto err_exit;
+ }
+
+ ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
+ if (ret < 0) {
+ error_report("tpm_passthrough: error while reading data from "
+ "TPM: %s (%i)\n",
+ strerror(errno), errno);
+ } else if (ret < sizeof(struct tpm_resp_hdr) ||
+ tpm_passthrough_get_size_from_buffer(out) != ret) {
+ ret = -1;
+ error_report("tpm_passthrough: received invalid response "
+ "packet from TPM\n");
+ }
+
+err_exit:
+ if (ret < 0) {
+ tpm_write_fatal_error_response(out, out_len);
+ }
+
+ return ret;
+}
+
+static int tpm_passthrough_unix_transfer(int tpm_fd,
+ const TPMLocality *locty_data)
+{
+ return tpm_passthrough_unix_tx_bufs(tpm_fd,
+ locty_data->w_buffer.buffer,
+ locty_data->w_offset,
+ locty_data->r_buffer.buffer,
+ locty_data->r_buffer.size);
+}
+
+static void tpm_passthrough_worker_thread(gpointer data,
+ gpointer user_data)
+{
+ TPMPassthruThreadParams *thr_parms = user_data;
+ TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
+ TPMBackendCmd cmd = (TPMBackendCmd)data;
+
+ dprintf("tpm_passthrough: processing command type %d\n", cmd);
+
+ switch (cmd) {
+ case TPM_BACKEND_CMD_PROCESS_CMD:
+ tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
+ thr_parms->tpm_state->locty_data);
+
+ thr_parms->recv_data_callback(thr_parms->tpm_state,
+ thr_parms->tpm_state->locty_number);
+ break;
+ case TPM_BACKEND_CMD_INIT:
+ case TPM_BACKEND_CMD_END:
+ case TPM_BACKEND_CMD_TPM_RESET:
+ /* nothing to do */
+ break;
+ }
+}
+
+/*
+ * Start the TPM (thread). If it had been started before, then terminate
+ * and start it again.
+ */
+static int tpm_passthrough_startup_tpm(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ /* terminate a running TPM */
+ tpm_backend_thread_end(&tpm_pt->tbt);
+
+ tpm_backend_thread_create(&tpm_pt->tbt,
+ tpm_passthrough_worker_thread,
+ &tb->s.tpm_pt->tpm_thread_params);
+
+ return 0;
+}
+
+static void tpm_passthrough_reset(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ dprintf("tpm_passthrough: CALL TO TPM_RESET!\n");
+
+ tpm_backend_thread_end(&tpm_pt->tbt);
+
+ tpm_pt->had_startup_error = false;
+}
+
+static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
+ TPMRecvDataCB *recv_data_cb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ tpm_pt->tpm_thread_params.tpm_state = s;
+ tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
+ tpm_pt->tpm_thread_params.tb = tb;
+
+ return 0;
+}
+
+static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
+{
+ return false;
+}
+
+static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ return tpm_pt->had_startup_error;
+}
+
+static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
+{
+ size_t wanted_size = 4096; /* Linux tpm.c buffer size */
+
+ if (sb->size != wanted_size) {
+ sb->buffer = g_realloc(sb->buffer, wanted_size);
+ sb->size = wanted_size;
+ }
+ return sb->size;
+}
+
+static void tpm_passthrough_deliver_request(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ tpm_backend_thread_deliver_request(&tpm_pt->tbt);
+}
+
+static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
+{
+ /* cancelling an ongoing command is known not to work with some TPMs */
+}
+
+static const char *tpm_passthrough_create_desc(void)
+{
+ return "Passthrough TPM backend driver";
+}
+
+/*
+ * A basic test of a TPM device. We expect a well formatted response header
+ * (error response is fine) within one second.
+ */
+static int tpm_passthrough_test_tpmdev(int fd)
+{
+ struct tpm_req_hdr req = {
+ .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
+ .len = cpu_to_be32(sizeof(req)),
+ .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
+ };
+ struct tpm_resp_hdr *resp;
+ fd_set readfds;
+ int n;
+ struct timeval tv = {
+ .tv_sec = 1,
+ .tv_usec = 0,
+ };
+ unsigned char buf[1024];
+
+ n = write(fd, &req, sizeof(req));
+ if (n < 0) {
+ return errno;
+ }
+ if (n != sizeof(req)) {
+ return EFAULT;
+ }
+
+ FD_ZERO(&readfds);
+ FD_SET(fd, &readfds);
+
+ /* wait for a second */
+ n = select(fd + 1, &readfds, NULL, NULL, &tv);
+ if (n != 1) {
+ return errno;
+ }
+
+ n = read(fd, &buf, sizeof(buf));
+ if (n < sizeof(struct tpm_resp_hdr)) {
+ return EFAULT;
+ }
+
+ resp = (struct tpm_resp_hdr *)buf;
+ /* check the header */
+ if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
+ be32_to_cpu(resp->len) != n) {
+ return EBADMSG;
+ }
+
+ return 0;
+}
+
+static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
+{
+ const char *value;
+
+ value = qemu_opt_get(opts, "path");
+ if (!value) {
+ value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
+ }
+
+ tb->s.tpm_pt->tpm_dev = g_strdup(value);
+
+ tb->path = g_strdup(tb->s.tpm_pt->tpm_dev);
+
+ tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
+ if (tb->s.tpm_pt->tpm_fd < 0) {
+ error_report("Cannot access TPM device using '%s': %s\n",
+ tb->s.tpm_pt->tpm_dev, strerror(errno));
+ goto err_free_parameters;
+ }
+
+ if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
+ error_report("'%s' is not a TPM device.\n",
+ tb->s.tpm_pt->tpm_dev);
+ goto err_close_tpmdev;
+ }
+
+ return 0;
+
+ err_close_tpmdev:
+ close(tb->s.tpm_pt->tpm_fd);
+ tb->s.tpm_pt->tpm_fd = -1;
+
+ err_free_parameters:
+ g_free(tb->path);
+ tb->path = NULL;
+
+ g_free(tb->s.tpm_pt->tpm_dev);
+ tb->s.tpm_pt->tpm_dev = NULL;
+
+ return 1;
+}
+
+static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
+{
+ TPMBackend *tb;
+
+ tb = g_new0(TPMBackend, 1);
+ tb->s.tpm_pt = g_new0(TPMPassthruState, 1);
+ tb->id = g_strdup(id);
+
+ tb->ops = &tpm_passthrough_driver;
+
+ if (tpm_passthrough_handle_device_opts(opts, tb)) {
+ goto err_exit;
+ }
+
+ return tb;
+
+err_exit:
+ g_free(tb->id);
+ g_free(tb->s.tpm_pt);
+ g_free(tb);
+
+ return NULL;
+}
+
+static void tpm_passthrough_destroy(TPMBackend *tb)
+{
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+
+ tpm_backend_thread_end(&tpm_pt->tbt);
+
+ close(tpm_pt->tpm_fd);
+
+ g_free(tb->id);
+ g_free(tb->path);
+ g_free(tb->s.tpm_pt->tpm_dev);
+ g_free(tb->s.tpm_pt);
+ g_free(tb);
+}
+
+const TPMDriverOps tpm_passthrough_driver = {
+ .type = "passthrough",
+ .desc = tpm_passthrough_create_desc,
+ .create = tpm_passthrough_create,
+ .destroy = tpm_passthrough_destroy,
+ .init = tpm_passthrough_init,
+ .startup_tpm = tpm_passthrough_startup_tpm,
+ .realloc_buffer = tpm_passthrough_realloc_buffer,
+ .reset = tpm_passthrough_reset,
+ .had_startup_error = tpm_passthrough_get_startup_error,
+ .deliver_request = tpm_passthrough_deliver_request,
+ .cancel_cmd = tpm_passthrough_cancel_cmd,
+ .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
+};
diff --git a/qemu-char.c b/qemu-char.c
index 242b799..22b92bd 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -538,6 +538,30 @@ int send_all(int fd, const void *_buf, int len1)
}
return len1 - len;
}
+
+int recv_all(int fd, void *_buf, int len1, bool single_read)
+{
+ int ret, len;
+ uint8_t *buf = _buf;
+
+ len = len1;
+ while ((len > 0) && (ret = read(fd, buf, len)) != 0) {
+ if (ret < 0) {
+ if (errno != EINTR && errno != EAGAIN) {
+ return -1;
+ }
+ continue;
+ } else {
+ if (single_read) {
+ return ret;
+ }
+ buf += ret;
+ len -= ret;
+ }
+ }
+ return len1 - len;
+}
+
#endif /* !_WIN32 */
#define STDIO_MAX_CLIENTS 1
diff --git a/qemu-options.hx b/qemu-options.hx
index 27a968e..225191f 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2159,10 +2159,12 @@ ETEXI
DEFHEADING()
#ifdef CONFIG_TPM
+# ifdef CONFIG_TPM_PASSTHROUGH
DEFHEADING(TPM device options:)
DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
- "-tpmdev [<type>],id=str[,option][,option][,...]\n",
+ "-tpmdev passthrough,id=id[,path=path]\n"
+ " use path to provide path to a character device; default is /dev/tpm0\n",
QEMU_ARCH_ALL)
STEXI
@@ -2172,6 +2174,7 @@ The general form of a TPM device option is:
@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
@findex -tpmdev
Backend type must be:
+@option{passthrough}.
The specific backend type will determine the applicable options.
The @code{-tpmdev} options requires a @code{-device} option.
@@ -2183,12 +2186,45 @@ Use ? to print all available TPM backend types.
qemu -tpmdev ?
@end example
+@item -tpmdev passthrough, id=@var{id}, path=@var{path}
+
+(Linux-host only) Enable access to the host's TPM using the passthrough
+driver.
+
+@option{path} specifies the path to the host's TPM device, i.e., on
+a Linux host this would be @code{/dev/tpm0}.
+@option{path} is optional and by default @code{/dev/tpm0} is used.
+
+Some notes about using the host's TPM with the passthrough driver:
+
+The TPM device accessed by the passthrough driver must not be
+used by any other application on the host.
+
+Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
+the VM's firmware (BIOS/UEFI) will not be able to initialize the
+TPM again and may therefore not show a TPM-specific menu that would
+otherwise allow the user to configure the TPM, e.g., allow the user to
+enable/disable or activate/deactivate the TPM.
+Further, if TPM ownership is released from within a VM then the host's TPM
+will get disabled and deactivated. To enable and activate the
+TPM again afterwards, the host has to be rebooted and the user is
+required to enter the firmware's menu to enable and activate the TPM.
+If the TPM is left disabled and/or deactivated most TPM commands will fail.
+
+To create a passthrough TPM use the following two options:
+@example
+-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
+@end example
+Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
+@code{tpmdev=tpm0} in the device option.
+
@end table
ETEXI
DEFHEADING()
+# endif
#endif
DEFHEADING(Linux/Multiboot boot specific:)
diff --git a/qemu_socket.h b/qemu_socket.h
index 02490ad..99ce174 100644
--- a/qemu_socket.h
+++ b/qemu_socket.h
@@ -37,6 +37,7 @@ int socket_set_cork(int fd, int v);
void socket_set_block(int fd);
void socket_set_nonblock(int fd);
int send_all(int fd, const void *buf, int len1);
+int recv_all(int fd, void *buf, int len1, bool single_read);
/* callback function for nonblocking connect
* valid fd on success, negative error code on failure
diff --git a/tpm.c b/tpm.c
index c9356b6..4730039 100644
--- a/tpm.c
+++ b/tpm.c
@@ -24,9 +24,26 @@ static QLIST_HEAD(, TPMBackend) tpm_backends =
#ifdef CONFIG_TPM
static const TPMDriverOps *be_drivers[] = {
+#ifdef CONFIG_TPM_PASSTHROUGH
+ &tpm_passthrough_driver,
+#endif
NULL,
};
+/*
+ * Write an error message in the given output buffer.
+ */
+void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
+{
+ if (out_len >= sizeof(struct tpm_resp_hdr)) {
+ struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
+
+ resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
+ resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
+ resp->errcode = cpu_to_be32(TPM_FAIL);
+ }
+}
+
const TPMDriverOps *tpm_get_backend_driver(const char *type)
{
int i;
diff --git a/tpm.h b/tpm.h
index 8943b61..b4f7774 100644
--- a/tpm.h
+++ b/tpm.h
@@ -18,6 +18,8 @@
struct TPMDriverOps;
typedef struct TPMDriverOps TPMDriverOps;
+typedef struct TPMPassthruState TPMPassthruState;
+
typedef struct TPMBackend {
char *id;
const char *fe_model;
@@ -25,6 +27,10 @@ typedef struct TPMBackend {
char *cancel_path;
const TPMDriverOps *ops;
+ union {
+ TPMPassthruState *tpm_pt;
+ } s;
+
QLIST_ENTRY(TPMBackend) list;
} TPMBackend;
@@ -74,11 +80,38 @@ struct TPMDriverOps {
#define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
+struct tpm_req_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t ordinal;
+} __attribute__((packed));
+
+struct tpm_resp_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t errcode;
+} __attribute__((packed));
+
+#define TPM_TAG_RQU_COMMAND 0xc1
+#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
+#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
+
+#define TPM_TAG_RSP_COMMAND 0xc4
+#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
+#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
+
+#define TPM_FAIL 9
+
+#define TPM_ORD_GetTicks 0xf1
+
int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
int tpm_init(void);
void tpm_cleanup(void);
TPMBackend *qemu_find_tpm(const char *id);
void tpm_display_backend_drivers(void);
const TPMDriverOps *tpm_get_backend_driver(const char *id);
+void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
+
+extern const TPMDriverOps tpm_passthrough_driver;
#endif /* QEMU_TPM_H */
diff --git a/vl.c b/vl.c
index 2cbbc0b..fad9c34 100644
--- a/vl.c
+++ b/vl.c
@@ -2940,11 +2940,13 @@ int main(int argc, char **argv, char **envp)
break;
}
#ifdef CONFIG_TPM
+# ifdef CONFIG_TPM_PASSTHROUGH
case QEMU_OPTION_tpmdev:
if (tpm_config_parse(qemu_find_opts("tpmdev"), optarg) < 0) {
exit(1);
}
break;
+# endif
#endif
case QEMU_OPTION_mempath:
mem_path = optarg;
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [Qemu-devel] [PATCH V20 5/8] Add a TPM Passthrough backend driver implementation
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 5/8] Add a TPM Passthrough backend driver implementation Stefan Berger
@ 2013-01-19 9:18 ` Blue Swirl
2013-01-19 14:29 ` Stefan Berger
2013-02-01 19:03 ` Corey Bryant
1 sibling, 1 reply; 21+ messages in thread
From: Blue Swirl @ 2013-01-19 9:18 UTC (permalink / raw)
To: Stefan Berger; +Cc: mst, qemu-devel, anthony, andreas.niederl
On Fri, Jan 18, 2013 at 4:02 PM, Stefan Berger
<stefanb@linux.vnet.ibm.com> wrote:
> From Andreas Niederl's original posting with adaptations where necessary:
>
> This patch is based of off version 9 of Stefan Berger's patch series
> "QEMU Trusted Platform Module (TPM) integration"
> and adds a new backend driver for it.
>
> This patch adds a passthrough backend driver for passing commands sent to the
> emulated TPM device directly to a TPM device opened on the host machine.
>
> Thus it is possible to use a hardware TPM device in a system running on QEMU,
> providing the ability to access a TPM in a special state (e.g. after a Trusted
> Boot).
>
> This functionality is being used in the acTvSM Trusted Virtualization Platform
> which is available on [1].
>
> Usage example:
> qemu-system-x86_64 -tpmdev passthrough,id=tpm0,path=/dev/tpm0 \
> -device tpm-tis,tpmdev=tpm0 \
> -cdrom test.iso -boot d
>
> Some notes about the host TPM:
> The TPM needs to be enabled and activated. If that's not the case one
> has to go through the BIOS/UEFI and enable and activate that TPM for TPM
> commands to work as expected.
> It may be necessary to boot the kernel using tpm_tis.force=1 in the boot
> command line or 'modprobe tpm_tis force=1' in case of using it as a module.
>
> Regards,
> Andreas Niederl, Stefan Berger
>
> [1] http://trustedjava.sourceforge.net/
>
> Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> ---
> configure | 3 +
> hw/Makefile.objs | 3 +-
> hw/tpm_backend.c | 58 ++++++++
> hw/tpm_backend.h | 43 ++++++
> hw/tpm_passthrough.c | 378 +++++++++++++++++++++++++++++++++++++++++++++++++++
> qemu-char.c | 24 ++++
> qemu-options.hx | 38 +++++-
> qemu_socket.h | 1 +
> tpm.c | 17 +++
> tpm.h | 33 +++++
> vl.c | 2 +
> 11 files changed, 598 insertions(+), 2 deletions(-)
> create mode 100644 hw/tpm_backend.c
> create mode 100644 hw/tpm_backend.h
> create mode 100644 hw/tpm_passthrough.c
>
> diff --git a/configure b/configure
> index a6f8c4c..73fc146 100755
> --- a/configure
> +++ b/configure
> @@ -4156,6 +4156,9 @@ fi
>
> if test "$tpm" = "yes"; then
> if test "$target_softmmu" = "yes" ; then
> + if test "$linux" = "yes" ; then
> + echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_host_mak
> + fi
> echo "CONFIG_TPM=y" >> $config_host_mak
> fi
> fi
> diff --git a/hw/Makefile.objs b/hw/Makefile.objs
> index 15eb567..a90c535 100644
> --- a/hw/Makefile.objs
> +++ b/hw/Makefile.objs
> @@ -142,7 +142,8 @@ common-obj-$(CONFIG_MIPSNET) += mipsnet.o
> common-obj-y += null-machine.o
>
> # TPM
> -common-obj-$(CONFIG_TPM) += tpm_tis.o
> +common-obj-$(CONFIG_TPM) += tpm_tis.o tpm_backend.o
> +common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
>
> # Sound
> sound-obj-y =
> diff --git a/hw/tpm_backend.c b/hw/tpm_backend.c
> new file mode 100644
> index 0000000..4cf0809
> --- /dev/null
> +++ b/hw/tpm_backend.c
> @@ -0,0 +1,58 @@
> +/*
> + * common TPM backend driver functions
> + *
> + * Copyright (c) 2012 IBM Corporation
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>
> + */
> +
> +#include "tpm.h"
> +#include "qemu-thread.h"
> +#include "hw/tpm_backend.h"
> +
> +void tpm_backend_thread_deliver_request(TPMBackendThread *tbt)
> +{
> + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_PROCESS_CMD, NULL);
> +}
> +
> +void tpm_backend_thread_create(TPMBackendThread *tbt,
> + GFunc func, gpointer user_data)
> +{
> + if (!tbt->pool) {
> + tbt->pool = g_thread_pool_new(func, user_data, 1, TRUE, NULL);
> + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_INIT, NULL);
> + }
> +}
> +
> +void tpm_backend_thread_end(TPMBackendThread *tbt)
> +{
> + if (tbt->pool) {
> + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_END, NULL);
> + g_thread_pool_free(tbt->pool, FALSE, TRUE);
> + tbt->pool = NULL;
> + }
> +}
> +
> +void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt,
> + GFunc func, gpointer user_data)
> +{
> + if (!tbt->pool) {
> + tpm_backend_thread_create(tbt, func, user_data);
> + } else {
> + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_TPM_RESET,
> + NULL);
> + }
> +}
> diff --git a/hw/tpm_backend.h b/hw/tpm_backend.h
> new file mode 100644
> index 0000000..f5fe198
> --- /dev/null
> +++ b/hw/tpm_backend.h
> @@ -0,0 +1,43 @@
> +/*
> + * common TPM backend driver functions
> + *
> + * Copyright (c) 2012 IBM Corporation
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>
> + */
> +
> +#ifndef HW_TPM_BACKEND_H
> +#define HW_TPM_BACKEND_H
> +
> +typedef struct TPMBackendThread {
> + GThreadPool *pool;
> +} TPMBackendThread;
> +
> +void tpm_backend_thread_deliver_request(TPMBackendThread *tbt);
> +void tpm_backend_thread_create(TPMBackendThread *tbt,
> + GFunc func, gpointer user_data);
> +void tpm_backend_thread_end(TPMBackendThread *tbt);
> +void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt,
> + GFunc func, gpointer user_data);
> +
> +typedef enum TPMBackendCmd {
> + TPM_BACKEND_CMD_INIT = 1,
> + TPM_BACKEND_CMD_PROCESS_CMD,
> + TPM_BACKEND_CMD_END,
> + TPM_BACKEND_CMD_TPM_RESET,
> +} TPMBackendCmd;
> +
> +#endif /* HW_TPM_BACKEND_H */
> diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
> new file mode 100644
> index 0000000..f9e6923
> --- /dev/null
> +++ b/hw/tpm_passthrough.c
> @@ -0,0 +1,378 @@
> +/*
> + * passthrough TPM driver
> + *
> + * Copyright (c) 2010, 2011 IBM Corporation
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + *
> + * Copyright (C) 2011 IAIK, Graz University of Technology
> + * Author: Andreas Niederl
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>
> + */
> +
> +#include "qemu-common.h"
> +#include "qemu-error.h"
> +#include "qemu_socket.h"
> +#include "tpm.h"
> +#include "hw/hw.h"
> +#include "hw/tpm_tis.h"
> +#include "hw/tpm_backend.h"
> +#include "hw/pc.h"
> +
> +/* #define DEBUG_TPM */
> +
> +#ifdef DEBUG_TPM
> +#define dprintf(fmt, ...) \
dprintf() is actually reserved to stdio.h. Please use tracepoints or
an uppercase macro name.
> + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
> +#else
> +#define dprintf(fmt, ...) \
> + do { } while (0)
> +#endif
> +
> +/* data structures */
> +
> +typedef struct TPMPassthruThreadParams {
> + TPMState *tpm_state;
> +
> + TPMRecvDataCB *recv_data_callback;
> + TPMBackend *tb;
> +} TPMPassthruThreadParams;
> +
> +struct TPMPassthruState {
> + TPMBackendThread tbt;
> +
> + TPMPassthruThreadParams tpm_thread_params;
> +
> + char *tpm_dev;
> + int tpm_fd;
> + bool had_startup_error;
> +};
> +
> +#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
> +
> +static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
> +{
> + return send_all(fd, buf, len);
> +}
> +
> +static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
> +{
> + return recv_all(fd, buf, len, true);
> +}
> +
> +static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
> +{
> + struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)buf;
> +
> + return be32_to_cpu(resp->len);
> +}
> +
> +static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
> + const uint8_t *in, uint32_t in_len,
> + uint8_t *out, uint32_t out_len)
> +{
> + int ret;
> +
> + ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
> + if (ret != in_len) {
> + error_report("tpm_passthrough: error while transmitting data "
> + "to TPM: %s (%i)\n",
> + strerror(errno), errno);
> + goto err_exit;
> + }
> +
> + ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
> + if (ret < 0) {
> + error_report("tpm_passthrough: error while reading data from "
> + "TPM: %s (%i)\n",
> + strerror(errno), errno);
> + } else if (ret < sizeof(struct tpm_resp_hdr) ||
> + tpm_passthrough_get_size_from_buffer(out) != ret) {
> + ret = -1;
> + error_report("tpm_passthrough: received invalid response "
> + "packet from TPM\n");
> + }
> +
> +err_exit:
> + if (ret < 0) {
> + tpm_write_fatal_error_response(out, out_len);
> + }
> +
> + return ret;
> +}
> +
> +static int tpm_passthrough_unix_transfer(int tpm_fd,
> + const TPMLocality *locty_data)
> +{
> + return tpm_passthrough_unix_tx_bufs(tpm_fd,
> + locty_data->w_buffer.buffer,
> + locty_data->w_offset,
> + locty_data->r_buffer.buffer,
> + locty_data->r_buffer.size);
> +}
> +
> +static void tpm_passthrough_worker_thread(gpointer data,
> + gpointer user_data)
> +{
> + TPMPassthruThreadParams *thr_parms = user_data;
> + TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
> + TPMBackendCmd cmd = (TPMBackendCmd)data;
> +
> + dprintf("tpm_passthrough: processing command type %d\n", cmd);
> +
> + switch (cmd) {
> + case TPM_BACKEND_CMD_PROCESS_CMD:
> + tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
> + thr_parms->tpm_state->locty_data);
> +
> + thr_parms->recv_data_callback(thr_parms->tpm_state,
> + thr_parms->tpm_state->locty_number);
> + break;
> + case TPM_BACKEND_CMD_INIT:
> + case TPM_BACKEND_CMD_END:
> + case TPM_BACKEND_CMD_TPM_RESET:
> + /* nothing to do */
> + break;
> + }
> +}
> +
> +/*
> + * Start the TPM (thread). If it had been started before, then terminate
> + * and start it again.
> + */
> +static int tpm_passthrough_startup_tpm(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + /* terminate a running TPM */
> + tpm_backend_thread_end(&tpm_pt->tbt);
> +
> + tpm_backend_thread_create(&tpm_pt->tbt,
> + tpm_passthrough_worker_thread,
> + &tb->s.tpm_pt->tpm_thread_params);
> +
> + return 0;
> +}
> +
> +static void tpm_passthrough_reset(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + dprintf("tpm_passthrough: CALL TO TPM_RESET!\n");
> +
> + tpm_backend_thread_end(&tpm_pt->tbt);
> +
> + tpm_pt->had_startup_error = false;
> +}
> +
> +static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
> + TPMRecvDataCB *recv_data_cb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + tpm_pt->tpm_thread_params.tpm_state = s;
> + tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
> + tpm_pt->tpm_thread_params.tb = tb;
> +
> + return 0;
> +}
> +
> +static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
> +{
> + return false;
> +}
> +
> +static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + return tpm_pt->had_startup_error;
> +}
> +
> +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
> +{
> + size_t wanted_size = 4096; /* Linux tpm.c buffer size */
> +
> + if (sb->size != wanted_size) {
> + sb->buffer = g_realloc(sb->buffer, wanted_size);
> + sb->size = wanted_size;
> + }
> + return sb->size;
> +}
> +
> +static void tpm_passthrough_deliver_request(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + tpm_backend_thread_deliver_request(&tpm_pt->tbt);
> +}
> +
> +static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
> +{
> + /* cancelling an ongoing command is known not to work with some TPMs */
> +}
> +
> +static const char *tpm_passthrough_create_desc(void)
> +{
> + return "Passthrough TPM backend driver";
> +}
> +
> +/*
> + * A basic test of a TPM device. We expect a well formatted response header
> + * (error response is fine) within one second.
> + */
> +static int tpm_passthrough_test_tpmdev(int fd)
> +{
> + struct tpm_req_hdr req = {
> + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
> + .len = cpu_to_be32(sizeof(req)),
> + .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
> + };
> + struct tpm_resp_hdr *resp;
> + fd_set readfds;
> + int n;
> + struct timeval tv = {
> + .tv_sec = 1,
> + .tv_usec = 0,
> + };
> + unsigned char buf[1024];
> +
> + n = write(fd, &req, sizeof(req));
> + if (n < 0) {
> + return errno;
> + }
> + if (n != sizeof(req)) {
> + return EFAULT;
> + }
> +
> + FD_ZERO(&readfds);
> + FD_SET(fd, &readfds);
> +
> + /* wait for a second */
> + n = select(fd + 1, &readfds, NULL, NULL, &tv);
> + if (n != 1) {
> + return errno;
> + }
> +
> + n = read(fd, &buf, sizeof(buf));
> + if (n < sizeof(struct tpm_resp_hdr)) {
> + return EFAULT;
> + }
> +
> + resp = (struct tpm_resp_hdr *)buf;
> + /* check the header */
> + if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
> + be32_to_cpu(resp->len) != n) {
> + return EBADMSG;
> + }
> +
> + return 0;
> +}
> +
> +static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
> +{
> + const char *value;
> +
> + value = qemu_opt_get(opts, "path");
> + if (!value) {
> + value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
> + }
> +
> + tb->s.tpm_pt->tpm_dev = g_strdup(value);
> +
> + tb->path = g_strdup(tb->s.tpm_pt->tpm_dev);
> +
> + tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
> + if (tb->s.tpm_pt->tpm_fd < 0) {
> + error_report("Cannot access TPM device using '%s': %s\n",
> + tb->s.tpm_pt->tpm_dev, strerror(errno));
> + goto err_free_parameters;
> + }
> +
> + if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
> + error_report("'%s' is not a TPM device.\n",
> + tb->s.tpm_pt->tpm_dev);
> + goto err_close_tpmdev;
> + }
> +
> + return 0;
> +
> + err_close_tpmdev:
> + close(tb->s.tpm_pt->tpm_fd);
> + tb->s.tpm_pt->tpm_fd = -1;
> +
> + err_free_parameters:
> + g_free(tb->path);
> + tb->path = NULL;
> +
> + g_free(tb->s.tpm_pt->tpm_dev);
> + tb->s.tpm_pt->tpm_dev = NULL;
> +
> + return 1;
> +}
> +
> +static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
> +{
> + TPMBackend *tb;
> +
> + tb = g_new0(TPMBackend, 1);
> + tb->s.tpm_pt = g_new0(TPMPassthruState, 1);
> + tb->id = g_strdup(id);
> +
> + tb->ops = &tpm_passthrough_driver;
> +
> + if (tpm_passthrough_handle_device_opts(opts, tb)) {
> + goto err_exit;
> + }
> +
> + return tb;
> +
> +err_exit:
> + g_free(tb->id);
> + g_free(tb->s.tpm_pt);
> + g_free(tb);
> +
> + return NULL;
> +}
> +
> +static void tpm_passthrough_destroy(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + tpm_backend_thread_end(&tpm_pt->tbt);
> +
> + close(tpm_pt->tpm_fd);
> +
> + g_free(tb->id);
> + g_free(tb->path);
> + g_free(tb->s.tpm_pt->tpm_dev);
> + g_free(tb->s.tpm_pt);
> + g_free(tb);
> +}
> +
> +const TPMDriverOps tpm_passthrough_driver = {
> + .type = "passthrough",
> + .desc = tpm_passthrough_create_desc,
> + .create = tpm_passthrough_create,
> + .destroy = tpm_passthrough_destroy,
> + .init = tpm_passthrough_init,
> + .startup_tpm = tpm_passthrough_startup_tpm,
> + .realloc_buffer = tpm_passthrough_realloc_buffer,
> + .reset = tpm_passthrough_reset,
> + .had_startup_error = tpm_passthrough_get_startup_error,
> + .deliver_request = tpm_passthrough_deliver_request,
> + .cancel_cmd = tpm_passthrough_cancel_cmd,
> + .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
> +};
> diff --git a/qemu-char.c b/qemu-char.c
> index 242b799..22b92bd 100644
> --- a/qemu-char.c
> +++ b/qemu-char.c
> @@ -538,6 +538,30 @@ int send_all(int fd, const void *_buf, int len1)
> }
> return len1 - len;
> }
> +
> +int recv_all(int fd, void *_buf, int len1, bool single_read)
> +{
> + int ret, len;
> + uint8_t *buf = _buf;
> +
> + len = len1;
> + while ((len > 0) && (ret = read(fd, buf, len)) != 0) {
> + if (ret < 0) {
> + if (errno != EINTR && errno != EAGAIN) {
> + return -1;
> + }
> + continue;
> + } else {
> + if (single_read) {
> + return ret;
> + }
> + buf += ret;
> + len -= ret;
> + }
> + }
> + return len1 - len;
> +}
> +
> #endif /* !_WIN32 */
>
> #define STDIO_MAX_CLIENTS 1
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 27a968e..225191f 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -2159,10 +2159,12 @@ ETEXI
> DEFHEADING()
>
> #ifdef CONFIG_TPM
> +# ifdef CONFIG_TPM_PASSTHROUGH
> DEFHEADING(TPM device options:)
>
> DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
> - "-tpmdev [<type>],id=str[,option][,option][,...]\n",
> + "-tpmdev passthrough,id=id[,path=path]\n"
> + " use path to provide path to a character device; default is /dev/tpm0\n",
> QEMU_ARCH_ALL)
> STEXI
>
> @@ -2172,6 +2174,7 @@ The general form of a TPM device option is:
> @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
> @findex -tpmdev
> Backend type must be:
> +@option{passthrough}.
>
> The specific backend type will determine the applicable options.
> The @code{-tpmdev} options requires a @code{-device} option.
> @@ -2183,12 +2186,45 @@ Use ? to print all available TPM backend types.
> qemu -tpmdev ?
> @end example
>
> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}
> +
> +(Linux-host only) Enable access to the host's TPM using the passthrough
> +driver.
> +
> +@option{path} specifies the path to the host's TPM device, i.e., on
> +a Linux host this would be @code{/dev/tpm0}.
> +@option{path} is optional and by default @code{/dev/tpm0} is used.
> +
> +Some notes about using the host's TPM with the passthrough driver:
> +
> +The TPM device accessed by the passthrough driver must not be
> +used by any other application on the host.
> +
> +Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
> +the VM's firmware (BIOS/UEFI) will not be able to initialize the
> +TPM again and may therefore not show a TPM-specific menu that would
> +otherwise allow the user to configure the TPM, e.g., allow the user to
> +enable/disable or activate/deactivate the TPM.
> +Further, if TPM ownership is released from within a VM then the host's TPM
> +will get disabled and deactivated. To enable and activate the
> +TPM again afterwards, the host has to be rebooted and the user is
> +required to enter the firmware's menu to enable and activate the TPM.
> +If the TPM is left disabled and/or deactivated most TPM commands will fail.
> +
> +To create a passthrough TPM use the following two options:
> +@example
> +-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
> +@end example
> +Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
> +@code{tpmdev=tpm0} in the device option.
> +
> @end table
>
> ETEXI
>
> DEFHEADING()
>
> +# endif
> #endif
>
> DEFHEADING(Linux/Multiboot boot specific:)
> diff --git a/qemu_socket.h b/qemu_socket.h
> index 02490ad..99ce174 100644
> --- a/qemu_socket.h
> +++ b/qemu_socket.h
> @@ -37,6 +37,7 @@ int socket_set_cork(int fd, int v);
> void socket_set_block(int fd);
> void socket_set_nonblock(int fd);
> int send_all(int fd, const void *buf, int len1);
> +int recv_all(int fd, void *buf, int len1, bool single_read);
>
> /* callback function for nonblocking connect
> * valid fd on success, negative error code on failure
> diff --git a/tpm.c b/tpm.c
> index c9356b6..4730039 100644
> --- a/tpm.c
> +++ b/tpm.c
> @@ -24,9 +24,26 @@ static QLIST_HEAD(, TPMBackend) tpm_backends =
> #ifdef CONFIG_TPM
>
> static const TPMDriverOps *be_drivers[] = {
> +#ifdef CONFIG_TPM_PASSTHROUGH
> + &tpm_passthrough_driver,
> +#endif
> NULL,
> };
>
> +/*
> + * Write an error message in the given output buffer.
> + */
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
> +{
> + if (out_len >= sizeof(struct tpm_resp_hdr)) {
> + struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
> +
> + resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
> + resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
> + resp->errcode = cpu_to_be32(TPM_FAIL);
> + }
> +}
> +
> const TPMDriverOps *tpm_get_backend_driver(const char *type)
> {
> int i;
> diff --git a/tpm.h b/tpm.h
> index 8943b61..b4f7774 100644
> --- a/tpm.h
> +++ b/tpm.h
> @@ -18,6 +18,8 @@
> struct TPMDriverOps;
> typedef struct TPMDriverOps TPMDriverOps;
>
> +typedef struct TPMPassthruState TPMPassthruState;
> +
> typedef struct TPMBackend {
> char *id;
> const char *fe_model;
> @@ -25,6 +27,10 @@ typedef struct TPMBackend {
> char *cancel_path;
> const TPMDriverOps *ops;
>
> + union {
> + TPMPassthruState *tpm_pt;
> + } s;
> +
> QLIST_ENTRY(TPMBackend) list;
> } TPMBackend;
>
> @@ -74,11 +80,38 @@ struct TPMDriverOps {
>
> #define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
>
> +struct tpm_req_hdr {
> + uint16_t tag;
> + uint32_t len;
> + uint32_t ordinal;
> +} __attribute__((packed));
QEMU_PACKED
> +
> +struct tpm_resp_hdr {
> + uint16_t tag;
> + uint32_t len;
> + uint32_t errcode;
> +} __attribute__((packed));
> +
> +#define TPM_TAG_RQU_COMMAND 0xc1
> +#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
> +#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
> +
> +#define TPM_TAG_RSP_COMMAND 0xc4
> +#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
> +#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
> +
> +#define TPM_FAIL 9
> +
> +#define TPM_ORD_GetTicks 0xf1
> +
> int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
> int tpm_init(void);
> void tpm_cleanup(void);
> TPMBackend *qemu_find_tpm(const char *id);
> void tpm_display_backend_drivers(void);
> const TPMDriverOps *tpm_get_backend_driver(const char *id);
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
> +
> +extern const TPMDriverOps tpm_passthrough_driver;
>
> #endif /* QEMU_TPM_H */
> diff --git a/vl.c b/vl.c
> index 2cbbc0b..fad9c34 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -2940,11 +2940,13 @@ int main(int argc, char **argv, char **envp)
> break;
> }
> #ifdef CONFIG_TPM
> +# ifdef CONFIG_TPM_PASSTHROUGH
> case QEMU_OPTION_tpmdev:
> if (tpm_config_parse(qemu_find_opts("tpmdev"), optarg) < 0) {
> exit(1);
> }
> break;
> +# endif
> #endif
> case QEMU_OPTION_mempath:
> mem_path = optarg;
> --
> 1.7.11.7
>
>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [Qemu-devel] [PATCH V20 5/8] Add a TPM Passthrough backend driver implementation
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 5/8] Add a TPM Passthrough backend driver implementation Stefan Berger
2013-01-19 9:18 ` Blue Swirl
@ 2013-02-01 19:03 ` Corey Bryant
1 sibling, 0 replies; 21+ messages in thread
From: Corey Bryant @ 2013-02-01 19:03 UTC (permalink / raw)
To: Stefan Berger; +Cc: mst, qemu-devel, anthony, andreas.niederl
On 01/18/2013 11:02 AM, Stefan Berger wrote:
> From Andreas Niederl's original posting with adaptations where necessary:
>
> This patch is based of off version 9 of Stefan Berger's patch series
> "QEMU Trusted Platform Module (TPM) integration"
> and adds a new backend driver for it.
>
> This patch adds a passthrough backend driver for passing commands sent to the
> emulated TPM device directly to a TPM device opened on the host machine.
>
> Thus it is possible to use a hardware TPM device in a system running on QEMU,
> providing the ability to access a TPM in a special state (e.g. after a Trusted
> Boot).
>
> This functionality is being used in the acTvSM Trusted Virtualization Platform
> which is available on [1].
>
> Usage example:
> qemu-system-x86_64 -tpmdev passthrough,id=tpm0,path=/dev/tpm0 \
> -device tpm-tis,tpmdev=tpm0 \
> -cdrom test.iso -boot d
>
> Some notes about the host TPM:
> The TPM needs to be enabled and activated. If that's not the case one
> has to go through the BIOS/UEFI and enable and activate that TPM for TPM
> commands to work as expected.
> It may be necessary to boot the kernel using tpm_tis.force=1 in the boot
> command line or 'modprobe tpm_tis force=1' in case of using it as a module.
>
> Regards,
> Andreas Niederl, Stefan Berger
>
> [1] http://trustedjava.sourceforge.net/
>
> Signed-off-by: Andreas Niederl <andreas.niederl@iaik.tugraz.at>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> ---
> configure | 3 +
> hw/Makefile.objs | 3 +-
> hw/tpm_backend.c | 58 ++++++++
> hw/tpm_backend.h | 43 ++++++
> hw/tpm_passthrough.c | 378 +++++++++++++++++++++++++++++++++++++++++++++++++++
> qemu-char.c | 24 ++++
> qemu-options.hx | 38 +++++-
> qemu_socket.h | 1 +
> tpm.c | 17 +++
> tpm.h | 33 +++++
> vl.c | 2 +
> 11 files changed, 598 insertions(+), 2 deletions(-)
> create mode 100644 hw/tpm_backend.c
> create mode 100644 hw/tpm_backend.h
> create mode 100644 hw/tpm_passthrough.c
>
> diff --git a/configure b/configure
> index a6f8c4c..73fc146 100755
> --- a/configure
> +++ b/configure
> @@ -4156,6 +4156,9 @@ fi
>
> if test "$tpm" = "yes"; then
> if test "$target_softmmu" = "yes" ; then
> + if test "$linux" = "yes" ; then
> + echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_host_mak
> + fi
> echo "CONFIG_TPM=y" >> $config_host_mak
> fi
> fi
> diff --git a/hw/Makefile.objs b/hw/Makefile.objs
> index 15eb567..a90c535 100644
> --- a/hw/Makefile.objs
> +++ b/hw/Makefile.objs
> @@ -142,7 +142,8 @@ common-obj-$(CONFIG_MIPSNET) += mipsnet.o
> common-obj-y += null-machine.o
>
> # TPM
> -common-obj-$(CONFIG_TPM) += tpm_tis.o
> +common-obj-$(CONFIG_TPM) += tpm_tis.o tpm_backend.o
> +common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
>
> # Sound
> sound-obj-y =
> diff --git a/hw/tpm_backend.c b/hw/tpm_backend.c
> new file mode 100644
> index 0000000..4cf0809
> --- /dev/null
> +++ b/hw/tpm_backend.c
> @@ -0,0 +1,58 @@
> +/*
> + * common TPM backend driver functions
> + *
> + * Copyright (c) 2012 IBM Corporation
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>
> + */
> +
> +#include "tpm.h"
> +#include "qemu-thread.h"
> +#include "hw/tpm_backend.h"
> +
> +void tpm_backend_thread_deliver_request(TPMBackendThread *tbt)
> +{
> + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_PROCESS_CMD, NULL);
> +}
> +
> +void tpm_backend_thread_create(TPMBackendThread *tbt,
> + GFunc func, gpointer user_data)
> +{
> + if (!tbt->pool) {
> + tbt->pool = g_thread_pool_new(func, user_data, 1, TRUE, NULL);
> + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_INIT, NULL);
> + }
> +}
> +
> +void tpm_backend_thread_end(TPMBackendThread *tbt)
> +{
> + if (tbt->pool) {
> + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_END, NULL);
> + g_thread_pool_free(tbt->pool, FALSE, TRUE);
> + tbt->pool = NULL;
> + }
> +}
> +
> +void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt,
> + GFunc func, gpointer user_data)
> +{
> + if (!tbt->pool) {
> + tpm_backend_thread_create(tbt, func, user_data);
> + } else {
> + g_thread_pool_push(tbt->pool, (gpointer)TPM_BACKEND_CMD_TPM_RESET,
> + NULL);
> + }
> +}
> diff --git a/hw/tpm_backend.h b/hw/tpm_backend.h
> new file mode 100644
> index 0000000..f5fe198
> --- /dev/null
> +++ b/hw/tpm_backend.h
> @@ -0,0 +1,43 @@
> +/*
> + * common TPM backend driver functions
> + *
> + * Copyright (c) 2012 IBM Corporation
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>
> + */
> +
> +#ifndef HW_TPM_BACKEND_H
> +#define HW_TPM_BACKEND_H
> +
> +typedef struct TPMBackendThread {
> + GThreadPool *pool;
> +} TPMBackendThread;
> +
> +void tpm_backend_thread_deliver_request(TPMBackendThread *tbt);
> +void tpm_backend_thread_create(TPMBackendThread *tbt,
> + GFunc func, gpointer user_data);
> +void tpm_backend_thread_end(TPMBackendThread *tbt);
> +void tpm_backend_thread_tpm_reset(TPMBackendThread *tbt,
> + GFunc func, gpointer user_data);
> +
> +typedef enum TPMBackendCmd {
> + TPM_BACKEND_CMD_INIT = 1,
> + TPM_BACKEND_CMD_PROCESS_CMD,
> + TPM_BACKEND_CMD_END,
> + TPM_BACKEND_CMD_TPM_RESET,
> +} TPMBackendCmd;
> +
> +#endif /* HW_TPM_BACKEND_H */
> diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
> new file mode 100644
> index 0000000..f9e6923
> --- /dev/null
> +++ b/hw/tpm_passthrough.c
> @@ -0,0 +1,378 @@
> +/*
> + * passthrough TPM driver
> + *
> + * Copyright (c) 2010, 2011 IBM Corporation
> + * Authors:
> + * Stefan Berger <stefanb@us.ibm.com>
> + *
> + * Copyright (C) 2011 IAIK, Graz University of Technology
> + * Author: Andreas Niederl
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>
> + */
> +
> +#include "qemu-common.h"
> +#include "qemu-error.h"
> +#include "qemu_socket.h"
> +#include "tpm.h"
> +#include "hw/hw.h"
> +#include "hw/tpm_tis.h"
> +#include "hw/tpm_backend.h"
> +#include "hw/pc.h"
> +
> +/* #define DEBUG_TPM */
> +
> +#ifdef DEBUG_TPM
> +#define dprintf(fmt, ...) \
> + do { fprintf(stderr, fmt, ## __VA_ARGS__); } while (0)
> +#else
> +#define dprintf(fmt, ...) \
> + do { } while (0)
> +#endif
> +
> +/* data structures */
> +
> +typedef struct TPMPassthruThreadParams {
> + TPMState *tpm_state;
> +
> + TPMRecvDataCB *recv_data_callback;
> + TPMBackend *tb;
> +} TPMPassthruThreadParams;
> +
> +struct TPMPassthruState {
> + TPMBackendThread tbt;
> +
> + TPMPassthruThreadParams tpm_thread_params;
> +
> + char *tpm_dev;
> + int tpm_fd;
> + bool had_startup_error;
> +};
> +
> +#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
> +
> +static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
> +{
> + return send_all(fd, buf, len);
> +}
> +
> +static int tpm_passthrough_unix_read(int fd, uint8_t *buf, uint32_t len)
> +{
> + return recv_all(fd, buf, len, true);
> +}
> +
> +static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
> +{
> + struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)buf;
> +
> + return be32_to_cpu(resp->len);
> +}
> +
> +static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
> + const uint8_t *in, uint32_t in_len,
> + uint8_t *out, uint32_t out_len)
> +{
> + int ret;
> +
> + ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
> + if (ret != in_len) {
> + error_report("tpm_passthrough: error while transmitting data "
> + "to TPM: %s (%i)\n",
> + strerror(errno), errno);
> + goto err_exit;
> + }
> +
> + ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
> + if (ret < 0) {
> + error_report("tpm_passthrough: error while reading data from "
> + "TPM: %s (%i)\n",
> + strerror(errno), errno);
> + } else if (ret < sizeof(struct tpm_resp_hdr) ||
> + tpm_passthrough_get_size_from_buffer(out) != ret) {
> + ret = -1;
> + error_report("tpm_passthrough: received invalid response "
> + "packet from TPM\n");
> + }
> +
> +err_exit:
> + if (ret < 0) {
> + tpm_write_fatal_error_response(out, out_len);
> + }
> +
> + return ret;
> +}
> +
> +static int tpm_passthrough_unix_transfer(int tpm_fd,
> + const TPMLocality *locty_data)
> +{
> + return tpm_passthrough_unix_tx_bufs(tpm_fd,
> + locty_data->w_buffer.buffer,
> + locty_data->w_offset,
> + locty_data->r_buffer.buffer,
> + locty_data->r_buffer.size);
> +}
> +
> +static void tpm_passthrough_worker_thread(gpointer data,
> + gpointer user_data)
> +{
> + TPMPassthruThreadParams *thr_parms = user_data;
> + TPMPassthruState *tpm_pt = thr_parms->tb->s.tpm_pt;
> + TPMBackendCmd cmd = (TPMBackendCmd)data;
> +
> + dprintf("tpm_passthrough: processing command type %d\n", cmd);
> +
> + switch (cmd) {
> + case TPM_BACKEND_CMD_PROCESS_CMD:
> + tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
> + thr_parms->tpm_state->locty_data);
> +
> + thr_parms->recv_data_callback(thr_parms->tpm_state,
> + thr_parms->tpm_state->locty_number);
> + break;
> + case TPM_BACKEND_CMD_INIT:
> + case TPM_BACKEND_CMD_END:
> + case TPM_BACKEND_CMD_TPM_RESET:
> + /* nothing to do */
> + break;
> + }
> +}
> +
> +/*
> + * Start the TPM (thread). If it had been started before, then terminate
> + * and start it again.
> + */
> +static int tpm_passthrough_startup_tpm(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + /* terminate a running TPM */
> + tpm_backend_thread_end(&tpm_pt->tbt);
> +
> + tpm_backend_thread_create(&tpm_pt->tbt,
> + tpm_passthrough_worker_thread,
> + &tb->s.tpm_pt->tpm_thread_params);
> +
> + return 0;
> +}
> +
> +static void tpm_passthrough_reset(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + dprintf("tpm_passthrough: CALL TO TPM_RESET!\n");
> +
> + tpm_backend_thread_end(&tpm_pt->tbt);
> +
> + tpm_pt->had_startup_error = false;
> +}
> +
> +static int tpm_passthrough_init(TPMBackend *tb, TPMState *s,
> + TPMRecvDataCB *recv_data_cb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + tpm_pt->tpm_thread_params.tpm_state = s;
> + tpm_pt->tpm_thread_params.recv_data_callback = recv_data_cb;
> + tpm_pt->tpm_thread_params.tb = tb;
> +
> + return 0;
> +}
> +
> +static bool tpm_passthrough_get_tpm_established_flag(TPMBackend *tb)
> +{
> + return false;
> +}
> +
> +static bool tpm_passthrough_get_startup_error(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + return tpm_pt->had_startup_error;
> +}
> +
> +static size_t tpm_passthrough_realloc_buffer(TPMSizedBuffer *sb)
> +{
> + size_t wanted_size = 4096; /* Linux tpm.c buffer size */
> +
> + if (sb->size != wanted_size) {
> + sb->buffer = g_realloc(sb->buffer, wanted_size);
> + sb->size = wanted_size;
> + }
> + return sb->size;
> +}
> +
> +static void tpm_passthrough_deliver_request(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + tpm_backend_thread_deliver_request(&tpm_pt->tbt);
> +}
> +
> +static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
> +{
> + /* cancelling an ongoing command is known not to work with some TPMs */
> +}
> +
> +static const char *tpm_passthrough_create_desc(void)
> +{
> + return "Passthrough TPM backend driver";
> +}
> +
> +/*
> + * A basic test of a TPM device. We expect a well formatted response header
> + * (error response is fine) within one second.
> + */
> +static int tpm_passthrough_test_tpmdev(int fd)
> +{
> + struct tpm_req_hdr req = {
> + .tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
> + .len = cpu_to_be32(sizeof(req)),
> + .ordinal = cpu_to_be32(TPM_ORD_GetTicks),
> + };
> + struct tpm_resp_hdr *resp;
> + fd_set readfds;
> + int n;
> + struct timeval tv = {
> + .tv_sec = 1,
> + .tv_usec = 0,
> + };
> + unsigned char buf[1024];
> +
> + n = write(fd, &req, sizeof(req));
> + if (n < 0) {
> + return errno;
> + }
> + if (n != sizeof(req)) {
> + return EFAULT;
> + }
> +
> + FD_ZERO(&readfds);
> + FD_SET(fd, &readfds);
> +
> + /* wait for a second */
> + n = select(fd + 1, &readfds, NULL, NULL, &tv);
> + if (n != 1) {
> + return errno;
> + }
> +
> + n = read(fd, &buf, sizeof(buf));
> + if (n < sizeof(struct tpm_resp_hdr)) {
> + return EFAULT;
> + }
> +
> + resp = (struct tpm_resp_hdr *)buf;
> + /* check the header */
> + if (be16_to_cpu(resp->tag) != TPM_TAG_RSP_COMMAND ||
> + be32_to_cpu(resp->len) != n) {
> + return EBADMSG;
> + }
> +
> + return 0;
> +}
> +
> +static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
> +{
> + const char *value;
> +
> + value = qemu_opt_get(opts, "path");
> + if (!value) {
> + value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
> + }
> +
> + tb->s.tpm_pt->tpm_dev = g_strdup(value);
> +
> + tb->path = g_strdup(tb->s.tpm_pt->tpm_dev);
> +
> + tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
> + if (tb->s.tpm_pt->tpm_fd < 0) {
> + error_report("Cannot access TPM device using '%s': %s\n",
> + tb->s.tpm_pt->tpm_dev, strerror(errno));
> + goto err_free_parameters;
> + }
> +
> + if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
> + error_report("'%s' is not a TPM device.\n",
> + tb->s.tpm_pt->tpm_dev);
> + goto err_close_tpmdev;
> + }
> +
> + return 0;
> +
> + err_close_tpmdev:
> + close(tb->s.tpm_pt->tpm_fd);
> + tb->s.tpm_pt->tpm_fd = -1;
> +
> + err_free_parameters:
> + g_free(tb->path);
> + tb->path = NULL;
> +
> + g_free(tb->s.tpm_pt->tpm_dev);
> + tb->s.tpm_pt->tpm_dev = NULL;
> +
> + return 1;
> +}
> +
> +static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
> +{
> + TPMBackend *tb;
> +
> + tb = g_new0(TPMBackend, 1);
> + tb->s.tpm_pt = g_new0(TPMPassthruState, 1);
> + tb->id = g_strdup(id);
> +
> + tb->ops = &tpm_passthrough_driver;
> +
> + if (tpm_passthrough_handle_device_opts(opts, tb)) {
> + goto err_exit;
> + }
> +
> + return tb;
> +
> +err_exit:
> + g_free(tb->id);
> + g_free(tb->s.tpm_pt);
> + g_free(tb);
> +
> + return NULL;
> +}
> +
> +static void tpm_passthrough_destroy(TPMBackend *tb)
> +{
> + TPMPassthruState *tpm_pt = tb->s.tpm_pt;
> +
> + tpm_backend_thread_end(&tpm_pt->tbt);
> +
> + close(tpm_pt->tpm_fd);
> +
> + g_free(tb->id);
> + g_free(tb->path);
> + g_free(tb->s.tpm_pt->tpm_dev);
> + g_free(tb->s.tpm_pt);
> + g_free(tb);
> +}
> +
> +const TPMDriverOps tpm_passthrough_driver = {
> + .type = "passthrough",
> + .desc = tpm_passthrough_create_desc,
> + .create = tpm_passthrough_create,
> + .destroy = tpm_passthrough_destroy,
> + .init = tpm_passthrough_init,
> + .startup_tpm = tpm_passthrough_startup_tpm,
> + .realloc_buffer = tpm_passthrough_realloc_buffer,
> + .reset = tpm_passthrough_reset,
> + .had_startup_error = tpm_passthrough_get_startup_error,
> + .deliver_request = tpm_passthrough_deliver_request,
> + .cancel_cmd = tpm_passthrough_cancel_cmd,
> + .get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag,
> +};
> diff --git a/qemu-char.c b/qemu-char.c
> index 242b799..22b92bd 100644
> --- a/qemu-char.c
> +++ b/qemu-char.c
> @@ -538,6 +538,30 @@ int send_all(int fd, const void *_buf, int len1)
> }
> return len1 - len;
> }
> +
> +int recv_all(int fd, void *_buf, int len1, bool single_read)
> +{
> + int ret, len;
> + uint8_t *buf = _buf;
> +
> + len = len1;
> + while ((len > 0) && (ret = read(fd, buf, len)) != 0) {
> + if (ret < 0) {
> + if (errno != EINTR && errno != EAGAIN) {
> + return -1;
> + }
> + continue;
> + } else {
> + if (single_read) {
> + return ret;
> + }
> + buf += ret;
> + len -= ret;
> + }
> + }
> + return len1 - len;
> +}
> +
> #endif /* !_WIN32 */
>
> #define STDIO_MAX_CLIENTS 1
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 27a968e..225191f 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -2159,10 +2159,12 @@ ETEXI
> DEFHEADING()
>
> #ifdef CONFIG_TPM
> +# ifdef CONFIG_TPM_PASSTHROUGH
> DEFHEADING(TPM device options:)
>
> DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
> - "-tpmdev [<type>],id=str[,option][,option][,...]\n",
> + "-tpmdev passthrough,id=id[,path=path]\n"
> + " use path to provide path to a character device; default is /dev/tpm0\n",
> QEMU_ARCH_ALL)
> STEXI
>
> @@ -2172,6 +2174,7 @@ The general form of a TPM device option is:
> @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
> @findex -tpmdev
> Backend type must be:
> +@option{passthrough}.
>
> The specific backend type will determine the applicable options.
> The @code{-tpmdev} options requires a @code{-device} option.
> @@ -2183,12 +2186,45 @@ Use ? to print all available TPM backend types.
> qemu -tpmdev ?
> @end example
>
> +@item -tpmdev passthrough, id=@var{id}, path=@var{path}
> +
> +(Linux-host only) Enable access to the host's TPM using the passthrough
> +driver.
> +
> +@option{path} specifies the path to the host's TPM device, i.e., on
> +a Linux host this would be @code{/dev/tpm0}.
> +@option{path} is optional and by default @code{/dev/tpm0} is used.
> +
> +Some notes about using the host's TPM with the passthrough driver:
> +
> +The TPM device accessed by the passthrough driver must not be
> +used by any other application on the host.
> +
> +Since the host's firmware (BIOS/UEFI) has already initialized the TPM,
> +the VM's firmware (BIOS/UEFI) will not be able to initialize the
> +TPM again and may therefore not show a TPM-specific menu that would
> +otherwise allow the user to configure the TPM, e.g., allow the user to
> +enable/disable or activate/deactivate the TPM.
> +Further, if TPM ownership is released from within a VM then the host's TPM
> +will get disabled and deactivated. To enable and activate the
> +TPM again afterwards, the host has to be rebooted and the user is
> +required to enter the firmware's menu to enable and activate the TPM.
> +If the TPM is left disabled and/or deactivated most TPM commands will fail.
> +
> +To create a passthrough TPM use the following two options:
> +@example
> +-tpmdev passthrough,id=tpm0 -device tpm-tis,tpmdev=tpm0
> +@end example
> +Note that the @code{-tpmdev} id is @code{tpm0} and is referenced by
> +@code{tpmdev=tpm0} in the device option.
> +
> @end table
>
> ETEXI
>
> DEFHEADING()
>
> +# endif
> #endif
>
> DEFHEADING(Linux/Multiboot boot specific:)
> diff --git a/qemu_socket.h b/qemu_socket.h
> index 02490ad..99ce174 100644
> --- a/qemu_socket.h
> +++ b/qemu_socket.h
> @@ -37,6 +37,7 @@ int socket_set_cork(int fd, int v);
> void socket_set_block(int fd);
> void socket_set_nonblock(int fd);
> int send_all(int fd, const void *buf, int len1);
> +int recv_all(int fd, void *buf, int len1, bool single_read);
>
> /* callback function for nonblocking connect
> * valid fd on success, negative error code on failure
> diff --git a/tpm.c b/tpm.c
> index c9356b6..4730039 100644
> --- a/tpm.c
> +++ b/tpm.c
> @@ -24,9 +24,26 @@ static QLIST_HEAD(, TPMBackend) tpm_backends =
> #ifdef CONFIG_TPM
>
> static const TPMDriverOps *be_drivers[] = {
> +#ifdef CONFIG_TPM_PASSTHROUGH
> + &tpm_passthrough_driver,
> +#endif
> NULL,
> };
>
> +/*
> + * Write an error message in the given output buffer.
> + */
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len)
> +{
> + if (out_len >= sizeof(struct tpm_resp_hdr)) {
> + struct tpm_resp_hdr *resp = (struct tpm_resp_hdr *)out;
> +
> + resp->tag = cpu_to_be16(TPM_TAG_RSP_COMMAND);
> + resp->len = cpu_to_be32(sizeof(struct tpm_resp_hdr));
> + resp->errcode = cpu_to_be32(TPM_FAIL);
> + }
> +}
> +
> const TPMDriverOps *tpm_get_backend_driver(const char *type)
> {
> int i;
> diff --git a/tpm.h b/tpm.h
> index 8943b61..b4f7774 100644
> --- a/tpm.h
> +++ b/tpm.h
> @@ -18,6 +18,8 @@
> struct TPMDriverOps;
> typedef struct TPMDriverOps TPMDriverOps;
>
> +typedef struct TPMPassthruState TPMPassthruState;
> +
> typedef struct TPMBackend {
> char *id;
> const char *fe_model;
> @@ -25,6 +27,10 @@ typedef struct TPMBackend {
> char *cancel_path;
> const TPMDriverOps *ops;
>
> + union {
> + TPMPassthruState *tpm_pt;
> + } s;
> +
> QLIST_ENTRY(TPMBackend) list;
> } TPMBackend;
>
> @@ -74,11 +80,38 @@ struct TPMDriverOps {
>
> #define TPM_DEFAULT_DEVICE_MODEL "tpm-tis"
>
> +struct tpm_req_hdr {
> + uint16_t tag;
> + uint32_t len;
> + uint32_t ordinal;
> +} __attribute__((packed));
> +
> +struct tpm_resp_hdr {
> + uint16_t tag;
> + uint32_t len;
> + uint32_t errcode;
> +} __attribute__((packed));
> +
> +#define TPM_TAG_RQU_COMMAND 0xc1
> +#define TPM_TAG_RQU_AUTH1_COMMAND 0xc2
> +#define TPM_TAG_RQU_AUTH2_COMMAND 0xc3
> +
> +#define TPM_TAG_RSP_COMMAND 0xc4
> +#define TPM_TAG_RSP_AUTH1_COMMAND 0xc5
> +#define TPM_TAG_RSP_AUTH2_COMMAND 0xc6
> +
> +#define TPM_FAIL 9
> +
> +#define TPM_ORD_GetTicks 0xf1
> +
> int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
> int tpm_init(void);
> void tpm_cleanup(void);
> TPMBackend *qemu_find_tpm(const char *id);
> void tpm_display_backend_drivers(void);
> const TPMDriverOps *tpm_get_backend_driver(const char *id);
> +void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
> +
> +extern const TPMDriverOps tpm_passthrough_driver;
>
> #endif /* QEMU_TPM_H */
> diff --git a/vl.c b/vl.c
> index 2cbbc0b..fad9c34 100644
> --- a/vl.c
> +++ b/vl.c
> @@ -2940,11 +2940,13 @@ int main(int argc, char **argv, char **envp)
> break;
> }
> #ifdef CONFIG_TPM
> +# ifdef CONFIG_TPM_PASSTHROUGH
> case QEMU_OPTION_tpmdev:
> if (tpm_config_parse(qemu_find_opts("tpmdev"), optarg) < 0) {
> exit(1);
> }
> break;
> +# endif
> #endif
> case QEMU_OPTION_mempath:
> mem_path = optarg;
>
Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
--
Regards,
Corey Bryant
^ permalink raw reply [flat|nested] 21+ messages in thread
* [Qemu-devel] [PATCH V20 6/8] Add support for cancelling of a TPM command
2013-01-18 16:02 [Qemu-devel] [PATCH V20 0/8] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (4 preceding siblings ...)
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 5/8] Add a TPM Passthrough backend driver implementation Stefan Berger
@ 2013-01-18 16:02 ` Stefan Berger
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 7/8] Introduce --enable-tpm-passthrough configure option Stefan Berger
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 8/8] Add fd parameter for TPM passthrough driver Stefan Berger
7 siblings, 0 replies; 21+ messages in thread
From: Stefan Berger @ 2013-01-18 16:02 UTC (permalink / raw)
To: stefanb, qemu-devel, anthony; +Cc: andreas.niederl, mst
This patch adds support for cancelling an executing TPM command.
In Linux for example a user can cancel a command through the TPM's
sysfs 'cancel' entry using
echo "1" > /sysfs/.../cancel
This patch propagates the cancellation to the host TPM's sysfs entry.
It also uses the possibility to cancel the command before QEMU VM
shutdown or reboot, which helps in preventing QEMU from hanging while
waiting for the completion of the command.
To relieve higher layers or users from having to determine the TPM's
cancel sysfs entry, the driver searches for the entry in well known
locations.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
hw/tpm_passthrough.c | 168 +++++++++++++++++++++++++++++++++++++++++++++++----
1 file changed, 155 insertions(+), 13 deletions(-)
diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
index f9e6923..1b17c30 100644
--- a/hw/tpm_passthrough.c
+++ b/hw/tpm_passthrough.c
@@ -22,6 +22,8 @@
* License along with this library; if not, see <http://www.gnu.org/licenses/>
*/
+#include <dirent.h>
+
#include "qemu-common.h"
#include "qemu-error.h"
#include "qemu_socket.h"
@@ -57,11 +59,18 @@ struct TPMPassthruState {
char *tpm_dev;
int tpm_fd;
+ bool tpm_executing;
+ bool tpm_op_canceled;
+ int cancel_fd;
bool had_startup_error;
};
#define TPM_PASSTHROUGH_DEFAULT_DEVICE "/dev/tpm0"
+/* functions */
+
+static void tpm_passthrough_cancel_cmd(TPMBackend *tb);
+
static int tpm_passthrough_unix_write(int fd, const uint8_t *buf, uint32_t len)
{
return send_all(fd, buf, len);
@@ -79,25 +88,34 @@ static uint32_t tpm_passthrough_get_size_from_buffer(const uint8_t *buf)
return be32_to_cpu(resp->len);
}
-static int tpm_passthrough_unix_tx_bufs(int tpm_fd,
+static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
const uint8_t *in, uint32_t in_len,
uint8_t *out, uint32_t out_len)
{
int ret;
- ret = tpm_passthrough_unix_write(tpm_fd, in, in_len);
+ tpm_pt->tpm_op_canceled = false;
+ tpm_pt->tpm_executing = true;
+
+ ret = tpm_passthrough_unix_write(tpm_pt->tpm_fd, in, in_len);
if (ret != in_len) {
- error_report("tpm_passthrough: error while transmitting data "
- "to TPM: %s (%i)\n",
- strerror(errno), errno);
+ if (!tpm_pt->tpm_op_canceled ||
+ (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
+ error_report("tpm_passthrough: error while transmitting data "
+ "to TPM: %s (%i)\n",
+ strerror(errno), errno);
+ }
goto err_exit;
}
- ret = tpm_passthrough_unix_read(tpm_fd, out, out_len);
+ ret = tpm_passthrough_unix_read(tpm_pt->tpm_fd, out, out_len);
if (ret < 0) {
- error_report("tpm_passthrough: error while reading data from "
- "TPM: %s (%i)\n",
- strerror(errno), errno);
+ if (!tpm_pt->tpm_op_canceled ||
+ (tpm_pt->tpm_op_canceled && errno != ECANCELED)) {
+ error_report("tpm_passthrough: error while reading data from "
+ "TPM: %s (%i)\n",
+ strerror(errno), errno);
+ }
} else if (ret < sizeof(struct tpm_resp_hdr) ||
tpm_passthrough_get_size_from_buffer(out) != ret) {
ret = -1;
@@ -110,13 +128,15 @@ err_exit:
tpm_write_fatal_error_response(out, out_len);
}
+ tpm_pt->tpm_executing = false;
+
return ret;
}
-static int tpm_passthrough_unix_transfer(int tpm_fd,
+static int tpm_passthrough_unix_transfer(TPMPassthruState *tpm_pt,
const TPMLocality *locty_data)
{
- return tpm_passthrough_unix_tx_bufs(tpm_fd,
+ return tpm_passthrough_unix_tx_bufs(tpm_pt,
locty_data->w_buffer.buffer,
locty_data->w_offset,
locty_data->r_buffer.buffer,
@@ -134,7 +154,7 @@ static void tpm_passthrough_worker_thread(gpointer data,
switch (cmd) {
case TPM_BACKEND_CMD_PROCESS_CMD:
- tpm_passthrough_unix_transfer(tpm_pt->tpm_fd,
+ tpm_passthrough_unix_transfer(tpm_pt,
thr_parms->tpm_state->locty_data);
thr_parms->recv_data_callback(thr_parms->tpm_state,
@@ -174,6 +194,8 @@ static void tpm_passthrough_reset(TPMBackend *tb)
tpm_backend_thread_end(&tpm_pt->tbt);
+ tpm_passthrough_cancel_cmd(tb);
+
tpm_pt->had_startup_error = false;
}
@@ -221,7 +243,24 @@ static void tpm_passthrough_deliver_request(TPMBackend *tb)
static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
{
- /* cancelling an ongoing command is known not to work with some TPMs */
+ TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+ int n;
+
+ /*
+ * As of Linux 3.7 the tpm_tis driver does not properly cancel
+ * commands for all TPMs.
+ * Only cancel if we're busy so we don't cancel someone else's
+ * command, e.g., a command executed on the host.
+ */
+ if (tpm_pt->cancel_fd >= 0 && tpm_pt->tpm_executing) {
+ n = write(tpm_pt->cancel_fd, "-", 1);
+ if (n != 1) {
+ error_report("Canceling TPM command failed: %s\n",
+ strerror(errno));
+ } else {
+ tpm_pt->tpm_op_canceled = true;
+ }
+ }
}
static const char *tpm_passthrough_create_desc(void)
@@ -281,6 +320,103 @@ static int tpm_passthrough_test_tpmdev(int fd)
return 0;
}
+/*
+ * Check whether the given base path, e.g., /sys/devices/platfrom/tpm_tis,
+ * is the sysfs entry of a TPM. A TPM sysfs entry should be uniquely
+ * recognizable by the file entries 'pcrs' and 'cancel'.
+ * Upon success 'true' is returned and the basebath buffer has '/cancel'
+ * appended.
+ */
+static bool tpm_passthrough_check_sysfs_cancel(char *basepath, size_t bufsz)
+{
+ char path[PATH_MAX];
+ struct stat statbuf;
+
+ snprintf(path, sizeof(path), "%s/pcrs", basepath);
+ if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
+ return false;
+ }
+
+ snprintf(path, sizeof(path), "%s/cancel", basepath);
+ if (stat(path, &statbuf) == -1 || !S_ISREG(statbuf.st_mode)) {
+ return false;
+ }
+
+ strncpy(basepath, path, bufsz);
+
+ return true;
+}
+
+static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb)
+{
+ int fd = -1;
+ unsigned int i = 0;
+ DIR *pnp_dir;
+ char path[PATH_MAX];
+ struct dirent entry, *result;
+ bool found = false;
+
+ while (!found) {
+ snprintf(path, sizeof(path), "/sys/devices/pnp%u", i);
+ pnp_dir = opendir(path);
+ if (pnp_dir != NULL) {
+ while (readdir_r(pnp_dir, &entry, &result) == 0 &&
+ result != NULL) {
+ if (!strcmp(entry.d_name, ".") ||
+ !strcmp(entry.d_name, "..")) {
+ continue;
+ }
+ snprintf(path, sizeof(path), "/sys/devices/pnp%u/%s",
+ i, entry.d_name);
+ if (!tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) {
+ continue;
+ }
+
+ fd = open(path, O_WRONLY);
+ found = true;
+ break;
+ }
+ closedir(pnp_dir);
+ } else {
+ break;
+ }
+ i++;
+ }
+
+ if (fd >= 0) {
+ goto exit;
+ }
+
+ /*
+ * alternative path if ACPI for TPM was not available:
+ * modprobe tpm_tis force=1
+ */
+ snprintf(path, sizeof(path), "/sys/devices/platform/tpm_tis");
+ if (tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) {
+ fd = open(path, O_WRONLY);
+ goto exit;
+ }
+
+ /*
+ * one more thing to try: /sys/class/misc/tpm%u/device/cancel
+ */
+ for (i = 0; i < 9; i++) {
+ snprintf(path, sizeof(path), "/sys/class/misc/tpm%u/device", i);
+ if (!tpm_passthrough_check_sysfs_cancel(path, sizeof(path))) {
+ continue;
+ }
+ fd = open(path, O_WRONLY);
+ break;
+ }
+
+exit:
+ if (fd >= 0) {
+ tb->cancel_path = g_strdup(path);
+ }
+
+ return fd;
+}
+
static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
{
const char *value;
@@ -337,6 +473,8 @@ static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id)
goto err_exit;
}
+ tb->s.tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb);
+
return tb;
err_exit:
@@ -351,12 +489,16 @@ static void tpm_passthrough_destroy(TPMBackend *tb)
{
TPMPassthruState *tpm_pt = tb->s.tpm_pt;
+ tpm_passthrough_cancel_cmd(tb);
+
tpm_backend_thread_end(&tpm_pt->tbt);
close(tpm_pt->tpm_fd);
+ close(tb->s.tpm_pt->cancel_fd);
g_free(tb->id);
g_free(tb->path);
+ g_free(tb->cancel_path);
g_free(tb->s.tpm_pt->tpm_dev);
g_free(tb->s.tpm_pt);
g_free(tb);
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [Qemu-devel] [PATCH V20 7/8] Introduce --enable-tpm-passthrough configure option
2013-01-18 16:02 [Qemu-devel] [PATCH V20 0/8] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (5 preceding siblings ...)
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 6/8] Add support for cancelling of a TPM command Stefan Berger
@ 2013-01-18 16:02 ` Stefan Berger
2013-02-01 19:21 ` Corey Bryant
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 8/8] Add fd parameter for TPM passthrough driver Stefan Berger
7 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2013-01-18 16:02 UTC (permalink / raw)
To: stefanb, qemu-devel, anthony; +Cc: andreas.niederl, mst
Introduce --enable-tpm-passthrough configure option.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
configure | 16 +++++++++++++++-
1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/configure b/configure
index 73fc146..f54d40e 100755
--- a/configure
+++ b/configure
@@ -224,6 +224,7 @@ coroutine=""
seccomp=""
glusterfs=""
tpm="no"
+tpm_passthrough="no"
# parse CC options first
for opt do
@@ -874,11 +875,20 @@ for opt do
;;
--enable-tpm) tpm="yes"
;;
+ --enable-tpm-passthrough) tpm_passthrough="yes"
+ ;;
*) echo "ERROR: unknown option $opt"; show_help="yes"
;;
esac
done
+if test "$tpm" = "no" ; then
+ if test "$tpm_passthrough" = "yes"; then
+ echo "ERROR: --enable-tpm-passthrough requires --enable-tpm"
+ exit 1
+ fi
+fi
+
case "$cpu" in
sparc)
LDFLAGS="-m32 $LDFLAGS"
@@ -1123,6 +1133,7 @@ echo " gthread, ucontext, sigaltstack, windows"
echo " --enable-glusterfs enable GlusterFS backend"
echo " --disable-glusterfs disable GlusterFS backend"
echo " --enable-tpm enable TPM support"
+echo " --enable-tpm-passthrough enable TPM passthrough driver"
echo ""
echo "NOTE: The object files are built at the place where configure is launched"
exit 1
@@ -3255,6 +3266,7 @@ echo "seccomp support $seccomp"
echo "coroutine backend $coroutine_backend"
echo "GlusterFS support $glusterfs"
echo "TPM support $tpm"
+echo "TPM passthrough $tpm_passthrough"
if test "$sdl_too_old" = "yes"; then
echo "-> Your SDL version is too old - please upgrade to have SDL support"
@@ -4157,7 +4169,9 @@ fi
if test "$tpm" = "yes"; then
if test "$target_softmmu" = "yes" ; then
if test "$linux" = "yes" ; then
- echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_host_mak
+ if test "$tpm_passthrough" = "yes" ; then
+ echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_host_mak
+ fi
fi
echo "CONFIG_TPM=y" >> $config_host_mak
fi
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [Qemu-devel] [PATCH V20 7/8] Introduce --enable-tpm-passthrough configure option
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 7/8] Introduce --enable-tpm-passthrough configure option Stefan Berger
@ 2013-02-01 19:21 ` Corey Bryant
0 siblings, 0 replies; 21+ messages in thread
From: Corey Bryant @ 2013-02-01 19:21 UTC (permalink / raw)
To: Stefan Berger; +Cc: mst, qemu-devel, anthony, andreas.niederl
On 01/18/2013 11:02 AM, Stefan Berger wrote:
> Introduce --enable-tpm-passthrough configure option.
>
> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
> ---
> configure | 16 +++++++++++++++-
> 1 file changed, 15 insertions(+), 1 deletion(-)
>
> diff --git a/configure b/configure
> index 73fc146..f54d40e 100755
> --- a/configure
> +++ b/configure
> @@ -224,6 +224,7 @@ coroutine=""
> seccomp=""
> glusterfs=""
> tpm="no"
> +tpm_passthrough="no"
>
> # parse CC options first
> for opt do
> @@ -874,11 +875,20 @@ for opt do
> ;;
> --enable-tpm) tpm="yes"
> ;;
> + --enable-tpm-passthrough) tpm_passthrough="yes"
> + ;;
> *) echo "ERROR: unknown option $opt"; show_help="yes"
> ;;
> esac
> done
>
> +if test "$tpm" = "no" ; then
> + if test "$tpm_passthrough" = "yes"; then
> + echo "ERROR: --enable-tpm-passthrough requires --enable-tpm"
> + exit 1
> + fi
> +fi
> +
> case "$cpu" in
> sparc)
> LDFLAGS="-m32 $LDFLAGS"
> @@ -1123,6 +1133,7 @@ echo " gthread, ucontext, sigaltstack, windows"
> echo " --enable-glusterfs enable GlusterFS backend"
> echo " --disable-glusterfs disable GlusterFS backend"
> echo " --enable-tpm enable TPM support"
> +echo " --enable-tpm-passthrough enable TPM passthrough driver"
> echo ""
> echo "NOTE: The object files are built at the place where configure is launched"
> exit 1
> @@ -3255,6 +3266,7 @@ echo "seccomp support $seccomp"
> echo "coroutine backend $coroutine_backend"
> echo "GlusterFS support $glusterfs"
> echo "TPM support $tpm"
> +echo "TPM passthrough $tpm_passthrough"
>
> if test "$sdl_too_old" = "yes"; then
> echo "-> Your SDL version is too old - please upgrade to have SDL support"
> @@ -4157,7 +4169,9 @@ fi
> if test "$tpm" = "yes"; then
> if test "$target_softmmu" = "yes" ; then
> if test "$linux" = "yes" ; then
> - echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_host_mak
> + if test "$tpm_passthrough" = "yes" ; then
> + echo "CONFIG_TPM_PASSTHROUGH=y" >> $config_host_mak
> + fi
> fi
> echo "CONFIG_TPM=y" >> $config_host_mak
> fi
>
Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
--
Regards,
Corey Bryant
^ permalink raw reply [flat|nested] 21+ messages in thread
* [Qemu-devel] [PATCH V20 8/8] Add fd parameter for TPM passthrough driver
2013-01-18 16:02 [Qemu-devel] [PATCH V20 0/8] Qemu Trusted Platform Module (TPM) integration Stefan Berger
` (6 preceding siblings ...)
2013-01-18 16:02 ` [Qemu-devel] [PATCH V20 7/8] Introduce --enable-tpm-passthrough configure option Stefan Berger
@ 2013-01-18 16:02 ` Stefan Berger
[not found] ` <50F991FE.3000901@redhat.com>
7 siblings, 1 reply; 21+ messages in thread
From: Stefan Berger @ 2013-01-18 16:02 UTC (permalink / raw)
To: stefanb, qemu-devel, anthony; +Cc: andreas.niederl, mst
Enable the passing of a file descriptor via fd=<..> to access the host's
TPM device using the TPM passthrough driver.
Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
---
hmp.c | 7 ++++++-
hw/tpm_passthrough.c | 53 ++++++++++++++++++++++++++++++++++++++++------------
qapi-schema.json | 4 +++-
qemu-config.c | 5 +++++
qemu-options.hx | 11 ++++++++---
tpm.c | 4 ++++
tpm.h | 1 +
7 files changed, 68 insertions(+), 17 deletions(-)
diff --git a/hmp.c b/hmp.c
index 8d63d03..ee6ab54 100644
--- a/hmp.c
+++ b/hmp.c
@@ -647,12 +647,17 @@ void hmp_info_tpm(Monitor *mon)
TPMInfo *ti = info->value;
monitor_printf(mon, " tpm%d: model=%s\n",
c, ti->model);
- monitor_printf(mon, " \\ %s: type=%s%s%s%s%s\n",
+ monitor_printf(mon, " \\ %s: type=%s%s%s%s%s",
ti->id, ti->type,
ti->has_path ? ",path=" : "",
ti->has_path ? ti->path : "",
ti->has_cancel_path ? ",cancel_path=" : "",
ti->has_cancel_path ? ti->cancel_path : "");
+ if (ti->has_fd) {
+ monitor_printf(mon, ",fd=%" PRId64,
+ ti->fd);
+ }
+ monitor_printf(mon, "\n");
c++;
}
qapi_free_TPMInfoList(info_list);
diff --git a/hw/tpm_passthrough.c b/hw/tpm_passthrough.c
index 1b17c30..3843268 100644
--- a/hw/tpm_passthrough.c
+++ b/hw/tpm_passthrough.c
@@ -420,26 +420,54 @@ exit:
static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
{
const char *value;
+ struct stat statbuf;
- value = qemu_opt_get(opts, "path");
- if (!value) {
- value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
- }
+ value = qemu_opt_get(opts, "fd");
+ if (value) {
+ if (qemu_opt_get(opts, "path")) {
+ error_report("fd= is invalid with path=");
+ goto err_exit;
+ }
+
+ tb->s.tpm_pt->tpm_fd = qemu_parse_fd(value);
+ if (tb->s.tpm_pt->tpm_fd < 0) {
+ error_report("Illegal file descriptor for TPM device.\n");
+ goto err_exit;
+ }
+
+ tb->tpm_fd = &tb->s.tpm_pt->tpm_fd;
+ } else {
+ value = qemu_opt_get(opts, "path");
+ if (!value) {
+ value = TPM_PASSTHROUGH_DEFAULT_DEVICE;
+ }
- tb->s.tpm_pt->tpm_dev = g_strdup(value);
+ tb->s.tpm_pt->tpm_dev = g_strdup(value);
- tb->path = g_strdup(tb->s.tpm_pt->tpm_dev);
+ tb->path = g_strdup(value);
+
+ tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
+ if (tb->s.tpm_pt->tpm_fd < 0) {
+ error_report("Cannot access TPM device using '%s': %s\n",
+ tb->s.tpm_pt->tpm_dev, strerror(errno));
+ goto err_free_parameters;
+ }
+ }
+
+ if (fstat(tb->s.tpm_pt->tpm_fd, &statbuf) != 0) {
+ error_report("Cannot determine file descriptor type for TPM "
+ "device: %s", strerror(errno));
+ goto err_close_tpmdev;
+ }
- tb->s.tpm_pt->tpm_fd = open(tb->s.tpm_pt->tpm_dev, O_RDWR);
- if (tb->s.tpm_pt->tpm_fd < 0) {
- error_report("Cannot access TPM device using '%s': %s\n",
- tb->s.tpm_pt->tpm_dev, strerror(errno));
+ /* only allow character devices for now */
+ if (!S_ISCHR(statbuf.st_mode)) {
+ error_report("TPM file descriptor is not a character device");
goto err_free_parameters;
}
if (tpm_passthrough_test_tpmdev(tb->s.tpm_pt->tpm_fd)) {
- error_report("'%s' is not a TPM device.\n",
- tb->s.tpm_pt->tpm_dev);
+ error_report("Device is not a TPM.\n");
goto err_close_tpmdev;
}
@@ -456,6 +484,7 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
g_free(tb->s.tpm_pt->tpm_dev);
tb->s.tpm_pt->tpm_dev = NULL;
+ err_exit:
return 1;
}
diff --git a/qapi-schema.json b/qapi-schema.json
index 82f753b..43b46e7 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3033,11 +3033,13 @@
#
# @cancel_path: #optional Path to TPM backend device's cancel sysfs entry
#
+# @fd: #optional File descriptor for the TPM backend device
+#
# Since: 1.5.0
##
{ 'type': 'TPMInfo',
'data': {'model': 'str', 'id': 'str', 'type': 'str', '*path': 'str',
- '*cancel_path': 'str' } }
+ '*cancel_path': 'str', '*fd' : 'int' } }
##
# @query-tpm
diff --git a/qemu-config.c b/qemu-config.c
index 59f605a..e0e1c6f 100644
--- a/qemu-config.c
+++ b/qemu-config.c
@@ -706,6 +706,11 @@ static QemuOptsList qemu_tpmdev_opts = {
.type = QEMU_OPT_STRING,
.help = "Persistent storage for TPM state",
},
+ {
+ .name = "fd",
+ .type = QEMU_OPT_STRING,
+ .help = "File descriptor for accessing the TPM",
+ },
{ /* end of list */ }
},
};
diff --git a/qemu-options.hx b/qemu-options.hx
index 225191f..bb7fe44 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -2163,8 +2163,9 @@ DEFHEADING()
DEFHEADING(TPM device options:)
DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \
- "-tpmdev passthrough,id=id[,path=path]\n"
- " use path to provide path to a character device; default is /dev/tpm0\n",
+ "-tpmdev passthrough,id=id[,path=path][,fd=h]\n"
+ " use path to provide path to a character device; default is /dev/tpm0\n"
+ " use fd to provide a file descriptor to a character device\n",
QEMU_ARCH_ALL)
STEXI
@@ -2186,7 +2187,7 @@ Use ? to print all available TPM backend types.
qemu -tpmdev ?
@end example
-@item -tpmdev passthrough, id=@var{id}, path=@var{path}
+@item -tpmdev passthrough, id=@var{id}, path=@var{path}, fd=@var{h}
(Linux-host only) Enable access to the host's TPM using the passthrough
driver.
@@ -2195,6 +2196,10 @@ driver.
a Linux host this would be @code{/dev/tpm0}.
@option{path} is optional and by default @code{/dev/tpm0} is used.
+@option{fd} specifies the file descriptor of the host's TPM device.
+@option{fd} and @option{path} are mutually exclusive.
+@option{fd} is optional.
+
Some notes about using the host's TPM with the passthrough driver:
The TPM device accessed by the passthrough driver must not be
diff --git a/tpm.c b/tpm.c
index 4730039..5cb9772 100644
--- a/tpm.c
+++ b/tpm.c
@@ -204,6 +204,10 @@ static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv)
res->cancel_path = g_strdup(drv->cancel_path);
res->has_cancel_path = true;
}
+ if (drv->tpm_fd != NULL && *drv->tpm_fd >= 0) {
+ res->fd = *drv->tpm_fd;
+ res->has_fd = true;
+ }
res->type = g_strdup(drv->ops->type);
return res;
diff --git a/tpm.h b/tpm.h
index b4f7774..7d3dcac 100644
--- a/tpm.h
+++ b/tpm.h
@@ -25,6 +25,7 @@ typedef struct TPMBackend {
const char *fe_model;
char *path;
char *cancel_path;
+ int *tpm_fd;
const TPMDriverOps *ops;
union {
--
1.7.11.7
^ permalink raw reply related [flat|nested] 21+ messages in thread