fwupd/plugins/intel-gsc/fu-igsc-device.c
2022-12-07 17:08:53 +00:00

845 lines
25 KiB
C

/*
* Copyright (C) 2022 Intel, Inc
* Copyright (C) 2022 Richard Hughes <richard@hughsie.com>
*
* 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;
}