| /* |
| * TPM configuration |
| * |
| * Copyright (C) 2011-2013 IBM Corporation |
| * |
| * Authors: |
| * Stefan Berger <stefanb@us.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. |
| * |
| * Based on net.c |
| */ |
| #include "config-host.h" |
| |
| #include "monitor/monitor.h" |
| #include "qapi/qmp/qerror.h" |
| #include "sysemu/tpm_backend.h" |
| #include "sysemu/tpm.h" |
| #include "qemu/config-file.h" |
| #include "qmp-commands.h" |
| |
| static QLIST_HEAD(, TPMBackend) tpm_backends = |
| QLIST_HEAD_INITIALIZER(tpm_backends); |
| |
| |
| #define TPM_MAX_MODELS 1 |
| #define TPM_MAX_DRIVERS 1 |
| |
| static TPMDriverOps const *be_drivers[TPM_MAX_DRIVERS] = { |
| NULL, |
| }; |
| |
| static enum TpmModel tpm_models[TPM_MAX_MODELS] = { |
| TPM_MODEL_MAX, |
| }; |
| |
| int tpm_register_model(enum TpmModel model) |
| { |
| int i; |
| |
| for (i = 0; i < TPM_MAX_MODELS; i++) { |
| if (tpm_models[i] == TPM_MODEL_MAX) { |
| tpm_models[i] = model; |
| return 0; |
| } |
| } |
| error_report("Could not register TPM model"); |
| return 1; |
| } |
| |
| static bool tpm_model_is_registered(enum TpmModel model) |
| { |
| int i; |
| |
| for (i = 0; i < TPM_MAX_MODELS; i++) { |
| if (tpm_models[i] == model) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| const TPMDriverOps *tpm_get_backend_driver(const char *type) |
| { |
| int i; |
| |
| for (i = 0; i < TPM_MAX_DRIVERS && be_drivers[i] != NULL; i++) { |
| if (!strcmp(TpmType_lookup[be_drivers[i]->type], type)) { |
| return be_drivers[i]; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| #ifdef CONFIG_TPM |
| |
| int tpm_register_driver(const TPMDriverOps *tdo) |
| { |
| int i; |
| |
| for (i = 0; i < TPM_MAX_DRIVERS; i++) { |
| if (!be_drivers[i]) { |
| be_drivers[i] = tdo; |
| return 0; |
| } |
| } |
| error_report("Could not register TPM driver"); |
| return 1; |
| } |
| |
| /* |
| * Walk the list of available TPM backend drivers and display them on the |
| * screen. |
| */ |
| static void tpm_display_backend_drivers(void) |
| { |
| int i; |
| |
| fprintf(stderr, "Supported TPM types (choose only one):\n"); |
| |
| for (i = 0; i < TPM_MAX_DRIVERS && be_drivers[i] != NULL; i++) { |
| fprintf(stderr, "%12s %s\n", |
| TpmType_lookup[be_drivers[i]->type], be_drivers[i]->desc()); |
| } |
| fprintf(stderr, "\n"); |
| } |
| |
| /* |
| * Find the TPM with the given Id |
| */ |
| TPMBackend *qemu_find_tpm(const char *id) |
| { |
| TPMBackend *drv; |
| |
| if (id) { |
| QLIST_FOREACH(drv, &tpm_backends, list) { |
| if (!strcmp(drv->id, id)) { |
| return drv; |
| } |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static int configure_tpm(QemuOpts *opts) |
| { |
| const char *value; |
| const char *id; |
| const TPMDriverOps *be; |
| TPMBackend *drv; |
| Error *local_err = NULL; |
| |
| if (!QLIST_EMPTY(&tpm_backends)) { |
| error_report("Only one TPM is allowed."); |
| return 1; |
| } |
| |
| id = qemu_opts_id(opts); |
| if (id == NULL) { |
| error_report(QERR_MISSING_PARAMETER, "id"); |
| return 1; |
| } |
| |
| value = qemu_opt_get(opts, "type"); |
| if (!value) { |
| error_report(QERR_MISSING_PARAMETER, "type"); |
| tpm_display_backend_drivers(); |
| return 1; |
| } |
| |
| be = tpm_get_backend_driver(value); |
| if (be == NULL) { |
| error_report(QERR_INVALID_PARAMETER_VALUE, |
| "type", "a TPM backend type"); |
| tpm_display_backend_drivers(); |
| return 1; |
| } |
| |
| /* validate backend specific opts */ |
| qemu_opts_validate(opts, be->opts, &local_err); |
| if (local_err) { |
| error_report_err(local_err); |
| return 1; |
| } |
| |
| drv = be->create(opts, id); |
| if (!drv) { |
| return 1; |
| } |
| |
| tpm_backend_open(drv, &local_err); |
| if (local_err) { |
| error_report_err(local_err); |
| return 1; |
| } |
| |
| QLIST_INSERT_HEAD(&tpm_backends, drv, list); |
| |
| return 0; |
| } |
| |
| static int tpm_init_tpmdev(void *dummy, QemuOpts *opts, Error **errp) |
| { |
| return configure_tpm(opts); |
| } |
| |
| /* |
| * Walk the list of TPM backend drivers that are in use and call their |
| * destroy function to have them cleaned up. |
| */ |
| void tpm_cleanup(void) |
| { |
| TPMBackend *drv, *next; |
| |
| QLIST_FOREACH_SAFE(drv, &tpm_backends, list, next) { |
| QLIST_REMOVE(drv, list); |
| tpm_backend_destroy(drv); |
| } |
| } |
| |
| /* |
| * Initialize the TPM. Process the tpmdev command line options describing the |
| * TPM backend. |
| */ |
| int tpm_init(void) |
| { |
| if (qemu_opts_foreach(qemu_find_opts("tpmdev"), |
| tpm_init_tpmdev, NULL, NULL)) { |
| return -1; |
| } |
| |
| atexit(tpm_cleanup); |
| return 0; |
| } |
| |
| /* |
| * Parse the TPM configuration options. |
| * To display all available TPM backends the user may use '-tpmdev help' |
| */ |
| int tpm_config_parse(QemuOptsList *opts_list, const char *optarg) |
| { |
| QemuOpts *opts; |
| |
| if (!strcmp(optarg, "help")) { |
| tpm_display_backend_drivers(); |
| return -1; |
| } |
| opts = qemu_opts_parse_noisily(opts_list, optarg, true); |
| if (!opts) { |
| return -1; |
| } |
| return 0; |
| } |
| |
| #endif /* CONFIG_TPM */ |
| |
| static const TPMDriverOps *tpm_driver_find_by_type(enum TpmType type) |
| { |
| int i; |
| |
| for (i = 0; i < TPM_MAX_DRIVERS && be_drivers[i] != NULL; i++) { |
| if (be_drivers[i]->type == type) { |
| return be_drivers[i]; |
| } |
| } |
| return NULL; |
| } |
| |
| static TPMInfo *qmp_query_tpm_inst(TPMBackend *drv) |
| { |
| TPMInfo *res = g_new0(TPMInfo, 1); |
| TPMPassthroughOptions *tpo; |
| |
| res->id = g_strdup(drv->id); |
| res->model = drv->fe_model; |
| res->options = g_new0(TpmTypeOptions, 1); |
| |
| switch (drv->ops->type) { |
| case TPM_TYPE_PASSTHROUGH: |
| res->options->kind = TPM_TYPE_OPTIONS_KIND_PASSTHROUGH; |
| tpo = g_new0(TPMPassthroughOptions, 1); |
| res->options->passthrough = tpo; |
| if (drv->path) { |
| tpo->path = g_strdup(drv->path); |
| tpo->has_path = true; |
| } |
| if (drv->cancel_path) { |
| tpo->cancel_path = g_strdup(drv->cancel_path); |
| tpo->has_cancel_path = true; |
| } |
| break; |
| case TPM_TYPE_MAX: |
| break; |
| } |
| |
| return res; |
| } |
| |
| /* |
| * Walk the list of active TPM backends and collect information about them |
| * following the schema description in qapi-schema.json. |
| */ |
| TPMInfoList *qmp_query_tpm(Error **errp) |
| { |
| TPMBackend *drv; |
| TPMInfoList *info, *head = NULL, *cur_item = NULL; |
| |
| QLIST_FOREACH(drv, &tpm_backends, list) { |
| if (!tpm_model_is_registered(drv->fe_model)) { |
| continue; |
| } |
| info = g_new0(TPMInfoList, 1); |
| info->value = qmp_query_tpm_inst(drv); |
| |
| if (!cur_item) { |
| head = cur_item = info; |
| } else { |
| cur_item->next = info; |
| cur_item = info; |
| } |
| } |
| |
| return head; |
| } |
| |
| TpmTypeList *qmp_query_tpm_types(Error **errp) |
| { |
| unsigned int i = 0; |
| TpmTypeList *head = NULL, *prev = NULL, *cur_item; |
| |
| for (i = 0; i < TPM_TYPE_MAX; i++) { |
| if (!tpm_driver_find_by_type(i)) { |
| continue; |
| } |
| cur_item = g_new0(TpmTypeList, 1); |
| cur_item->value = i; |
| |
| if (prev) { |
| prev->next = cur_item; |
| } |
| if (!head) { |
| head = cur_item; |
| } |
| prev = cur_item; |
| } |
| |
| return head; |
| } |
| |
| TpmModelList *qmp_query_tpm_models(Error **errp) |
| { |
| unsigned int i = 0; |
| TpmModelList *head = NULL, *prev = NULL, *cur_item; |
| |
| for (i = 0; i < TPM_MODEL_MAX; i++) { |
| if (!tpm_model_is_registered(i)) { |
| continue; |
| } |
| cur_item = g_new0(TpmModelList, 1); |
| cur_item->value = i; |
| |
| if (prev) { |
| prev->next = cur_item; |
| } |
| if (!head) { |
| head = cur_item; |
| } |
| prev = cur_item; |
| } |
| |
| return head; |
| } |