| /* |
| * 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 "qapi/error.h" |
| #include "qemu/error-report.h" |
| #include "sysemu/reset.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) |