fu-plugin-uefi-capsule.c 27.3 KB
Newer Older
1
/*
2
3
 * Copyright (C) 2016 Richard Hughes <richard@hughsie.com>
 * Copyright (C) 2015 Peter Jones <pjones@redhat.com>
4
 *
5
 * SPDX-License-Identifier: LGPL-2.1+
6
7
8
9
 */

#include "config.h"

10
#include <fcntl.h>
11
#include <gio/gunixmounts.h>
12
#include <glib/gi18n.h>
13

14
#include "fu-archive.h"
15
#include "fu-device-metadata.h"
16
#include "fu-plugin-vfuncs.h"
17

18
#include "fu-uefi-bgrt.h"
19
#include "fu-uefi-bootmgr.h"
20
#include "fu-uefi-common.h"
21
#include "fu-uefi-device.h"
22
#include "fu-efivar.h"
23

24
25
26
27
28
29
30
#ifndef HAVE_GIO_2_55_0
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(GUnixMountEntry, g_unix_mount_free)
#pragma clang diagnostic pop
#endif

31
struct FuPluginData {
32
	FuUefiBgrt		*bgrt;
33
	FuVolume		*esp;
34
35
};

36
37
38
void
fu_plugin_init (FuPlugin *plugin)
{
39
	FuPluginData *data = fu_plugin_alloc_data (plugin, sizeof (FuPluginData));
40
	data->bgrt = fu_uefi_bgrt_new ();
41
	fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "upower");
42
	fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "tpm");
43
	fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "tpm_eventlog");
44
	fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "dell");
45
	fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_METADATA_SOURCE, "linux_lockdown");
Richard Hughes's avatar
Richard Hughes committed
46
	fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_CONFLICTS, "uefi"); /* old name */
47
	fu_plugin_set_build_hash (plugin, FU_BUILD_HASH);
48
49
50
51

	/* for the uploaded report */
	if (fu_plugin_has_custom_flag (plugin, "use-legacy-bootmgr-desc"))
		fu_plugin_add_report_metadata (plugin, "BootMgrDesc", "legacy");
52
53
}

54
55
56
void
fu_plugin_destroy (FuPlugin *plugin)
{
Richard Hughes's avatar
Richard Hughes committed
57
	FuPluginData *data = fu_plugin_get_data (plugin);
58
59
	if (data->esp != NULL)
		g_object_unref (data->esp);
60
	g_object_unref (data->bgrt);
61
62
}

63
64
gboolean
fu_plugin_clear_results (FuPlugin *plugin, FuDevice *device, GError **error)
65
{
66
67
	FuUefiDevice *device_uefi = FU_UEFI_DEVICE (device);
	return fu_uefi_device_clear_status (device_uefi, error);
68
69
}

70
71
gboolean
fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error)
72
{
73
74
	FuUefiDevice *device_uefi = FU_UEFI_DEVICE (device);
	FuUefiDeviceStatus status = fu_uefi_device_get_status (device_uefi);
75
	const gchar *tmp;
76
77
	g_autofree gchar *err_msg = NULL;
	g_autofree gchar *version_str = NULL;
78
	g_autoptr(GError) error_local = NULL;
79

80
	/* trivial case */
81
	if (status == FU_UEFI_DEVICE_STATUS_SUCCESS) {
82
		fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS);
83
84
85
		return TRUE;
	}

86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
	/* check if something rudely removed our BOOTXXXX entry */
	if (!fu_uefi_bootmgr_verify_fwupd (&error_local)) {
		if (fu_plugin_has_custom_flag (plugin, "boot-order-lock")) {
			g_prefix_error (&error_local,
					"boot entry missing; "
					"perhaps 'Boot Order Lock' enabled in the BIOS: ");
			fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED_TRANSIENT);
		} else {
			g_prefix_error (&error_local, "boot entry missing: ");
			fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED);
		}
		fu_device_set_update_error (device, error_local->message);
		return TRUE;
	}

101
	/* something went wrong */
102
103
104
105
106
107
	if (status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_AC ||
	    status == FU_UEFI_DEVICE_STATUS_ERROR_PWR_EVT_BATT) {
		fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED_TRANSIENT);
	} else {
		fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED);
	}
108
109
110
111
112
	version_str = g_strdup_printf ("%u", fu_uefi_device_get_version_error (device_uefi));
	tmp = fu_uefi_device_status_to_string (status);
	if (tmp == NULL) {
		err_msg = g_strdup_printf ("failed to update to %s",
					   version_str);
113
	} else {
114
115
		err_msg = g_strdup_printf ("failed to update to %s: %s",
					   version_str, tmp);
116
	}
117
	fu_device_set_update_error (device, err_msg);
118
	return TRUE;
119
120
}

