Merge tpm 2017/12/15 v1
-----BEGIN PGP SIGNATURE----- iQEcBAABAgAGBQJaM1MfAAoJEHWtZYAqC0IRAj8H/AgVHuAf5huzKZkju/OwQ4z0 MxQwNFHbBgT5reRCjK3JAxTviOHUR7JTVLFFyLIbHQDX+VRDoxXWsuVPNdAgd8SF bA/ywmKlQcYJrdyf1Fole4JY+ZIndkgtUJnwuvC4LWmt/s7LYsNlwOfnARkvtpul 0QH+mlJYv+EeEIjeJDNlgcqxFo4qr8HfuJi2/qC7IEXIHcTYNpdk6gh7auCUVvGl tojocW0Da0G0Ce1ncFIME9doWlBu0ZiU+b3mjjDf5OVtXiT6Xce3o9bNTWsboHia iuvyEaFU/wXbHkn+i/50/DIP6o+u9wJ4MmYp3uJKlpen0SZndZ+UFxcBY7ZrP4g= =s0pV -----END PGP SIGNATURE----- Merge remote-tracking branch 'remotes/stefanberger/tags/pull-tpm-2017-12-15-1' into staging Merge tpm 2017/12/15 v1 # gpg: Signature made Fri 15 Dec 2017 04:44:15 GMT # gpg: using RSA key 0x75AD65802A0B4211 # gpg: Good signature from "Stefan Berger <stefanb@linux.vnet.ibm.com>" # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: B818 B9CA DF90 89C2 D5CE C66B 75AD 6580 2A0B 4211 * remotes/stefanberger/tags/pull-tpm-2017-12-15-1: (32 commits) tpm: tpm_passthrough: Fail startup if FE buffer size < BE buffer size tpm: tpm_emulator: get and set buffer size of device tpm: tpm_passthrough: Read the buffer size from the host device tpm: pull tpm_util_request() out of tpm_util_test() tpm: Move getting TPM buffer size to backends tpm: remove tpm_register_model() tpm-tis: use DEFINE_PROP_TPMBE qdev: add DEFINE_PROP_TPMBE tpm-tis: check that at most one TPM device exists tpm-tis: remove redundant 'tpm_tis:' in error messages tpm-emulator: add a FIXME comment about blocking cancel acpi: change TPM TIS data conditions tpm: add tpm_cmd_get_size() to tpm_util tpm: add TPM interface to lookup TPM version tpm: lookup the the TPM interface instead of TIS device tpm: rename qemu_find_tpm() -> qemu_find_tpm_be() tpm-tis: simplify header inclusion tpm-passthrough: workaround a possible race tpm-passthrough: simplify create() tpm-passthrough: make it safer to destroy after creation ... Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
commit
411ad78115
@ -17,16 +17,25 @@
|
||||
#include "qapi/error.h"
|
||||
#include "qapi/qmp/qerror.h"
|
||||
#include "sysemu/tpm.h"
|
||||
#include "hw/tpm/tpm_int.h"
|
||||
#include "qemu/thread.h"
|
||||
#include "qemu/main-loop.h"
|
||||
|
||||
static void tpm_backend_request_completed_bh(void *opaque)
|
||||
{
|
||||
TPMBackend *s = TPM_BACKEND(opaque);
|
||||
TPMIfClass *tic = TPM_IF_GET_CLASS(s->tpmif);
|
||||
|
||||
tic->request_completed(s->tpmif);
|
||||
}
|
||||
|
||||
static void tpm_backend_worker_thread(gpointer data, gpointer user_data)
|
||||
{
|
||||
TPMBackend *s = TPM_BACKEND(user_data);
|
||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
||||
|
||||
assert(k->handle_request != NULL);
|
||||
k->handle_request(s, (TPMBackendCmd *)data);
|
||||
|
||||
qemu_bh_schedule(s->bh);
|
||||
}
|
||||
|
||||
static void tpm_backend_thread_end(TPMBackend *s)
|
||||
@ -44,15 +53,22 @@ enum TpmType tpm_backend_get_type(TPMBackend *s)
|
||||
return k->type;
|
||||
}
|
||||
|
||||
int tpm_backend_init(TPMBackend *s, TPMState *state)
|
||||
int tpm_backend_init(TPMBackend *s, TPMIf *tpmif, Error **errp)
|
||||
{
|
||||
s->tpm_state = state;
|
||||
if (s->tpmif) {
|
||||
error_setg(errp, "TPM backend '%s' is already initialized", s->id);
|
||||
return -1;
|
||||
}
|
||||
|
||||
s->tpmif = tpmif;
|
||||
object_ref(OBJECT(tpmif));
|
||||
|
||||
s->had_startup_error = false;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int tpm_backend_startup_tpm(TPMBackend *s)
|
||||
int tpm_backend_startup_tpm(TPMBackend *s, size_t buffersize)
|
||||
{
|
||||
int res = 0;
|
||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
||||
@ -63,7 +79,7 @@ int tpm_backend_startup_tpm(TPMBackend *s)
|
||||
s->thread_pool = g_thread_pool_new(tpm_backend_worker_thread, s, 1, TRUE,
|
||||
NULL);
|
||||
|
||||
res = k->startup_tpm ? k->startup_tpm(s) : 0;
|
||||
res = k->startup_tpm ? k->startup_tpm(s, buffersize) : 0;
|
||||
|
||||
s->had_startup_error = (res != 0);
|
||||
|
||||
@ -97,8 +113,6 @@ void tpm_backend_cancel_cmd(TPMBackend *s)
|
||||
{
|
||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
||||
|
||||
assert(k->cancel_cmd);
|
||||
|
||||
k->cancel_cmd(s);
|
||||
}
|
||||
|
||||
@ -122,80 +136,44 @@ TPMVersion tpm_backend_get_tpm_version(TPMBackend *s)
|
||||
{
|
||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
||||
|
||||
assert(k->get_tpm_version);
|
||||
|
||||
return k->get_tpm_version(s);
|
||||
}
|
||||
|
||||
size_t tpm_backend_get_buffer_size(TPMBackend *s)
|
||||
{
|
||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
||||
|
||||
return k->get_buffer_size(s);
|
||||
}
|
||||
|
||||
TPMInfo *tpm_backend_query_tpm(TPMBackend *s)
|
||||
{
|
||||
TPMInfo *info = g_new0(TPMInfo, 1);
|
||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
||||
TPMIfClass *tic = TPM_IF_GET_CLASS(s->tpmif);
|
||||
|
||||
info->id = g_strdup(s->id);
|
||||
info->model = s->fe_model;
|
||||
if (k->get_tpm_options) {
|
||||
info->options = k->get_tpm_options(s);
|
||||
}
|
||||
info->model = tic->model;
|
||||
info->options = k->get_tpm_options(s);
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
static bool tpm_backend_prop_get_opened(Object *obj, Error **errp)
|
||||
{
|
||||
TPMBackend *s = TPM_BACKEND(obj);
|
||||
|
||||
return s->opened;
|
||||
}
|
||||
|
||||
void tpm_backend_open(TPMBackend *s, Error **errp)
|
||||
{
|
||||
object_property_set_bool(OBJECT(s), true, "opened", errp);
|
||||
}
|
||||
|
||||
static void tpm_backend_prop_set_opened(Object *obj, bool value, Error **errp)
|
||||
{
|
||||
TPMBackend *s = TPM_BACKEND(obj);
|
||||
TPMBackendClass *k = TPM_BACKEND_GET_CLASS(s);
|
||||
Error *local_err = NULL;
|
||||
|
||||
if (value == s->opened) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!value && s->opened) {
|
||||
error_setg(errp, QERR_PERMISSION_DENIED);
|
||||
return;
|
||||
}
|
||||
|
||||
if (k->opened) {
|
||||
k->opened(s, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
s->opened = true;
|
||||
}
|
||||
|
||||
static void tpm_backend_instance_init(Object *obj)
|
||||
{
|
||||
TPMBackend *s = TPM_BACKEND(obj);
|
||||
|
||||
object_property_add_bool(obj, "opened",
|
||||
tpm_backend_prop_get_opened,
|
||||
tpm_backend_prop_set_opened,
|
||||
NULL);
|
||||
s->fe_model = -1;
|
||||
s->bh = qemu_bh_new(tpm_backend_request_completed_bh, s);
|
||||
}
|
||||
|
||||
static void tpm_backend_instance_finalize(Object *obj)
|
||||
{
|
||||
TPMBackend *s = TPM_BACKEND(obj);
|
||||
|
||||
object_unref(OBJECT(s->tpmif));
|
||||
g_free(s->id);
|
||||
tpm_backend_thread_end(s);
|
||||
qemu_bh_delete(s->bh);
|
||||
}
|
||||
|
||||
static const TypeInfo tpm_backend_info = {
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "net/hub.h"
|
||||
#include "qapi/visitor.h"
|
||||
#include "chardev/char-fe.h"
|
||||
#include "sysemu/tpm_backend.h"
|
||||
#include "sysemu/iothread.h"
|
||||
|
||||
static void get_pointer(Object *obj, Visitor *v, Property *prop,
|
||||
@ -236,6 +237,69 @@ const PropertyInfo qdev_prop_chr = {
|
||||
.release = release_chr,
|
||||
};
|
||||
|
||||
/* --- character device --- */
|
||||
|
||||
static void get_tpm(Object *obj, Visitor *v, const char *name, void *opaque,
|
||||
Error **errp)
|
||||
{
|
||||
DeviceState *dev = DEVICE(obj);
|
||||
TPMBackend **be = qdev_get_prop_ptr(dev, opaque);
|
||||
char *p;
|
||||
|
||||
p = g_strdup(*be ? (*be)->id : "");
|
||||
visit_type_str(v, name, &p, errp);
|
||||
g_free(p);
|
||||
}
|
||||
|
||||
static void set_tpm(Object *obj, Visitor *v, const char *name, void *opaque,
|
||||
Error **errp)
|
||||
{
|
||||
DeviceState *dev = DEVICE(obj);
|
||||
Error *local_err = NULL;
|
||||
Property *prop = opaque;
|
||||
TPMBackend *s, **be = qdev_get_prop_ptr(dev, prop);
|
||||
char *str;
|
||||
|
||||
if (dev->realized) {
|
||||
qdev_prop_set_after_realize(dev, name, errp);
|
||||
return;
|
||||
}
|
||||
|
||||
visit_type_str(v, name, &str, &local_err);
|
||||
if (local_err) {
|
||||
error_propagate(errp, local_err);
|
||||
return;
|
||||
}
|
||||
|
||||
s = qemu_find_tpm_be(str);
|
||||
if (s == NULL) {
|
||||
error_setg(errp, "Property '%s.%s' can't find value '%s'",
|
||||
object_get_typename(obj), prop->name, str);
|
||||
} else if (tpm_backend_init(s, TPM_IF(obj), errp) == 0) {
|
||||
*be = s; /* weak reference, avoid cyclic ref */
|
||||
}
|
||||
g_free(str);
|
||||
}
|
||||
|
||||
static void release_tpm(Object *obj, const char *name, void *opaque)
|
||||
{
|
||||
DeviceState *dev = DEVICE(obj);
|
||||
Property *prop = opaque;
|
||||
TPMBackend **be = qdev_get_prop_ptr(dev, prop);
|
||||
|
||||
if (*be) {
|
||||
tpm_backend_reset(*be);
|
||||
}
|
||||
}
|
||||
|
||||
const PropertyInfo qdev_prop_tpm = {
|
||||
.name = "str",
|
||||
.description = "ID of a tpm to use as a backend",
|
||||
.get = get_tpm,
|
||||
.set = set_tpm,
|
||||
.release = release_tpm,
|
||||
};
|
||||
|
||||
/* --- netdev device --- */
|
||||
static void get_netdev(Object *obj, Visitor *v, const char *name,
|
||||
void *opaque, Error **errp)
|
||||
|
@ -208,7 +208,7 @@ static void acpi_get_misc_info(AcpiMiscInfo *info)
|
||||
}
|
||||
|
||||
info->has_hpet = hpet_find();
|
||||
info->tpm_version = tpm_get_version();
|
||||
info->tpm_version = tpm_get_version(tpm_find());
|
||||
info->pvpanic_port = pvpanic_port();
|
||||
info->applesmc_io_base = applesmc_port();
|
||||
}
|
||||
@ -2038,7 +2038,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
|
||||
}
|
||||
}
|
||||
|
||||
if (misc->tpm_version != TPM_VERSION_UNSPEC) {
|
||||
if (TPM_IS_TIS(tpm_find())) {
|
||||
aml_append(crs, aml_memory32_fixed(TPM_TIS_ADDR_BASE,
|
||||
TPM_TIS_ADDR_SIZE, AML_READ_WRITE));
|
||||
}
|
||||
@ -2204,7 +2204,7 @@ build_dsdt(GArray *table_data, BIOSLinker *linker,
|
||||
/* Scan all PCI buses. Generate tables to support hotplug. */
|
||||
build_append_pci_bus_devices(scope, bus, pm->pcihp_bridge_en);
|
||||
|
||||
if (misc->tpm_version != TPM_VERSION_UNSPEC) {
|
||||
if (TPM_IS_TIS(tpm_find())) {
|
||||
dev = aml_device("ISA.TPM");
|
||||
aml_append(dev, aml_name_decl("_HID", aml_eisaid("PNP0C31")));
|
||||
aml_append(dev, aml_name_decl("_STA", aml_int(0xF)));
|
||||
@ -2281,8 +2281,12 @@ build_tpm2(GArray *table_data, BIOSLinker *linker)
|
||||
tpm2_ptr = acpi_data_push(table_data, sizeof *tpm2_ptr);
|
||||
|
||||
tpm2_ptr->platform_class = cpu_to_le16(TPM2_ACPI_CLASS_CLIENT);
|
||||
tpm2_ptr->control_area_address = cpu_to_le64(0);
|
||||
tpm2_ptr->start_method = cpu_to_le32(TPM2_START_METHOD_MMIO);
|
||||
if (TPM_IS_TIS(tpm_find())) {
|
||||
tpm2_ptr->control_area_address = cpu_to_le64(0);
|
||||
tpm2_ptr->start_method = cpu_to_le32(TPM2_START_METHOD_MMIO);
|
||||
} else {
|
||||
g_warn_if_reached();
|
||||
}
|
||||
|
||||
build_header(linker, table_data,
|
||||
(void *)tpm2_ptr, "TPM2", sizeof(*tpm2_ptr), 4, NULL, NULL);
|
||||
|
@ -186,7 +186,6 @@ static int tpm_emulator_set_locality(TPMEmulator *tpm_emu, uint8_t locty_number,
|
||||
static void tpm_emulator_handle_request(TPMBackend *tb, TPMBackendCmd *cmd)
|
||||
{
|
||||
TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
|
||||
TPMIfClass *tic = TPM_IF_GET_CLASS(tb->tpm_state);
|
||||
Error *err = NULL;
|
||||
|
||||
DPRINTF("processing TPM command");
|
||||
@ -201,7 +200,6 @@ static void tpm_emulator_handle_request(TPMBackend *tb, TPMBackendCmd *cmd)
|
||||
goto error;
|
||||
}
|
||||
|
||||
tic->request_completed(TPM_IF(tb->tpm_state));
|
||||
return;
|
||||
|
||||
error:
|
||||
@ -234,13 +232,14 @@ static int tpm_emulator_check_caps(TPMEmulator *tpm_emu)
|
||||
switch (tpm_emu->tpm_version) {
|
||||
case TPM_VERSION_1_2:
|
||||
caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN | PTM_CAP_GET_TPMESTABLISHED |
|
||||
PTM_CAP_SET_LOCALITY | PTM_CAP_SET_DATAFD;
|
||||
PTM_CAP_SET_LOCALITY | PTM_CAP_SET_DATAFD | PTM_CAP_STOP |
|
||||
PTM_CAP_SET_BUFFERSIZE;
|
||||
tpm = "1.2";
|
||||
break;
|
||||
case TPM_VERSION_2_0:
|
||||
caps = PTM_CAP_INIT | PTM_CAP_SHUTDOWN | PTM_CAP_GET_TPMESTABLISHED |
|
||||
PTM_CAP_SET_LOCALITY | PTM_CAP_RESET_TPMESTABLISHED |
|
||||
PTM_CAP_SET_DATAFD;
|
||||
PTM_CAP_SET_DATAFD | PTM_CAP_STOP | PTM_CAP_SET_BUFFERSIZE;
|
||||
tpm = "2";
|
||||
break;
|
||||
case TPM_VERSION_UNSPEC:
|
||||
@ -257,12 +256,76 @@ static int tpm_emulator_check_caps(TPMEmulator *tpm_emu)
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpm_emulator_startup_tpm(TPMBackend *tb)
|
||||
static int tpm_emulator_stop_tpm(TPMBackend *tb)
|
||||
{
|
||||
TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
|
||||
ptm_res res;
|
||||
|
||||
if (tpm_emulator_ctrlcmd(tpm_emu, CMD_STOP, &res, 0, sizeof(res)) < 0) {
|
||||
error_report("tpm-emulator: Could not stop TPM: %s",
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = be32_to_cpu(res);
|
||||
if (res) {
|
||||
error_report("tpm-emulator: TPM result for CMD_STOP: 0x%x", res);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpm_emulator_set_buffer_size(TPMBackend *tb,
|
||||
size_t wanted_size,
|
||||
size_t *actual_size)
|
||||
{
|
||||
TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
|
||||
ptm_setbuffersize psbs;
|
||||
|
||||
if (tpm_emulator_stop_tpm(tb) < 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
psbs.u.req.buffersize = cpu_to_be32(wanted_size);
|
||||
|
||||
if (tpm_emulator_ctrlcmd(tpm_emu, CMD_SET_BUFFERSIZE, &psbs,
|
||||
sizeof(psbs.u.req), sizeof(psbs.u.resp)) < 0) {
|
||||
error_report("tpm-emulator: Could not set buffer size: %s",
|
||||
strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
|
||||
psbs.u.resp.tpm_result = be32_to_cpu(psbs.u.resp.tpm_result);
|
||||
if (psbs.u.resp.tpm_result != 0) {
|
||||
error_report("tpm-emulator: TPM result for set buffer size : 0x%x",
|
||||
psbs.u.resp.tpm_result);
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (actual_size) {
|
||||
*actual_size = be32_to_cpu(psbs.u.resp.buffersize);
|
||||
}
|
||||
|
||||
DPRINTF("buffer size: %u, min: %u, max: %u\n",
|
||||
be32_to_cpu(psbs.u.resp.buffersize),
|
||||
be32_to_cpu(psbs.u.resp.minsize),
|
||||
be32_to_cpu(psbs.u.resp.maxsize));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int tpm_emulator_startup_tpm(TPMBackend *tb, size_t buffersize)
|
||||
{
|
||||
TPMEmulator *tpm_emu = TPM_EMULATOR(tb);
|
||||
ptm_init init;
|
||||
ptm_res res;
|
||||
|
||||
if (buffersize != 0 &&
|
||||
tpm_emulator_set_buffer_size(tb, buffersize, NULL) < 0) {
|
||||
goto err_exit;
|
||||
}
|
||||
|
||||
DPRINTF("%s", __func__);
|
||||
if (tpm_emulator_ctrlcmd(tpm_emu, CMD_INIT, &init, sizeof(init),
|
||||
sizeof(init)) < 0) {
|
||||
@ -340,6 +403,7 @@ static void tpm_emulator_cancel_cmd(TPMBackend *tb)
|
||||
return;
|
||||
}
|
||||
|
||||
/* FIXME: make the function non-blocking, or it may block a VCPU */
|
||||
if (tpm_emulator_ctrlcmd(tpm_emu, CMD_CANCEL_TPM_CMD, &res, 0,
|
||||
sizeof(res)) < 0) {
|
||||
error_report("tpm-emulator: Could not cancel command: %s",
|
||||
@ -357,6 +421,17 @@ static TPMVersion tpm_emulator_get_tpm_version(TPMBackend *tb)
|
||||
return tpm_emu->tpm_version;
|
||||
}
|
||||
|
||||
static size_t tpm_emulator_get_buffer_size(TPMBackend *tb)
|
||||
{
|
||||
size_t actual_size;
|
||||
|
||||
if (tpm_emulator_set_buffer_size(tb, 0, &actual_size) < 0) {
|
||||
return 4096;
|
||||
}
|
||||
|
||||
return actual_size;
|
||||
}
|
||||
|
||||
static int tpm_emulator_block_migration(TPMEmulator *tpm_emu)
|
||||
{
|
||||
Error *err = NULL;
|
||||
@ -465,22 +540,16 @@ err:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static TPMBackend *tpm_emulator_create(QemuOpts *opts, const char *id)
|
||||
static TPMBackend *tpm_emulator_create(QemuOpts *opts)
|
||||
{
|
||||
TPMBackend *tb = TPM_BACKEND(object_new(TYPE_TPM_EMULATOR));
|
||||
|
||||
tb->id = g_strdup(id);
|
||||
|
||||
if (tpm_emulator_handle_device_opts(TPM_EMULATOR(tb), opts)) {
|
||||
goto err_exit;
|
||||
object_unref(OBJECT(tb));
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return tb;
|
||||
|
||||
err_exit:
|
||||
object_unref(OBJECT(tb));
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static TpmTypeOptions *tpm_emulator_get_tpm_options(TPMBackend *tb)
|
||||
@ -563,6 +632,7 @@ static void tpm_emulator_class_init(ObjectClass *klass, void *data)
|
||||
tbc->get_tpm_established_flag = tpm_emulator_get_tpm_established_flag;
|
||||
tbc->reset_tpm_established_flag = tpm_emulator_reset_tpm_established_flag;
|
||||
tbc->get_tpm_version = tpm_emulator_get_tpm_version;
|
||||
tbc->get_buffer_size = tpm_emulator_get_buffer_size;
|
||||
tbc->get_tpm_options = tpm_emulator_get_tpm_options;
|
||||
|
||||
tbc->handle_request = tpm_emulator_handle_request;
|
||||
|
@ -13,28 +13,8 @@
|
||||
#define TPM_TPM_INT_H
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
#define TYPE_TPM_IF "tpm-if"
|
||||
#define TPM_IF_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(TPMIfClass, (klass), TYPE_TPM_IF)
|
||||
#define TPM_IF_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(TPMIfClass, (obj), TYPE_TPM_IF)
|
||||
#define TPM_IF(obj) \
|
||||
INTERFACE_CHECK(TPMIf, (obj), TYPE_TPM_IF)
|
||||
|
||||
typedef struct TPMIf {
|
||||
Object parent_obj;
|
||||
} TPMIf;
|
||||
|
||||
typedef struct TPMIfClass {
|
||||
InterfaceClass parent_class;
|
||||
|
||||
/* run in thread pool by backend */
|
||||
void (*request_completed)(TPMIf *obj);
|
||||
} TPMIfClass;
|
||||
|
||||
#define TPM_STANDARD_CMDLINE_OPTS \
|
||||
#define TPM_STANDARD_CMDLINE_OPTS \
|
||||
{ \
|
||||
.name = "type", \
|
||||
.type = QEMU_OPT_STRING, \
|
||||
@ -65,11 +45,20 @@ struct tpm_resp_hdr {
|
||||
|
||||
#define TPM_ORD_ContinueSelfTest 0x53
|
||||
#define TPM_ORD_GetTicks 0xf1
|
||||
#define TPM_ORD_GetCapability 0x65
|
||||
|
||||
#define TPM_CAP_PROPERTY 0x05
|
||||
|
||||
#define TPM_CAP_PROP_INPUT_BUFFER 0x124
|
||||
|
||||
/* TPM2 defines */
|
||||
#define TPM2_ST_NO_SESSIONS 0x8001
|
||||
|
||||
#define TPM2_CC_ReadClock 0x00000181
|
||||
#define TPM2_CC_GetCapability 0x0000017a
|
||||
|
||||
#define TPM2_CAP_TPM_PROPERTIES 0x6
|
||||
|
||||
#define TPM2_PT_MAX_COMMAND_SIZE 0x11e
|
||||
|
||||
#endif /* TPM_TPM_INT_H */
|
||||
|
@ -169,6 +169,28 @@ struct ptm_getconfig {
|
||||
#define PTM_CONFIG_FLAG_FILE_KEY 0x1
|
||||
#define PTM_CONFIG_FLAG_MIGRATION_KEY 0x2
|
||||
|
||||
/*
|
||||
* PTM_SET_BUFFERSIZE: Set the buffer size to be used by the TPM.
|
||||
* A 0 on input queries for the current buffer size. Any other
|
||||
* number will try to set the buffer size. The returned number is
|
||||
* the buffer size that will be used, which can be larger than the
|
||||
* requested one, if it was below the minimum, or smaller than the
|
||||
* requested one, if it was above the maximum.
|
||||
*/
|
||||
struct ptm_setbuffersize {
|
||||
union {
|
||||
struct {
|
||||
uint32_t buffersize; /* 0 to query for current buffer size */
|
||||
} req; /* request */
|
||||
struct {
|
||||
ptm_res tpm_result;
|
||||
uint32_t buffersize; /* buffer size in use */
|
||||
uint32_t minsize; /* min. supported buffer size */
|
||||
uint32_t maxsize; /* max. supported buffer size */
|
||||
} resp; /* response */
|
||||
} u;
|
||||
};
|
||||
|
||||
|
||||
typedef uint64_t ptm_cap;
|
||||
typedef struct ptm_est ptm_est;
|
||||
@ -179,6 +201,7 @@ typedef struct ptm_init ptm_init;
|
||||
typedef struct ptm_getstate ptm_getstate;
|
||||
typedef struct ptm_setstate ptm_setstate;
|
||||
typedef struct ptm_getconfig ptm_getconfig;
|
||||
typedef struct ptm_setbuffersize ptm_setbuffersize;
|
||||
|
||||
/* capability flags returned by PTM_GET_CAPABILITY */
|
||||
#define PTM_CAP_INIT (1)
|
||||
@ -194,6 +217,7 @@ typedef struct ptm_getconfig ptm_getconfig;
|
||||
#define PTM_CAP_STOP (1 << 10)
|
||||
#define PTM_CAP_GET_CONFIG (1 << 11)
|
||||
#define PTM_CAP_SET_DATAFD (1 << 12)
|
||||
#define PTM_CAP_SET_BUFFERSIZE (1 << 13)
|
||||
|
||||
enum {
|
||||
PTM_GET_CAPABILITY = _IOR('P', 0, ptm_cap),
|
||||
@ -212,6 +236,7 @@ enum {
|
||||
PTM_STOP = _IOR('P', 13, ptm_res),
|
||||
PTM_GET_CONFIG = _IOR('P', 14, ptm_getconfig),
|
||||
PTM_SET_DATAFD = _IOR('P', 15, ptm_res),
|
||||
PTM_SET_BUFFERSIZE = _IOWR('P', 16, ptm_setbuffersize),
|
||||
};
|
||||
|
||||
/*
|
||||
@ -240,7 +265,8 @@ enum {
|
||||
CMD_SET_STATEBLOB,
|
||||
CMD_STOP,
|
||||
CMD_GET_CONFIG,
|
||||
CMD_SET_DATAFD
|
||||
CMD_SET_DATAFD,
|
||||
CMD_SET_BUFFERSIZE,
|
||||
};
|
||||
|
||||
#endif /* _TPM_IOCTL_H */
|
||||
|
@ -57,6 +57,7 @@ struct TPMPassthruState {
|
||||
int cancel_fd;
|
||||
|
||||
TPMVersion tpm_version;
|
||||
size_t tpm_buffersize;
|
||||
};
|
||||
|
||||
typedef struct TPMPassthruState TPMPassthruState;
|
||||
@ -89,6 +90,7 @@ static int tpm_passthrough_unix_tx_bufs(TPMPassthruState *tpm_pt,
|
||||
bool is_selftest;
|
||||
const struct tpm_resp_hdr *hdr;
|
||||
|
||||
/* FIXME: protect shared variables or use other sync mechanism */
|
||||
tpm_pt->tpm_op_canceled = false;
|
||||
tpm_pt->tpm_executing = true;
|
||||
*selftest_done = false;
|
||||
@ -139,14 +141,11 @@ err_exit:
|
||||
static void tpm_passthrough_handle_request(TPMBackend *tb, TPMBackendCmd *cmd)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
TPMIfClass *tic = TPM_IF_GET_CLASS(tb->tpm_state);
|
||||
|
||||
DPRINTF("tpm_passthrough: processing command %p\n", cmd);
|
||||
|
||||
tpm_passthrough_unix_tx_bufs(tpm_pt, cmd->in, cmd->in_len,
|
||||
cmd->out, cmd->out_len, &cmd->selftest_done);
|
||||
|
||||
tic->request_completed(TPM_IF(tb->tpm_state));
|
||||
}
|
||||
|
||||
static void tpm_passthrough_reset(TPMBackend *tb)
|
||||
@ -181,12 +180,11 @@ static void tpm_passthrough_cancel_cmd(TPMBackend *tb)
|
||||
*/
|
||||
if (tpm_pt->tpm_executing) {
|
||||
if (tpm_pt->cancel_fd >= 0) {
|
||||
tpm_pt->tpm_op_canceled = true;
|
||||
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 "
|
||||
@ -202,6 +200,19 @@ static TPMVersion tpm_passthrough_get_tpm_version(TPMBackend *tb)
|
||||
return tpm_pt->tpm_version;
|
||||
}
|
||||
|
||||
static size_t tpm_passthrough_get_buffer_size(TPMBackend *tb)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
int ret;
|
||||
|
||||
ret = tpm_util_get_buffer_size(tpm_pt->tpm_fd, tpm_pt->tpm_version,
|
||||
&tpm_pt->tpm_buffersize);
|
||||
if (ret < 0) {
|
||||
tpm_pt->tpm_buffersize = 4096;
|
||||
}
|
||||
return tpm_pt->tpm_buffersize;
|
||||
}
|
||||
|
||||
/*
|
||||
* Unless path or file descriptor set has been provided by user,
|
||||
* determine the sysfs cancel file following kernel documentation
|
||||
@ -229,9 +240,7 @@ static int tpm_passthrough_open_sysfs_cancel(TPMPassthruState *tpm_pt)
|
||||
if (snprintf(path, sizeof(path), "/sys/class/misc/%s/device/cancel",
|
||||
dev) < sizeof(path)) {
|
||||
fd = qemu_open(path, O_WRONLY);
|
||||
if (fd >= 0) {
|
||||
tpm_pt->options->cancel_path = g_strdup(path);
|
||||
} else {
|
||||
if (fd < 0) {
|
||||
error_report("tpm_passthrough: Could not open TPM cancel "
|
||||
"path %s : %s", path, strerror(errno));
|
||||
}
|
||||
@ -244,9 +253,9 @@ static int tpm_passthrough_open_sysfs_cancel(TPMPassthruState *tpm_pt)
|
||||
return fd;
|
||||
}
|
||||
|
||||
static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
|
||||
static int
|
||||
tpm_passthrough_handle_device_opts(TPMPassthruState *tpm_pt, QemuOpts *opts)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
const char *value;
|
||||
|
||||
value = qemu_opt_get(opts, "cancel-path");
|
||||
@ -266,52 +275,47 @@ static int tpm_passthrough_handle_device_opts(QemuOpts *opts, TPMBackend *tb)
|
||||
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;
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (tpm_util_test_tpmdev(tpm_pt->tpm_fd, &tpm_pt->tpm_version)) {
|
||||
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:
|
||||
qapi_free_TPMPassthroughOptions(tpm_pt->options);
|
||||
tpm_pt->options = NULL;
|
||||
tpm_pt->tpm_dev = NULL;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
if (tpm_passthrough_handle_device_opts(opts, tb)) {
|
||||
goto err_exit;
|
||||
return -1;
|
||||
}
|
||||
|
||||
tpm_pt->cancel_fd = tpm_passthrough_open_sysfs_cancel(tpm_pt);
|
||||
if (tpm_pt->cancel_fd < 0) {
|
||||
goto err_exit;
|
||||
return -1;
|
||||
}
|
||||
|
||||
return tb;
|
||||
return 0;
|
||||
}
|
||||
|
||||
err_exit:
|
||||
object_unref(obj);
|
||||
static TPMBackend *tpm_passthrough_create(QemuOpts *opts)
|
||||
{
|
||||
Object *obj = object_new(TYPE_TPM_PASSTHROUGH);
|
||||
|
||||
return NULL;
|
||||
if (tpm_passthrough_handle_device_opts(TPM_PASSTHROUGH(obj), opts)) {
|
||||
object_unref(obj);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return TPM_BACKEND(obj);
|
||||
}
|
||||
|
||||
static int tpm_passthrough_startup_tpm(TPMBackend *tb, size_t buffersize)
|
||||
{
|
||||
TPMPassthruState *tpm_pt = TPM_PASSTHROUGH(tb);
|
||||
|
||||
if (buffersize && buffersize < tpm_pt->tpm_buffersize) {
|
||||
error_report("Requested buffer size of %zu is smaller than host TPM's "
|
||||
"fixed buffer size of %zu",
|
||||
buffersize, tpm_pt->tpm_buffersize);
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static TpmTypeOptions *tpm_passthrough_get_tpm_options(TPMBackend *tb)
|
||||
@ -355,8 +359,12 @@ static void tpm_passthrough_inst_finalize(Object *obj)
|
||||
|
||||
tpm_passthrough_cancel_cmd(TPM_BACKEND(obj));
|
||||
|
||||
qemu_close(tpm_pt->tpm_fd);
|
||||
qemu_close(tpm_pt->cancel_fd);
|
||||
if (tpm_pt->tpm_fd >= 0) {
|
||||
qemu_close(tpm_pt->tpm_fd);
|
||||
}
|
||||
if (tpm_pt->cancel_fd >= 0) {
|
||||
qemu_close(tpm_pt->cancel_fd);
|
||||
}
|
||||
qapi_free_TPMPassthroughOptions(tpm_pt->options);
|
||||
}
|
||||
|
||||
@ -368,12 +376,14 @@ static void tpm_passthrough_class_init(ObjectClass *klass, void *data)
|
||||
tbc->opts = tpm_passthrough_cmdline_opts;
|
||||
tbc->desc = "Passthrough TPM backend driver";
|
||||
tbc->create = tpm_passthrough_create;
|
||||
tbc->startup_tpm = tpm_passthrough_startup_tpm;
|
||||
tbc->reset = tpm_passthrough_reset;
|
||||
tbc->cancel_cmd = tpm_passthrough_cancel_cmd;
|
||||
tbc->get_tpm_established_flag = tpm_passthrough_get_tpm_established_flag;
|
||||
tbc->reset_tpm_established_flag =
|
||||
tpm_passthrough_reset_tpm_established_flag;
|
||||
tbc->get_tpm_version = tpm_passthrough_get_tpm_version;
|
||||
tbc->get_buffer_size = tpm_passthrough_get_buffer_size;
|
||||
tbc->get_tpm_options = tpm_passthrough_get_tpm_options;
|
||||
tbc->handle_request = tpm_passthrough_handle_request;
|
||||
}
|
||||
|
105
hw/tpm/tpm_tis.c
105
hw/tpm/tpm_tis.c
@ -24,17 +24,13 @@
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "hw/isa/isa.h"
|
||||
#include "qapi/error.h"
|
||||
|
||||
#include "hw/acpi/tpm.h"
|
||||
#include "hw/pci/pci_ids.h"
|
||||
#include "sysemu/tpm_backend.h"
|
||||
#include "tpm_int.h"
|
||||
#include "sysemu/block-backend.h"
|
||||
#include "exec/address-spaces.h"
|
||||
#include "hw/hw.h"
|
||||
#include "hw/i386/pc.h"
|
||||
#include "hw/pci/pci_ids.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu-common.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "hw/acpi/tpm.h"
|
||||
#include "tpm_util.h"
|
||||
|
||||
#define TPM_TIS_NUM_LOCALITIES 5 /* per spec */
|
||||
#define TPM_TIS_LOCALITY_SHIFT 12
|
||||
@ -72,11 +68,10 @@ typedef struct TPMLocality {
|
||||
TPMSizedBuffer r_buffer;
|
||||
} TPMLocality;
|
||||
|
||||
struct TPMState {
|
||||
typedef struct TPMState {
|
||||
ISADevice busdev;
|
||||
MemoryRegion mmio;
|
||||
|
||||
QEMUBH *bh;
|
||||
uint32_t offset;
|
||||
uint8_t buf[TPM_TIS_BUFFER_MAX];
|
||||
|
||||
@ -89,13 +84,13 @@ struct TPMState {
|
||||
qemu_irq irq;
|
||||
uint32_t irq_num;
|
||||
|
||||
uint8_t locty_number;
|
||||
TPMBackendCmd cmd;
|
||||
|
||||
char *backend;
|
||||
TPMBackend *be_driver;
|
||||
TPMVersion be_tpm_version;
|
||||
};
|
||||
|
||||
size_t be_buffer_size;
|
||||
} TPMState;
|
||||
|
||||
#define TPM(obj) OBJECT_CHECK(TPMState, (obj), TYPE_TPM_TIS)
|
||||
|
||||
@ -222,7 +217,7 @@ static uint8_t tpm_tis_locality_from_addr(hwaddr addr)
|
||||
|
||||
static uint32_t tpm_tis_get_size_from_buffer(const TPMSizedBuffer *sb)
|
||||
{
|
||||
return be32_to_cpu(*(uint32_t *)&sb->buffer[2]);
|
||||
return tpm_cmd_get_size(sb->buffer);
|
||||
}
|
||||
|
||||
static void tpm_tis_show_buffer(const TPMSizedBuffer *sb, const char *string)
|
||||
@ -411,10 +406,20 @@ static void tpm_tis_prep_abort(TPMState *s, uint8_t locty, uint8_t newlocty)
|
||||
tpm_tis_abort(s, locty);
|
||||
}
|
||||
|
||||
static void tpm_tis_receive_bh(void *opaque)
|
||||
/*
|
||||
* Callback from the TPM to indicate that the response was received.
|
||||
*/
|
||||
static void tpm_tis_request_completed(TPMIf *ti)
|
||||
{
|
||||
TPMState *s = opaque;
|
||||
TPMState *s = TPM(ti);
|
||||
uint8_t locty = s->cmd.locty;
|
||||
uint8_t l;
|
||||
|
||||
if (s->cmd.selftest_done) {
|
||||
for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
|
||||
s->loc[locty].sts |= TPM_TIS_STS_SELFTEST_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
tpm_tis_sts_set(&s->loc[locty],
|
||||
TPM_TIS_STS_VALID | TPM_TIS_STS_DATA_AVAILABLE);
|
||||
@ -432,23 +437,6 @@ static void tpm_tis_receive_bh(void *opaque)
|
||||
TPM_TIS_INT_DATA_AVAILABLE | TPM_TIS_INT_STS_VALID);
|
||||
}
|
||||
|
||||
static void tpm_tis_request_completed(TPMIf *ti)
|
||||
{
|
||||
TPMState *s = TPM(ti);
|
||||
|
||||
bool is_selftest_done = s->cmd.selftest_done;
|
||||
uint8_t locty = s->cmd.locty;
|
||||
uint8_t l;
|
||||
|
||||
if (is_selftest_done) {
|
||||
for (l = 0; l < TPM_TIS_NUM_LOCALITIES; l++) {
|
||||
s->loc[locty].sts |= TPM_TIS_STS_SELFTEST_DONE;
|
||||
}
|
||||
}
|
||||
|
||||
qemu_bh_schedule(s->bh);
|
||||
}
|
||||
|
||||
/*
|
||||
* Read a byte of response data
|
||||
*/
|
||||
@ -986,15 +974,14 @@ static const MemoryRegionOps tpm_tis_memory_ops = {
|
||||
},
|
||||
};
|
||||
|
||||
static int tpm_tis_do_startup_tpm(TPMState *s)
|
||||
static int tpm_tis_do_startup_tpm(TPMState *s, uint32_t buffersize)
|
||||
{
|
||||
return tpm_backend_startup_tpm(s->be_driver);
|
||||
return tpm_backend_startup_tpm(s->be_driver, buffersize);
|
||||
}
|
||||
|
||||
static void tpm_tis_realloc_buffer(TPMSizedBuffer *sb)
|
||||
static void tpm_tis_realloc_buffer(TPMSizedBuffer *sb,
|
||||
size_t wanted_size)
|
||||
{
|
||||
size_t wanted_size = 4096; /* Linux tpm.c buffer size */
|
||||
|
||||
if (sb->size != wanted_size) {
|
||||
sb->buffer = g_realloc(sb->buffer, wanted_size);
|
||||
sb->size = wanted_size;
|
||||
@ -1004,9 +991,9 @@ static void tpm_tis_realloc_buffer(TPMSizedBuffer *sb)
|
||||
/*
|
||||
* Get the TPMVersion of the backend device being used
|
||||
*/
|
||||
TPMVersion tpm_tis_get_tpm_version(Object *obj)
|
||||
static enum TPMVersion tpm_tis_get_tpm_version(TPMIf *ti)
|
||||
{
|
||||
TPMState *s = TPM(obj);
|
||||
TPMState *s = TPM(ti);
|
||||
|
||||
if (tpm_backend_had_startup_error(s->be_driver)) {
|
||||
return TPM_VERSION_UNSPEC;
|
||||
@ -1025,6 +1012,7 @@ static void tpm_tis_reset(DeviceState *dev)
|
||||
int c;
|
||||
|
||||
s->be_tpm_version = tpm_backend_get_tpm_version(s->be_driver);
|
||||
s->be_buffer_size = tpm_backend_get_buffer_size(s->be_driver);
|
||||
|
||||
tpm_backend_reset(s->be_driver);
|
||||
|
||||
@ -1051,12 +1039,12 @@ static void tpm_tis_reset(DeviceState *dev)
|
||||
s->loc[c].state = TPM_TIS_STATE_IDLE;
|
||||
|
||||
s->loc[c].w_offset = 0;
|
||||
tpm_tis_realloc_buffer(&s->loc[c].w_buffer);
|
||||
tpm_tis_realloc_buffer(&s->loc[c].w_buffer, s->be_buffer_size);
|
||||
s->loc[c].r_offset = 0;
|
||||
tpm_tis_realloc_buffer(&s->loc[c].r_buffer);
|
||||
tpm_tis_realloc_buffer(&s->loc[c].r_buffer, s->be_buffer_size);
|
||||
}
|
||||
|
||||
tpm_tis_do_startup_tpm(s);
|
||||
tpm_tis_do_startup_tpm(s, 0);
|
||||
}
|
||||
|
||||
static const VMStateDescription vmstate_tpm_tis = {
|
||||
@ -1066,7 +1054,7 @@ static const VMStateDescription vmstate_tpm_tis = {
|
||||
|
||||
static Property tpm_tis_properties[] = {
|
||||
DEFINE_PROP_UINT32("irq", TPMState, irq_num, TPM_TIS_IRQ),
|
||||
DEFINE_PROP_STRING("tpmdev", TPMState, backend),
|
||||
DEFINE_PROP_TPMBE("tpmdev", TPMState, be_driver),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
@ -1074,29 +1062,21 @@ static void tpm_tis_realizefn(DeviceState *dev, Error **errp)
|
||||
{
|
||||
TPMState *s = TPM(dev);
|
||||
|
||||
s->be_driver = qemu_find_tpm(s->backend);
|
||||
if (!tpm_find()) {
|
||||
error_setg(errp, "at most one TPM device is permitted");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!s->be_driver) {
|
||||
error_setg(errp, "tpm_tis: backend driver with id %s could not be "
|
||||
"found", s->backend);
|
||||
error_setg(errp, "'tpmdev' property is required");
|
||||
return;
|
||||
}
|
||||
|
||||
s->be_driver->fe_model = TPM_MODEL_TPM_TIS;
|
||||
|
||||
if (tpm_backend_init(s->be_driver, s)) {
|
||||
error_setg(errp, "tpm_tis: backend driver with id %s could not be "
|
||||
"initialized", s->backend);
|
||||
return;
|
||||
}
|
||||
|
||||
if (s->irq_num > 15) {
|
||||
error_setg(errp, "tpm_tis: IRQ %d for TPM TIS is outside valid range "
|
||||
"of 0 to 15", s->irq_num);
|
||||
error_setg(errp, "IRQ %d is outside valid range of 0 to 15",
|
||||
s->irq_num);
|
||||
return;
|
||||
}
|
||||
|
||||
s->bh = qemu_bh_new(tpm_tis_receive_bh, s);
|
||||
|
||||
isa_init_irq(&s->busdev, &s->irq, s->irq_num);
|
||||
|
||||
memory_region_add_subregion(isa_address_space(ISA_DEVICE(dev)),
|
||||
@ -1121,6 +1101,8 @@ static void tpm_tis_class_init(ObjectClass *klass, void *data)
|
||||
dc->props = tpm_tis_properties;
|
||||
dc->reset = tpm_tis_reset;
|
||||
dc->vmsd = &vmstate_tpm_tis;
|
||||
tc->model = TPM_MODEL_TPM_TIS;
|
||||
tc->get_version = tpm_tis_get_tpm_version;
|
||||
tc->request_completed = tpm_tis_request_completed;
|
||||
}
|
||||
|
||||
@ -1139,7 +1121,6 @@ static const TypeInfo tpm_tis_info = {
|
||||
static void tpm_tis_register(void)
|
||||
{
|
||||
type_register_static(&tpm_tis_info);
|
||||
tpm_register_model(TPM_MODEL_TPM_TIS);
|
||||
}
|
||||
|
||||
type_init(tpm_tis_register)
|
||||
|
@ -20,10 +20,19 @@
|
||||
*/
|
||||
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "tpm_util.h"
|
||||
#include "tpm_int.h"
|
||||
#include "exec/memory.h"
|
||||
|
||||
#define DEBUG_TPM 0
|
||||
|
||||
#define DPRINTF(fmt, ...) do { \
|
||||
if (DEBUG_TPM) { \
|
||||
fprintf(stderr, "tpm-util:"fmt"\n", ## __VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
/*
|
||||
* Write an error message in the given output buffer.
|
||||
*/
|
||||
@ -50,13 +59,13 @@ bool tpm_util_is_selftest(const uint8_t *in, uint32_t in_len)
|
||||
}
|
||||
|
||||
/*
|
||||
* A basic test of a TPM device. We expect a well formatted response header
|
||||
* (error response is fine) within one second.
|
||||
* Send request to a TPM device. We expect a response within one second.
|
||||
*/
|
||||
static int tpm_util_test(int fd,
|
||||
unsigned char *request,
|
||||
size_t requestlen,
|
||||
uint16_t *return_tag)
|
||||
static int tpm_util_request(int fd,
|
||||
unsigned char *request,
|
||||
size_t requestlen,
|
||||
unsigned char *response,
|
||||
size_t responselen)
|
||||
{
|
||||
struct tpm_resp_hdr *resp;
|
||||
fd_set readfds;
|
||||
@ -65,7 +74,6 @@ static int tpm_util_test(int fd,
|
||||
.tv_sec = 1,
|
||||
.tv_usec = 0,
|
||||
};
|
||||
unsigned char buf[1024];
|
||||
|
||||
n = write(fd, request, requestlen);
|
||||
if (n < 0) {
|
||||
@ -84,17 +92,40 @@ static int tpm_util_test(int fd,
|
||||
return -errno;
|
||||
}
|
||||
|
||||
n = read(fd, &buf, sizeof(buf));
|
||||
n = read(fd, response, responselen);
|
||||
if (n < sizeof(struct tpm_resp_hdr)) {
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
resp = (struct tpm_resp_hdr *)buf;
|
||||
resp = (struct tpm_resp_hdr *)response;
|
||||
/* check the header */
|
||||
if (be32_to_cpu(resp->len) != n) {
|
||||
return -EMSGSIZE;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* A basic test of a TPM device. We expect a well formatted response header
|
||||
* (error response is fine).
|
||||
*/
|
||||
static int tpm_util_test(int fd,
|
||||
unsigned char *request,
|
||||
size_t requestlen,
|
||||
uint16_t *return_tag)
|
||||
{
|
||||
struct tpm_resp_hdr *resp;
|
||||
unsigned char buf[1024];
|
||||
ssize_t ret;
|
||||
|
||||
ret = tpm_util_request(fd, request, requestlen,
|
||||
buf, sizeof(buf));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
resp = (struct tpm_resp_hdr *)buf;
|
||||
*return_tag = be16_to_cpu(resp->tag);
|
||||
|
||||
return 0;
|
||||
@ -151,3 +182,109 @@ int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version)
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
int tpm_util_get_buffer_size(int tpm_fd, TPMVersion tpm_version,
|
||||
size_t *buffersize)
|
||||
{
|
||||
unsigned char buf[1024];
|
||||
int ret;
|
||||
|
||||
switch (tpm_version) {
|
||||
case TPM_VERSION_1_2: {
|
||||
const struct tpm_req_get_buffer_size {
|
||||
struct tpm_req_hdr hdr;
|
||||
uint32_t capability;
|
||||
uint32_t len;
|
||||
uint32_t subcap;
|
||||
} QEMU_PACKED tpm_get_buffer_size = {
|
||||
.hdr = {
|
||||
.tag = cpu_to_be16(TPM_TAG_RQU_COMMAND),
|
||||
.len = cpu_to_be32(sizeof(tpm_get_buffer_size)),
|
||||
.ordinal = cpu_to_be32(TPM_ORD_GetCapability),
|
||||
},
|
||||
.capability = cpu_to_be32(TPM_CAP_PROPERTY),
|
||||
.len = cpu_to_be32(sizeof(uint32_t)),
|
||||
.subcap = cpu_to_be32(TPM_CAP_PROP_INPUT_BUFFER),
|
||||
};
|
||||
struct tpm_resp_get_buffer_size {
|
||||
struct tpm_resp_hdr hdr;
|
||||
uint32_t len;
|
||||
uint32_t buffersize;
|
||||
} QEMU_PACKED *tpm_resp = (struct tpm_resp_get_buffer_size *)buf;
|
||||
|
||||
ret = tpm_util_request(tpm_fd, (unsigned char *)&tpm_get_buffer_size,
|
||||
sizeof(tpm_get_buffer_size), buf, sizeof(buf));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (be32_to_cpu(tpm_resp->hdr.len) != sizeof(*tpm_resp) ||
|
||||
be32_to_cpu(tpm_resp->len) != sizeof(uint32_t)) {
|
||||
DPRINTF("tpm_resp->hdr.len = %u, expected = %zu\n",
|
||||
be32_to_cpu(tpm_resp->hdr.len), sizeof(*tpm_resp));
|
||||
DPRINTF("tpm_resp->len = %u, expected = %zu\n",
|
||||
be32_to_cpu(tpm_resp->len), sizeof(uint32_t));
|
||||
error_report("tpm_util: Got unexpected response to "
|
||||
"TPM_GetCapability; errcode: 0x%x",
|
||||
be32_to_cpu(tpm_resp->hdr.errcode));
|
||||
return -EFAULT;
|
||||
}
|
||||
*buffersize = be32_to_cpu(tpm_resp->buffersize);
|
||||
break;
|
||||
}
|
||||
case TPM_VERSION_2_0: {
|
||||
const struct tpm2_req_get_buffer_size {
|
||||
struct tpm_req_hdr hdr;
|
||||
uint32_t capability;
|
||||
uint32_t property;
|
||||
uint32_t count;
|
||||
} QEMU_PACKED tpm2_get_buffer_size = {
|
||||
.hdr = {
|
||||
.tag = cpu_to_be16(TPM2_ST_NO_SESSIONS),
|
||||
.len = cpu_to_be32(sizeof(tpm2_get_buffer_size)),
|
||||
.ordinal = cpu_to_be32(TPM2_CC_GetCapability),
|
||||
},
|
||||
.capability = cpu_to_be32(TPM2_CAP_TPM_PROPERTIES),
|
||||
.property = cpu_to_be32(TPM2_PT_MAX_COMMAND_SIZE),
|
||||
.count = cpu_to_be32(2), /* also get TPM2_PT_MAX_RESPONSE_SIZE */
|
||||
};
|
||||
struct tpm2_resp_get_buffer_size {
|
||||
struct tpm_resp_hdr hdr;
|
||||
uint8_t more;
|
||||
uint32_t capability;
|
||||
uint32_t count;
|
||||
uint32_t property1;
|
||||
uint32_t value1;
|
||||
uint32_t property2;
|
||||
uint32_t value2;
|
||||
} QEMU_PACKED *tpm2_resp = (struct tpm2_resp_get_buffer_size *)buf;
|
||||
|
||||
ret = tpm_util_request(tpm_fd, (unsigned char *)&tpm2_get_buffer_size,
|
||||
sizeof(tpm2_get_buffer_size), buf, sizeof(buf));
|
||||
if (ret < 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (be32_to_cpu(tpm2_resp->hdr.len) != sizeof(*tpm2_resp) ||
|
||||
be32_to_cpu(tpm2_resp->count) != 2) {
|
||||
DPRINTF("tpm2_resp->hdr.len = %u, expected = %zu\n",
|
||||
be32_to_cpu(tpm2_resp->hdr.len), sizeof(*tpm2_resp));
|
||||
DPRINTF("tpm2_resp->len = %u, expected = %u\n",
|
||||
be32_to_cpu(tpm2_resp->count), 2);
|
||||
error_report("tpm_util: Got unexpected response to "
|
||||
"TPM2_GetCapability; errcode: 0x%x",
|
||||
be32_to_cpu(tpm2_resp->hdr.errcode));
|
||||
return -EFAULT;
|
||||
}
|
||||
*buffersize = MAX(be32_to_cpu(tpm2_resp->value1),
|
||||
be32_to_cpu(tpm2_resp->value2));
|
||||
break;
|
||||
}
|
||||
case TPM_VERSION_UNSPEC:
|
||||
return -EFAULT;
|
||||
}
|
||||
|
||||
DPRINTF("buffersize of device: %zu\n", *buffersize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
@ -22,7 +22,8 @@
|
||||
#ifndef TPM_TPM_UTIL_H
|
||||
#define TPM_TPM_UTIL_H
|
||||
|
||||
#include "sysemu/tpm_backend.h"
|
||||
#include "sysemu/tpm.h"
|
||||
#include "qemu/bswap.h"
|
||||
|
||||
void tpm_util_write_fatal_error_response(uint8_t *out, uint32_t out_len);
|
||||
|
||||
@ -30,4 +31,12 @@ bool tpm_util_is_selftest(const uint8_t *in, uint32_t in_len);
|
||||
|
||||
int tpm_util_test_tpmdev(int tpm_fd, TPMVersion *tpm_version);
|
||||
|
||||
static inline uint32_t tpm_cmd_get_size(const void *b)
|
||||
{
|
||||
return be32_to_cpu(*(const uint32_t *)(b + 2));
|
||||
}
|
||||
|
||||
int tpm_util_get_buffer_size(int tpm_fd, TPMVersion tpm_version,
|
||||
size_t *buffersize);
|
||||
|
||||
#endif /* TPM_TPM_UTIL_H */
|
||||
|
@ -17,6 +17,7 @@ extern const PropertyInfo qdev_prop_int64;
|
||||
extern const PropertyInfo qdev_prop_size;
|
||||
extern const PropertyInfo qdev_prop_string;
|
||||
extern const PropertyInfo qdev_prop_chr;
|
||||
extern const PropertyInfo qdev_prop_tpm;
|
||||
extern const PropertyInfo qdev_prop_ptr;
|
||||
extern const PropertyInfo qdev_prop_macaddr;
|
||||
extern const PropertyInfo qdev_prop_on_off_auto;
|
||||
@ -186,6 +187,8 @@ extern const PropertyInfo qdev_prop_link;
|
||||
|
||||
#define DEFINE_PROP_CHR(_n, _s, _f) \
|
||||
DEFINE_PROP(_n, _s, _f, qdev_prop_chr, CharBackend)
|
||||
#define DEFINE_PROP_TPMBE(_n, _s, _f) \
|
||||
DEFINE_PROP(_n, _s, _f, qdev_prop_tpm, TPMBackend *)
|
||||
#define DEFINE_PROP_STRING(_n, _s, _f) \
|
||||
DEFINE_PROP(_n, _s, _f, qdev_prop_string, char*)
|
||||
#define DEFINE_PROP_NETDEV(_n, _s, _f) \
|
||||
|
@ -12,35 +12,59 @@
|
||||
#ifndef QEMU_TPM_H
|
||||
#define QEMU_TPM_H
|
||||
|
||||
#include "qemu/option.h"
|
||||
#include "qom/object.h"
|
||||
|
||||
typedef struct TPMState TPMState;
|
||||
#include "qapi-types.h"
|
||||
|
||||
int tpm_config_parse(QemuOptsList *opts_list, const char *optarg);
|
||||
int tpm_init(void);
|
||||
void tpm_cleanup(void);
|
||||
|
||||
typedef enum TPMVersion {
|
||||
typedef enum TPMVersion {
|
||||
TPM_VERSION_UNSPEC = 0,
|
||||
TPM_VERSION_1_2 = 1,
|
||||
TPM_VERSION_2_0 = 2,
|
||||
} TPMVersion;
|
||||
|
||||
TPMVersion tpm_tis_get_tpm_version(Object *obj);
|
||||
#define TYPE_TPM_IF "tpm-if"
|
||||
#define TPM_IF_CLASS(klass) \
|
||||
OBJECT_CLASS_CHECK(TPMIfClass, (klass), TYPE_TPM_IF)
|
||||
#define TPM_IF_GET_CLASS(obj) \
|
||||
OBJECT_GET_CLASS(TPMIfClass, (obj), TYPE_TPM_IF)
|
||||
#define TPM_IF(obj) \
|
||||
INTERFACE_CHECK(TPMIf, (obj), TYPE_TPM_IF)
|
||||
|
||||
typedef struct TPMIf {
|
||||
Object parent_obj;
|
||||
} TPMIf;
|
||||
|
||||
typedef struct TPMIfClass {
|
||||
InterfaceClass parent_class;
|
||||
|
||||
enum TpmModel model;
|
||||
void (*request_completed)(TPMIf *obj);
|
||||
enum TPMVersion (*get_version)(TPMIf *obj);
|
||||
} TPMIfClass;
|
||||
|
||||
#define TYPE_TPM_TIS "tpm-tis"
|
||||
|
||||
static inline TPMVersion tpm_get_version(void)
|
||||
{
|
||||
#ifdef CONFIG_TPM
|
||||
Object *obj = object_resolve_path_type("", TYPE_TPM_TIS, NULL);
|
||||
#define TPM_IS_TIS(chr) \
|
||||
object_dynamic_cast(OBJECT(chr), TYPE_TPM_TIS)
|
||||
|
||||
if (obj) {
|
||||
return tpm_tis_get_tpm_version(obj);
|
||||
/* returns NULL unless there is exactly one TPM device */
|
||||
static inline TPMIf *tpm_find(void)
|
||||
{
|
||||
Object *obj = object_resolve_path_type("", TYPE_TPM_IF, NULL);
|
||||
|
||||
return TPM_IF(obj);
|
||||
}
|
||||
|
||||
static inline TPMVersion tpm_get_version(TPMIf *ti)
|
||||
{
|
||||
if (!ti) {
|
||||
return TPM_VERSION_UNSPEC;
|
||||
}
|
||||
#endif
|
||||
return TPM_VERSION_UNSPEC;
|
||||
|
||||
return TPM_IF_GET_CLASS(ti)->get_version(ti);
|
||||
}
|
||||
|
||||
#endif /* QEMU_TPM_H */
|
||||
|
@ -43,14 +43,14 @@ struct TPMBackend {
|
||||
Object parent;
|
||||
|
||||
/*< protected >*/
|
||||
TPMIf *tpmif;
|
||||
bool opened;
|
||||
TPMState *tpm_state;
|
||||
GThreadPool *thread_pool;
|
||||
bool had_startup_error;
|
||||
QEMUBH *bh;
|
||||
|
||||
/* <public> */
|
||||
char *id;
|
||||
enum TpmModel fe_model;
|
||||
|
||||
QLIST_ENTRY(TPMBackend) list;
|
||||
};
|
||||
@ -63,24 +63,27 @@ struct TPMBackendClass {
|
||||
/* get a descriptive text of the backend to display to the user */
|
||||
const char *desc;
|
||||
|
||||
TPMBackend *(*create)(QemuOpts *opts, const char *id);
|
||||
TPMBackend *(*create)(QemuOpts *opts);
|
||||
|
||||
/* start up the TPM on the backend */
|
||||
int (*startup_tpm)(TPMBackend *t);
|
||||
/* start up the TPM on the backend - optional */
|
||||
int (*startup_tpm)(TPMBackend *t, size_t buffersize);
|
||||
|
||||
/* optional */
|
||||
void (*reset)(TPMBackend *t);
|
||||
|
||||
void (*cancel_cmd)(TPMBackend *t);
|
||||
|
||||
/* optional */
|
||||
bool (*get_tpm_established_flag)(TPMBackend *t);
|
||||
|
||||
/* optional */
|
||||
int (*reset_tpm_established_flag)(TPMBackend *t, uint8_t locty);
|
||||
|
||||
TPMVersion (*get_tpm_version)(TPMBackend *t);
|
||||
|
||||
TpmTypeOptions *(*get_tpm_options)(TPMBackend *t);
|
||||
size_t (*get_buffer_size)(TPMBackend *t);
|
||||
|
||||
void (*opened)(TPMBackend *s, Error **errp);
|
||||
TpmTypeOptions *(*get_tpm_options)(TPMBackend *t);
|
||||
|
||||
void (*handle_request)(TPMBackend *s, TPMBackendCmd *cmd);
|
||||
};
|
||||
@ -96,22 +99,25 @@ enum TpmType tpm_backend_get_type(TPMBackend *s);
|
||||
/**
|
||||
* tpm_backend_init:
|
||||
* @s: the backend to initialized
|
||||
* @state: TPMState
|
||||
* @tpmif: TPM interface
|
||||
* @datacb: callback for sending data to frontend
|
||||
* @errp: a pointer to return the #Error object if an error occurs.
|
||||
*
|
||||
* Initialize the backend with the given variables.
|
||||
*
|
||||
* Returns 0 on success.
|
||||
*/
|
||||
int tpm_backend_init(TPMBackend *s, TPMState *state);
|
||||
int tpm_backend_init(TPMBackend *s, TPMIf *tpmif, Error **errp);
|
||||
|
||||
/**
|
||||
* tpm_backend_startup_tpm:
|
||||
* @s: the backend whose TPM support is to be started
|
||||
* @buffersize: the buffer size the TPM is supposed to use,
|
||||
* 0 to leave it as-is
|
||||
*
|
||||
* Returns 0 on success.
|
||||
*/
|
||||
int tpm_backend_startup_tpm(TPMBackend *s);
|
||||
int tpm_backend_startup_tpm(TPMBackend *s, size_t buffersize);
|
||||
|
||||
/**
|
||||
* tpm_backend_had_startup_error:
|
||||
@ -170,16 +176,6 @@ bool tpm_backend_get_tpm_established_flag(TPMBackend *s);
|
||||
*/
|
||||
int tpm_backend_reset_tpm_established_flag(TPMBackend *s, uint8_t locty);
|
||||
|
||||
/**
|
||||
* tpm_backend_open:
|
||||
* @s: the backend to open
|
||||
* @errp: a pointer to return the #Error object if an error occurs.
|
||||
*
|
||||
* This function will open the backend if it is not already open. Calling this
|
||||
* function on an already opened backend will not result in an error.
|
||||
*/
|
||||
void tpm_backend_open(TPMBackend *s, Error **errp);
|
||||
|
||||
/**
|
||||
* tpm_backend_get_tpm_version:
|
||||
* @s: the backend to call into
|
||||
@ -190,6 +186,16 @@ void tpm_backend_open(TPMBackend *s, Error **errp);
|
||||
*/
|
||||
TPMVersion tpm_backend_get_tpm_version(TPMBackend *s);
|
||||
|
||||
/**
|
||||
* tpm_backend_get_buffer_size:
|
||||
* @s: the backend to call into
|
||||
*
|
||||
* Get the TPM's buffer size.
|
||||
*
|
||||
* Returns buffer size.
|
||||
*/
|
||||
size_t tpm_backend_get_buffer_size(TPMBackend *s);
|
||||
|
||||
/**
|
||||
* tpm_backend_query_tpm:
|
||||
* @s: the backend
|
||||
@ -200,8 +206,6 @@ TPMVersion tpm_backend_get_tpm_version(TPMBackend *s);
|
||||
*/
|
||||
TPMInfo *tpm_backend_query_tpm(TPMBackend *s);
|
||||
|
||||
TPMBackend *qemu_find_tpm(const char *id);
|
||||
|
||||
void tpm_register_model(enum TpmModel model);
|
||||
TPMBackend *qemu_find_tpm_be(const char *id);
|
||||
|
||||
#endif
|
||||
|
34
tpm.c
34
tpm.c
@ -23,13 +23,6 @@
|
||||
static QLIST_HEAD(, TPMBackend) tpm_backends =
|
||||
QLIST_HEAD_INITIALIZER(tpm_backends);
|
||||
|
||||
static bool tpm_models[TPM_MODEL__MAX];
|
||||
|
||||
void tpm_register_model(enum TpmModel model)
|
||||
{
|
||||
tpm_models[model] = true;
|
||||
}
|
||||
|
||||
static const TPMBackendClass *
|
||||
tpm_be_find_by_type(enum TpmType type)
|
||||
{
|
||||
@ -69,7 +62,7 @@ static void tpm_display_backend_drivers(void)
|
||||
/*
|
||||
* Find the TPM with the given Id
|
||||
*/
|
||||
TPMBackend *qemu_find_tpm(const char *id)
|
||||
TPMBackend *qemu_find_tpm_be(const char *id)
|
||||
{
|
||||
TPMBackend *drv;
|
||||
|
||||
@ -127,17 +120,12 @@ static int tpm_init_tpmdev(void *dummy, QemuOpts *opts, Error **errp)
|
||||
return 1;
|
||||
}
|
||||
|
||||
drv = be->create(opts, id);
|
||||
drv = be->create(opts);
|
||||
if (!drv) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
tpm_backend_open(drv, &local_err);
|
||||
if (local_err) {
|
||||
error_report_err(local_err);
|
||||
return 1;
|
||||
}
|
||||
|
||||
drv->id = g_strdup(id);
|
||||
QLIST_INSERT_HEAD(&tpm_backends, drv, list);
|
||||
|
||||
return 0;
|
||||
@ -200,9 +188,10 @@ TPMInfoList *qmp_query_tpm(Error **errp)
|
||||
TPMInfoList *info, *head = NULL, *cur_item = NULL;
|
||||
|
||||
QLIST_FOREACH(drv, &tpm_backends, list) {
|
||||
if (!tpm_models[drv->fe_model]) {
|
||||
if (!drv->tpmif) {
|
||||
continue;
|
||||
}
|
||||
|
||||
info = g_new0(TPMInfoList, 1);
|
||||
info->value = tpm_backend_query_tpm(drv);
|
||||
|
||||
@ -240,18 +229,16 @@ TpmTypeList *qmp_query_tpm_types(Error **errp)
|
||||
|
||||
return head;
|
||||
}
|
||||
|
||||
TpmModelList *qmp_query_tpm_models(Error **errp)
|
||||
{
|
||||
unsigned int i = 0;
|
||||
TpmModelList *head = NULL, *prev = NULL, *cur_item;
|
||||
GSList *e, *l = object_class_get_list(TYPE_TPM_IF, false);
|
||||
|
||||
for (e = l; e; e = e->next) {
|
||||
TPMIfClass *c = TPM_IF_CLASS(e->data);
|
||||
|
||||
for (i = 0; i < TPM_MODEL__MAX; i++) {
|
||||
if (!tpm_models[i]) {
|
||||
continue;
|
||||
}
|
||||
cur_item = g_new0(TpmModelList, 1);
|
||||
cur_item->value = i;
|
||||
cur_item->value = c->model;
|
||||
|
||||
if (prev) {
|
||||
prev->next = cur_item;
|
||||
@ -261,6 +248,7 @@ TpmModelList *qmp_query_tpm_models(Error **errp)
|
||||
}
|
||||
prev = cur_item;
|
||||
}
|
||||
g_slist_free(l);
|
||||
|
||||
return head;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user