/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- * * Copyright (C) 2016-2017 Richard Hughes * * Licensed under the GNU General Public License Version 2 * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ #include "config.h" #include #include #include #include #include "fu-plugin.h" #include "fu-plugin-vfuncs.h" #ifndef UX_CAPSULE_GUID #define UX_CAPSULE_GUID EFI_GUID(0x3b8c8162,0x188c,0x46a4,0xaec9,0xbe,0x43,0xf1,0xd6,0x56,0x97) #endif void fu_plugin_init (FuPlugin *plugin) { fu_plugin_add_rule (plugin, FU_PLUGIN_RULE_RUN_AFTER, "upower"); } static gchar * fu_plugin_uefi_guid_to_string (efi_guid_t *guid_raw) { g_autofree gchar *guid = g_strdup ("00000000-0000-0000-0000-000000000000"); if (efi_guid_to_str (guid_raw, &guid) < 0) return NULL; return g_steal_pointer (&guid); } static fwup_resource * fu_plugin_uefi_find (fwup_resource_iter *iter, const gchar *guid_str, GError **error) { efi_guid_t *guid_raw; fwup_resource *re_matched = NULL; fwup_resource *re = NULL; /* get the hardware we're referencing */ while (fwup_resource_iter_next (iter, &re) > 0) { g_autofree gchar *guid_tmp = NULL; /* convert to strings */ fwup_get_guid (re, &guid_raw); guid_tmp = fu_plugin_uefi_guid_to_string (guid_raw); if (guid_tmp == NULL) { g_warning ("failed to convert guid to string"); continue; } /* FIXME: also match hardware_instance too */ if (g_strcmp0 (guid_str, guid_tmp) == 0) { re_matched = re; break; } } /* paradoxically, no hardware matched */ if (re_matched == NULL) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "No UEFI firmware matched %s", guid_str); } return re_matched; } static fwup_resource * fu_plugin_uefi_find_raw (fwup_resource_iter *iter, efi_guid_t *guid) { fwup_resource *re_matched = NULL; fwup_resource *re = NULL; while (fwup_resource_iter_next (iter, &re) > 0) { efi_guid_t *guid_tmp; fwup_get_guid (re, &guid_tmp); if (efi_guid_cmp (guid_tmp, guid) == 0) { re_matched = re; break; } } return re_matched; } static void _fwup_resource_iter_free (fwup_resource_iter *iter) { fwup_resource_iter_destroy (&iter); } G_DEFINE_AUTOPTR_CLEANUP_FUNC(fwup_resource_iter, _fwup_resource_iter_free); gboolean fu_plugin_clear_results (FuPlugin *plugin, FuDevice *device, GError **error) { fwup_resource *re = NULL; g_autoptr(fwup_resource_iter) iter = NULL; /* get the hardware we're referencing */ fwup_resource_iter_create (&iter); re = fu_plugin_uefi_find (iter, fu_device_get_guid_default (device), error); if (re == NULL) return FALSE; if (fwup_clear_status (re) < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot create clear UEFI status for %s", fu_device_get_guid_default (device)); return FALSE; } return TRUE; } gboolean fu_plugin_get_results (FuPlugin *plugin, FuDevice *device, GError **error) { const gchar *tmp; fwup_resource *re = NULL; guint32 status = 0; guint32 version = 0; time_t when = 0; g_autoptr(fwup_resource_iter) iter = NULL; /* get the hardware we're referencing */ fwup_resource_iter_create (&iter); re = fu_plugin_uefi_find (iter, fu_device_get_guid_default (device), error); if (re == NULL) return FALSE; if (fwup_get_last_attempt_info (re, &version, &status, &when) < 0) { g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot get UEFI status for %s", fu_device_get_guid_default (device)); return FALSE; } if (status == FWUP_LAST_ATTEMPT_STATUS_SUCCESS) { fu_device_set_update_state (device, FWUPD_UPDATE_STATE_SUCCESS); } else { g_autofree gchar *err_msg = NULL; g_autofree gchar *version_str = g_strdup_printf ("%u", version); fu_device_set_update_state (device, FWUPD_UPDATE_STATE_FAILED); tmp = fwup_last_attempt_status_to_string (status); if (tmp == NULL) { err_msg = g_strdup_printf ("failed to update to %s", version_str); } else { err_msg = g_strdup_printf ("failed to update to %s: %s", version_str, tmp); } fu_device_set_update_error (device, err_msg); } return TRUE; } static gboolean fu_plugin_uefi_update_resource (fwup_resource *re, guint64 hardware_instance, GBytes *blob, GError **error) { int rc; rc = fwup_set_up_update_with_buf (re, hardware_instance, g_bytes_get_data (blob, NULL), g_bytes_get_size (blob)); if (rc < 0) { g_autoptr(GString) str = g_string_new (NULL); rc = 1; for (int i = 0; rc > 0; i++) { char *filename = NULL; char *function = NULL; char *message = NULL; int line = 0; int err = 0; rc = efi_error_get (i, &filename, &function, &line, &message, &err); if (rc <= 0) break; g_string_append_printf (str, "{error #%d} %s:%d %s(): %s: %s\t", i, filename, line, function, message, strerror (err)); } if (str->len > 1) g_string_truncate (str, str->len - 1); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "UEFI firmware update failed: %s", str->str); return FALSE; } return TRUE; } static GBytes * fu_plugin_uefi_get_splash_data (guint width, guint height, GError **error) { const gchar * const *langs = g_get_language_names (); const gchar *localedir = LOCALEDIR; const gsize chunk_size = 1024 * 1024; gsize buf_idx = 0; gsize buf_sz = chunk_size; gssize len; g_autofree gchar *basename = NULL; g_autofree guint8 *buf = NULL; g_autoptr(GBytes) compressed_data = NULL; g_autoptr(GConverter) conv = NULL; g_autoptr(GInputStream) stream_compressed = NULL; g_autoptr(GInputStream) stream_raw = NULL; /* ensure this is sane */ if (!g_str_has_prefix (localedir, "/")) localedir = "/usr/share/locale"; /* find the closest locale match, falling back to `en` and `C` */ basename = g_strdup_printf ("fwupd-%u-%u.bmp.gz", width, height); for (guint i = 0; langs[i] != NULL; i++) { g_autofree gchar *fn = NULL; if (g_str_has_suffix (langs[i], ".UTF-8")) continue; fn = g_build_filename (localedir, langs[i], "LC_IMAGES", basename, NULL); if (g_file_test (fn, G_FILE_TEST_EXISTS)) { compressed_data = fu_common_get_contents_bytes (fn, error); if (compressed_data == NULL) return NULL; break; } g_debug ("no %s found", fn); } /* we found nothing */ if (compressed_data == NULL) { g_autofree gchar *tmp = g_strjoinv (",", (gchar **) langs); g_set_error (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get splash file for %s in %s", tmp, localedir); return NULL; } /* decompress data */ stream_compressed = g_memory_input_stream_new_from_bytes (compressed_data); conv = G_CONVERTER (g_zlib_decompressor_new (G_ZLIB_COMPRESSOR_FORMAT_GZIP)); stream_raw = g_converter_input_stream_new (stream_compressed, conv); buf = g_malloc0 (buf_sz); while ((len = g_input_stream_read (stream_raw, buf + buf_idx, buf_sz - buf_idx, NULL, error)) > 0) { buf_idx += len; if (buf_sz - buf_idx < chunk_size) { buf_sz += chunk_size; buf = g_realloc (buf, buf_sz); } } if (len < 0) { g_prefix_error (error, "failed to decompress file: "); return NULL; } g_debug ("decompressed image to %" G_GSIZE_FORMAT "kb", buf_idx / 1024); return g_bytes_new_take (g_steal_pointer (&buf), buf_idx); } static gboolean fu_plugin_uefi_update_splash (GError **error) { fwup_resource *re = NULL; guint best_idx = G_MAXUINT; guint32 lowest_border_pixels = G_MAXUINT; #ifdef HAVE_FWUP_GET_BGRT_INFO int rc; #endif guint32 screen_height = 768; guint32 screen_width = 1024; g_autoptr(fwup_resource_iter) iter = NULL; g_autoptr(GBytes) image_bmp = NULL; 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 } }; /* is this supported? */ fwup_resource_iter_create (&iter); re = fu_plugin_uefi_find_raw (iter, &UX_CAPSULE_GUID); if (re == NULL) return TRUE; /* get the boot graphics resource table data */ #ifdef HAVE_FWUP_GET_BGRT_INFO rc = fwup_get_ux_capsule_info (&screen_width, &screen_height); if (rc < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to get BGRT screen size"); return FALSE; } g_debug ("BGRT screen size %" G_GUINT32_FORMAT " x%" G_GUINT32_FORMAT, screen_width, screen_height); #endif /* 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 */ image_bmp = fu_plugin_uefi_get_splash_data (sizes[best_idx].width, sizes[best_idx].height, error); if (image_bmp == NULL) return FALSE; /* perform the upload */ return fu_plugin_uefi_update_resource (re, 0, image_bmp, error); } gboolean fu_plugin_update (FuPlugin *plugin, FuDevice *device, GBytes *blob_fw, FwupdInstallFlags flags, GError **error) { fwup_resource *re = NULL; guint64 hardware_instance = 0; /* FIXME */ g_autoptr(fwup_resource_iter) iter = NULL; const gchar *str; g_autofree gchar *efibootmgr_path = NULL; g_autofree gchar *boot_variables = NULL; g_autoptr(GError) error_splash = NULL; /* get the hardware we're referencing */ fwup_resource_iter_create (&iter); re = fu_plugin_uefi_find (iter, fu_device_get_guid_default (device), error); if (re == NULL) return FALSE; /* TRANSLATORS: this is shown when updating the firmware after the reboot */ str = _("Installing firmware update…"); g_assert (str != NULL); /* perform the update */ g_debug ("Performing UEFI capsule update"); fu_device_set_status (device, FWUPD_STATUS_SCHEDULING); if (!fu_plugin_uefi_update_splash (&error_splash)) { g_warning ("failed to upload BGRT splash text: %s", error_splash->message); } if (!fu_plugin_uefi_update_resource (re, hardware_instance, blob_fw, error)) return FALSE; /* record boot information to system log for future debugging */ efibootmgr_path = g_find_program_in_path ("efibootmgr"); if (efibootmgr_path != NULL) { if (!g_spawn_command_line_sync ("efibootmgr -v", &boot_variables, NULL, NULL, error)) return FALSE; g_message ("Boot Information:\n%s", boot_variables); } return TRUE; } static AsVersionParseFlag fu_plugin_uefi_get_version_format_for_type (FuPlugin *plugin, guint32 uefi_type) { const gchar *content; const gchar *quirk; /* we have no information for devices */ if (uefi_type == FWUP_RESOURCE_TYPE_DEVICE_FIRMWARE) return AS_VERSION_PARSE_FLAG_USE_TRIPLET; content = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_MANUFACTURER); if (content == NULL) return AS_VERSION_PARSE_FLAG_USE_TRIPLET; /* any quirks match */ quirk = fu_plugin_lookup_quirk_by_id (plugin, FU_QUIRKS_UEFI_VERSION_FORMAT, content); if (g_strcmp0 (quirk, "none") == 0) return AS_VERSION_PARSE_FLAG_NONE; /* fall back */ return AS_VERSION_PARSE_FLAG_USE_TRIPLET; } gboolean fu_plugin_unlock (FuPlugin *plugin, FuDevice *device, GError **error) { gint rc; g_debug ("unlocking UEFI device %s", fu_device_get_id (device)); rc = fwup_enable_esrt(); if (rc <= 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "failed to unlock UEFI device"); return FALSE; } else if (rc == 1) g_debug ("UEFI device is already unlocked"); else if (rc == 2) g_debug ("Successfully unlocked UEFI device"); else if (rc == 3) g_debug ("UEFI device will be unlocked on next reboot"); return TRUE; } static const gchar * fu_plugin_uefi_uefi_type_to_string (guint32 uefi_type) { if (uefi_type == FWUP_RESOURCE_TYPE_UNKNOWN) return "Unknown Firmware"; if (uefi_type == FWUP_RESOURCE_TYPE_SYSTEM_FIRMWARE) return "System Firmware"; if (uefi_type == FWUP_RESOURCE_TYPE_DEVICE_FIRMWARE) return "Device Firmware"; if (uefi_type == FWUP_RESOURCE_TYPE_UEFI_DRIVER) return "UEFI Driver"; if (uefi_type == FWUP_RESOURCE_TYPE_FMP) return "Firmware Management Protocol"; return NULL; } static gchar * fu_plugin_uefi_get_name_for_type (FuPlugin *plugin, guint32 uefi_type) { GString *display_name; /* set Display Name prefix for capsules that are not PCI cards */ display_name = g_string_new (fu_plugin_uefi_uefi_type_to_string (uefi_type)); if (uefi_type == FWUP_RESOURCE_TYPE_DEVICE_FIRMWARE) { g_string_prepend (display_name, "UEFI "); } else { const gchar *tmp; tmp = fu_plugin_get_dmi_value (plugin, FU_HWIDS_KEY_PRODUCT_NAME); if (tmp != NULL && tmp[0] != '\0') { g_string_prepend (display_name, " "); g_string_prepend (display_name, tmp); } } return g_string_free (display_name, FALSE); } static void fu_plugin_uefi_coldplug_resource (FuPlugin *plugin, fwup_resource *re) { AsVersionParseFlag parse_flags; efi_guid_t *guid_raw; guint32 uefi_type; guint32 version_raw; guint64 hardware_instance = 0; /* FIXME */ g_autofree gchar *guid = NULL; g_autofree gchar *id = NULL; g_autofree gchar *name = NULL; g_autofree gchar *version_lowest = NULL; g_autofree gchar *version = NULL; g_autoptr(FuDevice) dev = NULL; /* detect the fake GUID used for uploading the image */ fwup_get_guid (re, &guid_raw); if (efi_guid_cmp (guid_raw, &UX_CAPSULE_GUID) == 0) { g_debug ("skipping entry, detected fake BGRT"); return; } /* convert to strings */ guid = fu_plugin_uefi_guid_to_string (guid_raw); if (guid == NULL) { g_warning ("failed to convert guid to string"); return; } fwup_get_fw_type (re, &uefi_type); parse_flags = fu_plugin_uefi_get_version_format_for_type (plugin, uefi_type); fwup_get_fw_version (re, &version_raw); version = as_utils_version_from_uint32 (version_raw, parse_flags); id = g_strdup_printf ("UEFI-%s-dev%" G_GUINT64_FORMAT, guid, hardware_instance); dev = fu_device_new (); if (uefi_type == FWUP_RESOURCE_TYPE_DEVICE_FIRMWARE) { /* nothing better in the icon naming spec */ fu_device_add_icon (dev, "audio-card"); } else { /* this is probably system firmware */ fu_device_add_icon (dev, "computer"); } fu_device_set_id (dev, id); fu_device_add_guid (dev, guid); fu_device_set_version (dev, version); name = fu_plugin_uefi_get_name_for_type (plugin, uefi_type); if (name != NULL) fu_device_set_name (dev, name); fwup_get_lowest_supported_fw_version (re, &version_raw); if (version_raw != 0) { version_lowest = as_utils_version_from_uint32 (version_raw, parse_flags); fu_device_set_version_lowest (dev, version_lowest); } fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_INTERNAL); if (g_file_test ("/sys/firmware/efi/efivars", G_FILE_TEST_IS_DIR) || g_file_test ("/sys/firmware/efi/vars", G_FILE_TEST_IS_DIR)) { fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_NEEDS_REBOOT); } else { g_warning ("Kernel support for EFI variables missing"); } fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_plugin_device_add (plugin, dev); } gboolean fu_plugin_coldplug (FuPlugin *plugin, GError **error) { fwup_resource *re; gint supported; g_autoptr(fwup_resource_iter) iter = NULL; /* supported = 0 : ESRT unspported supported = 1 : unlocked, ESRT supported supported = 2 : it is locked but can be unlocked to support ESRT supported = 3 : it is locked, has been marked to be unlocked on next boot calling unlock again is OK. */ supported = fwup_supported (); if (supported == 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "UEFI firmware updating not supported"); return FALSE; } if (supported == 2) { g_autoptr(FuDevice) dev = fu_device_new (); fu_device_set_id (dev, "UEFI-dummy-dev0"); fu_device_add_guid (dev, "2d47f29b-83a2-4f31-a2e8-63474f4d4c2e"); fu_device_set_version (dev, "0"); fu_device_add_icon (dev, "computer"); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag (dev, FWUPD_DEVICE_FLAG_LOCKED); fu_plugin_device_add (plugin, dev); return TRUE; } /* add each device */ if (fwup_resource_iter_create (&iter) < 0) { g_set_error_literal (error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "Cannot create fwup iter"); return FALSE; } while (fwup_resource_iter_next (iter, &re) > 0) fu_plugin_uefi_coldplug_resource (plugin, re); return TRUE; }