121
122
123
124
void
fu_plugin_add_security_attrs (FuPlugin *plugin, FuSecurityAttrs *attrs)
{
	g_autoptr(FwupdSecurityAttr) attr = NULL;
125
	g_autoptr(GError) error = NULL;
126
127

	/* create attr */
128
	attr = fwupd_security_attr_new (FWUPD_SECURITY_ATTR_ID_UEFI_SECUREBOOT);
129
	fwupd_security_attr_set_plugin (attr, fu_plugin_get_name (plugin));
130
131
	fu_security_attrs_append (attrs, attr);

132
133
134
135
136
137
138
139
140
	/* SB not available or disabled */
	if (!fu_efivar_secure_boot_enabled_full (&error)) {
		if (g_error_matches (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED)) {
			fwupd_security_attr_set_result (attr, FWUPD_SECURITY_ATTR_RESULT_NOT_FOUND);
			return;
		}
		fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_RUNTIME_ISSUE);
141
		fwupd_security_attr_set_result (attr, FWUPD_SECURITY_ATTR_RESULT_NOT_ENABLED);
142
143
144
145
146
		return;
	}

	/* success */
	fwupd_security_attr_add_flag (attr, FWUPD_SECURITY_ATTR_FLAG_SUCCESS);
147
	fwupd_security_attr_set_result (attr, FWUPD_SECURITY_ATTR_RESULT_ENABLED);
148
149
}

150
static GBytes *
Richard Hughes's avatar
Richard Hughes committed
151
fu_plugin_uefi_capsule_get_splash_data (guint width, guint height, GError **error)
152
153
{
	const gchar * const *langs = g_get_language_names ();
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
	g_autofree gchar *datadir_pkg = NULL;
	g_autofree gchar *filename_archive = NULL;
	g_autofree gchar *langs_str = NULL;
	g_autoptr(FuArchive) archive = NULL;
	g_autoptr(GBytes) blob_archive = NULL;

	/* load archive */
	datadir_pkg = fu_common_get_path (FU_PATH_KIND_DATADIR_PKG);
	filename_archive = g_build_filename (datadir_pkg, "uefi-capsule-ux.tar.xz", NULL);
	blob_archive = fu_common_get_contents_bytes (filename_archive, error);
	if (blob_archive == NULL)
		return NULL;
	archive = fu_archive_new (blob_archive, FU_ARCHIVE_FLAG_NONE, error);
	if (archive == NULL)
		return NULL;
169
170
171

	/* find the closest locale match, falling back to `en` and `C` */
	for (guint i = 0; langs[i] != NULL; i++) {
172
		GBytes *blob_tmp;
173
174
175
		g_autofree gchar *fn = NULL;
		if (g_str_has_suffix (langs[i], ".UTF-8"))
			continue;
176
177
178
179
180
		fn = g_strdup_printf ("fwupd-%s-%u-%u.bmp", langs[i], width, height);
		blob_tmp = fu_archive_lookup_by_fn (archive, fn, NULL);
		if (blob_tmp != NULL) {
			g_debug ("using UX image %s", fn);
			return g_bytes_ref (blob_tmp);
181
182
183
184
185
		}
		g_debug ("no %s found", fn);
	}

	/* we found nothing */
186
187
188
189
190
191
192
	langs_str = g_strjoinv (",", (gchar **) langs);
	g_set_error (error,
		     FWUPD_ERROR,
		     FWUPD_ERROR_NOT_SUPPORTED,
		     "failed to get splash file for %s in %s",
		     langs_str, datadir_pkg);
	return NULL;
193
194
}

195
static guint8
Richard Hughes's avatar
Richard Hughes committed
196
fu_plugin_uefi_capsule_calc_checksum (const guint8 *buf, gsize sz)
197
198
199
200
201
202
203
{
	guint8 csum = 0;
	for (gsize i = 0; i < sz; i++)
		csum += buf[i];
	return csum;
}

204
static gboolean
Richard Hughes's avatar
Richard Hughes committed
205
206
207
208
fu_plugin_uefi_capsule_write_splash_data (FuPlugin *plugin,
					  FuDevice *device,
					  GBytes *blob,
					  GError **error)
