fwupd/plugins/redfish/fu-redfish-device.c
Richard Hughes 090a7c8b9a Add API to wait for a device
This allows us to ignore all the delays when the device is emulated, with the
idea being to do dozens of device emulations in the CI tests.

Also, do not call fu_progress_sleep() when the device is emulated.
2023-02-23 13:04:11 -06:00

931 lines
30 KiB
C

/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#include "config.h"
#include "fu-redfish-backend.h"
#include "fu-redfish-common.h"
#include "fu-redfish-device.h"
typedef struct {
FuRedfishBackend *backend;
JsonObject *member;
guint64 milestone;
gchar *build;
guint reset_pre_delay; /* default of 0ms */
guint reset_post_delay; /* default of 0ms */
} FuRedfishDevicePrivate;
enum { PROP_0, PROP_BACKEND, PROP_MEMBER, PROP_LAST };
G_DEFINE_TYPE_WITH_PRIVATE(FuRedfishDevice, fu_redfish_device, FU_TYPE_DEVICE)
#define GET_PRIVATE(o) (fu_redfish_device_get_instance_private(o))
static void
fu_redfish_device_to_string(FuDevice *device, guint idt, GString *str)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE(device);
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
if (priv->milestone > 0x0)
fu_string_append_kx(str, idt, "Milestone", priv->milestone);
if (priv->build != NULL)
fu_string_append(str, idt, "Build", priv->build);
fu_string_append_ku(str, idt, "ResetPretDelay", priv->reset_pre_delay);
fu_string_append_ku(str, idt, "ResetPostDelay", priv->reset_post_delay);
}
static void
fu_redfish_device_set_device_class(FuRedfishDevice *self, const gchar *tmp)
{
if (g_strcmp0(tmp, "NetworkController") == 0) {
fu_device_add_icon(FU_DEVICE(self), "network-wired");
return;
}
if (g_strcmp0(tmp, "MassStorageController") == 0) {
fu_device_add_icon(FU_DEVICE(self), "drive-multidisk");
return;
}
if (g_strcmp0(tmp, "DisplayController") == 0) {
fu_device_add_icon(FU_DEVICE(self), "video-display");
return;
}
if (g_strcmp0(tmp, "DockingStation") == 0) {
fu_device_add_icon(FU_DEVICE(self), "dock");
return;
}
if (g_strcmp0(tmp, "WirelessController") == 0) {
fu_device_add_icon(FU_DEVICE(self), "network-wireless");
return;
}
g_debug("no icon mapping for %s", tmp);
}
static gboolean
fu_redfish_device_probe_related_pcie_item(FuRedfishDevice *self, const gchar *uri, GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
JsonObject *json_obj;
guint64 vendor_id = 0x0;
guint64 model_id = 0x0;
guint64 subsystem_vendor_id = 0x0;
guint64 subsystem_model_id = 0x0;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(priv->backend);
/* get URI */
if (!fu_redfish_request_perform(request,
uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object(request);
/* optional properties */
if (json_object_has_member(json_obj, "DeviceClass")) {
const gchar *tmp = json_object_get_string_member(json_obj, "DeviceClass");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_device_class(self, tmp);
}
if (json_object_has_member(json_obj, "VendorId")) {
const gchar *tmp = json_object_get_string_member(json_obj, "VendorId");
if (tmp != NULL && tmp[0] != '\0') {
if (!fu_strtoull(tmp, &vendor_id, 0, G_MAXUINT16, error))
return FALSE;
}
}
if (json_object_has_member(json_obj, "DeviceId")) {
const gchar *tmp = json_object_get_string_member(json_obj, "DeviceId");
if (tmp != NULL && tmp[0] != '\0') {
if (!fu_strtoull(tmp, &model_id, 0, G_MAXUINT16, error))
return FALSE;
}
}
if (json_object_has_member(json_obj, "SubsystemVendorId")) {
const gchar *tmp = json_object_get_string_member(json_obj, "SubsystemVendorId");
if (tmp != NULL && tmp[0] != '\0') {
if (!fu_strtoull(tmp, &subsystem_vendor_id, 0, G_MAXUINT16, error))
return FALSE;
}
}
if (json_object_has_member(json_obj, "SubsystemId")) {
const gchar *tmp = json_object_get_string_member(json_obj, "SubsystemId");
if (tmp != NULL && tmp[0] != '\0') {
if (!fu_strtoull(tmp, &subsystem_model_id, 0, G_MAXUINT16, error))
return FALSE;
}
}
/* add vendor ID */
if (vendor_id != 0x0) {
g_autofree gchar *vendor_id_str = NULL;
vendor_id_str = g_strdup_printf("PCI:0x%04X", (guint)vendor_id);
fu_device_add_vendor_id(FU_DEVICE(self), vendor_id_str);
}
/* add more instance IDs if possible */
if (vendor_id != 0x0)
fu_device_add_instance_u16(FU_DEVICE(self), "VEN", vendor_id);
if (model_id != 0x0)
fu_device_add_instance_u16(FU_DEVICE(self), "DEV", model_id);
if (subsystem_vendor_id != 0x0 && subsystem_model_id != 0x0) {
g_autofree gchar *subsys = NULL;
subsys = g_strdup_printf("%04X%04X",
(guint)subsystem_vendor_id,
(guint)subsystem_model_id);
fu_device_add_instance_str(FU_DEVICE(self), "SUBSYS", subsys);
}
fu_device_build_instance_id(FU_DEVICE(self), NULL, "PCI", "VEN", "DEV", NULL);
fu_device_build_instance_id(FU_DEVICE(self), NULL, "PCI", "VEN", "DEV", "SUBSYS", NULL);
/* success */
return TRUE;
}
static gboolean
fu_redfish_device_probe_related_pcie_functions(FuRedfishDevice *self,
const gchar *uri,
GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
JsonObject *json_obj;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(priv->backend);
/* get URI */
if (!fu_redfish_request_perform(request,
uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object(request);
if (json_object_has_member(json_obj, "Members")) {
JsonArray *members_array = json_object_get_array_member(json_obj, "Members");
for (guint i = 0; i < json_array_get_length(members_array); i++) {
JsonObject *related_item;
related_item = json_array_get_object_element(members_array, i);
if (json_object_has_member(related_item, "@odata.id")) {
const gchar *id =
json_object_get_string_member(related_item, "@odata.id");
if (!fu_redfish_device_probe_related_pcie_item(self, id, error))
return FALSE;
}
}
}
/* success */
return TRUE;
}
static gboolean
fu_redfish_device_probe_related_item(FuRedfishDevice *self, const gchar *uri, GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
JsonObject *json_obj;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(priv->backend);
/* get URI */
if (!fu_redfish_request_perform(request,
uri,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON |
FU_REDFISH_REQUEST_PERFORM_FLAG_USE_CACHE,
error))
return FALSE;
json_obj = fu_redfish_request_get_json_object(request);
/* optional properties */
if (json_object_has_member(json_obj, "SerialNumber")) {
const gchar *tmp = json_object_get_string_member(json_obj, "SerialNumber");
if (tmp != NULL && tmp[0] != '\0' && g_strcmp0(tmp, "N/A") != 0)
fu_device_set_serial(FU_DEVICE(self), tmp);
}
if (json_object_has_member(json_obj, "HotPluggable")) {
/* this is better than the heuristic we get from the device name */
if (json_object_get_boolean_member(json_obj, "HotPluggable"))
fu_device_remove_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
else
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
}
/* sometimes an array, sometimes an object! */
if (json_object_has_member(json_obj, "PCIeFunctions")) {
JsonNode *pcie_functions = json_object_get_member(json_obj, "PCIeFunctions");
if (JSON_NODE_HOLDS_OBJECT(pcie_functions)) {
JsonObject *obj = json_node_get_object(pcie_functions);
if (json_object_has_member(obj, "@odata.id")) {
const gchar *id = json_object_get_string_member(obj, "@odata.id");
if (!fu_redfish_device_probe_related_pcie_functions(self,
id,
error))
return FALSE;
}
}
}
return TRUE;
}
/* parses a Lenovo XCC-format version like "11A-1.02" */
static gboolean
fu_redfish_device_set_version_lenovo(FuRedfishDevice *self, const gchar *version, GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
g_autofree gchar *out_build = NULL;
g_autofree gchar *out_version = NULL;
/* split up Lenovo format */
if (!fu_redfish_common_parse_version_lenovo(version, &out_build, &out_version, error))
return FALSE;
/* split out milestone */
priv->milestone = g_ascii_strtoull(out_build, NULL, 10);
if (priv->milestone == 0) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"version milestone invalid");
return FALSE;
}
/* odd numbered builds are unsigned */
if (priv->milestone % 2 != 0) {
fu_device_add_private_flag(FU_DEVICE(self), FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD);
}
/* build is only one letter from A -> Z */
if (!g_ascii_isalpha(out_build[2])) {
g_set_error(error, G_IO_ERROR, G_IO_ERROR_INVALID_DATA, "build letter invalid");
return FALSE;
}
priv->build = g_strndup(out_build + 2, 1);
fu_device_set_version(FU_DEVICE(self), out_version);
fu_device_set_version_format(FU_DEVICE(self), fu_version_guess_format(out_version));
return TRUE;
}
static void
fu_redfish_device_set_version(FuRedfishDevice *self, const gchar *tmp)
{
/* OEM specific */
if (g_strcmp0(fu_device_get_vendor(FU_DEVICE(self)), "Lenovo") == 0) {
g_autoptr(GError) error_local = NULL;
if (!fu_redfish_device_set_version_lenovo(self, tmp, &error_local)) {
g_debug("failed to parse Lenovo version %s: %s", tmp, error_local->message);
}
}
/* fallback */
if (fu_device_get_version(FU_DEVICE(self)) == NULL) {
g_autofree gchar *ver = fu_redfish_common_fix_version(tmp);
if (ver != NULL) {
fu_device_set_version(FU_DEVICE(self), ver);
fu_device_set_version_format(FU_DEVICE(self), fu_version_guess_format(ver));
}
}
}
static void
fu_redfish_device_set_version_lowest(FuRedfishDevice *self, const gchar *tmp)
{
/* OEM specific */
if (g_strcmp0(fu_device_get_vendor(FU_DEVICE(self)), "Lenovo") == 0) {
g_autoptr(GError) error_local = NULL;
g_autofree gchar *out_version = NULL;
if (!fu_redfish_common_parse_version_lenovo(tmp,
NULL,
&out_version,
&error_local)) {
g_debug("failed to parse Lenovo version %s: %s", tmp, error_local->message);
}
fu_device_set_version_lowest(FU_DEVICE(self), out_version);
}
/* fallback */
if (fu_device_get_version_lowest(FU_DEVICE(self)) == NULL) {
g_autofree gchar *ver = fu_redfish_common_fix_version(tmp);
fu_device_set_version_lowest(FU_DEVICE(self), ver);
}
}
static void
fu_redfish_device_set_name(FuRedfishDevice *self, const gchar *name)
{
/* useless */
if (g_str_has_prefix(name, "Firmware:"))
name += 9;
/* device type */
if (g_str_has_prefix(name, "DEVICE-")) {
name += 7;
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
} else if (g_str_has_prefix(name, "DISK-")) {
name += 5;
fu_device_add_icon(FU_DEVICE(self), "drive-harddisk");
} else if (g_str_has_prefix(name, "POWER-")) {
name += 6;
fu_device_add_icon(FU_DEVICE(self), "ac-adapter");
fu_device_set_summary(FU_DEVICE(self), "Redfish power supply unit");
} else {
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_INTERNAL);
}
/* heuristics */
if (g_strcmp0(name, "BMC") == 0)
fu_device_set_summary(FU_DEVICE(self), "Redfish baseboard management controller");
if (g_str_has_suffix(name, "HBA") == 0)
fu_device_set_summary(FU_DEVICE(self), "Redfish host bus adapter");
/* success */
fu_device_set_name(FU_DEVICE(self), name);
}
static void
fu_redfish_device_set_vendor(FuRedfishDevice *self, const gchar *vendor)
{
g_autofree gchar *vendor_upper = NULL;
g_autofree gchar *vendor_id = NULL;
/* fixup a common mistake */
if (g_strcmp0(vendor, "LEN") == 0 || g_strcmp0(vendor, "LNVO") == 0)
vendor = "Lenovo";
fu_device_set_vendor(FU_DEVICE(self), vendor);
/* add vendor-id */
vendor_upper = g_ascii_strup(vendor, -1);
g_strdelimit(vendor_upper, " ", '_');
vendor_id = g_strdup_printf("REDFISH:%s", vendor_upper);
fu_device_add_vendor_id(FU_DEVICE(self), vendor_id);
}
static void
fu_redfish_backend_smc_license_check(FuDevice *device)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE(device);
FuRedfishBackend *backend = fu_redfish_device_get_backend(self);
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(backend);
g_autoptr(GError) error_local = NULL;
/* see if we don't get an license error */
if (!fu_redfish_request_perform(request,
fu_redfish_backend_get_push_uri_path(backend),
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
&error_local)) {
if (g_error_matches(error_local, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED))
fu_device_add_problem(device, FWUPD_DEVICE_PROBLEM_MISSING_LICENSE);
else
g_debug("supermicro license check returned %s\n", error_local->message);
}
}
static gboolean
fu_redfish_device_probe(FuDevice *dev, GError **error)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE(dev);
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
JsonObject *member = priv->member;
const gchar *guid = NULL;
g_autofree gchar *guid_lower = NULL;
/* required to POST later */
if (!json_object_has_member(member, "@odata.id")) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no @odata.id string");
return FALSE;
}
fu_device_set_physical_id(dev, "Redfish-Inventory");
fu_device_set_logical_id(dev, json_object_get_string_member(member, "@odata.id"));
if (json_object_has_member(member, "Id")) {
const gchar *tmp = json_object_get_string_member(member, "Id");
if (tmp != NULL)
fu_device_set_backend_id(dev, tmp);
}
/* get SoftwareId, falling back to vendor-specific versions */
if (json_object_has_member(member, "SoftwareId")) {
guid = json_object_get_string_member(member, "SoftwareId");
} else if (json_object_has_member(member, "Oem")) {
JsonObject *oem = json_object_get_object_member(member, "Oem");
if (oem != NULL && json_object_has_member(oem, "Hpe")) {
JsonObject *hpe = json_object_get_object_member(oem, "Hpe");
if (hpe != NULL && json_object_has_member(hpe, "DeviceClass"))
guid = json_object_get_string_member(hpe, "DeviceClass");
}
}
/* GUID is required */
if (guid == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_FOUND,
"no GUID for device");
return FALSE;
}
/* device properties */
if (json_object_has_member(member, "Manufacturer")) {
const gchar *tmp = json_object_get_string_member(member, "Manufacturer");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_vendor(self, tmp);
}
/* the version can encode the instance ID suffix */
if (json_object_has_member(member, "Version")) {
const gchar *tmp = json_object_get_string_member(member, "Version");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_version(self, tmp);
}
/* ReleaseDate may or may not have a timezone */
if (json_object_has_member(member, "ReleaseDate")) {
const gchar *tmp = json_object_get_string_member(member, "ReleaseDate");
if (tmp != NULL && tmp[0] != '\0') {
g_autoptr(GDateTime) dt = NULL;
g_autoptr(GTimeZone) tz = g_time_zone_new_utc();
dt = g_date_time_new_from_iso8601(tmp, tz);
if (dt != NULL) {
guint64 unixtime = (guint64)g_date_time_to_unix(dt);
fu_device_set_version_build_date(dev, unixtime);
} else {
g_warning("failed to parse ISO8601 %s", tmp);
}
}
}
/* add IDs */
fu_device_add_instance_strsafe(dev, "VENDOR", fu_device_get_vendor(dev));
fu_device_add_instance_str(dev, "SOFTWAREID", guid);
fu_device_add_instance_str(dev, "ID", fu_device_get_backend_id(dev));
/* some vendors use a GUID, others use an ID like BMC-AFBT-10 */
guid_lower = g_ascii_strdown(guid, -1);
if (fwupd_guid_is_valid(guid_lower)) {
fu_device_add_guid(dev, guid_lower);
} else {
if (fu_device_has_private_flag(dev, FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD))
fu_device_add_instance_str(dev, "TYPE", "UNSIGNED");
fu_device_build_instance_id(dev,
NULL,
"REDFISH",
"VENDOR",
"SOFTWAREID",
"TYPE",
NULL);
fu_device_build_instance_id(dev, NULL, "REDFISH", "VENDOR", "SOFTWAREID", NULL);
}
/* used for quirking and parenting */
fu_device_build_instance_id(dev, NULL, "REDFISH", "VENDOR", "ID", NULL);
if (json_object_has_member(member, "Name")) {
const gchar *tmp = json_object_get_string_member(member, "Name");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_name(self, tmp);
}
if (json_object_has_member(member, "LowestSupportedVersion")) {
const gchar *tmp = json_object_get_string_member(member, "LowestSupportedVersion");
if (tmp != NULL && tmp[0] != '\0')
fu_redfish_device_set_version_lowest(self, tmp);
}
if (json_object_has_member(member, "Description")) {
const gchar *tmp = json_object_get_string_member(member, "Description");
if (tmp != NULL && tmp[0] != '\0')
fu_device_set_description(dev, tmp);
}
/* reasons why the device might not be updatable */
if (json_object_has_member(member, "Updateable")) {
if (json_object_get_boolean_member(member, "Updateable"))
fu_device_add_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE);
}
if (fu_device_has_private_flag(dev, FU_REDFISH_DEVICE_FLAG_IS_BACKUP))
fu_device_inhibit(dev, "is-backup", "Is a backup partition");
else
fu_device_uninhibit(dev, "is-backup");
/* use related items to set extra instance IDs */
if (fu_device_has_flag(dev, FWUPD_DEVICE_FLAG_UPDATABLE) &&
json_object_has_member(member, "RelatedItem")) {
JsonArray *related_item_array = json_object_get_array_member(member, "RelatedItem");
for (guint i = 0; i < json_array_get_length(related_item_array); i++) {
JsonObject *related_item;
related_item = json_array_get_object_element(related_item_array, i);
if (json_object_has_member(related_item, "@odata.id")) {
const gchar *id =
json_object_get_string_member(related_item, "@odata.id");
if (!fu_redfish_device_probe_related_item(self, id, error))
return FALSE;
}
}
}
/* for Supermicro check whether we have a proper Redfish license installed */
if (g_strcmp0("SMCI", fu_device_get_vendor(dev)) == 0)
fu_redfish_backend_smc_license_check(dev);
/* success */
return TRUE;
}
FuRedfishBackend *
fu_redfish_device_get_backend(FuRedfishDevice *self)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
return priv->backend;
}
typedef struct {
FwupdError error_code;
gchar *location;
gboolean completed;
GHashTable *messages_seen;
FuProgress *progress;
} FuRedfishDevicePollCtx;
static void
fu_redfish_device_poll_set_message_id(FuRedfishDevice *self,
FuRedfishDevicePollCtx *ctx,
const gchar *message_id)
{
/* ignore */
if (g_strcmp0(message_id, "TaskEvent.1.0.TaskProgressChanged") == 0 ||
g_strcmp0(message_id, "TaskEvent.1.0.TaskCompletedWarning") == 0 ||
g_strcmp0(message_id, "TaskEvent.1.0.TaskCompletedOK") == 0 ||
g_strcmp0(message_id, "Base.1.6.Success") == 0)
return;
/* set flags */
if (g_strcmp0(message_id, "Base.1.10.ResetRequired") == 0) {
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_NEEDS_REBOOT);
return;
}
/* set error code */
if (g_strcmp0(message_id, "Update.1.0.AwaitToActivate") == 0) {
ctx->error_code = FWUPD_ERROR_NEEDS_USER_ACTION;
return;
}
if (g_strcmp0(message_id, "Update.1.0.TransferFailed") == 0) {
ctx->error_code = FWUPD_ERROR_WRITE;
return;
}
if (g_strcmp0(message_id, "Update.1.0.ActivateFailed") == 0) {
ctx->error_code = FWUPD_ERROR_INVALID_FILE;
return;
}
if (g_strcmp0(message_id, "Update.1.0.VerificationFailed") == 0 ||
g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyFailed") == 0) {
ctx->error_code = FWUPD_ERROR_INVALID_FILE;
return;
}
if (g_strcmp0(message_id, "Update.1.0.ApplyFailed") == 0) {
ctx->error_code = FWUPD_ERROR_WRITE;
return;
}
/* set status */
if (g_strcmp0(message_id, "Update.1.1.TargetDetermined") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING);
return;
}
if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateAssignment") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING);
return;
}
if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyInProgress") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE);
return;
}
if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.PayloadApplyCompleted") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_IDLE);
return;
}
if (g_strcmp0(message_id, "LenovoFirmwareUpdateRegistry.1.0.UpdateVerifyInProgress") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_VERIFY);
return;
}
if (g_strcmp0(message_id, "Update.1.1.TransferringToComponent") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_LOADING);
return;
}
if (g_strcmp0(message_id, "Update.1.1.VerifyingAtComponent") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_VERIFY);
return;
}
if (g_strcmp0(message_id, "Update.1.1.UpdateInProgress") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE);
return;
}
if (g_strcmp0(message_id, "Update.1.1.UpdateSuccessful") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_IDLE);
return;
}
if (g_strcmp0(message_id, "Update.1.1.InstallingOnComponent") == 0) {
fu_progress_set_status(ctx->progress, FWUPD_STATUS_DEVICE_WRITE);
return;
}
}
static gboolean
fu_redfish_device_poll_task_once(FuRedfishDevice *self, FuRedfishDevicePollCtx *ctx, GError **error)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
JsonObject *json_obj;
const gchar *message = "Unknown failure";
const gchar *state_tmp;
g_autoptr(FuRedfishRequest) request = fu_redfish_backend_request_new(priv->backend);
/* create URI and poll */
if (!fu_redfish_request_perform(request,
ctx->location,
FU_REDFISH_REQUEST_PERFORM_FLAG_LOAD_JSON,
error))
return FALSE;
/* percentage is optional */
json_obj = fu_redfish_request_get_json_object(request);
if (json_object_has_member(json_obj, "PercentComplete")) {
gint64 pc = json_object_get_int_member(json_obj, "PercentComplete");
if (pc >= 0 && pc <= 100)
fu_progress_set_percentage(ctx->progress, (guint)pc);
}
/* print all messages we've not seen yet */
if (json_object_has_member(json_obj, "Messages")) {
JsonArray *json_msgs = json_object_get_array_member(json_obj, "Messages");
guint json_msgs_sz = json_array_get_length(json_msgs);
for (guint i = 0; i < json_msgs_sz; i++) {
JsonObject *json_message = json_array_get_object_element(json_msgs, i);
const gchar *message_id = NULL;
g_autofree gchar *message_key = NULL;
/* set additional device properties */
if (json_object_has_member(json_message, "MessageId"))
message_id =
json_object_get_string_member(json_message, "MessageId");
if (json_object_has_member(json_message, "Message"))
message = json_object_get_string_member(json_message, "Message");
/* ignore messages we've seen before */
message_key = g_strdup_printf("%s;%s", message_id, message);
if (g_hash_table_contains(ctx->messages_seen, message_key)) {
g_debug("ignoring %s", message_key);
continue;
}
g_hash_table_add(ctx->messages_seen, g_steal_pointer(&message_key));
/* use the message */
g_debug("message #%u [%s]: %s", i, message_id, message);
fu_redfish_device_poll_set_message_id(self, ctx, message_id);
}
}
/* use taskstate to set context */
if (!json_object_has_member(json_obj, "TaskState")) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"no TaskState for task manager");
return FALSE;
}
state_tmp = json_object_get_string_member(json_obj, "TaskState");
g_debug("TaskState now %s", state_tmp);
if (g_strcmp0(state_tmp, "Completed") == 0) {
ctx->completed = TRUE;
return TRUE;
}
if (g_strcmp0(state_tmp, "Cancelled") == 0) {
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_CANCELLED, "Task was cancelled");
return FALSE;
}
if (g_strcmp0(state_tmp, "Exception") == 0 ||
g_strcmp0(state_tmp, "UserIntervention") == 0) {
g_set_error_literal(error, FWUPD_ERROR, ctx->error_code, message);
return FALSE;
}
/* try again */
return TRUE;
}
static FuRedfishDevicePollCtx *
fu_redfish_device_poll_ctx_new(FuProgress *progress, const gchar *location)
{
FuRedfishDevicePollCtx *ctx = g_new0(FuRedfishDevicePollCtx, 1);
ctx->messages_seen = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, NULL);
ctx->location = g_strdup(location);
ctx->error_code = FWUPD_ERROR_INTERNAL;
ctx->progress = g_object_ref(progress);
return ctx;
}
static void
fu_redfish_device_poll_ctx_free(FuRedfishDevicePollCtx *ctx)
{
g_hash_table_unref(ctx->messages_seen);
g_object_unref(ctx->progress);
g_free(ctx->location);
g_free(ctx);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuRedfishDevicePollCtx, fu_redfish_device_poll_ctx_free)
#pragma clang diagnostic pop
gboolean
fu_redfish_device_poll_task(FuRedfishDevice *self,
const gchar *location,
FuProgress *progress,
GError **error)
{
const guint timeout = 2400;
g_autoptr(GTimer) timer = g_timer_new();
g_autoptr(FuRedfishDevicePollCtx) ctx = fu_redfish_device_poll_ctx_new(progress, location);
/* sleep and then reprobe hardware */
do {
fu_device_sleep(FU_DEVICE(self), 1000); /* ms */
if (!fu_redfish_device_poll_task_once(self, ctx, error))
return FALSE;
if (ctx->completed)
return TRUE;
} while (g_timer_elapsed(timer, NULL) < timeout);
/* success */
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"failed to poll %s for success after %u seconds",
location,
timeout);
return FALSE;
}
guint
fu_redfish_device_get_reset_pre_delay(FuRedfishDevice *self)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
return priv->reset_pre_delay;
}
guint
fu_redfish_device_get_reset_post_delay(FuRedfishDevice *self)
{
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
return priv->reset_post_delay;
}
static gboolean
fu_redfish_device_set_quirk_kv(FuDevice *device,
const gchar *key,
const gchar *value,
GError **error)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE(device);
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
guint64 tmp = 0;
if (g_strcmp0(key, "RedfishResetPreDelay") == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error))
return FALSE;
priv->reset_pre_delay = tmp;
return TRUE;
}
if (g_strcmp0(key, "RedfishResetPostDelay") == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error))
return FALSE;
priv->reset_post_delay = tmp;
return TRUE;
}
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"quirk key not supported");
return FALSE;
}
static void
fu_redfish_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE(object);
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
switch (prop_id) {
case PROP_BACKEND:
g_value_set_object(value, priv->backend);
break;
case PROP_MEMBER:
g_value_set_pointer(value, priv->member);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
fu_redfish_device_set_property(GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE(object);
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
switch (prop_id) {
case PROP_BACKEND:
g_set_object(&priv->backend, g_value_get_object(value));
break;
case PROP_MEMBER:
priv->member = json_object_ref(g_value_get_pointer(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
fu_redfish_device_init(FuRedfishDevice *self)
{
fu_device_set_summary(FU_DEVICE(self), "Redfish device");
fu_device_add_protocol(FU_DEVICE(self), "org.dmtf.redfish");
fu_device_add_flag(FU_DEVICE(self), FWUPD_DEVICE_FLAG_REQUIRE_AC);
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME);
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT);
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON);
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR);
fu_device_add_internal_flag(FU_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED);
fu_device_register_private_flag(FU_DEVICE(self),
FU_REDFISH_DEVICE_FLAG_IS_BACKUP,
"is-backup");
fu_device_register_private_flag(FU_DEVICE(self),
FU_REDFISH_DEVICE_FLAG_UNSIGNED_BUILD,
"unsigned-build");
fu_device_register_private_flag(FU_DEVICE(self),
FU_REDFISH_DEVICE_FLAG_WILDCARD_TARGETS,
"wildcard-targets");
fu_device_register_private_flag(FU_DEVICE(self),
FU_REDFISH_DEVICE_FLAG_MANAGER_RESET,
"manager-reset");
}
static void
fu_redfish_device_finalize(GObject *object)
{
FuRedfishDevice *self = FU_REDFISH_DEVICE(object);
FuRedfishDevicePrivate *priv = GET_PRIVATE(self);
if (priv->backend != NULL)
g_object_unref(priv->backend);
if (priv->member != NULL)
json_object_unref(priv->member);
g_free(priv->build);
G_OBJECT_CLASS(fu_redfish_device_parent_class)->finalize(object);
}
static void
fu_redfish_device_class_init(FuRedfishDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
GParamSpec *pspec;
FuDeviceClass *klass_device = FU_DEVICE_CLASS(klass);
object_class->get_property = fu_redfish_device_get_property;
object_class->set_property = fu_redfish_device_set_property;
object_class->finalize = fu_redfish_device_finalize;
klass_device->to_string = fu_redfish_device_to_string;
klass_device->probe = fu_redfish_device_probe;
klass_device->set_quirk_kv = fu_redfish_device_set_quirk_kv;
/**
* FuRedfishDevice:backend:
*
* The backend that added the device.
*/
pspec =
g_param_spec_object("backend",
NULL,
NULL,
FU_TYPE_BACKEND,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_BACKEND, pspec);
/**
* FuRedfishDevice:member:
*
* The JSON root member for the device.
*/
pspec =
g_param_spec_pointer("member",
NULL,
NULL,
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_MEMBER, pspec);
}