static TPMBackend *tpm_passthrough_create(QemuOpts *opts, const char *id) { Object *obj = object_new(TYPE_TPM_PASSTHROUGH); TPMBackend *tb = TPM_BACKEND(obj); TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); 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; } tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tb); if (tpm_pt->cancel_fd < 0) { goto err_exit; } return tb; err_exit: g_free(tb->id); return NULL; }
static void tpm_passthrough_worker_thread(gpointer data, gpointer user_data) { TPMPassthruThreadParams *thr_parms = user_data; TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(thr_parms->tb); TPMBackendCmd cmd = (TPMBackendCmd)data; bool selftest_done = false; DPRINTF("tpm_passthrough: processing command type %d\n", cmd); switch (cmd) { case TPM_BACKEND_CMD_PROCESS_CMD: tpm_passthrough_unix_transfer(tpm_pt, thr_parms->tpm_state->locty_data, &selftest_done); thr_parms->recv_data_callback(thr_parms->tpm_state, thr_parms->tpm_state->locty_number, selftest_done); break; case TPM_BACKEND_CMD_INIT: case TPM_BACKEND_CMD_END: case TPM_BACKEND_CMD_TPM_RESET: /* nothing to do */ break; } }
static void tpm_passthrough_cancel_cmd(TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); int n; /* * As of Linux 3.7 the tpm_tis driver does not properly cancel * commands on all TPM manufacturers' 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->tpm_executing) { if (tpm_pt->cancel_fd >= 0) { n = write(tpm_pt->cancel_fd, "-", 1); if (n != 1) { error_report("Canceling TPM command failed: %s", strerror(errno)); } else { tpm_pt->tpm_op_canceled = true; } } else { error_report("Cannot cancel TPM command due to missing " "TPM sysfs cancel entry"); } } }
static int tpm_passthrough_init(TPMBackend *tb, TPMState *s, TPMRecvDataCB *recv_data_cb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); 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 void tpm_passthrough_reset(TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); DPRINTF("tpm_passthrough: CALL TO TPM_RESET!\n"); tpm_passthrough_cancel_cmd(tb); tpm_backend_thread_end(&tpm_pt->tbt); tpm_pt->had_startup_error = false; }
/* * 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 = TPM_PASSTHROUGH(tb); /* terminate a running TPM */ tpm_backend_thread_end(&tpm_pt->tbt); tpm_backend_thread_create(&tpm_pt->tbt, tpm_passthrough_worker_thread, &tpm_pt->tpm_thread_params); return 0; }
static void tpm_passthrough_destroy(TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); tpm_passthrough_cancel_cmd(tb); tpm_backend_thread_end(&tpm_pt->tbt); qemu_close(tpm_pt->tpm_fd); qemu_close(tpm_pt->cancel_fd); g_free(tb->id); g_free(tb->path); g_free(tb->cancel_path); g_free(tpm_pt->tpm_dev); }
static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); const char *value; value = qemu_opt_get(opts, "cancel-path"); if (value) { tb->cancel_path = g_strdup(value); } value = qemu_opt_get(opts, "path"); if (!value) { value = TPM_PASSTHROUGH_DEFAULT_DEVICE; } tpm_pt->tpm_dev = g_strdup(value); tb->path = g_strdup(tpm_pt->tpm_dev); tpm_pt->tpm_fd = qemu_open(tpm_pt->tpm_dev, O_RDWR); if (tpm_pt->tpm_fd < 0) { error_report("Cannot access TPM device using '%s': %s\n", tpm_pt->tpm_dev, strerror(errno)); goto err_free_parameters; } if (tpm_passthrough_test_tpmdev(tpm_pt->tpm_fd)) { error_report("'%s' is not a TPM device.\n", tpm_pt->tpm_dev); goto err_close_tpmdev; } return 0; err_close_tpmdev: qemu_close(tpm_pt->tpm_fd); tpm_pt->tpm_fd = -1; err_free_parameters: g_free(tb->path); tb->path = NULL; g_free(tpm_pt->tpm_dev); tpm_pt->tpm_dev = NULL; return 1; }
/* * Unless path or file descriptor set has been provided by user, * determine the sysfs cancel file following kernel documentation * in Documentation/ABI/stable/sysfs-class-tpm. * From /dev/tpm0 create /sys/class/misc/tpm0/device/cancel */ static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); int fd = -1; char *dev; char path[PATH_MAX]; if (tb->cancel_path) { fd = qemu_open(tb->cancel_path, O_WRONLY); if (fd < 0) { error_report("Could not open TPM cancel path : %s", strerror(errno)); } return fd; } dev = strrchr(tpm_pt->tpm_dev, '/'); if (dev) { dev++; if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel", dev) < sizeof(path)) { fd = qemu_open(path, O_WRONLY); if (fd >= 0) { tb->cancel_path = g_strdup(path); } else { error_report("tpm_passthrough: Could not open TPM cancel " "path %s : %s", path, strerror(errno)); } } } else { error_report("tpm_passthrough: Bad TPM device path %s", tpm_pt->tpm_dev); } return fd; }
static TPMVersion tpm_passthrough_get_tpm_version(TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); return tpm_pt->tpm_version; }
static void tpm_passthrough_deliver_request(TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); tpm_backend_thread_deliver_request(&tpm_pt->tbt); }
static bool tpm_passthrough_get_startup_error(TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); return tpm_pt->had_startup_error; }
/* * 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; } /* * Unless path or file descriptor set has been provided by user, * determine the sysfs cancel file following kernel documentation * in Documentation/ABI/stable/sysfs-class-tpm. * From /dev/tpm0 create /sys/class/misc/tpm0/device/cancel */ static int tpm_passthrough_open_sysfs_cancel(TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); int fd = -1; char *dev; char path[PATH_MAX]; if (tb->cancel_path) { fd = qemu_open(tb->cancel_path, O_WRONLY); if (fd < 0) { error_report("Could not open TPM cancel path : %s", strerror(errno)); } return fd; } dev = strrchr(tpm_pt->tpm_dev, '/'); if (dev) { dev++; if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel", dev) < sizeof(path)) { fd = qemu_open(path, O_WRONLY); if (fd >= 0) { tb->cancel_path = g_strdup(path); } else { error_report("tpm_passthrough: Could not open TPM cancel " "path %s : %s", path, strerror(errno)); } } } else { error_report("tpm_passthrough: Bad TPM device path %s", tpm_pt->tpm_dev); } return fd; } static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb) { TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb); const char *value; value = qemu_opt_get(opts, "cancel-path"); tb->cancel_path = g_strdup(value); value = qemu_opt_get(opts, "path"); if (!value) { value = TPM_PASSTHROUGH_DEFAULT_DEVICE; } tpm_pt->tpm_dev = g_strdup(value); tb->path = g_strdup(tpm_pt->tpm_dev); tpm_pt->tpm_fd = qemu_open(tpm_pt->tpm_dev, O_RDWR); if (tpm_pt->tpm_fd < 0) { error_report("Cannot access TPM device using '%s': %s", tpm_pt->tpm_dev, strerror(errno)); goto err_free_parameters; } if (tpm_passthrough_test_tpmdev(tpm_pt->tpm_fd)) { error_report("'%s' is not a TPM device.", tpm_pt->tpm_dev); goto err_close_tpmdev; } return 0; err_close_tpmdev: qemu_close(tpm_pt->tpm_fd); tpm_pt->tpm_fd = -1; err_free_parameters: g_free(tb->path); tb->path = NULL; g_free(tpm_pt->tpm_dev); tpm_pt->tpm_dev = NULL; return 1; }