209
210
211
212
213
214
{
	FuPluginData *data = fu_plugin_get_data (plugin);
	guint32 screen_x, screen_y;
	gsize buf_size = g_bytes_get_size (blob);
	gssize size;
	guint32 height, width;
215
	guint8 csum = 0;
216
	efi_ux_capsule_header_t header = { 0 };
217
218
	efi_capsule_header_t capsule_header = {
		.flags = EFI_CAPSULE_HEADER_FLAGS_PERSIST_ACROSS_RESET,
Richard Hughes's avatar
Richard Hughes committed
219
		.guid = { 0x0 },
220
221
222
		.header_size = sizeof(efi_capsule_header_t),
		.capsule_image_size = 0
	};
223
	g_autofree gchar *esp_path = NULL;
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
	g_autofree gchar *fn = NULL;
	g_autofree gchar *directory = NULL;
	g_autofree gchar *basename = NULL;
	g_autoptr(GFile) ofile = NULL;
	g_autoptr(GOutputStream) ostream = NULL;

	/* get screen dimensions */
	if (!fu_uefi_get_framebuffer_size (&screen_x, &screen_y, error))
		return FALSE;
	if (!fu_uefi_get_bitmap_size ((const guint8 *) g_bytes_get_data (blob, NULL),
				      buf_size, &width, &height, error)) {
		g_prefix_error (error, "splash invalid: ");
		return FALSE;
	}

	/* save to a predicatable filename */
240
	esp_path = fu_volume_get_mount_point (data->esp);
241
	directory = fu_uefi_get_esp_path_for_os (device, esp_path);
242
	basename = g_strdup_printf ("fwupd-%s.cap", FU_EFIVAR_GUID_UX_CAPSULE);
243
244
245
246
247
248
249
250
251
	fn = g_build_filename (directory, "fw", basename, NULL);
	if (!fu_common_mkdir_parent (fn, error))
		return FALSE;
	ofile = g_file_new_for_path (fn);
	ostream = G_OUTPUT_STREAM (g_file_replace (ofile, NULL, FALSE,
				   G_FILE_CREATE_NONE, NULL, error));
	if (ostream == NULL)
		return FALSE;

Richard Hughes's avatar
Richard Hughes committed
252
253
254
255
256
	if (!fwupd_guid_from_string (FU_EFIVAR_GUID_UX_CAPSULE,
				     &capsule_header.guid,
				     FWUPD_GUID_FLAG_MIXED_ENDIAN,
				     error))
		return FALSE;
257
258
259
	capsule_header.capsule_image_size =
		g_bytes_get_size (blob) +
		sizeof(efi_capsule_header_t) +
260
		sizeof(efi_ux_capsule_header_t);
261
262
263
264
265
266
267
268

	header.version = 1;
	header.image_type = 0;
	header.reserved = 0;
	header.x_offset = (screen_x / 2) - (width / 2);
	header.y_offset = fu_uefi_bgrt_get_yoffset (data->bgrt) +
				fu_uefi_bgrt_get_height (data->bgrt);

269
	/* header, payload and image has to add to zero */
Richard Hughes's avatar
Richard Hughes committed
270
271
272
273
274
275
	csum += fu_plugin_uefi_capsule_calc_checksum ((guint8 *) &capsule_header,
						      sizeof(capsule_header));
	csum += fu_plugin_uefi_capsule_calc_checksum ((guint8 *) &header,
						      sizeof(header));
	csum += fu_plugin_uefi_capsule_calc_checksum (g_bytes_get_data (blob, NULL),
						      g_bytes_get_size (blob));
276
277
	header.checksum = 0x100 - csum;

278
279
280
281
282
283
284
285
286
287
288
289
	/* write capsule file */
	size = g_output_stream_write (ostream, &capsule_header,
				      capsule_header.header_size, NULL, error);
	if (size < 0)
		return FALSE;
	size = g_output_stream_write (ostream, &header, sizeof(header), NULL, error);
	if (size < 0)
		return FALSE;
	size = g_output_stream_write_bytes (ostream, blob, NULL, error);
	if (size < 0)
		return FALSE;

290
	/* write display capsule location as UPDATE_INFO */
Richard Hughes's avatar
Richard Hughes committed
291
292
293
294
	return fu_uefi_device_write_update_info (FU_UEFI_DEVICE (device), fn,
						 "fwupd-ux-capsule",
						 FU_EFIVAR_GUID_UX_CAPSULE,
						 error);
295
296
}

297
static gboolean
Richard Hughes's avatar
Richard Hughes committed
298
fu_plugin_uefi_capsule_update_splash (FuPlugin *plugin, FuDevice *device, GError **error)
299
{
300
	FuPluginData *data = fu_plugin_get_data (plugin);
301
302
303
304
305
	guint best_idx = G_MAXUINT;
	guint32 lowest_border_pixels = G_MAXUINT;
	guint32 screen_height = 768;
	guint32 screen_width = 1024;
	g_autoptr(GBytes) image_bmp = NULL;
306

307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
	struct {
		guint32	 width;
		guint32	 height;
	} sizes[] = {
		{ 640, 480 },	/* matching the sizes in po/make-images */
		{ 800, 600 },
		{ 1024, 768 },
		{ 1920, 1080 },
		{ 3840, 2160 },
		{ 5120, 2880 },
		{ 5688, 3200 },
		{ 7680, 4320 },
		{ 0, 0 }
	};

322
323
324
	/* no UX capsule support, so deleting var if it exists */
	if (fu_device_has_custom_flag (device, "no-ux-capsule")) {
		g_debug ("not providing UX capsule");
325
		return fu_efivar_delete (FU_EFIVAR_GUID_FWUPDATE,
326
327
328
					    "fwupd-ux-capsule", error);
	}

329
	/* get the boot graphics resource table data */
330
331
332
333
334
335
336
	if (!fu_uefi_bgrt_get_supported (data->bgrt)) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "BGRT is not supported");
		return FALSE;
	}
