aboutsummaryrefslogtreecommitdiff
path: root/tests
diff options
context:
space:
mode:
authorMarc-André Lureau <marcandre.lureau@redhat.com>2018-01-29 19:33:07 +0100
committerStefan Berger <stefanb@linux.vnet.ibm.com>2018-01-29 14:22:50 -0500
commit4ab6cb4c62273bb46102e5ae1d6af691b47cbcd8 (patch)
tree92eb34d622967e4ff8f8e6e5b4338f773617dca2 /tests
parent6a8a23549a6ddb4af47b032db76badf131c5c2e6 (diff)
tpm: add CRB device
tpm_crb is a device for TPM 2.0 Command Response Buffer (CRB) Interface as defined in TCG PC Client Platform TPM Profile (PTP) Specification Family “2.0” Level 00 Revision 01.03 v22. The PTP allows device implementation to switch between TIS and CRB model at run time, but given that CRB is a simpler device to implement, I chose to implement it as a different device. The device doesn't implement other locality than 0 for now (my laptop TPM doesn't either, so I assume this isn't so bad) Tested with some success with Linux upstream and Windows 10, seabios & modified ovmf. The device is recognized and correctly transmit command/response with passthrough & emu. However, we are missing PPI ACPI part atm. Signed-off-by: Marc-André Lureau <marcandre.lureau@redhat.com> Reviewed-by: Stefan Berger <stefanb@linux.vnet.ibm.com> Signed-off-by: Stefan Berger <stefanb@linux.vnet.ibm.com>
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile.include2
-rw-r--r--tests/tpm-crb-test.c275
2 files changed, 277 insertions, 0 deletions
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 851aafe9d1..ca82e0c0cc 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -286,6 +286,7 @@ check-qtest-i386-$(CONFIG_VHOST_USER_NET_TEST_i386) += tests/vhost-user-test$(EX
ifeq ($(CONFIG_VHOST_USER_NET_TEST_i386),)
check-qtest-x86_64-$(CONFIG_VHOST_USER_NET_TEST_x86_64) += tests/vhost-user-test$(EXESUF)
endif
+check-qtest-i386-$(CONFIG_TPM) += tests/tpm-crb-test$(EXESUF)
check-qtest-i386-$(CONFIG_SLIRP) += tests/test-netfilter$(EXESUF)
check-qtest-i386-$(CONFIG_POSIX) += tests/test-filter-mirror$(EXESUF)
check-qtest-i386-$(CONFIG_POSIX) += tests/test-filter-redirector$(EXESUF)
@@ -708,6 +709,7 @@ tests/test-crypto-tlssession$(EXESUF): tests/test-crypto-tlssession.o \
tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y)
tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \
tests/io-channel-helpers.o $(test-io-obj-y)
+tests/tpm-crb-test$(EXESUF): tests/tpm-crb-test.o $(test-io-obj-y)
tests/test-io-channel-file$(EXESUF): tests/test-io-channel-file.o \
tests/io-channel-helpers.o $(test-io-obj-y)
tests/test-io-channel-tls$(EXESUF): tests/test-io-channel-tls.o \
diff --git a/tests/tpm-crb-test.c b/tests/tpm-crb-test.c
new file mode 100644
index 0000000000..8bf1507e00
--- /dev/null
+++ b/tests/tpm-crb-test.c
@@ -0,0 +1,275 @@
+/*
+ * QTest testcase for TPM CRB
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * Authors:
+ * Marc-André Lureau <marcandre.lureau@redhat.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.
+ */
+
+#include "qemu/osdep.h"
+#include <glib/gstdio.h>
+
+#include "hw/acpi/tpm.h"
+#include "hw/tpm/tpm_ioctl.h"
+#include "io/channel-socket.h"
+#include "libqtest.h"
+#include "qapi/error.h"
+
+#define TPM_RC_FAILURE 0x101
+#define TPM2_ST_NO_SESSIONS 0x8001
+
+struct tpm_hdr {
+ uint16_t tag;
+ uint32_t len;
+ uint32_t code; /*ordinal/error */
+ char buffer[];
+} QEMU_PACKED;
+
+typedef struct TestState {
+ CompatGMutex data_mutex;
+ CompatGCond data_cond;
+ SocketAddress *addr;
+ QIOChannel *tpm_ioc;
+ GThread *emu_tpm_thread;
+ struct tpm_hdr *tpm_msg;
+} TestState;
+
+static void test_wait_cond(TestState *s)
+{
+ gint64 end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+
+ g_mutex_lock(&s->data_mutex);
+ if (!g_cond_wait_until(&s->data_cond, &s->data_mutex, end_time)) {
+ g_assert_not_reached();
+ }
+ g_mutex_unlock(&s->data_mutex);
+}
+
+static void *emu_tpm_thread(void *data)
+{
+ TestState *s = data;
+ QIOChannel *ioc = s->tpm_ioc;
+
+ s->tpm_msg = g_new(struct tpm_hdr, 1);
+ while (true) {
+ int minhlen = sizeof(s->tpm_msg->tag) + sizeof(s->tpm_msg->len);
+
+ if (!qio_channel_read(ioc, (char *)s->tpm_msg, minhlen, &error_abort)) {
+ break;
+ }
+ s->tpm_msg->tag = be16_to_cpu(s->tpm_msg->tag);
+ s->tpm_msg->len = be32_to_cpu(s->tpm_msg->len);
+ g_assert_cmpint(s->tpm_msg->len, >=, minhlen);
+ g_assert_cmpint(s->tpm_msg->tag, ==, TPM2_ST_NO_SESSIONS);
+
+ s->tpm_msg = g_realloc(s->tpm_msg, s->tpm_msg->len);
+ qio_channel_read(ioc, (char *)&s->tpm_msg->code,
+ s->tpm_msg->len - minhlen, &error_abort);
+ s->tpm_msg->code = be32_to_cpu(s->tpm_msg->code);
+
+ /* reply error */
+ s->tpm_msg->tag = cpu_to_be16(TPM2_ST_NO_SESSIONS);
+ s->tpm_msg->len = cpu_to_be32(sizeof(struct tpm_hdr));
+ s->tpm_msg->code = cpu_to_be32(TPM_RC_FAILURE);
+ qio_channel_write(ioc, (char *)s->tpm_msg, be32_to_cpu(s->tpm_msg->len),
+ &error_abort);
+ }
+
+ g_free(s->tpm_msg);
+ s->tpm_msg = NULL;
+ object_unref(OBJECT(s->tpm_ioc));
+ return NULL;
+}
+
+static void *emu_ctrl_thread(void *data)
+{
+ TestState *s = data;
+ QIOChannelSocket *lioc = qio_channel_socket_new();
+ QIOChannel *ioc;
+
+ qio_channel_socket_listen_sync(lioc, s->addr, &error_abort);
+ g_cond_signal(&s->data_cond);
+
+ qio_channel_wait(QIO_CHANNEL(lioc), G_IO_IN);
+ ioc = QIO_CHANNEL(qio_channel_socket_accept(lioc, &error_abort));
+ g_assert(ioc);
+
+ {
+ uint32_t cmd = 0;
+ struct iovec iov = { .iov_base = &cmd, .iov_len = sizeof(cmd) };
+ int *pfd = NULL;
+ size_t nfd = 0;
+
+ qio_channel_readv_full(ioc, &iov, 1, &pfd, &nfd, &error_abort);
+ cmd = be32_to_cpu(cmd);
+ g_assert_cmpint(cmd, ==, CMD_SET_DATAFD);
+ g_assert_cmpint(nfd, ==, 1);
+ s->tpm_ioc = QIO_CHANNEL(qio_channel_socket_new_fd(*pfd, &error_abort));
+ g_free(pfd);
+
+ cmd = 0;
+ qio_channel_write(ioc, (char *)&cmd, sizeof(cmd), &error_abort);
+
+ s->emu_tpm_thread = g_thread_new(NULL, emu_tpm_thread, s);
+ }
+
+ while (true) {
+ uint32_t cmd;
+ ssize_t ret;
+
+ ret = qio_channel_read(ioc, (char *)&cmd, sizeof(cmd), NULL);
+ if (ret <= 0) {
+ break;
+ }
+
+ cmd = be32_to_cpu(cmd);
+ switch (cmd) {
+ case CMD_GET_CAPABILITY: {
+ ptm_cap cap = cpu_to_be64(0x3fff);
+ qio_channel_write(ioc, (char *)&cap, sizeof(cap), &error_abort);
+ break;
+ }
+ case CMD_INIT: {
+ ptm_init init;
+ qio_channel_read(ioc, (char *)&init.u.req, sizeof(init.u.req),
+ &error_abort);
+ init.u.resp.tpm_result = 0;
+ qio_channel_write(ioc, (char *)&init.u.resp, sizeof(init.u.resp),
+ &error_abort);
+ break;
+ }
+ case CMD_SHUTDOWN: {
+ ptm_res res = 0;
+ qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort);
+ qio_channel_close(s->tpm_ioc, &error_abort);
+ g_thread_join(s->emu_tpm_thread);
+ break;
+ }
+ case CMD_STOP: {
+ ptm_res res = 0;
+ qio_channel_write(ioc, (char *)&res, sizeof(res), &error_abort);
+ break;
+ }
+ case CMD_SET_BUFFERSIZE: {
+ ptm_setbuffersize sbs;
+ qio_channel_read(ioc, (char *)&sbs.u.req, sizeof(sbs.u.req),
+ &error_abort);
+ sbs.u.resp.buffersize = sbs.u.req.buffersize ?: cpu_to_be32(4096);
+ sbs.u.resp.tpm_result = 0;
+ sbs.u.resp.minsize = cpu_to_be32(128);
+ sbs.u.resp.maxsize = cpu_to_be32(4096);
+ qio_channel_write(ioc, (char *)&sbs.u.resp, sizeof(sbs.u.resp),
+ &error_abort);
+ break;
+ }
+ case CMD_SET_LOCALITY: {
+ ptm_loc loc;
+ /* Note: this time it's not u.req / u.resp... */
+ qio_channel_read(ioc, (char *)&loc, sizeof(loc), &error_abort);
+ g_assert_cmpint(loc.u.req.loc, ==, 0);
+ loc.u.resp.tpm_result = 0;
+ qio_channel_write(ioc, (char *)&loc, sizeof(loc), &error_abort);
+ break;
+ }
+ default:
+ g_debug("unimplemented %u", cmd);
+ g_assert_not_reached();
+ }
+ }
+
+ object_unref(OBJECT(ioc));
+ object_unref(OBJECT(lioc));
+ return NULL;
+}
+
+#define TPM_CMD "\x80\x01\x00\x00\x00\x0c\x00\x00\x01\x44\x00\x00"
+
+static void tpm_crb_test(const void *data)
+{
+ const TestState *s = data;
+ uint32_t intfid = readl(TPM_CRB_ADDR_BASE + A_CRB_INTF_ID);
+ uint32_t csize = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_SIZE);
+ uint64_t caddr = readq(TPM_CRB_ADDR_BASE + A_CRB_CTRL_CMD_LADDR);
+ uint32_t rsize = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_SIZE);
+ uint64_t raddr = readq(TPM_CRB_ADDR_BASE + A_CRB_CTRL_RSP_ADDR);
+
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceType), ==, 1);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceVersion), ==, 1);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapLocality), ==, 0);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapCRBIdleBypass), ==, 0);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapDataXferSizeSupport),
+ ==, 3);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapFIFO), ==, 0);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, CapCRB), ==, 1);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, InterfaceSelector), ==, 1);
+ g_assert_cmpint(FIELD_EX32(intfid, CRB_INTF_ID, RID), ==, 0);
+
+ g_assert_cmpint(csize, >=, 128);
+ g_assert_cmpint(rsize, >=, 128);
+ g_assert_cmpint(caddr, >, TPM_CRB_ADDR_BASE);
+ g_assert_cmpint(raddr, >, TPM_CRB_ADDR_BASE);
+
+ memwrite(caddr, TPM_CMD, sizeof(TPM_CMD));
+
+ uint32_t sts, start = 1;
+ uint64_t end_time = g_get_monotonic_time() + 5 * G_TIME_SPAN_SECOND;
+ writel(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START, start);
+ do {
+ start = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+ if ((start & 1) == 0) {
+ break;
+ }
+ } while (g_get_monotonic_time() < end_time);
+ start = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_START);
+ g_assert_cmpint(start & 1, ==, 0);
+ sts = readl(TPM_CRB_ADDR_BASE + A_CRB_CTRL_STS);
+ g_assert_cmpint(sts & 1, ==, 0);
+
+ struct tpm_hdr tpm_msg;
+ memread(raddr, &tpm_msg, sizeof(tpm_msg));
+ g_assert_cmpmem(&tpm_msg, sizeof(tpm_msg), s->tpm_msg, sizeof(*s->tpm_msg));
+}
+
+int main(int argc, char **argv)
+{
+ int ret;
+ char *args, *tmp_path = g_dir_make_tmp("qemu-tpm-crb-test.XXXXXX", NULL);
+ GThread *thread;
+ TestState test;
+
+ module_call_init(MODULE_INIT_QOM);
+ g_test_init(&argc, &argv, NULL);
+
+ test.addr = g_new0(SocketAddress, 1);
+ test.addr->type = SOCKET_ADDRESS_TYPE_UNIX;
+ test.addr->u.q_unix.path = g_build_filename(tmp_path, "sock", NULL);
+ g_mutex_init(&test.data_mutex);
+ g_cond_init(&test.data_cond);
+
+ thread = g_thread_new(NULL, emu_ctrl_thread, &test);
+ test_wait_cond(&test);
+
+ args = g_strdup_printf(
+ "-chardev socket,id=chr,path=%s "
+ "-tpmdev emulator,id=dev,chardev=chr "
+ "-device tpm-crb,tpmdev=dev",
+ test.addr->u.q_unix.path);
+ qtest_start(args);
+
+ qtest_add_data_func("/tpm-crb/test", &test, tpm_crb_test);
+ ret = g_test_run();
+
+ qtest_end();
+
+ g_thread_join(thread);
+ g_unlink(test.addr->u.q_unix.path);
+ qapi_free_SocketAddress(test.addr);
+ g_rmdir(tmp_path);
+ g_free(tmp_path);
+ g_free(args);
+ return ret;
+}