Add a TPM Passthrough backend driver implementation

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>
Reviewed-by: Corey Bryant <coreyb@linux.vnet.ibm.com>
Reviewed-by: Joel Schopp <jschopp@linux.vnet.ibm.com>
Message-id: 1361987275-26289-6-git-send-email-stefanb@linux.vnet.ibm.com
Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
This commit is contained in:
Stefan Berger 2013-02-27 12:47:53 -05:00 committed by Anthony Liguori
parent ab214c2960
commit 4549a8b7ee
9 changed files with 599 additions and 1 deletions

View File

@ -38,6 +38,7 @@ int socket_set_nodelay(int fd);
void socket_set_block(int fd); void socket_set_block(int fd);
void socket_set_nonblock(int fd); void socket_set_nonblock(int fd);
int send_all(int fd, const void *buf, int len1); 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 /* callback function for nonblocking connect
* valid fd on success, negative error code on failure * valid fd on success, negative error code on failure

View File

@ -533,6 +533,30 @@ int send_all(int fd, const void *_buf, int len1)
} }
return len1 - len; 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 */ #endif /* !_WIN32 */
typedef struct IOWatchPoll typedef struct IOWatchPoll

View File

@ -2221,7 +2221,8 @@ DEFHEADING()
DEFHEADING(TPM device options:) DEFHEADING(TPM device options:)
DEF("tpmdev", HAS_ARG, QEMU_OPTION_tpmdev, \ 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) QEMU_ARCH_ALL)
STEXI STEXI
@ -2231,6 +2232,7 @@ The general form of a TPM device option is:
@item -tpmdev @var{backend} ,id=@var{id} [,@var{options}] @item -tpmdev @var{backend} ,id=@var{id} [,@var{options}]
@findex -tpmdev @findex -tpmdev
Backend type must be: Backend type must be:
@option{passthrough}.
The specific backend type will determine the applicable options. The specific backend type will determine the applicable options.
The @code{-tpmdev} option requires a @code{-device} option. The @code{-tpmdev} option requires a @code{-device} option.
@ -2242,6 +2244,38 @@ Use 'help' to print all available TPM backend types.
qemu -tpmdev help qemu -tpmdev help
@end example @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 @end table
ETEXI ETEXI

View File

@ -1,4 +1,6 @@
common-obj-y = tpm.o common-obj-y = tpm.o
ifeq ($(CONFIG_TPM),y) ifeq ($(CONFIG_TPM),y)
common-obj-y += tpm_backend.o
common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o common-obj-$(CONFIG_TPM_TIS) += tpm_tis.o
common-obj-$(CONFIG_TPM_PASSTHROUGH) += tpm_passthrough.o
endif endif

View File

@ -61,6 +61,20 @@ static bool tpm_model_is_registered(enum TpmModel model)
return false; return false;
} }
/*
* 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) const TPMDriverOps *tpm_get_backend_driver(const char *type)
{ {
int i; int i;

58
tpm/tpm_backend.c Normal file
View File

@ -0,0 +1,58 @@
/*
* common TPM backend driver functions
*
* Copyright (c) 2012-2013 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/tpm.h"
#include "qemu/thread.h"
#include "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);
}
}

45
tpm/tpm_backend.h Normal file
View File

@ -0,0 +1,45 @@
/*
* common TPM backend driver functions
*
* Copyright (c) 2012-2013 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 TPM_TPM_BACKEND_H
#define TPM_TPM_BACKEND_H
#include <glib.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 /* TPM_TPM_BACKEND_H */

View File

@ -18,6 +18,8 @@
struct TPMDriverOps; struct TPMDriverOps;
typedef struct TPMDriverOps TPMDriverOps; typedef struct TPMDriverOps TPMDriverOps;
typedef struct TPMPassthruState TPMPassthruState;
typedef struct TPMBackend { typedef struct TPMBackend {
char *id; char *id;
enum TpmModel fe_model; enum TpmModel fe_model;
@ -25,6 +27,10 @@ typedef struct TPMBackend {
char *cancel_path; char *cancel_path;
const TPMDriverOps *ops; const TPMDriverOps *ops;
union {
TPMPassthruState *tpm_pt;
} s;
QLIST_ENTRY(TPMBackend) list; QLIST_ENTRY(TPMBackend) list;
} TPMBackend; } TPMBackend;
@ -74,10 +80,37 @@ struct TPMDriverOps {
bool (*get_tpm_established_flag)(TPMBackend *t); bool (*get_tpm_established_flag)(TPMBackend *t);
}; };
struct tpm_req_hdr {
uint16_t tag;
uint32_t len;
uint32_t ordinal;
} QEMU_PACKED;
struct tpm_resp_hdr {
uint16_t tag;
uint32_t len;
uint32_t errcode;
} QEMU_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
TPMBackend *qemu_find_tpm(const char *id); TPMBackend *qemu_find_tpm(const char *id);
int tpm_register_model(enum TpmModel model); int tpm_register_model(enum TpmModel model);
int tpm_register_driver(const TPMDriverOps *tdo); int tpm_register_driver(const TPMDriverOps *tdo);
void tpm_display_backend_drivers(void); void tpm_display_backend_drivers(void);
const TPMDriverOps *tpm_get_backend_driver(const char *type); const TPMDriverOps *tpm_get_backend_driver(const char *type);
void tpm_write_fatal_error_response(uint8_t *out, uint32_t out_len);
extern const TPMDriverOps tpm_passthrough_driver;
#endif /* TPM_TPM_INT_H */ #endif /* TPM_TPM_INT_H */

387
tpm/tpm_passthrough.c Normal file
View File

@ -0,0 +1,387 @@
/*
* passthrough TPM driver
*
* Copyright (c) 2010 - 2013 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 "qapi/error.h"
#include "qemu/sockets.h"
#include "tpm_int.h"
#include "hw/hw.h"
#include "hw/pc.h"
#include "tpm_tis.h"
#include "tpm_backend.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 = qemu_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:
qemu_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);
/* let frontend set the fe_model to proper value */
tb->fe_model = -1;
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);
qemu_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 = TPM_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,
};
static void tpm_passthrough_register(void)
{
tpm_register_driver(&tpm_passthrough_driver);
}
type_init(tpm_passthrough_register)