337
	if (!fu_uefi_get_framebuffer_size (&screen_width, &screen_height, error))
338
		return FALSE;
339
	g_debug ("framebuffer size %" G_GUINT32_FORMAT " x%" G_GUINT32_FORMAT,
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
		 screen_width, screen_height);

	/* find the 'best sized' pre-generated image */
	for (guint i = 0; sizes[i].width != 0; i++) {
		guint32 border_pixels;

		/* disregard any images that are bigger than the screen */
		if (sizes[i].width > screen_width)
			continue;
		if (sizes[i].height > screen_height)
			continue;

		/* is this the best fit for the display */
		border_pixels = (screen_width * screen_height) -
				(sizes[i].width * sizes[i].height);
		if (border_pixels < lowest_border_pixels) {
			lowest_border_pixels = border_pixels;
			best_idx = i;
		}
	}
	if (best_idx == G_MAXUINT) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "failed to find a suitable image to use");
		return FALSE;
	}

	/* get the raw data */
Richard Hughes's avatar
Richard Hughes committed
369
370
371
	image_bmp = fu_plugin_uefi_capsule_get_splash_data (sizes[best_idx].width,
							    sizes[best_idx].height,
							    error);
372
373
374
375
	if (image_bmp == NULL)
		return FALSE;

	/* perform the upload */
Richard Hughes's avatar
Richard Hughes committed
376
	return fu_plugin_uefi_capsule_write_splash_data (plugin, device, image_bmp, error);
377
378
}

379
gboolean
380
381
382
383
384
fu_plugin_update (FuPlugin *plugin,
		  FuDevice *device,
		  GBytes *blob_fw,
		  FwupdInstallFlags flags,
		  GError **error)
385
{
386
	const gchar *str;
387
	guint32 flashes_left;
388
	g_autoptr(GError) error_splash = NULL;
389

390
391
392
393
394
395
396
397
398
399
400
	/* test the flash counter */
	flashes_left = fu_device_get_flashes_left (device);
	if (flashes_left > 0) {
		g_debug ("%s has %" G_GUINT32_FORMAT " flashes left",
			 fu_device_get_name (device),
			 flashes_left);
		if ((flags & FWUPD_INSTALL_FLAG_FORCE) == 0 && flashes_left <= 2) {
			g_set_error (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "%s only has %" G_GUINT32_FORMAT " flashes left -- "
401
				     "see https://github.com/fwupd/fwupd/wiki/Dell-TPM:-flashes-left for more information.",
402
403
404
405
406
				     fu_device_get_name (device), flashes_left);
			return FALSE;
		}
	}

407
408
409
410
	/* TRANSLATORS: this is shown when updating the firmware after the reboot */
	str = _("Installing firmware update…");
	g_assert (str != NULL);

411
	/* perform the update */
412
	fu_device_set_status (device, FWUPD_STATUS_SCHEDULING);
Richard Hughes's avatar
Richard Hughes committed
413
	if (!fu_plugin_uefi_capsule_update_splash (plugin, device, &error_splash)) {
414
415
		g_debug ("failed to upload UEFI UX capsule text: %s",
			 error_splash->message);
416
	}
417

418
	return fu_device_write_firmware (device, blob_fw, flags, error);
419
420
}

421
static void
Richard Hughes's avatar
Richard Hughes committed
422
fu_plugin_uefi_capsule_load_config (FuPlugin *plugin, FuDevice *device)
423
{
424
	gboolean disable_shim;
425
	gboolean fallback_removable_path;
426
427
428
429
430
431
432
433
434
435
	guint64 sz_reqd = FU_UEFI_COMMON_REQUIRED_ESP_FREE_SPACE;
	g_autofree gchar *require_esp_free_space = NULL;

	/* parse free space needed for ESP */
	require_esp_free_space = fu_plugin_get_config_value (plugin, "RequireESPFreeSpace");
	if (require_esp_free_space != NULL)
		sz_reqd = fu_common_strtoull (require_esp_free_space);
	fu_device_set_metadata_integer (device, "RequireESPFreeSpace", sz_reqd);

	/* shim used for SB or not? */
436
	disable_shim = fu_plugin_get_config_value_boolean (plugin, "DisableShimForSecureBoot");
437
438
	fu_device_set_metadata_boolean (device,
					"RequireShimForSecureBoot",
439
					!disable_shim);
440
441
442
443
444
445

	/* check if using UEFI removeable path */
	fallback_removable_path = fu_plugin_get_config_value_boolean (plugin, "FallbacktoRemovablePath");
	fu_device_set_metadata_boolean (device,
					"FallbacktoRemovablePath",
					fallback_removable_path);
446
447
}

