diff --git a/contrib/ci/Dockerfile-debian.in b/contrib/ci/Dockerfile-debian.in
index 02c3e45ba..d4c07989c 100644
--- a/contrib/ci/Dockerfile-debian.in
+++ b/contrib/ci/Dockerfile-debian.in
@@ -11,6 +11,10 @@ Pin-Priority: 800\n\
\n\
Package: lintian\n\
Pin: release a=unstable\n\
+Pin-Priority: 901\n\
+\n\
+Package: tpm-udev\n\
+Pin: release a=unstable\n\
Pin-Priority: 901\n'\
> /etc/apt/preferences
RUN cat /etc/apt/preferences
diff --git a/contrib/ci/build_windows.sh b/contrib/ci/build_windows.sh
index 9abd5185b..163559def 100755
--- a/contrib/ci/build_windows.sh
+++ b/contrib/ci/build_windows.sh
@@ -22,6 +22,7 @@ meson .. \
-Dplugin_altos=false \
-Dplugin_dell=false \
-Dplugin_nvme=false \
+ -Dplugin_tpm=false \
-Dsystemd=false \
-Dplugin_emmc=false \
-Dplugin_amt=false \
diff --git a/contrib/ci/dependencies.xml b/contrib/ci/dependencies.xml
index cc1789c1d..9c6d77064 100644
--- a/contrib/ci/dependencies.xml
+++ b/contrib/ci/dependencies.xml
@@ -1329,13 +1329,9 @@
tpm2-tss-devel
-
- amd64
- arm64
- armhf
- i386
-
+
+ libtss2-dev:s390x
diff --git a/contrib/fwupd.spec.in b/contrib/fwupd.spec.in
index 130039278..80305034d 100644
--- a/contrib/fwupd.spec.in
+++ b/contrib/fwupd.spec.in
@@ -352,6 +352,7 @@ rm ${RPM_BUILD_ROOT}%{_sbindir}/flashrom
%{_libdir}/fwupd-plugins-3/libfu_plugin_thelio_io.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_thunderbolt.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_thunderbolt_power.so
+%{_libdir}/fwupd-plugins-3/libfu_plugin_tpm.so
%if 0%{?have_uefi}
%{_libdir}/fwupd-plugins-3/libfu_plugin_uefi.so
%{_libdir}/fwupd-plugins-3/libfu_plugin_uefi_recovery.so
diff --git a/libfwupdplugin/fu-udev-device.c b/libfwupdplugin/fu-udev-device.c
index 78fc5fae3..07feded94 100644
--- a/libfwupdplugin/fu-udev-device.c
+++ b/libfwupdplugin/fu-udev-device.c
@@ -653,6 +653,16 @@ fu_udev_device_set_physical_id (FuUdevDevice *self, const gchar *subsystem, GErr
return FALSE;
}
physical_id = g_strdup_printf ("HID_PHYS=%s", tmp);
+ } else if (g_strcmp0 (subsystem, "tpm") == 0) {
+ tmp = g_udev_device_get_property (udev_device, "DEVNAME");
+ if (tmp == NULL) {
+ g_set_error_literal (error,
+ G_IO_ERROR,
+ G_IO_ERROR_NOT_FOUND,
+ "failed to find DEVPATH");
+ return FALSE;
+ }
+ physical_id = g_strdup_printf ("DEVNAME=%s", tmp);
} else {
g_set_error (error,
G_IO_ERROR,
diff --git a/meson.build b/meson.build
index ec9b8a3fb..1e9b8a4cc 100644
--- a/meson.build
+++ b/meson.build
@@ -277,6 +277,10 @@ if cc.has_function('pwrite', args : '-D_XOPEN_SOURCE')
conf.set('HAVE_PWRITE', '1')
endif
+if build_standalone and get_option('plugin_tpm')
+ tpm2tss = dependency('tss2-esys', version : '>= 2.0')
+endif
+
if build_standalone and get_option('plugin_uefi')
cairo = dependency('cairo')
fontconfig = cc.find_library('fontconfig')
diff --git a/meson_options.txt b/meson_options.txt
index 17c30446a..be0adfef6 100644
--- a/meson_options.txt
+++ b/meson_options.txt
@@ -17,6 +17,7 @@ option('plugin_emmc', type : 'boolean', value : true, description : 'enable eMMC
option('plugin_synaptics', type: 'boolean', value: true, description : 'enable Synaptics MST hub support')
option('plugin_thunderbolt', type : 'boolean', value : true, description : 'enable Thunderbolt support')
option('plugin_redfish', type : 'boolean', value : true, description : 'enable Redfish support')
+option('plugin_tpm', type : 'boolean', value : true, description : 'enable TPM support')
option('plugin_uefi', type : 'boolean', value : true, description : 'enable UEFI support')
option('plugin_nvme', type : 'boolean', value : true, description : 'enable NVMe support')
option('plugin_modem_manager', type : 'boolean', value : false, description : 'enable ModemManager support')
diff --git a/plugins/dell/fu-plugin-dell.c b/plugins/dell/fu-plugin-dell.c
index efd8498ac..b448c74a6 100644
--- a/plugins/dell/fu-plugin-dell.c
+++ b/plugins/dell/fu-plugin-dell.c
@@ -852,6 +852,9 @@ fu_plugin_init (FuPlugin *plugin)
/* make sure that UEFI plugin is ready to receive devices */
fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "uefi");
+
+ /* our TPM device is upgradable! */
+ fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_BETTER_THAN, "tpm");
}
void
diff --git a/plugins/meson.build b/plugins/meson.build
index 73f74eb20..cce9b252a 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -29,6 +29,10 @@ endif
# depends on dfu
subdir('csr')
+if get_option('plugin_tpm') and get_option('gudev')
+subdir('tpm')
+endif
+
if get_option('plugin_emmc') and get_option('gudev')
subdir('emmc')
endif
diff --git a/plugins/tpm/README.md b/plugins/tpm/README.md
new file mode 100644
index 000000000..d4f539769
--- /dev/null
+++ b/plugins/tpm/README.md
@@ -0,0 +1,27 @@
+TPM Support
+===========
+
+Introduction
+------------
+
+This allows enumerating Trusted Platform Modules, also known as "TPM" devices,
+although it does not allow the user to update the firmware on them.
+
+GUID Generation
+---------------
+
+These devices use custom GUIDs:
+
+
+ * `TPM\VEN_$(manufacturer)&DEV_$(type)`
+ * `TPM\VEN_$(manufacturer)&MOD_$(vendor-string)`
+ * `TPM\VEN_$(manufacturer)&DEV_$(type)_VER_$(family)`,
+ * `TPM\VEN_$(manufacturer)&MOD_$(vendor-string)_VER_$(family)`
+
+...where `family` is either `2.0` or `1.2`
+
+Example GUIDs from a real system containing a TPM from Intel:
+```
+ Guid: 34801700-3a50-5b05-820c-fe14580e4c2d <- TPM\VEN_INTC&DEV_0000
+ Guid: 03f304f4-223e-54f4-b2c1-c3cf3b5817c6 <- TPM\VEN_INTC&DEV_0000&VER_2.0
+```
diff --git a/plugins/tpm/fu-plugin-tpm.c b/plugins/tpm/fu-plugin-tpm.c
new file mode 100644
index 000000000..fec3842eb
--- /dev/null
+++ b/plugins/tpm/fu-plugin-tpm.c
@@ -0,0 +1,20 @@
+/*
+ * Copyright (C) 2019 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include "fu-hash.h"
+#include "fu-plugin-vfuncs.h"
+
+#include "fu-tpm-device.h"
+
+void
+fu_plugin_init (FuPlugin *plugin)
+{
+ fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
+ fu_plugin_add_udev_subsystem (plugin, "tpm");
+ fu_plugin_set_device_gtype (plugin, FU_TYPE_TPM_DEVICE);
+}
diff --git a/plugins/tpm/fu-tpm-device.c b/plugins/tpm/fu-tpm-device.c
new file mode 100644
index 000000000..0ebd0311c
--- /dev/null
+++ b/plugins/tpm/fu-tpm-device.c
@@ -0,0 +1,258 @@
+/*
+ * Copyright (C) 2019 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#include "config.h"
+
+#include
+
+#include "fu-tpm-device.h"
+
+struct _FuTpmDevice {
+ FuUdevDevice parent_instance;
+};
+
+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)
+
+static gboolean
+fu_tpm_device_probe (FuUdevDevice *device, GError **error)
+{
+ return fu_udev_device_set_physical_id (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] != '\0')
+ 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)
+{
+ FwupdVersionFormat verfmt;
+ TSS2_RC rc;
+ const gchar *tmp;
+ guint32 tpm_type = 0;
+ guint32 version1 = 0;
+ guint32 version2 = 0;
+ guint64 version_raw;
+ g_autofree gchar *family = NULL;
+ 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 */
+ family = fu_tpm_device_get_string (ctx, TPM2_PT_FAMILY_INDICATOR, error);
+ if (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, family);
+ fu_device_add_instance_id (device, id3);
+ id4 = g_strdup_printf ("TPM\\VEN_%s&MOD_%s&VER_%s", manufacturer, model, family);
+ fu_device_add_instance_id (device, id4);
+
+ /* enforce vendors can only ship updates for thier own hardware */
+ vendor_id = g_strdup_printf ("TPM:%s", manufacturer);
+ fu_device_set_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 (device, version, verfmt);
+
+ /* 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);
+}
+
+static void
+fu_tpm_device_finalize (GObject *object)
+{
+ 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);
+ FuUdevDeviceClass *klass_udev_device = FU_UDEV_DEVICE_CLASS (klass);
+ object_class->finalize = fu_tpm_device_finalize;
+ klass_device->setup = fu_tpm_device_setup;
+ klass_udev_device->probe = fu_tpm_device_probe;
+}
diff --git a/plugins/tpm/fu-tpm-device.h b/plugins/tpm/fu-tpm-device.h
new file mode 100644
index 000000000..55c1ef312
--- /dev/null
+++ b/plugins/tpm/fu-tpm-device.h
@@ -0,0 +1,12 @@
+/*
+ * Copyright (C) 2019 Richard Hughes
+ *
+ * SPDX-License-Identifier: LGPL-2.1+
+ */
+
+#pragma once
+
+#include "fu-plugin.h"
+
+#define FU_TYPE_TPM_DEVICE (fu_tpm_device_get_type ())
+G_DECLARE_FINAL_TYPE (FuTpmDevice, fu_tpm_device, FU, TPM_DEVICE, FuUdevDevice)
diff --git a/plugins/tpm/meson.build b/plugins/tpm/meson.build
new file mode 100644
index 000000000..3c1db60e2
--- /dev/null
+++ b/plugins/tpm/meson.build
@@ -0,0 +1,31 @@
+cargs = ['-DG_LOG_DOMAIN="FuPluginTpm"']
+
+install_data([
+ 'tpm.quirk',
+ ],
+ install_dir: join_paths(datadir, 'fwupd', 'quirks.d')
+)
+
+shared_module('fu_plugin_tpm',
+ fu_hash,
+ sources : [
+ 'fu-plugin-tpm.c',
+ 'fu-tpm-device.c',
+ ],
+ include_directories : [
+ root_incdir,
+ fwupd_incdir,
+ fwupdplugin_incdir,
+ ],
+ install : true,
+ install_dir: plugin_dir,
+ link_with : [
+ fwupdplugin,
+ fwupd,
+ ],
+ c_args : cargs,
+ dependencies : [
+ plugin_deps,
+ tpm2tss,
+ ],
+)
diff --git a/plugins/tpm/tpm.quirk b/plugins/tpm/tpm.quirk
new file mode 100644
index 000000000..4bba8583f
--- /dev/null
+++ b/plugins/tpm/tpm.quirk
@@ -0,0 +1,2 @@
+[DeviceInstanceId=TPM]
+Plugin = tpm