mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-10 04:51:39 +00:00
273 lines
8.5 KiB
C
273 lines
8.5 KiB
C
/*
|
|
* Copyright (C) 2019 Richard Hughes <richard@hughsie.com>
|
|
*
|
|
* SPDX-License-Identifier: LGPL-2.1+
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <tss2/tss2_esys.h>
|
|
|
|
#include "fu-tpm-device.h"
|
|
|
|
struct _FuTpmDevice {
|
|
FuUdevDevice parent_instance;
|
|
gchar *family;
|
|
};
|
|
|
|
G_DEFINE_TYPE (FuTpmDevice, fu_tpm_device, FU_TYPE_UDEV_DEVICE)
|
|
|
|
static void Esys_Finalize_autoptr_cleanup (ESYS_CONTEXT *esys_context)
|
|
{
|
|
Esys_Finalize (&esys_context);
|
|
}
|
|
G_DEFINE_AUTOPTR_CLEANUP_FUNC (ESYS_CONTEXT, Esys_Finalize_autoptr_cleanup)
|
|
|
|
const gchar *
|
|
fu_tpm_device_get_family (FuTpmDevice *self)
|
|
{
|
|
return self->family;
|
|
}
|
|
|
|
static gboolean
|
|
fu_tpm_device_probe (FuDevice *device, GError **error)
|
|
{
|
|
/* FuUdevDevice->probe */
|
|
if (!FU_DEVICE_CLASS (fu_tpm_device_parent_class)->probe (device, error))
|
|
return FALSE;
|
|
return fu_udev_device_set_physical_id (FU_UDEV_DEVICE (device), "tpm", error);
|
|
}
|
|
|
|
static gboolean
|
|
fu_tpm_device_get_uint32 (ESYS_CONTEXT *ctx, guint32 query, guint32 *val, GError **error)
|
|
{
|
|
TSS2_RC rc;
|
|
g_autofree TPMS_CAPABILITY_DATA *capability = NULL;
|
|
|
|
g_return_val_if_fail (val != NULL, FALSE);
|
|
|
|
rc = Esys_GetCapability (ctx, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE,
|
|
TPM2_CAP_TPM_PROPERTIES, query, 1, NULL, &capability);
|
|
if (rc != TSS2_RC_SUCCESS) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"capability request failed for query %x", query);
|
|
return FALSE;
|
|
}
|
|
if (capability->data.tpmProperties.count == 0) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"no properties returned for query %x", query);
|
|
return FALSE;
|
|
}
|
|
if (capability->data.tpmProperties.tpmProperty[0].property != query) {
|
|
g_set_error (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"wrong query returned (got %x expected %x)",
|
|
capability->data.tpmProperties.tpmProperty[0].property,
|
|
query);
|
|
return FALSE;
|
|
}
|
|
|
|
*val = capability->data.tpmProperties.tpmProperty[0].value;
|
|
return TRUE;
|
|
}
|
|
|
|
static gchar *
|
|
fu_tpm_device_get_string (ESYS_CONTEXT *ctx, guint32 query, GError **error)
|
|
{
|
|
guint32 val_be = 0;
|
|
guint32 val;
|
|
gchar result[5] = {'\0'};
|
|
|
|
/* return four bytes */
|
|
if (!fu_tpm_device_get_uint32 (ctx, query, &val_be, error))
|
|
return NULL;
|
|
val = GUINT32_FROM_BE(val_be);
|
|
memcpy (result, (gchar *) &val, 4);
|
|
|
|
/* convert non-ASCII into spaces */
|
|
for (guint i = 0; i < 4; i++) {
|
|
if (!g_ascii_isgraph (result[i]))
|
|
result[i] = 0x20;
|
|
}
|
|
|
|
return fu_common_strstrip (result);
|
|
}
|
|
|
|
/* taken from TCG-TPM-Vendor-ID-Registry-Version-1.01-Revision-1.00.pdf */
|
|
static const gchar *
|
|
fu_tpm_device_convert_manufacturer (const gchar *manufacturer)
|
|
{
|
|
if (g_strcmp0 (manufacturer, "AMD") == 0)
|
|
return "AMD";
|
|
if (g_strcmp0 (manufacturer, "ATML") == 0)
|
|
return "Atmel";
|
|
if (g_strcmp0 (manufacturer, "BRCM") == 0)
|
|
return "Broadcom";
|
|
if (g_strcmp0 (manufacturer, "HPE") == 0)
|
|
return "HPE";
|
|
if (g_strcmp0 (manufacturer, "IBM") == 0)
|
|
return "IBM";
|
|
if (g_strcmp0 (manufacturer, "IFX") == 0)
|
|
return "Infineon";
|
|
if (g_strcmp0 (manufacturer, "INTC") == 0)
|
|
return "Intel";
|
|
if (g_strcmp0 (manufacturer, "LEN") == 0)
|
|
return "Lenovo";
|
|
if (g_strcmp0 (manufacturer, "MSFT") == 0)
|
|
return "Microsoft";
|
|
if (g_strcmp0 (manufacturer, "NSM") == 0)
|
|
return "National Semiconductor";
|
|
if (g_strcmp0 (manufacturer, "NTZ") == 0)
|
|
return "Nationz";
|
|
if (g_strcmp0 (manufacturer, "NTC") == 0)
|
|
return "Nuvoton Technology";
|
|
if (g_strcmp0 (manufacturer, "QCOM") == 0)
|
|
return "Qualcomm";
|
|
if (g_strcmp0 (manufacturer, "SMSC") == 0)
|
|
return "SMSC";
|
|
if (g_strcmp0 (manufacturer, "STM") == 0)
|
|
return "ST Microelectronics";
|
|
if (g_strcmp0 (manufacturer, "SMSN") == 0)
|
|
return "Samsung";
|
|
if (g_strcmp0 (manufacturer, "SNS") == 0)
|
|
return "Sinosun";
|
|
if (g_strcmp0 (manufacturer, "TXN") == 0)
|
|
return "Texas Instruments";
|
|
if (g_strcmp0 (manufacturer, "WEC") == 0)
|
|
return "Winbond";
|
|
if (g_strcmp0 (manufacturer, "ROCC") == 0)
|
|
return "Fuzhou Rockchip";
|
|
if (g_strcmp0 (manufacturer, "GOOG") == 0)
|
|
return "Google";
|
|
return NULL;
|
|
}
|
|
|
|
static gboolean
|
|
fu_tpm_device_setup (FuDevice *device, GError **error)
|
|
{
|
|
FuTpmDevice *self = FU_TPM_DEVICE (device);
|
|
FwupdVersionFormat verfmt;
|
|
TSS2_RC rc;
|
|
const gchar *tmp;
|
|
guint32 tpm_type = 0;
|
|
guint32 version1 = 0;
|
|
guint32 version2 = 0;
|
|
guint64 version_raw;
|
|
g_autofree gchar *id1 = NULL;
|
|
g_autofree gchar *id2 = NULL;
|
|
g_autofree gchar *id3 = NULL;
|
|
g_autofree gchar *id4 = NULL;
|
|
g_autofree gchar *manufacturer = NULL;
|
|
g_autofree gchar *model1 = NULL;
|
|
g_autofree gchar *model2 = NULL;
|
|
g_autofree gchar *model3 = NULL;
|
|
g_autofree gchar *model4 = NULL;
|
|
g_autofree gchar *model = NULL;
|
|
g_autofree gchar *vendor_id = NULL;
|
|
g_autofree gchar *version = NULL;
|
|
g_autoptr(ESYS_CONTEXT) ctx = NULL;
|
|
|
|
/* setup TSS */
|
|
rc = Esys_Initialize (&ctx, NULL, NULL);
|
|
if (rc != TSS2_RC_SUCCESS) {
|
|
g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND,
|
|
"failed to initialize TPM library");
|
|
return FALSE;
|
|
}
|
|
rc = Esys_Startup (ctx, TPM2_SU_CLEAR);
|
|
if (rc != TSS2_RC_SUCCESS) {
|
|
g_set_error_literal (error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED,
|
|
"failed to initialize TPM");
|
|
return FALSE;
|
|
}
|
|
|
|
/* lookup guaranteed details from TPM */
|
|
self->family = fu_tpm_device_get_string (ctx, TPM2_PT_FAMILY_INDICATOR, error);
|
|
if (self->family == NULL) {
|
|
g_prefix_error (error, "failed to read TPM family: ");
|
|
return FALSE;
|
|
}
|
|
manufacturer = fu_tpm_device_get_string (ctx, TPM2_PT_MANUFACTURER, error);
|
|
if (manufacturer == NULL) {
|
|
g_prefix_error (error, "failed to read TPM manufacturer: ");
|
|
return FALSE;
|
|
}
|
|
model1 = fu_tpm_device_get_string (ctx, TPM2_PT_VENDOR_STRING_1, error);
|
|
if (model1 == NULL) {
|
|
g_prefix_error (error, "failed to read TPM vendor string: ");
|
|
return FALSE;
|
|
}
|
|
if (!fu_tpm_device_get_uint32 (ctx, TPM2_PT_VENDOR_TPM_TYPE, &tpm_type, error)) {
|
|
g_prefix_error (error, "failed to read TPM type: ");
|
|
return FALSE;
|
|
}
|
|
|
|
/* these are not guaranteed by spec and may be NULL */
|
|
model2 = fu_tpm_device_get_string (ctx, TPM2_PT_VENDOR_STRING_2, error);
|
|
model3 = fu_tpm_device_get_string (ctx, TPM2_PT_VENDOR_STRING_3, error);
|
|
model4 = fu_tpm_device_get_string (ctx, TPM2_PT_VENDOR_STRING_4, error);
|
|
model = g_strjoin ("", model1, model2, model3, model4, NULL);
|
|
|
|
/* add GUIDs to daemon */
|
|
id1 = g_strdup_printf ("TPM\\VEN_%s&DEV_%04X", manufacturer, tpm_type);
|
|
fu_device_add_instance_id (device, id1);
|
|
id2 = g_strdup_printf ("TPM\\VEN_%s&MOD_%s", manufacturer, model);
|
|
fu_device_add_instance_id (device, id2);
|
|
id3 = g_strdup_printf ("TPM\\VEN_%s&DEV_%04X&VER_%s", manufacturer, tpm_type, self->family);
|
|
fu_device_add_instance_id (device, id3);
|
|
id4 = g_strdup_printf ("TPM\\VEN_%s&MOD_%s&VER_%s", manufacturer, model, self->family);
|
|
fu_device_add_instance_id (device, id4);
|
|
|
|
/* enforce vendors can only ship updates for their own hardware */
|
|
vendor_id = g_strdup_printf ("TPM:%s", manufacturer);
|
|
fu_device_add_vendor_id (device, vendor_id);
|
|
tmp = fu_tpm_device_convert_manufacturer (manufacturer);
|
|
fu_device_set_vendor (device, tmp != NULL ? tmp : manufacturer);
|
|
|
|
/* get version */
|
|
if (!fu_tpm_device_get_uint32 (ctx, TPM2_PT_FIRMWARE_VERSION_1, &version1, error))
|
|
return FALSE;
|
|
if (!fu_tpm_device_get_uint32 (ctx, TPM2_PT_FIRMWARE_VERSION_2, &version2, error))
|
|
return FALSE;
|
|
version_raw = ((guint64) version1) << 32 | ((guint64) version2);
|
|
fu_device_set_version_raw (device, version_raw);
|
|
|
|
/* this has to be done after _add_instance_id() sets the quirks */
|
|
verfmt = fu_device_get_version_format (device);
|
|
version = fu_common_version_from_uint64 (version_raw, verfmt);
|
|
fu_device_set_version_format (device, verfmt);
|
|
fu_device_set_version (device, version);
|
|
|
|
/* success */
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
fu_tpm_device_init (FuTpmDevice *self)
|
|
{
|
|
fu_device_set_name (FU_DEVICE (self), "TPM");
|
|
fu_device_set_version_format (FU_DEVICE (self), FWUPD_VERSION_FORMAT_QUAD);
|
|
fu_device_add_flag (FU_DEVICE (self), FWUPD_DEVICE_FLAG_INTERNAL);
|
|
fu_device_add_icon (FU_DEVICE (self), "computer");
|
|
fu_udev_device_set_flags (FU_UDEV_DEVICE (self), FU_UDEV_DEVICE_FLAG_NONE);
|
|
fu_device_add_instance_id_full (FU_DEVICE (self), "system-tpm",
|
|
FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS);
|
|
}
|
|
|
|
static void
|
|
fu_tpm_device_finalize (GObject *object)
|
|
{
|
|
FuTpmDevice *self = FU_TPM_DEVICE (object);
|
|
g_free (self->family);
|
|
G_OBJECT_CLASS (fu_tpm_device_parent_class)->finalize (object);
|
|
}
|
|
|
|
static void
|
|
fu_tpm_device_class_init (FuTpmDeviceClass *klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
FuDeviceClass *klass_device = FU_DEVICE_CLASS (klass);
|
|
object_class->finalize = fu_tpm_device_finalize;
|
|
klass_device->setup = fu_tpm_device_setup;
|
|
klass_device->probe = fu_tpm_device_probe;
|
|
}
|