448
static void
Richard Hughes's avatar
Richard Hughes committed
449
fu_plugin_uefi_capsule_register_proxy_device (FuPlugin *plugin, FuDevice *device)
450
{
451
	FuPluginData *data = fu_plugin_get_data (plugin);
452
	g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_dev (device);
453
	g_autoptr(GError) error_local = NULL;
454
455

	/* load all configuration variables */
Richard Hughes's avatar
Richard Hughes committed
456
	fu_plugin_uefi_capsule_load_config (plugin, FU_DEVICE (dev));
457
458
459
460
461
462
463
464
	if (data->esp == NULL)
		data->esp = fu_common_get_esp_default (&error_local);
	if (data->esp == NULL) {
		fu_device_set_update_error (FU_DEVICE (dev), error_local->message);
		fu_device_remove_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_UPDATABLE);
	} else {
		fu_uefi_device_set_esp (dev, data->esp);
	}
465
466
467
468
469
470
	fu_plugin_device_add (plugin, FU_DEVICE (dev));
}

void
fu_plugin_device_registered (FuPlugin *plugin, FuDevice *device)
{
471
	if (fu_device_get_metadata (device, FU_DEVICE_METADATA_UEFI_DEVICE_KIND) != NULL) {
472
473
474
475
476
		if (fu_device_get_guid_default (device) == NULL) {
			g_autofree gchar *dbg = fu_device_to_string (device);
			g_warning ("cannot create proxy device as no GUID: %s", dbg);
			return;
		}
Richard Hughes's avatar
Richard Hughes committed
477
		fu_plugin_uefi_capsule_register_proxy_device (plugin, device);
478
479
480
	}
}

481
static const gchar *
Richard Hughes's avatar
Richard Hughes committed
482
fu_plugin_uefi_capsule_uefi_type_to_string (FuUefiDeviceKind device_kind)
483
{
484
	if (device_kind == FU_UEFI_DEVICE_KIND_UNKNOWN)
485
		return "Unknown Firmware";
486
	if (device_kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE)
487
		return "System Firmware";
488
	if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE)
489
		return "Device Firmware";
490
	if (device_kind == FU_UEFI_DEVICE_KIND_UEFI_DRIVER)
491
		return "UEFI Driver";
492
	if (device_kind == FU_UEFI_DEVICE_KIND_FMP)
493
494
495
496
		return "Firmware Management Protocol";
	return NULL;
}

497
static gchar *
Richard Hughes's avatar
Richard Hughes committed
498
fu_plugin_uefi_capsule_get_name_for_type (FuPlugin *plugin, FuUefiDeviceKind device_kind)
499
500
501
502
{
	GString *display_name;

	/* set Display Name prefix for capsules that are not PCI cards */
Richard Hughes's avatar
Richard Hughes committed
503
	display_name = g_string_new (fu_plugin_uefi_capsule_uefi_type_to_string (device_kind));
504
	if (device_kind == FU_UEFI_DEVICE_KIND_DEVICE_FIRMWARE)
505
506
507
508
		g_string_prepend (display_name, "UEFI ");
	return g_string_free (display_name, FALSE);
}

509
static gboolean
Richard Hughes's avatar
Richard Hughes committed
510
fu_plugin_uefi_capsule_coldplug_device (FuPlugin *plugin, FuUefiDevice *dev, GError **error)
511
{
512
	FuUefiDeviceKind device_kind;
513
514
515
516

	/* probe to get add GUIDs (and hence any quirk fixups) */
	if (!fu_device_probe (FU_DEVICE (dev), error))
		return FALSE;
517
518
	if (!fu_device_setup (FU_DEVICE (dev), error))
		return FALSE;
519

520
	/* if not already set by quirks */
521
522
523
	if (fu_device_get_custom_flags (FU_DEVICE (dev)) == NULL &&
	    fu_plugin_has_custom_flag (plugin, "use-legacy-bootmgr-desc")) {
		fu_device_set_custom_flags (FU_DEVICE (dev), "use-legacy-bootmgr-desc");
524
525
	}

526
	/* set fallback name if nothing else is set */
527
	device_kind = fu_uefi_device_get_kind (dev);
528
	if (fu_device_get_name (FU_DEVICE (dev)) == NULL) {
529
		g_autofree gchar *name = NULL;
Richard Hughes's avatar
Richard Hughes committed
530
		name = fu_plugin_uefi_capsule_get_name_for_type (plugin, device_kind);
531
532
		if (name != NULL)
			fu_device_set_name (FU_DEVICE (dev), name);
533
534
535
536
		if (device_kind != FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) {
			fu_device_add_internal_flag (FU_DEVICE (dev),
						     FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY);
		}
537
	}
538
539
	/* set fallback vendor if nothing else is set */
	if (fu_device_get_vendor (FU_DEVICE (dev)) == NULL &&
540
	    device_kind == FU_UEFI_DEVICE_KIND_SYSTEM_FIRMWARE) {
541
542
543
544
		const gchar *vendor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER);
		if (vendor != NULL)
			fu_device_set_vendor (FU_DEVICE (dev), vendor);
	}
