/* * SPAPR TPM Proxy/Hypercall * * Copyright IBM Corp. 2019 * * Authors: * Michael Roth <mdroth@linux.vnet.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. */ #include "qemu/osdep.h" #include "qemu-common.h" #include "qapi/error.h" #include "qemu/error-report.h" #include "sysemu/reset.h" #include "cpu.h" #include "hw/ppc/spapr.h" #include "hw/qdev-properties.h" #include "trace.h" #define TPM_SPAPR_BUFSIZE 4096 enum { TPM_COMM_OP_EXECUTE = 1, TPM_COMM_OP_CLOSE_SESSION = 2, }; static void spapr_tpm_proxy_reset(void *opaque) { SpaprTpmProxy *tpm_proxy = SPAPR_TPM_PROXY(opaque); if (tpm_proxy->host_fd != -1) { close(tpm_proxy->host_fd); tpm_proxy->host_fd = -1; } } static ssize_t tpm_execute(SpaprTpmProxy *tpm_proxy, target_ulong *args) { uint64_t data_in = ppc64_phys_to_real(args[1]); target_ulong data_in_size = args[2]; uint64_t data_out = ppc64_phys_to_real(args[3]); target_ulong data_out_size = args[4]; uint8_t buf_in[TPM_SPAPR_BUFSIZE]; uint8_t buf_out[TPM_SPAPR_BUFSIZE]; ssize_t ret; trace_spapr_tpm_execute(data_in, data_in_size, data_out, data_out_size); if (data_in_size > TPM_SPAPR_BUFSIZE) { error_report("invalid TPM input buffer size: " TARGET_FMT_lu, data_in_size); return H_P3; } if (data_out_size < TPM_SPAPR_BUFSIZE) { error_report("invalid TPM output buffer size: " TARGET_FMT_lu, data_out_size); return H_P5; } if (tpm_proxy->host_fd == -1) { tpm_proxy->host_fd = open(tpm_proxy->host_path, O_RDWR); if (tpm_proxy->host_fd == -1) { error_report("failed to open TPM device %s: %d", tpm_proxy->host_path, errno); return H_RESOURCE; } } cpu_physical_memory_read(data_in, buf_in, data_in_size); do { ret = write(tpm_proxy->host_fd, buf_in, data_in_size); if (ret > 0) { data_in_size -= ret; } } while ((ret >= 0 && data_in_size > 0) || (ret == -1 && errno == EINTR)); if (ret == -1) { error_report("failed to write to TPM device %s: %d", tpm_proxy->host_path, errno); return H_RESOURCE; } do { ret = read(tpm_proxy->host_fd, buf_out, data_out_size); } while (ret == 0 || (ret == -1 && errno == EINTR)); if (ret == -1) { error_report("failed to read from TPM device %s: %d", tpm_proxy->host_path, errno); return H_RESOURCE; } cpu_physical_memory_write(data_out, buf_out, ret); args[0] = ret; return H_SUCCESS; } static target_ulong h_tpm_comm(PowerPCCPU *cpu, SpaprMachineState *spapr, target_ulong opcode, target_ulong *args) { target_ulong op = args[0]; SpaprTpmProxy *tpm_proxy = spapr->tpm_proxy; if (!tpm_proxy) { error_report("TPM proxy not available"); return H_FUNCTION; } trace_spapr_h_tpm_comm(tpm_proxy->host_path, op); switch (op) { case TPM_COMM_OP_EXECUTE: return tpm_execute(tpm_proxy, args); case TPM_COMM_OP_CLOSE_SESSION: spapr_tpm_proxy_reset(tpm_proxy); return H_SUCCESS; default: return H_PARAMETER; } } static void spapr_tpm_proxy_realize(DeviceState *d, Error **errp) { SpaprTpmProxy *tpm_proxy = SPAPR_TPM_PROXY(d); if (tpm_proxy->host_path == NULL) { error_setg(errp, "must specify 'host-path' option for device"); return; } tpm_proxy->host_fd = -1; qemu_register_reset(spapr_tpm_proxy_reset, tpm_proxy); } static void spapr_tpm_proxy_unrealize(DeviceState *d) { SpaprTpmProxy *tpm_proxy = SPAPR_TPM_PROXY(d); qemu_unregister_reset(spapr_tpm_proxy_reset, tpm_proxy); } static Property spapr_tpm_proxy_properties[] = { DEFINE_PROP_STRING("host-path", SpaprTpmProxy, host_path), DEFINE_PROP_END_OF_LIST(), }; static void spapr_tpm_proxy_class_init(ObjectClass *k, void *data) { DeviceClass *dk = DEVICE_CLASS(k); dk->realize = spapr_tpm_proxy_realize; dk->unrealize = spapr_tpm_proxy_unrealize; dk->user_creatable = true; device_class_set_props(dk, spapr_tpm_proxy_properties); } static const TypeInfo spapr_tpm_proxy_info = { .name = TYPE_SPAPR_TPM_PROXY, .parent = TYPE_DEVICE, .instance_size = sizeof(SpaprTpmProxy), .class_init = spapr_tpm_proxy_class_init, }; static void spapr_tpm_proxy_register_types(void) { type_register_static(&spapr_tpm_proxy_info); spapr_register_hypercall(SVM_H_TPM_COMM, h_tpm_comm); } type_init(spapr_tpm_proxy_register_types)