/* * Copyright (C) 2022 Intel, Inc * Copyright (C) 2022 Richard Hughes * * SPDX-License-Identifier: LGPL-2.1+ OR Apache-2.0 */ #include "config.h" #include "fu-igsc-aux-device.h" #include "fu-igsc-code-firmware.h" #include "fu-igsc-device.h" #include "fu-igsc-oprom-device.h" struct _FuIgscDevice { FuMeiDevice parent_instance; gchar *project; guint32 hw_sku; guint16 subsystem_vendor; guint16 subsystem_model; gboolean oprom_code_devid_enforcement; }; #define FU_IGSC_DEVICE_FLAG_HAS_AUX (1 << 0) #define FU_IGSC_DEVICE_FLAG_HAS_OPROM (1 << 1) #define FU_IGSC_DEVICE_MEI_WRITE_TIMEOUT 60000 /* 60 sec */ #define FU_IGSC_DEVICE_MEI_READ_TIMEOUT 480000 /* 480 sec */ G_DEFINE_TYPE(FuIgscDevice, fu_igsc_device, FU_TYPE_MEI_DEVICE) struct igsc_fw_version { char project[4]; /* project code name */ guint16 hotfix; guint16 build; } __attribute__((packed)); #define GSC_FWU_STATUS_SUCCESS 0x0 #define GSC_FWU_STATUS_SIZE_ERROR 0x5 #define GSC_FWU_STATUS_UPDATE_OPROM_INVALID_STRUCTURE 0x1035 #define GSC_FWU_STATUS_UPDATE_OPROM_SECTION_NOT_EXIST 0x1032 #define GSC_FWU_STATUS_INVALID_COMMAND 0x8D #define GSC_FWU_STATUS_INVALID_PARAMS 0x85 #define GSC_FWU_STATUS_FAILURE 0x9E #define GSC_FWU_GET_CONFIG_FORMAT_VERSION 0x1 static void fu_igsc_device_to_string(FuDevice *device, guint idt, GString *str) { FuIgscDevice *self = FU_IGSC_DEVICE(device); FU_DEVICE_CLASS(fu_igsc_device_parent_class)->to_string(device, idt, str); fu_string_append(str, idt, "Project", self->project); fu_string_append_kx(str, idt, "HwSku", self->hw_sku); fu_string_append_kx(str, idt, "SubsystemVendor", self->subsystem_vendor); fu_string_append_kx(str, idt, "SubsystemModel", self->subsystem_model); fu_string_append_kb(str, idt, "OpromCodeDevidEnforcement", self->oprom_code_devid_enforcement); } gboolean fu_igsc_device_get_oprom_code_devid_enforcement(FuIgscDevice *self) { g_return_val_if_fail(FU_IS_IGSC_DEVICE(self), FALSE); return self->oprom_code_devid_enforcement; } guint16 fu_igsc_device_get_ssvid(FuIgscDevice *self) { g_return_val_if_fail(FU_IS_IGSC_DEVICE(self), G_MAXUINT16); return self->subsystem_vendor; } guint16 fu_igsc_device_get_ssdid(FuIgscDevice *self) { g_return_val_if_fail(FU_IS_IGSC_DEVICE(self), G_MAXUINT16); return self->subsystem_model; } static gboolean fu_igsc_device_heci_validate_response_header(FuIgscDevice *self, struct gsc_fwu_heci_response *resp_header, enum gsc_fwu_heci_command_id command_id, GError **error) { if (resp_header->header.command_id != command_id) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid command ID (%d): ", resp_header->header.command_id); return FALSE; } if (!resp_header->header.is_response) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "not a response"); return FALSE; } if (resp_header->status != GSC_FWU_STATUS_SUCCESS) { const gchar *msg; switch (resp_header->status) { case GSC_FWU_STATUS_SIZE_ERROR: msg = "num of bytes to read/write/erase is bigger than partition size"; break; case GSC_FWU_STATUS_UPDATE_OPROM_INVALID_STRUCTURE: msg = "wrong oprom signature"; break; case GSC_FWU_STATUS_UPDATE_OPROM_SECTION_NOT_EXIST: msg = "update oprom section does not exists on flash"; break; case GSC_FWU_STATUS_INVALID_COMMAND: msg = "invalid HECI message sent"; break; case GSC_FWU_STATUS_INVALID_PARAMS: msg = "invalid command parameters"; break; case GSC_FWU_STATUS_FAILURE: /* fall through */ default: msg = "general firmware error"; break; } g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "HECI message failed: %s [0x%x]: ", msg, resp_header->status); return FALSE; } if (resp_header->reserved != 0) { g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "HECI message response is leaking data"); return FALSE; } /* success */ return TRUE; } static gboolean fu_igsc_device_command(FuIgscDevice *self, const guint8 *req_buf, gsize req_bufsz, guint8 *resp_buf, gsize resp_bufsz, GError **error) { gsize resp_readsz = 0; if (!fu_mei_device_write(FU_MEI_DEVICE(self), req_buf, req_bufsz, FU_IGSC_DEVICE_MEI_WRITE_TIMEOUT, error)) return FALSE; if (!fu_mei_device_read(FU_MEI_DEVICE(self), resp_buf, resp_bufsz, &resp_readsz, FU_IGSC_DEVICE_MEI_READ_TIMEOUT, error)) return FALSE; if (resp_readsz != resp_bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "read 0x%x bytes but expected 0x%x", (guint)resp_readsz, (guint)resp_bufsz); return FALSE; } return TRUE; } gboolean fu_igsc_device_get_version_raw(FuIgscDevice *self, enum gsc_fwu_heci_partition_version partition, guint8 *buf, gsize bufsz, GError **error) { struct gsc_fwu_heci_version_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_GET_IP_VERSION, .partition = partition}; guint8 res_buf[100]; struct gsc_fwu_heci_version_resp *res = (struct gsc_fwu_heci_version_resp *)res_buf; if (!fu_igsc_device_command(self, (const guint8 *)&req, sizeof(req), res_buf, sizeof(struct gsc_fwu_heci_version_resp) + bufsz, error)) { g_prefix_error(error, "invalid HECI message response: "); return FALSE; } if (!fu_igsc_device_heci_validate_response_header(self, &res->response, req.header.command_id, error)) return FALSE; if (res->partition != partition) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid HECI message response payload: 0x%x: ", res->partition); return FALSE; } if (bufsz > 0 && res->version_length != bufsz) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid HECI message response version_length: 0x%x, expected 0x%x: ", res->version_length, (guint)bufsz); return FALSE; } if (buf != NULL) { if (!fu_memcpy_safe(buf, bufsz, 0x0, /* dst */ res->version, res->version_length, 0x0, /* src*/ res->version_length, error)) { return FALSE; } } /* success */ return TRUE; } gboolean fu_igsc_device_get_aux_version(FuIgscDevice *self, guint32 *oem_version, guint16 *major_version, guint16 *major_vcn, GError **error) { struct gsc_fw_data_heci_version_req req = { .header.command_id = GSC_FWU_HECI_COMMAND_ID_GET_GFX_DATA_UPDATE_INFO}; struct gsc_fw_data_heci_version_resp res = {0x0}; if (!fu_igsc_device_command(self, (const guint8 *)&req, sizeof(req), (guint8 *)&res, sizeof(res), error)) return FALSE; if (!fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error)) return FALSE; /* success */ *major_vcn = res.major_vcn; *major_version = res.major_version; if (res.oem_version_fitb_valid) { *oem_version = res.oem_version_fitb; } else { *oem_version = res.oem_version_nvm; } return TRUE; } static gboolean fu_igsc_device_get_subsystem_ids(FuIgscDevice *self, GError **error) { struct gsc_fwu_heci_get_subsystem_ids_message_req req = { .header.command_id = GSC_FWU_HECI_COMMAND_ID_GET_SUBSYSTEM_IDS}; struct gsc_fwu_heci_get_subsystem_ids_message_resp res = {0x0}; if (!fu_igsc_device_command(self, (const guint8 *)&req, sizeof(req), (guint8 *)&res, sizeof(res), error)) return FALSE; if (!fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error)) return FALSE; /* success */ self->subsystem_vendor = res.ssvid; self->subsystem_model = res.ssdid; return TRUE; } #define GSC_IFWI_TAG_SOC2_SKU_BIT 0x1 #define GSC_IFWI_TAG_SOC3_SKU_BIT 0x2 #define GSC_IFWI_TAG_SOC1_SKU_BIT 0x4 #define GSC_DG2_SKUID_SOC1 0 #define GSC_DG2_SKUID_SOC2 1 #define GSC_DG2_SKUID_SOC3 2 static gboolean fu_igsc_device_get_config(FuIgscDevice *self, GError **error) { struct gsc_fwu_heci_get_config_message_req req = { .header.command_id = GSC_FWU_HECI_COMMAND_ID_GET_CONFIG, }; struct gsc_fwu_heci_get_config_message_resp res = {0x0}; if (!fu_igsc_device_command(self, (const guint8 *)&req, sizeof(req), (guint8 *)&res, sizeof(res), error)) { g_prefix_error(error, "invalid HECI message response: "); return FALSE; } if (!fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error)) return FALSE; if (res.format_version != GSC_FWU_GET_CONFIG_FORMAT_VERSION) { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid config version 0x%x, expected 0x%x", res.format_version, (guint)GSC_FWU_GET_CONFIG_FORMAT_VERSION); return FALSE; } /* convert to firmware bit mask for easier comparison */ if (res.hw_sku == GSC_DG2_SKUID_SOC1) { self->hw_sku = GSC_IFWI_TAG_SOC1_SKU_BIT; } else if (res.hw_sku == GSC_DG2_SKUID_SOC3) { self->hw_sku = GSC_IFWI_TAG_SOC3_SKU_BIT; } else if (res.hw_sku == GSC_DG2_SKUID_SOC2) { self->hw_sku = GSC_IFWI_TAG_SOC2_SKU_BIT; } else { g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "invalid hw sku 0x%x, expected 0..2", res.hw_sku); return FALSE; } self->oprom_code_devid_enforcement = res.oprom_code_devid_enforcement; /* success */ return TRUE; } static gboolean fu_igsc_device_open(FuDevice *device, GError **error) { /* open then create context */ if (!FU_DEVICE_CLASS(fu_igsc_device_parent_class)->open(device, error)) return FALSE; return fu_mei_device_connect(FU_MEI_DEVICE(device), 0, error); } static gboolean fu_igsc_device_setup(FuDevice *device, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); FuContext *ctx = fu_device_get_context(FU_DEVICE(self)); g_autofree gchar *version = NULL; struct igsc_fw_version fw_code_version; /* get current version */ if (!fu_igsc_device_get_version_raw(self, GSC_FWU_HECI_PART_VERSION_GFX_FW, (guint8 *)&fw_code_version, sizeof(fw_code_version), error)) { g_prefix_error(error, "cannot cannot get fw version: "); return FALSE; } self->project = g_strdup_printf("%c%c%c%c", fw_code_version.project[0], fw_code_version.project[1], fw_code_version.project[2], fw_code_version.project[3]); version = g_strdup_printf("%u.%u", fw_code_version.hotfix, fw_code_version.build); fu_device_set_version(device, version); /* get hardware SKU if supported */ if (g_strcmp0(self->project, "DG02") == 0) { if (!fu_igsc_device_get_config(self, error)) { g_prefix_error(error, "cannot cannot get SKU: "); return FALSE; } } /* allow vendors to differentiate their products */ if (!fu_igsc_device_get_subsystem_ids(self, error)) return FALSE; if (self->subsystem_vendor != 0x0 && self->subsystem_model != 0x0) { g_autofree gchar *subsys = g_strdup_printf("%04X%04X", self->subsystem_vendor, self->subsystem_model); fu_device_add_instance_str(device, "SUBSYS", subsys); } /* some devices have children */ if (fu_device_has_private_flag(device, FU_IGSC_DEVICE_FLAG_HAS_AUX)) { g_autoptr(FuIgscAuxDevice) device_child = fu_igsc_aux_device_new(ctx); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(device_child)); } if (fu_device_has_private_flag(device, FU_IGSC_DEVICE_FLAG_HAS_OPROM)) { g_autoptr(FuIgscOpromDevice) device_code = NULL; g_autoptr(FuIgscOpromDevice) device_data = NULL; device_code = fu_igsc_oprom_device_new(ctx, GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_CODE); device_data = fu_igsc_oprom_device_new(ctx, GSC_FWU_HECI_PAYLOAD_TYPE_OPROM_DATA); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(device_code)); fu_device_add_child(FU_DEVICE(self), FU_DEVICE(device_data)); } /* success */ return TRUE; } /* @line is indexed from 1 */ static gboolean fu_igsc_device_get_fw_status(FuIgscDevice *self, guint line, guint32 *fw_status, GError **error) { guint64 tmp64 = 0; g_autofree gchar *tmp = NULL; g_autofree gchar *hex = NULL; g_return_val_if_fail(line >= 1, FALSE); /* read value and convert to hex */ tmp = fu_mei_device_get_fw_status(FU_MEI_DEVICE(self), line - 1, error); if (tmp == NULL) { g_prefix_error(error, "device is corrupted: "); return FALSE; } hex = g_strdup_printf("0x%s", tmp); if (!fu_strtoull(hex, &tmp64, 0x1, G_MAXUINT32 - 0x1, error)) { g_prefix_error(error, "fw_status %s is invalid: ", tmp); return FALSE; } /* success */ if (fw_status != NULL) *fw_status = tmp64; return TRUE; } static gboolean fu_igsc_device_probe(FuDevice *device, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); /* check firmware status */ if (!fu_igsc_device_get_fw_status(self, 1, NULL, error)) return FALSE; /* add extra instance IDs */ fu_device_add_instance_str(device, "PART", "FWCODE"); if (!fu_device_build_instance_id(device, error, "MEI", "VEN", "DEV", "PART", NULL)) return FALSE; return fu_device_build_instance_id(device, error, "MEI", "VEN", "DEV", "SUBSYS", "PART", NULL); } static FuFirmware * fu_igsc_device_prepare_firmware(FuDevice *device, GBytes *fw, FwupdInstallFlags flags, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); g_autoptr(FuFirmware) firmware = fu_igsc_code_firmware_new(); /* check project code */ if (!fu_firmware_parse(firmware, fw, flags, error)) return NULL; if (g_strcmp0(self->project, fu_firmware_get_id(firmware)) != 0) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware is for a different project, got %s, expected %s", fu_firmware_get_id(firmware), self->project); return NULL; } if (self->hw_sku != fu_igsc_code_firmware_get_hw_sku(FU_IGSC_CODE_FIRMWARE(firmware))) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "firmware is for a different SKU, got 0x%x, expected 0x%x", fu_igsc_code_firmware_get_hw_sku(FU_IGSC_CODE_FIRMWARE(firmware)), self->hw_sku); return NULL; } /* success */ return g_steal_pointer(&firmware); } static gboolean fu_igsc_device_update_end(FuIgscDevice *self, GError **error) { struct gsc_fwu_heci_end_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_END}; return fu_mei_device_write(FU_MEI_DEVICE(self), (const guint8 *)&req, sizeof(req), FU_IGSC_DEVICE_MEI_WRITE_TIMEOUT, error); } static gboolean fu_igsc_device_update_data(FuIgscDevice *self, const guint8 *data, guint32 length, GError **error) { struct gsc_fwu_heci_data_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_DATA, .data_length = length}; struct gsc_fwu_heci_data_resp res = {0x0}; g_autoptr(GByteArray) buf = g_byte_array_new(); g_byte_array_append(buf, (const guint8 *)&req, sizeof(req)); g_byte_array_append(buf, data, length); if (!fu_igsc_device_command(self, buf->data, buf->len, (guint8 *)&res, sizeof(res), error)) return FALSE; return fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error); } static gboolean fu_igsc_device_update_start(FuIgscDevice *self, guint32 payload_type, GBytes *fw_info, GBytes *fw, GError **error) { struct gsc_fwu_heci_start_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_START, .payload_type = payload_type, .update_img_length = g_bytes_get_size(fw), .flags = {0}}; struct gsc_fwu_heci_start_resp res = {0x0}; g_autoptr(GByteArray) buf = g_byte_array_new(); g_byte_array_append(buf, (const guint8 *)&req, sizeof(req)); if (fw_info != NULL) fu_byte_array_append_bytes(buf, fw_info); if (!fu_igsc_device_command(self, buf->data, buf->len, (guint8 *)&res, sizeof(res), error)) return FALSE; return fu_igsc_device_heci_validate_response_header(self, &res.response, req.header.command_id, error); } static gboolean fu_igsc_device_no_update(FuIgscDevice *self, GError **error) { struct gsc_fwu_heci_no_update_req req = {.header.command_id = GSC_FWU_HECI_COMMAND_ID_NO_UPDATE}; return fu_mei_device_write(FU_MEI_DEVICE(self), (const guint8 *)&req, sizeof(req), FU_IGSC_DEVICE_MEI_WRITE_TIMEOUT, error); } static gboolean fu_igsc_device_write_chunks(FuIgscDevice *self, GPtrArray *chunks, FuProgress *progress, GError **error) { fu_progress_set_id(progress, G_STRLOC); fu_progress_set_steps(progress, chunks->len); for (guint i = 0; i < chunks->len; i++) { FuChunk *chk = g_ptr_array_index(chunks, i); if (!fu_igsc_device_update_data(self, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk), error)) { g_prefix_error(error, "failed on chunk %u (@0x%x): ", i, fu_chunk_get_address(chk)); return FALSE; } fu_progress_step_done(progress); } return TRUE; } /* the expectation is that it will fail eventually */ static gboolean fu_igsc_device_wait_for_reset(FuIgscDevice *self, GError **error) { struct igsc_fw_version fw_code_version; for (guint i = 0; i < 20; i++) { if (!fu_igsc_device_get_version_raw(self, GSC_FWU_HECI_PART_VERSION_GFX_FW, (guint8 *)&fw_code_version, sizeof(fw_code_version), NULL)) return TRUE; g_usleep(1000 * 100); } g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_TIMED_OUT, "device did not reset"); return FALSE; } static gboolean fu_igsc_device_reconnect_cb(FuDevice *self, gpointer user_data, GError **error) { return fu_mei_device_connect(FU_MEI_DEVICE(self), 0, error); } gboolean fu_igsc_device_write_blob(FuIgscDevice *self, enum gsc_fwu_heci_payload_type payload_type, GBytes *fw_info, GBytes *fw, FuProgress *progress, GError **error) { gboolean cp_mode; guint32 sts5 = 0; gsize payloadsz = fu_mei_device_get_max_msg_length(FU_MEI_DEVICE(self)) - sizeof(struct gsc_fwu_heci_data_req); g_autoptr(GPtrArray) chunks = NULL; /* progress */ if (payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "get-status"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "update-start"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 50, "write-chunks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "update-end"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "wait-for-reboot"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 46, "reconnect"); } else { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "get-status"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "update-start"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96, "write-chunks"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 1, "update-end"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "wait-for-reboot"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 0, "reconnect"); } /* need to get the new version in a loop? */ if (!fu_igsc_device_get_fw_status(self, 5, &sts5, error)) return FALSE; cp_mode = (sts5 & HECI1_CSE_FS_MODE_MASK) == HECI1_CSE_FS_CP_MODE; fu_progress_step_done(progress); /* start */ if (!fu_igsc_device_update_start(self, payload_type, fw_info, fw, error)) { g_prefix_error(error, "failed to start: "); return FALSE; } fu_progress_step_done(progress); /* data */ chunks = fu_chunk_array_new_from_bytes(fw, 0x0, 0x0, payloadsz); if (!fu_igsc_device_write_chunks(self, chunks, fu_progress_get_child(progress), error)) return FALSE; fu_progress_step_done(progress); /* stop */ if (!fu_igsc_device_update_end(self, error)) { g_prefix_error(error, "failed to end: "); return FALSE; } fu_progress_step_done(progress); /* detect a firmware reboot */ if (payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW || payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_FWDATA) { if (!fu_igsc_device_wait_for_reset(self, error)) return FALSE; } fu_progress_step_done(progress); /* after Gfx FW update there is a FW reset so driver reconnect is needed */ if (payload_type == GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW) { if (cp_mode) { if (!fu_igsc_device_wait_for_reset(self, error)) return FALSE; } if (!fu_device_retry_full(FU_DEVICE(self), fu_igsc_device_reconnect_cb, 200, 300 /* ms */, NULL, error)) return FALSE; if (!fu_igsc_device_no_update(self, error)) { g_prefix_error(error, "failed to send no-update: "); return FALSE; } fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } fu_progress_step_done(progress); /* success */ return TRUE; } static gboolean fu_igsc_device_write_firmware(FuDevice *device, FuFirmware *firmware, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); g_autoptr(GBytes) fw_info = NULL; g_autoptr(GBytes) fw_payload = NULL; /* get image, and install on ourself */ fw_info = fu_firmware_get_image_by_idx_bytes(firmware, FU_IFWI_FPT_FIRMWARE_IDX_INFO, error); if (fw_info == NULL) return FALSE; fw_payload = fu_firmware_get_image_by_idx_bytes(firmware, FU_IFWI_FPT_FIRMWARE_IDX_FWIM, error); if (fw_payload == NULL) return FALSE; return fu_igsc_device_write_blob(self, GSC_FWU_HECI_PAYLOAD_TYPE_GFX_FW, fw_info, fw_payload, progress, error); } static gboolean fu_igsc_device_set_pci_power_policy(FuIgscDevice *self, const gchar *val, GError **error) { g_autoptr(FuUdevDevice) parent = NULL; /* get PCI parent */ parent = fu_udev_device_get_parent_with_subsystem(FU_UDEV_DEVICE(self), "pci"); if (parent == NULL) { g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "no PCI parent"); return FALSE; } return fu_udev_device_write_sysfs(parent, "power/control", val, error); } static gboolean fu_igsc_device_prepare(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); return fu_igsc_device_set_pci_power_policy(self, "on", error); } static gboolean fu_igsc_device_cleanup(FuDevice *device, FuProgress *progress, FwupdInstallFlags flags, GError **error) { FuIgscDevice *self = FU_IGSC_DEVICE(device); return fu_igsc_device_set_pci_power_policy(self, "auto", error); } static void fu_igsc_device_set_progress(FuDevice *self, FuProgress *progress) { fu_progress_set_id(progress, G_STRLOC); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 1, "detach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_WRITE, 96, "write"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_RESTART, 2, "attach"); fu_progress_add_step(progress, FWUPD_STATUS_DEVICE_BUSY, 1, "reload"); } static void fu_igsc_device_init(FuIgscDevice *self) { fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_UPDATABLE); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL); fu_device_set_vendor(FU_DEVICE(self), "Intel"); fu_device_set_name(FU_DEVICE(self), "Graphics Card"); fu_device_set_summary(FU_DEVICE(self), "Discrete Graphics Card"); fu_device_add_icon(FU_DEVICE(self), "video-display"); fu_device_add_protocol(FU_DEVICE(self), "com.intel.gsc"); fu_device_add_icon(FU_DEVICE(self), "gpu"); fu_device_set_version_format(FU_DEVICE(self), FWUPD_VERSION_FORMAT_PAIR); fu_device_set_remove_delay(FU_DEVICE(self), 60000); fu_device_register_private_flag(FU_DEVICE(self), FU_IGSC_DEVICE_FLAG_HAS_AUX, "has-aux"); fu_device_register_private_flag(FU_DEVICE(self), FU_IGSC_DEVICE_FLAG_HAS_OPROM, "has-oprom"); } static void fu_igsc_device_finalize(GObject *object) { FuIgscDevice *self = FU_IGSC_DEVICE(object); g_free(self->project); G_OBJECT_CLASS(fu_igsc_device_parent_class)->finalize(object); } static void fu_igsc_device_class_init(FuIgscDeviceClass *klass) { FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass); GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->finalize = fu_igsc_device_finalize; klass_device->set_progress = fu_igsc_device_set_progress; klass_device->to_string = fu_igsc_device_to_string; klass_device->open = fu_igsc_device_open; klass_device->setup = fu_igsc_device_setup; klass_device->probe = fu_igsc_device_probe; klass_device->prepare = fu_igsc_device_prepare; klass_device->cleanup = fu_igsc_device_cleanup; klass_device->prepare_firmware = fu_igsc_device_prepare_firmware; klass_device->write_firmware = fu_igsc_device_write_firmware; }