545

546
	/* set vendor ID as the BIOS vendor */
547
	if (device_kind != FU_UEFI_DEVICE_KIND_FMP) {
548
549
550
551
		const gchar *dmi_vendor;
		dmi_vendor = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_BIOS_VENDOR);
		if (dmi_vendor != NULL) {
			g_autofree gchar *vendor_id = g_strdup_printf ("DMI:%s", dmi_vendor);
552
			fu_device_add_vendor_id (FU_DEVICE (dev), vendor_id);
553
554
555
		}
	}

556
557
	/* success */
	return TRUE;
558
559
}

560
static void
Richard Hughes's avatar
Richard Hughes committed
561
fu_plugin_uefi_capsule_test_secure_boot (FuPlugin *plugin)
562
563
{
	const gchar *result_str = "Disabled";
564
	if (fu_efivar_secure_boot_enabled ())
565
566
567
568
		result_str = "Enabled";
	fu_plugin_add_report_metadata (plugin, "SecureBoot", result_str);
}

569
static gboolean
Richard Hughes's avatar
Richard Hughes committed
570
fu_plugin_uefi_capsule_smbios_enabled (FuPlugin *plugin, GError **error)
571
{
572
573
574
575
576
577
578
579
580
581
582
	const guint8 *data;
	gsize sz;
	g_autoptr(GBytes) bios_information = fu_plugin_get_smbios_data (plugin, 0);
	if (bios_information == NULL) {
		const gchar *tmp = g_getenv ("FWUPD_DELL_FAKE_SMBIOS");
		if (tmp != NULL)
			return TRUE;
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "SMBIOS not supported");
583
		return FALSE;
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
	}
	data = g_bytes_get_data (bios_information, &sz);
	if (sz < 0x13) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_INVALID_FILE,
			     "offset bigger than size %" G_GSIZE_FORMAT, sz);
		return FALSE;
	}
	if (data[1] < 0x13) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "SMBIOS 2.3 not supported");
		return FALSE;
	}
	if (!(data[0x13] & (1 << 3))) {
		g_set_error_literal (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "System does not support UEFI mode");
		return FALSE;
	}
607
608
609
610
611
612
	return TRUE;
}

gboolean
fu_plugin_startup (FuPlugin *plugin, GError **error)
{
613
	FuPluginData *data = fu_plugin_get_data (plugin);
614
	guint64 nvram_total;
615
	g_autofree gchar *esp_path = NULL;
616
	g_autofree gchar *nvram_total_str = NULL;
617
618
	g_autoptr(GError) error_local = NULL;

619
620
621
622
	/* don't let user's environment influence test suite failures */
	if (g_getenv ("FWUPD_UEFI_TEST") != NULL)
		return TRUE;

623
624
625
626
627
	/* some platforms have broken SMBIOS data */
	if (fu_plugin_has_custom_flag (plugin, "uefi-force-enable"))
		return TRUE;

	/* check SMBIOS for 'UEFI Specification is supported' */
Richard Hughes's avatar
Richard Hughes committed
628
	if (!fu_plugin_uefi_capsule_smbios_enabled (plugin, &error_local)) {
629
630
631
632
633
634
635
636
637
638
639
640
		g_autofree gchar *fw = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW);
		g_autofree gchar *fn = g_build_filename (fw, "efi", NULL);
		if (g_file_test (fn, G_FILE_TEST_EXISTS)) {
			g_warning ("SMBIOS BIOS Characteristics Extension Byte 2 is invalid -- "
				   "UEFI Specification is unsupported, but %s exists: %s",
				   fn, error_local->message);
			return TRUE;
		}
		g_propagate_error (error, g_steal_pointer (&error_local));
		return FALSE;
	}

641
642
643
	/* are the EFI dirs set up so we can update each device */
	if (!fu_efivar_supported (error))
		return FALSE;
644
645
646
	nvram_total = fu_efivar_space_used (error);
	if (nvram_total == G_MAXUINT64)
		return FALSE;
647
	nvram_total_str = g_strdup_printf ("%" G_GUINT64_FORMAT, nvram_total);
648
	fu_plugin_add_report_metadata (plugin, "EfivarNvramUsed", nvram_total_str);
649

