| /* | 
 |  * 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 "system/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 const Property spapr_tpm_proxy_properties[] = { | 
 |     DEFINE_PROP_STRING("host-path", SpaprTpmProxy, host_path), | 
 | }; | 
 |  | 
 | 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) |