650
651
652
653
654
655
656
657
658
659
660
	/* override the default ESP path */
	esp_path = fu_plugin_get_config_value (plugin, "OverrideESPMountPoint");
	if (esp_path != NULL) {
		data->esp = fu_common_get_esp_for_path (esp_path, error);
		if (data->esp == NULL) {
			g_prefix_error (error, "invalid OverrideESPMountPoint=%s "
					"specified in config: ", esp_path);
			return FALSE;
		}
	}

661
662
663
664
665
	/* test for invalid ESP in coldplug, and set the update-error rather
	 * than showing no output if the plugin had self-disabled here */
	return TRUE;
}

666
static gboolean
Richard Hughes's avatar
Richard Hughes committed
667
fu_plugin_uefi_capsule_ensure_efivarfs_rw (GError **error)
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
{
	g_autofree gchar *sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW);
	g_autofree gchar *sysfsefivardir = g_build_filename (sysfsfwdir, "efi", "efivars", NULL);
	g_autoptr(GUnixMountEntry) mount = g_unix_mount_at (sysfsefivardir, NULL);

	if (mount == NULL) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_NOT_FOUND,
			     "%s was not mounted", sysfsefivardir);
		return FALSE;
	}
	if (g_unix_mount_is_readonly (mount)) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_NOT_SUPPORTED,
			     "%s is read only", sysfsefivardir);
		return FALSE;
	}

	return TRUE;
}

691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
gboolean
fu_plugin_unlock (FuPlugin *plugin, FuDevice *device, GError **error)
{
	FuUefiDevice *device_uefi = FU_UEFI_DEVICE (device);
	FuDevice *device_alt = NULL;
	FwupdDeviceFlags device_flags_alt = 0;
	guint flashes_left = 0;
	guint flashes_left_alt = 0;

	if (fu_uefi_device_get_kind (device_uefi) !=
	    FU_UEFI_DEVICE_KIND_DELL_TPM_FIRMWARE) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_NOT_SUPPORTED,
			     "Unable to unlock %s",
			     fu_device_get_name (device));
		return FALSE;
	}

	/* for unlocking TPM1.2 <-> TPM2.0 switching */
	g_debug ("Unlocking upgrades for: %s (%s)", fu_device_get_name (device),
		 fu_device_get_id (device));
	device_alt = fu_device_get_alternate (device);
	if (device_alt == NULL) {
		g_set_error (error,
			     FWUPD_ERROR,
			     FWUPD_ERROR_NOT_SUPPORTED,
			     "No alternate device for %s",
			     fu_device_get_name (device));
		return FALSE;
	}
	g_debug ("Preventing upgrades for: %s (%s)", fu_device_get_name (device_alt),
		 fu_device_get_id (device_alt));

	flashes_left = fu_device_get_flashes_left (device);
	flashes_left_alt = fu_device_get_flashes_left (device_alt);
	if (flashes_left == 0) {
		/* flashes left == 0 on both means no flashes left */
		if (flashes_left_alt == 0) {
			g_set_error (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "ERROR: %s has no flashes left.",
				     fu_device_get_name (device));
		/* flashes left == 0 on just unlocking device is ownership */
		} else {
			g_set_error (error,
				     FWUPD_ERROR,
				     FWUPD_ERROR_NOT_SUPPORTED,
				     "ERROR: %s is currently OWNED. "
				     "Ownership must be removed to switch modes.",
				     fu_device_get_name (device_alt));
		}
		return FALSE;
	}

	/* clone the info from real device but prevent it from being flashed */
	device_flags_alt = fu_device_get_flags (device_alt);
	fu_device_set_flags (device, device_flags_alt);
750
	fu_device_remove_flag (device_alt, FWUPD_DEVICE_FLAG_UPDATABLE);
751
752

	/* make sure that this unlocked device can be updated */
753
754
	fu_device_set_version_format (device, FWUPD_VERSION_FORMAT_QUAD);
	fu_device_set_version (device, "0.0.0.0");
755
756
757
	return TRUE;
}

758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
static void
fu_plugin_uefi_update_state_notify_cb (GObject *object,
				       GParamSpec *pspec,
				       FuPlugin *plugin)
{
	FuDevice *device = FU_DEVICE (object);
	GPtrArray *devices;
	g_autofree gchar *msg = NULL;

	/* device is not in needs-reboot state */
	if (fu_device_get_update_state (device) != FWUPD_UPDATE_STATE_NEEDS_REBOOT)
		return;

	/* only do this on hardware that cannot coalesce multiple capsules */
	if (!fu_plugin_has_custom_flag (plugin, "no-coalesce"))
		return;

	/* mark every other device for this plugin as non-updatable */
	msg = g_strdup_printf ("Cannot update as %s [%s] needs reboot",
			       fu_device_get_name (device),
			       fu_device_get_id (device));
	devices = fu_plugin_get_devices (plugin);
	for (guint i = 0; i < devices->len; i++) {
		FuDevice *device_tmp = g_ptr_array_index (devices, i);
		if (device_tmp == device)
			continue;
		fu_device_remove_flag (device_tmp, FWUPD_DEVICE_FLAG_UPDATABLE);
		fu_device_add_flag (device_tmp, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN);
		fu_device_set_update_error (device_tmp, msg);
	}
}

790
791
792
gboolean
fu_plugin_coldplug (FuPlugin *plugin, GError **error)
{
793
	FuPluginData *data = fu_plugin_get_data (plugin);
794
	const gchar *str;
795
796
	g_autofree gchar *esrt_path = NULL;
	g_autofree gchar *sysfsfwdir = NULL;
797
	g_autoptr(GError) error_udisks2 = NULL;
798
	g_autoptr(GError) error_efivarfs = NULL;
799
800
	g_autoptr(GError) error_local = NULL;
	g_autoptr(GPtrArray) entries = NULL;
801

802
803
804
	/* get the directory of ESRT entries */
	sysfsfwdir = fu_common_get_path (FU_PATH_KIND_SYSFSDIR_FW);
	esrt_path = g_build_filename (sysfsfwdir, "efi", "esrt", NULL);
805
806
807
	entries = fu_uefi_get_esrt_entry_paths (esrt_path, error);
	if (entries == NULL)
		return FALSE;
808

809
	/* make sure that efivarfs is rw */
Richard Hughes's avatar
Richard Hughes committed
810
	if (!fu_plugin_uefi_capsule_ensure_efivarfs_rw (&error_efivarfs)) {
811
812
813
		fu_plugin_add_flag (plugin, FWUPD_PLUGIN_FLAG_EFIVAR_NOT_MOUNTED);
		fu_plugin_add_flag (plugin, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE);
		fu_plugin_add_flag (plugin, FWUPD_PLUGIN_FLAG_USER_WARNING);
814
		g_warning ("%s", error_efivarfs->message);
815
	}
816

817
818
	if (data->esp == NULL) {
		data->esp = fu_common_get_esp_default (&error_udisks2);
819
820
821
822
		if (data->esp == NULL) {
			fu_plugin_add_flag (plugin, FWUPD_PLUGIN_FLAG_ESP_NOT_FOUND);
			fu_plugin_add_flag (plugin, FWUPD_PLUGIN_FLAG_CLEAR_UPDATABLE);
			fu_plugin_add_flag (plugin, FWUPD_PLUGIN_FLAG_USER_WARNING);
823
			g_warning ("cannot find default ESP: %s", error_udisks2->message);
824
		}
825
826
	}

827
828
829
	/* add each device */
	for (guint i = 0; i < entries->len; i++) {
		const gchar *path = g_ptr_array_index (entries, i);
830
831
832
833
834
835
		g_autoptr(GError) error_parse = NULL;
		g_autoptr(FuUefiDevice) dev = fu_uefi_device_new_from_entry (path, &error_parse);
		if (dev == NULL) {
			g_warning ("failed to add %s: %s", path, error_parse->message);
			continue;
		}
836
		fu_device_set_quirks (FU_DEVICE (dev), fu_plugin_get_quirks (plugin));
837
838
		if (data->esp != NULL)
			fu_uefi_device_set_esp (FU_UEFI_DEVICE (dev), data->esp);
Richard Hughes's avatar
Richard Hughes committed
839
		if (!fu_plugin_uefi_capsule_coldplug_device (plugin, dev, error))
840
			return FALSE;
841
842
843
		fu_device_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_UPDATABLE);
		fu_device_add_flag (FU_DEVICE (dev), FWUPD_DEVICE_FLAG_USABLE_DURING_UPDATE);

844
		/* load all configuration variables */
Richard Hughes's avatar
Richard Hughes committed
845
		fu_plugin_uefi_capsule_load_config (plugin, FU_DEVICE (dev));
846
847
848
849
850
851

		/* watch in case we set needs-reboot in the engine */
		g_signal_connect (dev, "notify::update-state",
				  G_CALLBACK (fu_plugin_uefi_update_state_notify_cb),
				  plugin);

852
		fu_plugin_device_add (plugin, FU_DEVICE (dev));
853
	}
854
855

	/* for debugging problems later */
Richard Hughes's avatar
Richard Hughes committed
856
	fu_plugin_uefi_capsule_test_secure_boot (plugin);
857
858
859
860
861
	if (!fu_uefi_bgrt_setup (data->bgrt, &error_local))
		g_debug ("BGRT setup failed: %s", error_local->message);
	str = fu_uefi_bgrt_get_supported (data->bgrt) ? "Enabled" : "Disabled";
	g_debug ("UX Capsule support : %s", str);
	fu_plugin_add_report_metadata (plugin, "UEFIUXCapsule", str);
862

863
	return TRUE;
864
}