fwupd/libfwupdplugin/fu-device.c

5554 lines
150 KiB
C

/*
* Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuDevice"
#include "config.h"
#include <gio/gio.h>
#include <string.h>
#include "fwupd-common.h"
#include "fwupd-device-private.h"
#include "fu-common.h"
#include "fu-device-private.h"
#include "fu-mutex.h"
#include "fu-quirks.h"
#include "fu-string.h"
#include "fu-version-common.h"
#define FU_DEVICE_RETRY_OPEN_COUNT 5
#define FU_DEVICE_RETRY_OPEN_DELAY 500 /* ms */
/**
* FuDevice:
*
* A physical or logical device that is exported to the daemon.
*
* See also: [class@FuDeviceLocker], [class@Fwupd.Device]
*/
static void
fu_device_finalize(GObject *object);
static void
fu_device_inhibit_full(FuDevice *self,
FwupdDeviceProblem problem,
const gchar *inhibit_id,
const gchar *reason);
typedef struct {
gchar *alternate_id;
gchar *equivalent_id;
gchar *physical_id;
gchar *logical_id;
gchar *backend_id;
gchar *proxy_guid;
FuDevice *alternate;
FuDevice *proxy; /* noref */
FuContext *ctx;
GHashTable *inhibits; /* (nullable) */
GHashTable *metadata; /* (nullable) */
GRWLock metadata_mutex;
GPtrArray *parent_guids;
GRWLock parent_guids_mutex;
GPtrArray *parent_physical_ids; /* (nullable) */
guint remove_delay; /* ms */
guint request_cnts[FWUPD_REQUEST_KIND_LAST];
gint order;
guint priority;
guint poll_id;
gint poll_locker_cnt;
gboolean done_probe;
gboolean done_setup;
gboolean device_id_valid;
guint64 size_min;
guint64 size_max;
gint open_refcount; /* atomic */
GType specialized_gtype;
GType firmware_gtype;
GPtrArray *possible_plugins;
GPtrArray *retry_recs; /* of FuDeviceRetryRecovery */
guint retry_delay;
FuDeviceInternalFlags internal_flags;
guint64 private_flags;
GPtrArray *private_flag_items; /* (nullable) */
gchar *custom_flags;
gulong notify_flags_handler_id;
GHashTable *instance_hash;
} FuDevicePrivate;
typedef struct {
GQuark domain;
gint code;
FuDeviceRetryFunc recovery_func;
} FuDeviceRetryRecovery;
typedef struct {
FwupdDeviceProblem problem;
gchar *inhibit_id;
gchar *reason;
} FuDeviceInhibit;
enum {
PROP_0,
PROP_PHYSICAL_ID,
PROP_LOGICAL_ID,
PROP_BACKEND_ID,
PROP_CONTEXT,
PROP_PROXY,
PROP_PARENT,
PROP_LAST
};
enum { SIGNAL_CHILD_ADDED, SIGNAL_CHILD_REMOVED, SIGNAL_REQUEST, SIGNAL_LAST };
static guint signals[SIGNAL_LAST] = {0};
G_DEFINE_TYPE_WITH_PRIVATE(FuDevice, fu_device, FWUPD_TYPE_DEVICE)
#define GET_PRIVATE(o) (fu_device_get_instance_private(o))
static void
fu_device_get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
FuDevice *self = FU_DEVICE(object);
FuDevicePrivate *priv = GET_PRIVATE(self);
switch (prop_id) {
case PROP_PHYSICAL_ID:
g_value_set_string(value, priv->physical_id);
break;
case PROP_LOGICAL_ID:
g_value_set_string(value, priv->logical_id);
break;
case PROP_BACKEND_ID:
g_value_set_string(value, priv->backend_id);
break;
case PROP_CONTEXT:
g_value_set_object(value, priv->ctx);
break;
case PROP_PROXY:
g_value_set_object(value, priv->proxy);
break;
case PROP_PARENT:
g_value_set_object(value, fu_device_get_parent(self));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
static void
fu_device_set_property(GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
{
FuDevice *self = FU_DEVICE(object);
switch (prop_id) {
case PROP_PHYSICAL_ID:
fu_device_set_physical_id(self, g_value_get_string(value));
break;
case PROP_LOGICAL_ID:
fu_device_set_logical_id(self, g_value_get_string(value));
break;
case PROP_BACKEND_ID:
fu_device_set_backend_id(self, g_value_get_string(value));
break;
case PROP_CONTEXT:
fu_device_set_context(self, g_value_get_object(value));
break;
case PROP_PROXY:
fu_device_set_proxy(self, g_value_get_object(value));
break;
case PROP_PARENT:
fu_device_set_parent(self, g_value_get_object(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/**
* fu_device_internal_flag_to_string:
* @flag: an internal device flag, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON
*
* Converts an internal device flag to a string.
*
* Returns: identifier string
*
* Since: 1.5.5
**/
const gchar *
fu_device_internal_flag_to_string(FuDeviceInternalFlags flag)
{
if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON)
return "md-set-icon";
if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME)
return "md-set-name";
if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY)
return "md-set-name-category";
if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT)
return "md-set-verfmt";
if (flag == FU_DEVICE_INTERNAL_FLAG_ONLY_SUPPORTED)
return "only-supported";
if (flag == FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS)
return "no-auto-instance-ids";
if (flag == FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER)
return "ensure-semver";
if (flag == FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN)
return "retry-open";
if (flag == FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID)
return "replug-match-guid";
if (flag == FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION)
return "inherit-activation";
if (flag == FU_DEVICE_INTERNAL_FLAG_IS_OPEN)
return "is-open";
if (flag == FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER)
return "no-serial-number";
if (flag == FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN)
return "auto-parent-children";
if (flag == FU_DEVICE_INTERNAL_FLAG_ATTACH_EXTRA_RESET)
return "attach-extra-reset";
if (flag == FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN)
return "inhibit-children";
if (flag == FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN)
return "no-auto-remove-children";
if (flag == FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN)
return "use-parent-for-open";
if (flag == FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY)
return "use-parent-for-battery";
if (flag == FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK)
return "use-proxy-fallback";
if (flag == FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE)
return "no-auto-remove";
if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR)
return "md-set-vendor";
if (flag == FU_DEVICE_INTERNAL_FLAG_NO_LID_CLOSED)
return "no-lid-closed";
if (flag == FU_DEVICE_INTERNAL_FLAG_NO_PROBE)
return "no-probe";
if (flag == FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED)
return "md-set-signed";
if (flag == FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING)
return "auto-pause-polling";
if (flag == FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG)
return "only-wait-for-replug";
return NULL;
}
/**
* fu_device_internal_flag_from_string:
* @flag: a string, e.g. `md-set-icon`
*
* Converts a string to an internal device flag.
*
* Returns: enumerated value
*
* Since: 1.5.5
**/
FuDeviceInternalFlags
fu_device_internal_flag_from_string(const gchar *flag)
{
if (g_strcmp0(flag, "md-set-icon") == 0)
return FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON;
if (g_strcmp0(flag, "md-set-name") == 0)
return FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME;
if (g_strcmp0(flag, "md-set-name-category") == 0)
return FU_DEVICE_INTERNAL_FLAG_MD_SET_NAME_CATEGORY;
if (g_strcmp0(flag, "md-set-verfmt") == 0)
return FU_DEVICE_INTERNAL_FLAG_MD_SET_VERFMT;
if (g_strcmp0(flag, "only-supported") == 0)
return FU_DEVICE_INTERNAL_FLAG_ONLY_SUPPORTED;
if (g_strcmp0(flag, "no-auto-instance-ids") == 0)
return FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS;
if (g_strcmp0(flag, "ensure-semver") == 0)
return FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER;
if (g_strcmp0(flag, "retry-open") == 0)
return FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN;
if (g_strcmp0(flag, "replug-match-guid") == 0)
return FU_DEVICE_INTERNAL_FLAG_REPLUG_MATCH_GUID;
if (g_strcmp0(flag, "inherit-activation") == 0)
return FU_DEVICE_INTERNAL_FLAG_INHERIT_ACTIVATION;
if (g_strcmp0(flag, "is-open") == 0)
return FU_DEVICE_INTERNAL_FLAG_IS_OPEN;
if (g_strcmp0(flag, "no-serial-number") == 0)
return FU_DEVICE_INTERNAL_FLAG_NO_SERIAL_NUMBER;
if (g_strcmp0(flag, "auto-parent-children") == 0)
return FU_DEVICE_INTERNAL_FLAG_AUTO_PARENT_CHILDREN;
if (g_strcmp0(flag, "attach-extra-reset") == 0)
return FU_DEVICE_INTERNAL_FLAG_ATTACH_EXTRA_RESET;
if (g_strcmp0(flag, "inhibit-children") == 0)
return FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN;
if (g_strcmp0(flag, "no-auto-remove-children") == 0)
return FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE_CHILDREN;
if (g_strcmp0(flag, "use-parent-for-open") == 0)
return FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN;
if (g_strcmp0(flag, "use-parent-for-battery") == 0)
return FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY;
if (g_strcmp0(flag, "use-proxy-fallback") == 0)
return FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK;
if (g_strcmp0(flag, "no-auto-remove") == 0)
return FU_DEVICE_INTERNAL_FLAG_NO_AUTO_REMOVE;
if (g_strcmp0(flag, "md-set-vendor") == 0)
return FU_DEVICE_INTERNAL_FLAG_MD_SET_VENDOR;
if (g_strcmp0(flag, "no-lid-closed") == 0)
return FU_DEVICE_INTERNAL_FLAG_NO_LID_CLOSED;
if (g_strcmp0(flag, "no-probe") == 0)
return FU_DEVICE_INTERNAL_FLAG_NO_PROBE;
if (g_strcmp0(flag, "md-set-signed") == 0)
return FU_DEVICE_INTERNAL_FLAG_MD_SET_SIGNED;
if (g_strcmp0(flag, "auto-pause-polling") == 0)
return FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING;
if (g_strcmp0(flag, "only-wait-for-replug") == 0)
return FU_DEVICE_INTERNAL_FLAG_ONLY_WAIT_FOR_REPLUG;
return FU_DEVICE_INTERNAL_FLAG_UNKNOWN;
}
/**
* fu_device_add_internal_flag:
* @self: a #FuDevice
* @flag: an internal device flag, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON
*
* Adds a private flag that stays internal to the engine and is not leaked to the client.
*
* Since: 1.5.5
**/
void
fu_device_add_internal_flag(FuDevice *self, FuDeviceInternalFlags flag)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->internal_flags |= flag;
}
/**
* fu_device_remove_internal_flag:
* @self: a #FuDevice
* @flag: an internal device flag, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON
*
* Removes a private flag that stays internal to the engine and is not leaked to the client.
*
* Since: 1.5.5
**/
void
fu_device_remove_internal_flag(FuDevice *self, FuDeviceInternalFlags flag)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->internal_flags &= ~flag;
}
/**
* fu_device_has_internal_flag:
* @self: a #FuDevice
* @flag: an internal device flag, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON
*
* Tests for a private flag that stays internal to the engine and is not leaked to the client.
*
* Since: 1.5.5
**/
gboolean
fu_device_has_internal_flag(FuDevice *self, FuDeviceInternalFlags flag)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
return (priv->internal_flags & flag) > 0;
}
/**
* fu_device_get_internal_flags:
* @self: a #FuDevice
*
* Gets all the internal flags.
*
* Returns: flags, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON
*
* Since: 1.7.1
**/
FuDeviceInternalFlags
fu_device_get_internal_flags(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FU_DEVICE_INTERNAL_FLAG_UNKNOWN);
return priv->internal_flags;
}
/**
* fu_device_set_internal_flags:
* @self: a #FuDevice
* @flags: internal device flags, e.g. %FU_DEVICE_INTERNAL_FLAG_MD_SET_ICON
*
* Sets the internal flags.
*
* Since: 1.7.1
**/
void
fu_device_set_internal_flags(FuDevice *self, FuDeviceInternalFlags flags)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->internal_flags = flags;
}
/**
* fu_device_add_private_flag:
* @self: a #FuDevice
* @flag: a device flag
*
* Adds a private flag that can be used by the plugin for any purpose.
*
* Since: 1.6.2
**/
void
fu_device_add_private_flag(FuDevice *self, guint64 flag)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->private_flags |= flag;
}
/**
* fu_device_remove_private_flag:
* @self: a #FuDevice
* @flag: a device flag
*
* Removes a private flag that can be used by the plugin for any purpose.
*
* Since: 1.6.2
**/
void
fu_device_remove_private_flag(FuDevice *self, guint64 flag)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->private_flags &= ~flag;
}
/**
* fu_device_has_private_flag:
* @self: a #FuDevice
* @flag: a device flag
*
* Tests for a private flag that can be used by the plugin for any purpose.
*
* Since: 1.6.2
**/
gboolean
fu_device_has_private_flag(FuDevice *self, guint64 flag)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
return (priv->private_flags & flag) > 0;
}
/**
* fu_device_get_private_flags:
* @self: a #FuDevice
*
* Returns all the private flags that can be used by the plugin for any purpose.
*
* Returns: flags
*
* Since: 1.6.2
**/
guint64
fu_device_get_private_flags(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), G_MAXUINT64);
return priv->private_flags;
}
/**
* fu_device_get_request_cnt:
* @self: a #FuDevice
* @request_kind: the type of request
*
* Returns the number of requests of a specific kind. This function is only
* useful to the daemon, which uses it to synthesize artificial events for
* plugins not yet ported to [class@Fwupd.Request].
*
* Returns: integer, usually 0
*
* Since: 1.6.2
**/
guint
fu_device_get_request_cnt(FuDevice *self, FwupdRequestKind request_kind)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), G_MAXUINT);
g_return_val_if_fail(request_kind < FWUPD_REQUEST_KIND_LAST, G_MAXUINT);
return priv->request_cnts[request_kind];
}
/**
* fu_device_set_private_flags:
* @self: a #FuDevice
* @flag: flags
*
* Sets private flags that can be used by the plugin for any purpose.
*
* Since: 1.6.2
**/
void
fu_device_set_private_flags(FuDevice *self, guint64 flag)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->private_flags = flag;
}
/**
* fu_device_get_possible_plugins:
* @self: a #FuDevice
*
* Gets the list of possible plugin names, typically added from quirk files.
*
* Returns: (element-type utf8) (transfer container): plugin names
*
* Since: 1.3.3
**/
GPtrArray *
fu_device_get_possible_plugins(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
return g_ptr_array_ref(priv->possible_plugins);
}
/**
* fu_device_add_possible_plugin:
* @self: a #FuDevice
* @plugin: a plugin name, e.g. `dfu`
*
* Adds a plugin name to the list of plugins that *might* be able to handle this
* device. This is tyically called from a quirk handler.
*
* Duplicate plugin names are ignored.
*
* Since: 1.5.1
**/
void
fu_device_add_possible_plugin(FuDevice *self, const gchar *plugin)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(plugin != NULL);
/* add if it does not already exist */
#if GLIB_CHECK_VERSION(2, 54, 3)
if (g_ptr_array_find_with_equal_func(priv->possible_plugins, plugin, g_str_equal, NULL))
return;
#endif
g_ptr_array_add(priv->possible_plugins, g_strdup(plugin));
}
/**
* fu_device_retry_add_recovery:
* @self: a #FuDevice
* @domain: a #GQuark, or %0 for all domains
* @code: a #GError code
* @func: (scope async) (nullable): a function to recover the device
*
* Sets the optional function to be called when fu_device_retry() fails, which
* is possibly a device reset.
*
* If @func is %NULL then recovery is not possible and an error is returned
* straight away.
*
* Since: 1.4.0
**/
void
fu_device_retry_add_recovery(FuDevice *self, GQuark domain, gint code, FuDeviceRetryFunc func)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
FuDeviceRetryRecovery *rec;
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(domain != 0);
rec = g_new(FuDeviceRetryRecovery, 1);
rec->domain = domain;
rec->code = code;
rec->recovery_func = func;
g_ptr_array_add(priv->retry_recs, rec);
}
/**
* fu_device_retry_set_delay:
* @self: a #FuDevice
* @delay: delay in ms
*
* Sets the recovery delay between failed retries.
*
* Since: 1.4.0
**/
void
fu_device_retry_set_delay(FuDevice *self, guint delay)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->retry_delay = delay;
}
/**
* fu_device_retry_full:
* @self: a #FuDevice
* @func: (scope async): a function to execute
* @count: the number of tries to try the function
* @delay: the delay between each try in ms
* @user_data: (nullable): a helper to pass to @func
* @error: (nullable): optional return location for an error
*
* Calls a specific function a number of times, optionally handling the error
* with a reset action.
*
* If fu_device_retry_add_recovery() has not been used then all errors are
* considered non-fatal until the last try.
*
* If the reset function returns %FALSE, then the function returns straight away
* without processing any pending retries.
*
* Since: 1.5.5
**/
gboolean
fu_device_retry_full(FuDevice *self,
FuDeviceRetryFunc func,
guint count,
guint delay,
gpointer user_data,
GError **error)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(func != NULL, FALSE);
g_return_val_if_fail(count >= 1, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
for (guint i = 0;; i++) {
g_autoptr(GError) error_local = NULL;
/* delay */
if (i > 0 && delay > 0)
g_usleep(delay * 1000);
/* run function, if success return success */
if (func(self, user_data, &error_local))
break;
/* sanity check */
if (error_local == NULL) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"exec failed but no error set!");
return FALSE;
}
/* too many retries */
if (i >= count - 1) {
g_propagate_prefixed_error(error,
g_steal_pointer(&error_local),
"failed after %u retries: ",
count);
return FALSE;
}
/* show recoverable error on the console */
if (priv->retry_recs->len == 0) {
g_debug("failed on try %u of %u: %s", i + 1, count, error_local->message);
continue;
}
/* find the condition that matches */
for (guint j = 0; j < priv->retry_recs->len; j++) {
FuDeviceRetryRecovery *rec = g_ptr_array_index(priv->retry_recs, j);
if (g_error_matches(error_local, rec->domain, rec->code)) {
if (rec->recovery_func != NULL) {
if (!rec->recovery_func(self, user_data, error))
return FALSE;
} else {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_FAILED,
"device recovery not possible");
return FALSE;
}
}
}
}
/* success */
return TRUE;
}
/**
* fu_device_retry:
* @self: a #FuDevice
* @func: (scope async): a function to execute
* @count: the number of tries to try the function
* @user_data: (nullable): a helper to pass to @func
* @error: (nullable): optional return location for an error
*
* Calls a specific function a number of times, optionally handling the error
* with a reset action.
*
* If fu_device_retry_add_recovery() has not been used then all errors are
* considered non-fatal until the last try.
*
* If the reset function returns %FALSE, then the function returns straight away
* without processing any pending retries.
*
* Since: 1.4.0
**/
gboolean
fu_device_retry(FuDevice *self,
FuDeviceRetryFunc func,
guint count,
gpointer user_data,
GError **error)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
return fu_device_retry_full(self, func, count, priv->retry_delay, user_data, error);
}
static gboolean
fu_device_poll_locker_open_cb(GObject *device, GError **error)
{
FuDevice *self = FU_DEVICE(device);
FuDevicePrivate *priv = GET_PRIVATE(self);
g_atomic_int_inc(&priv->poll_locker_cnt);
return TRUE;
}
static gboolean
fu_device_poll_locker_close_cb(GObject *device, GError **error)
{
FuDevice *self = FU_DEVICE(device);
FuDevicePrivate *priv = GET_PRIVATE(self);
g_atomic_int_dec_and_test(&priv->poll_locker_cnt);
return TRUE;
}
/**
* fu_device_poll_locker_new:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Returns a device locker that prevents polling on the device. If there are no open poll lockers
* then the poll callback will be called.
*
* Use %FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING to opt into this functionality.
*
* Returns: (transfer full): a #FuDeviceLocker
*
* Since: 1.8.1
**/
FuDeviceLocker *
fu_device_poll_locker_new(FuDevice *self, GError **error)
{
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
return fu_device_locker_new_full(self,
fu_device_poll_locker_open_cb,
fu_device_poll_locker_close_cb,
error);
}
/**
* fu_device_poll:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Polls a device, typically querying the hardware for status.
*
* Returns: %TRUE for success
*
* Since: 1.1.2
**/
gboolean
fu_device_poll(FuDevice *self, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* subclassed */
if (klass->poll != NULL) {
if (!klass->poll(self, error))
return FALSE;
}
return TRUE;
}
static gboolean
fu_device_poll_cb(gpointer user_data)
{
FuDevice *self = FU_DEVICE(user_data);
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GError) error_local = NULL;
/* device is being detached, written, read, or attached */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_AUTO_PAUSE_POLLING) &&
priv->poll_locker_cnt > 0) {
g_debug("ignoring poll callback as an action is in progress");
return G_SOURCE_CONTINUE;
}
if (!fu_device_poll(self, &error_local)) {
g_warning("disabling polling: %s", error_local->message);
priv->poll_id = 0;
return G_SOURCE_REMOVE;
}
return G_SOURCE_CONTINUE;
}
/**
* fu_device_set_poll_interval:
* @self: a #FuPlugin
* @interval: duration in ms, or 0 to disable
*
* Polls the hardware every interval period. If the subclassed `->poll()` method
* returns %FALSE then a warning is printed to the console and the poll is
* disabled until the next call to fu_device_set_poll_interval().
*
* Since: 1.1.2
**/
void
fu_device_set_poll_interval(FuDevice *self, guint interval)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
if (priv->poll_id != 0) {
g_source_remove(priv->poll_id);
priv->poll_id = 0;
}
if (interval == 0)
return;
if (interval % 1000 == 0) {
priv->poll_id = g_timeout_add_seconds(interval / 1000, fu_device_poll_cb, self);
} else {
priv->poll_id = g_timeout_add(interval, fu_device_poll_cb, self);
}
}
/**
* fu_device_get_order:
* @self: a #FuPlugin
*
* Gets the device order, where higher numbers are installed after lower
* numbers.
*
* Returns: the integer value
*
* Since: 1.0.8
**/
gint
fu_device_get_order(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), 0);
return priv->order;
}
/**
* fu_device_set_order:
* @self: a #FuDevice
* @order: an integer value
*
* Sets the device order, where higher numbers are installed after lower
* numbers.
*
* Since: 1.0.8
**/
void
fu_device_set_order(FuDevice *self, gint order)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->order = order;
}
/**
* fu_device_get_priority:
* @self: a #FuPlugin
*
* Gets the device priority, where higher numbers are better.
*
* Returns: the integer value
*
* Since: 1.1.1
**/
guint
fu_device_get_priority(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), 0);
return priv->priority;
}
/**
* fu_device_set_priority:
* @self: a #FuDevice
* @priority: an integer value
*
* Sets the device priority, where higher numbers are better.
*
* Since: 1.1.1
**/
void
fu_device_set_priority(FuDevice *self, guint priority)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->priority = priority;
}
/**
* fu_device_get_equivalent_id:
* @self: a #FuDevice
*
* Gets any equivalent ID for a device
*
* Returns: (transfer none): a #gchar or NULL
*
* Since: 0.6.1
**/
const gchar *
fu_device_get_equivalent_id(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->equivalent_id;
}
/**
* fu_device_set_equivalent_id:
* @self: a #FuDevice
* @equivalent_id: (nullable): a string
*
* Sets any equivalent ID for a device
*
* Since: 0.6.1
**/
void
fu_device_set_equivalent_id(FuDevice *self, const gchar *equivalent_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
/* not changed */
if (g_strcmp0(priv->equivalent_id, equivalent_id) == 0)
return;
g_free(priv->equivalent_id);
priv->equivalent_id = g_strdup(equivalent_id);
}
/**
* fu_device_get_alternate_id:
* @self: a #FuDevice
*
* Gets any alternate device ID. An alternate device may be linked to the primary
* device in some way.
*
* Returns: (transfer none): a device or %NULL
*
* Since: 1.1.0
**/
const gchar *
fu_device_get_alternate_id(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->alternate_id;
}
/**
* fu_device_set_alternate_id:
* @self: a #FuDevice
* @alternate_id: (nullable): Another #FuDevice ID
*
* Sets any alternate device ID. An alternate device may be linked to the primary
* device in some way.
*
* Since: 1.1.0
**/
void
fu_device_set_alternate_id(FuDevice *self, const gchar *alternate_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
/* not changed */
if (g_strcmp0(priv->alternate_id, alternate_id) == 0)
return;
g_free(priv->alternate_id);
priv->alternate_id = g_strdup(alternate_id);
}
/**
* fu_device_get_alternate:
* @self: a #FuDevice
*
* Gets any alternate device. An alternate device may be linked to the primary
* device in some way.
*
* The alternate object will be matched from the ID set in fu_device_set_alternate_id()
* and will be assigned by the daemon. This means if the ID is not found as an
* added device, then this function will return %NULL.
*
* Returns: (transfer none): a device or %NULL
*
* Since: 0.7.2
**/
FuDevice *
fu_device_get_alternate(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->alternate;
}
/**
* fu_device_set_alternate:
* @self: a #FuDevice
* @alternate: Another #FuDevice
*
* Sets any alternate device. An alternate device may be linked to the primary
* device in some way.
*
* This function is only usable by the daemon, not directly from plugins.
*
* Since: 0.7.2
**/
void
fu_device_set_alternate(FuDevice *self, FuDevice *alternate)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_set_object(&priv->alternate, alternate);
}
/**
* fu_device_get_parent:
* @self: a #FuDevice
*
* Gets any parent device. An parent device is logically "above" the current
* device and this may be reflected in client tools.
*
* This information also allows the plugin to optionally verify the parent
* device, for instance checking the parent device firmware version.
*
* The parent object is not refcounted and if destroyed this function will then
* return %NULL.
*
* Returns: (transfer none): a device or %NULL
*
* Since: 1.0.8
**/
FuDevice *
fu_device_get_parent(FuDevice *self)
{
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return FU_DEVICE(fwupd_device_get_parent(FWUPD_DEVICE(self)));
}
/**
* fu_device_get_root:
* @self: a #FuDevice
*
* Gets the root parent device. A parent device is logically "above" the current
* device and this may be reflected in client tools.
*
* If there is no parent device defined, then @self is returned.
*
* Returns: (transfer full): a device
*
* Since: 1.4.0
**/
FuDevice *
fu_device_get_root(FuDevice *self)
{
FuDevice *parent;
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
do {
parent = fu_device_get_parent(self);
if (parent != NULL)
self = parent;
} while (parent != NULL);
return g_object_ref(self);
}
static void
fu_device_set_composite_id(FuDevice *self, const gchar *composite_id)
{
GPtrArray *children;
/* subclassed simple setter */
fwupd_device_set_composite_id(FWUPD_DEVICE(self), composite_id);
/* all children */
children = fu_device_get_children(self);
for (guint i = 0; i < children->len; i++) {
FuDevice *child_tmp = g_ptr_array_index(children, i);
fu_device_set_composite_id(child_tmp, composite_id);
}
}
/**
* fu_device_set_parent:
* @self: a #FuDevice
* @parent: (nullable): a device
*
* Sets any parent device. An parent device is logically "above" the current
* device and this may be reflected in client tools.
*
* This information also allows the plugin to optionally verify the parent
* device, for instance checking the parent device firmware version.
*
* Since: 1.0.8
**/
void
fu_device_set_parent(FuDevice *self, FuDevice *parent)
{
g_return_if_fail(FU_IS_DEVICE(self));
/* debug */
if (parent != NULL) {
g_debug("setting parent of %s [%s] to be %s [%s]",
fu_device_get_name(self),
fu_device_get_id(self),
fu_device_get_name(parent),
fu_device_get_id(parent));
}
/* set the composite ID on the children and grandchildren */
if (parent != NULL)
fu_device_set_composite_id(self, fu_device_get_composite_id(parent));
/* if the parent has a context, make the child inherit it */
if (parent != NULL) {
if (fu_device_get_context(self) == NULL && fu_device_get_context(parent) != NULL)
fu_device_set_context(self, fu_device_get_context(parent));
}
fwupd_device_set_parent(FWUPD_DEVICE(self), FWUPD_DEVICE(parent));
g_object_notify(G_OBJECT(self), "parent");
}
/**
* fu_device_set_proxy:
* @self: a #FuDevice
* @proxy: a device
*
* Sets any proxy device. A proxy device can be used to perform an action on
* behalf of another device, for instance attach()ing it after a successful
* update.
*
* Since: 1.4.1
**/
void
fu_device_set_proxy(FuDevice *self, FuDevice *proxy)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
/* copy from proxy */
if (proxy != NULL) {
if (fu_device_get_context(self) == NULL && fu_device_get_context(proxy) != NULL)
fu_device_set_context(self, fu_device_get_context(proxy));
if (fu_device_get_physical_id(self) == NULL &&
fu_device_get_physical_id(proxy) != NULL)
fu_device_set_physical_id(self, fu_device_get_physical_id(proxy));
}
if (priv->proxy != NULL)
g_object_remove_weak_pointer(G_OBJECT(priv->proxy), (gpointer *)&priv->proxy);
if (proxy != NULL)
g_object_add_weak_pointer(G_OBJECT(proxy), (gpointer *)&priv->proxy);
priv->proxy = proxy;
g_object_notify(G_OBJECT(self), "proxy");
}
/**
* fu_device_get_proxy:
* @self: a #FuDevice
*
* Gets any proxy device. A proxy device can be used to perform an action on
* behalf of another device, for instance attach()ing it after a successful
* update.
*
* The proxy object is not refcounted and if destroyed this function will then
* return %NULL.
*
* Returns: (transfer none): a device or %NULL
*
* Since: 1.4.1
**/
FuDevice *
fu_device_get_proxy(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->proxy;
}
/**
* fu_device_get_proxy_with_fallback:
* @self: a #FuDevice
*
* Gets the proxy device if %FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK is set, falling back to the
* device itself.
*
* Returns: (transfer none): a device
*
* Since: 1.6.2
**/
FuDevice *
fu_device_get_proxy_with_fallback(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PROXY_FALLBACK) &&
priv->proxy != NULL)
return priv->proxy;
return self;
}
/**
* fu_device_get_children:
* @self: a #FuDevice
*
* Gets any child devices. A child device is logically "below" the current
* device and this may be reflected in client tools.
*
* Returns: (transfer none) (element-type FuDevice): child devices
*
* Since: 1.0.8
**/
GPtrArray *
fu_device_get_children(FuDevice *self)
{
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return fwupd_device_get_children(FWUPD_DEVICE(self));
}
/**
* fu_device_add_child:
* @self: a #FuDevice
* @child: Another #FuDevice
*
* Sets any child device. An child device is logically linked to the primary
* device in some way.
*
* Since: 1.0.8
**/
void
fu_device_add_child(FuDevice *self, FuDevice *child)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
FuDevicePrivate *priv_child = GET_PRIVATE(child);
GPtrArray *children;
g_autoptr(GError) error = NULL;
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(FU_IS_DEVICE(child));
/* add if the child does not already exist */
fwupd_device_add_child(FWUPD_DEVICE(self), FWUPD_DEVICE(child));
/* propagate inhibits to children */
if (priv->inhibits != NULL &&
fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN)) {
g_autoptr(GList) values = g_hash_table_get_values(priv->inhibits);
for (GList *l = values; l != NULL; l = l->next) {
FuDeviceInhibit *inhibit = (FuDeviceInhibit *)l->data;
fu_device_inhibit_full(child,
inhibit->problem,
inhibit->inhibit_id,
inhibit->reason);
}
}
/* ensure the parent has the MAX() of the children's removal delay */
children = fu_device_get_children(self);
for (guint i = 0; i < children->len; i++) {
FuDevice *child_tmp = g_ptr_array_index(children, i);
guint remove_delay = fu_device_get_remove_delay(child_tmp);
if (remove_delay > priv->remove_delay) {
g_debug("setting remove delay to %ums as child is greater than %ums",
remove_delay,
priv->remove_delay);
priv->remove_delay = remove_delay;
}
}
/* copy from main device if unset */
if (fu_device_get_physical_id(child) == NULL && fu_device_get_physical_id(self) != NULL)
fu_device_set_physical_id(child, fu_device_get_physical_id(self));
if (priv_child->backend_id == NULL && priv->backend_id != NULL)
fu_device_set_backend_id(child, priv->backend_id);
if (priv_child->ctx == NULL && priv->ctx != NULL)
fu_device_set_context(child, priv->ctx);
if (fu_device_get_vendor(child) == NULL)
fu_device_set_vendor(child, fu_device_get_vendor(self));
if (priv_child->remove_delay == 0 && priv->remove_delay != 0)
fu_device_set_remove_delay(child, priv->remove_delay);
if (fu_device_get_vendor_ids(child)->len == 0) {
GPtrArray *vendor_ids = fu_device_get_vendor_ids(self);
for (guint i = 0; i < vendor_ids->len; i++) {
const gchar *vendor_id = g_ptr_array_index(vendor_ids, i);
fu_device_add_vendor_id(child, vendor_id);
}
}
if (fu_device_get_icons(child)->len == 0) {
GPtrArray *icons = fu_device_get_icons(self);
for (guint i = 0; i < icons->len; i++) {
const gchar *icon_name = g_ptr_array_index(icons, i);
fu_device_add_icon(child, icon_name);
}
}
/* ensure the ID is converted */
if (!fu_device_ensure_id(child, &error))
g_warning("failed to ensure child: %s", error->message);
/* ensure the parent is also set on the child */
fu_device_set_parent(child, self);
/* signal to the plugin in case this is done after setup */
g_signal_emit(self, signals[SIGNAL_CHILD_ADDED], 0, child);
}
/**
* fu_device_remove_child:
* @self: a #FuDevice
* @child: Another #FuDevice
*
* Removes child device.
*
* Since: 1.6.2
**/
void
fu_device_remove_child(FuDevice *self, FuDevice *child)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(FU_IS_DEVICE(child));
/* proxy */
fwupd_device_remove_child(FWUPD_DEVICE(self), FWUPD_DEVICE(child));
/* signal to the plugin */
g_signal_emit(self, signals[SIGNAL_CHILD_REMOVED], 0, child);
}
/**
* fu_device_get_parent_guids:
* @self: a #FuDevice
*
* Gets any parent device GUIDs. If a device is added to the daemon that matches
* any GUIDs added from fu_device_add_parent_guid() then this device is marked the parent of @self.
*
* Returns: (transfer none) (element-type utf8): a list of GUIDs
*
* Since: 1.0.8
**/
GPtrArray *
fu_device_get_parent_guids(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GRWLockReaderLocker) locker =
g_rw_lock_reader_locker_new(&priv->parent_guids_mutex);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
g_return_val_if_fail(locker != NULL, NULL);
return priv->parent_guids;
}
/**
* fu_device_has_parent_guid:
* @self: a #FuDevice
* @guid: a GUID
*
* Searches the list of parent GUIDs for a string match.
*
* Returns: %TRUE if the parent GUID exists
*
* Since: 1.0.8
**/
gboolean
fu_device_has_parent_guid(FuDevice *self, const gchar *guid)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GRWLockReaderLocker) locker =
g_rw_lock_reader_locker_new(&priv->parent_guids_mutex);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(guid != NULL, FALSE);
g_return_val_if_fail(locker != NULL, FALSE);
for (guint i = 0; i < priv->parent_guids->len; i++) {
const gchar *guid_tmp = g_ptr_array_index(priv->parent_guids, i);
if (g_strcmp0(guid_tmp, guid) == 0)
return TRUE;
}
return FALSE;
}
/**
* fu_device_add_parent_guid:
* @self: a #FuDevice
* @guid: a GUID
*
* Sets any parent device using a GUID. An parent device is logically linked to
* the primary device in some way and can be added before or after @self.
*
* The GUIDs are searched in order, and so the order of adding GUIDs may be
* important if more than one parent device might match.
*
* If the parent device is removed, any children logically linked to it will
* also be removed.
*
* Since: 1.0.8
**/
void
fu_device_add_parent_guid(FuDevice *self, const gchar *guid)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GRWLockWriterLocker) locker = NULL;
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(guid != NULL);
/* make valid */
if (!fwupd_guid_is_valid(guid)) {
g_autofree gchar *tmp = fwupd_guid_hash_string(guid);
if (fu_device_has_parent_guid(self, tmp))
return;
g_debug("using %s for %s", tmp, guid);
g_ptr_array_add(priv->parent_guids, g_steal_pointer(&tmp));
return;
}
/* already valid */
if (fu_device_has_parent_guid(self, guid))
return;
locker = g_rw_lock_writer_locker_new(&priv->parent_guids_mutex);
g_return_if_fail(locker != NULL);
g_ptr_array_add(priv->parent_guids, g_strdup(guid));
}
/**
* fu_device_get_parent_physical_ids:
* @self: a #FuDevice
*
* Gets any parent device IDs. If a device is added to the daemon that matches
* the physical ID added from fu_device_add_parent_physical_id() then this
* device is marked the parent of @self.
*
* Returns: (transfer none) (element-type utf8) (nullable): a list of IDs
*
* Since: 1.6.2
**/
GPtrArray *
fu_device_get_parent_physical_ids(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->parent_physical_ids;
}
/**
* fu_device_has_parent_physical_id:
* @self: a #FuDevice
* @physical_id: a device physical ID
*
* Searches the list of parent IDs for a string match.
*
* Returns: %TRUE if the parent ID exists
*
* Since: 1.6.2
**/
gboolean
fu_device_has_parent_physical_id(FuDevice *self, const gchar *physical_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(physical_id != NULL, FALSE);
if (priv->parent_physical_ids == NULL)
return FALSE;
for (guint i = 0; i < priv->parent_physical_ids->len; i++) {
const gchar *tmp = g_ptr_array_index(priv->parent_physical_ids, i);
if (g_strcmp0(tmp, physical_id) == 0)
return TRUE;
}
return FALSE;
}
/**
* fu_device_add_parent_physical_id:
* @self: a #FuDevice
* @physical_id: a device physical ID
*
* Sets any parent device using the physical ID. An parent device is logically
* linked to the primary device in some way and can be added before or after @self.
*
* The IDs are searched in order, and so the order of adding IDs may be
* important if more than one parent device might match.
*
* Since: 1.6.2
**/
void
fu_device_add_parent_physical_id(FuDevice *self, const gchar *physical_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(physical_id != NULL);
/* ensure exists */
if (priv->parent_physical_ids == NULL)
priv->parent_physical_ids = g_ptr_array_new_with_free_func(g_free);
/* already present */
if (fu_device_has_parent_physical_id(self, physical_id))
return;
g_ptr_array_add(priv->parent_physical_ids, g_strdup(physical_id));
}
static gboolean
fu_device_add_child_by_type_guid(FuDevice *self, GType type, const gchar *guid, GError **error)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(FuDevice) child = NULL;
child = g_object_new(type, "context", priv->ctx, "logical-id", guid, NULL);
fu_device_add_guid(child, guid);
if (fu_device_get_physical_id(self) != NULL)
fu_device_set_physical_id(child, fu_device_get_physical_id(self));
if (!fu_device_ensure_id(self, error))
return FALSE;
if (!fu_device_probe(child, error))
return FALSE;
fu_device_convert_instance_ids(child);
fu_device_add_child(self, child);
return TRUE;
}
static gboolean
fu_device_add_child_by_kv(FuDevice *self, const gchar *str, GError **error)
{
g_auto(GStrv) split = g_strsplit(str, "|", -1);
/* type same as parent */
if (g_strv_length(split) == 1) {
return fu_device_add_child_by_type_guid(self, G_OBJECT_TYPE(self), split[1], error);
}
/* type specified */
if (g_strv_length(split) == 2) {
GType devtype = g_type_from_name(split[0]);
if (devtype == 0) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"no GType registered");
return FALSE;
}
return fu_device_add_child_by_type_guid(self, devtype, split[1], error);
}
/* more than one '|' */
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_FOUND,
"unable to add parse child section");
return FALSE;
}
static gboolean
fu_device_set_quirk_inhibit_section(FuDevice *self, const gchar *value, GError **error)
{
g_auto(GStrv) sections = NULL;
g_return_val_if_fail(value != NULL, FALSE);
/* sanity check */
sections = g_strsplit(value, ":", -1);
if (g_strv_length(sections) != 2) {
g_set_error_literal(error,
G_IO_ERROR,
G_IO_ERROR_NOT_SUPPORTED,
"quirk key not supported, expected k1:v1[,k2:v2][,k3:]");
return FALSE;
}
/* allow empty string to unset quirk */
if (g_strcmp0(sections[1], "") != 0)
fu_device_inhibit(self, sections[0], sections[1]);
else
fu_device_uninhibit(self, sections[0]);
/* success */
return TRUE;
}
static gboolean
fu_device_set_quirk_kv(FuDevice *self, const gchar *key, const gchar *value, GError **error)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
guint64 tmp;
if (g_strcmp0(key, FU_QUIRKS_PLUGIN) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++)
fu_device_add_possible_plugin(self, sections[i]);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_FLAGS) == 0) {
fu_device_set_custom_flags(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_NAME) == 0) {
fu_device_set_name(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_SUMMARY) == 0) {
fu_device_set_summary(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_BRANCH) == 0) {
fu_device_set_branch(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_VENDOR) == 0) {
fu_device_set_vendor(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_VENDOR_ID) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++)
fu_device_add_vendor_id(self, sections[i]);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_PROTOCOL) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++)
fu_device_add_protocol(self, sections[i]);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_ISSUE) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++)
fu_device_add_issue(self, sections[i]);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_VERSION) == 0) {
fu_device_set_version(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_UPDATE_MESSAGE) == 0) {
fu_device_set_update_message(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_UPDATE_IMAGE) == 0) {
fu_device_set_update_image(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_ICON) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++)
fu_device_add_icon(self, sections[i]);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_GUID) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++)
fu_device_add_guid(self, sections[i]);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_COUNTERPART_GUID) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++)
fu_device_add_counterpart_guid(self, sections[i]);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_PARENT_GUID) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++)
fu_device_add_parent_guid(self, sections[i]);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_PROXY_GUID) == 0) {
fu_device_set_proxy_guid(self, value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_FIRMWARE_SIZE_MIN) == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT64, error))
return FALSE;
fu_device_set_firmware_size_min(self, tmp);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_FIRMWARE_SIZE_MAX) == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT64, error))
return FALSE;
fu_device_set_firmware_size_max(self, tmp);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_FIRMWARE_SIZE) == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT64, error))
return FALSE;
fu_device_set_firmware_size(self, tmp);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_INSTALL_DURATION) == 0) {
if (!fu_strtoull(value, &tmp, 0, 60 * 60 * 24, error))
return FALSE;
fu_device_set_install_duration(self, tmp);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_PRIORITY) == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT8, error))
return FALSE;
fu_device_set_priority(self, tmp);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_BATTERY_THRESHOLD) == 0) {
if (!fu_strtoull(value, &tmp, 0, 100, error))
return FALSE;
fu_device_set_battery_threshold(self, tmp);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_REMOVE_DELAY) == 0) {
if (!fu_strtoull(value, &tmp, 0, G_MAXUINT, error))
return FALSE;
fu_device_set_remove_delay(self, tmp);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_VERSION_FORMAT) == 0) {
fu_device_set_version_format(self, fwupd_version_format_from_string(value));
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_INHIBIT) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++) {
if (!fu_device_set_quirk_inhibit_section(self, sections[i], error))
return FALSE;
}
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_GTYPE) == 0) {
if (priv->specialized_gtype != G_TYPE_INVALID) {
g_debug("already set GType to %s, ignoring %s",
g_type_name(priv->specialized_gtype),
value);
return TRUE;
}
priv->specialized_gtype = g_type_from_name(value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_FIRMWARE_GTYPE) == 0) {
if (priv->firmware_gtype != G_TYPE_INVALID) {
g_debug("already set firmware GType to %s, ignoring %s",
g_type_name(priv->firmware_gtype),
value);
return TRUE;
}
priv->firmware_gtype = g_type_from_name(value);
return TRUE;
}
if (g_strcmp0(key, FU_QUIRKS_CHILDREN) == 0) {
g_auto(GStrv) sections = g_strsplit(value, ",", -1);
for (guint i = 0; sections[i] != NULL; i++) {
if (!fu_device_add_child_by_kv(self, sections[i], error))
return FALSE;
}
return TRUE;
}
/* optional device-specific method */
if (klass->set_quirk_kv != NULL)
return klass->set_quirk_kv(self, key, value, error);
/* failed */
g_set_error_literal(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED, "quirk key not supported");
return FALSE;
}
/**
* fu_device_get_specialized_gtype:
* @self: a #FuDevice
*
* Gets the specialized type of the device
*
* Returns:#GType
*
* Since: 1.3.3
**/
GType
fu_device_get_specialized_gtype(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
return priv->specialized_gtype;
}
/**
* fu_device_get_firmware_gtype:
* @self: a #FuDevice
*
* Gets the default firmware type for the device.
*
* Returns: #GType
*
* Since: 1.7.2
**/
GType
fu_device_get_firmware_gtype(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
return priv->firmware_gtype;
}
/**
* fu_device_set_firmware_gtype:
* @self: a #FuDevice
* @firmware_gtype: a #GType
*
* Sets the default firmware type for the device.
*
* Since: 1.7.2
**/
void
fu_device_set_firmware_gtype(FuDevice *self, GType firmware_gtype)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
priv->firmware_gtype = firmware_gtype;
}
static void
fu_device_quirks_iter_cb(FuContext *ctx, const gchar *key, const gchar *value, gpointer user_data)
{
FuDevice *self = FU_DEVICE(user_data);
g_autoptr(GError) error = NULL;
if (!fu_device_set_quirk_kv(self, key, value, &error)) {
if (!g_error_matches(error, G_IO_ERROR, G_IO_ERROR_NOT_SUPPORTED)) {
g_warning("failed to set quirk key %s=%s: %s", key, value, error->message);
}
}
}
static void
fu_device_add_guid_quirks(FuDevice *self, const gchar *guid)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
if (priv->ctx == NULL) {
g_autofree gchar *str = fu_device_to_string(self);
g_critical("no FuContext assigned for %s", str);
return;
}
fu_context_lookup_quirk_by_id_iter(priv->ctx, guid, fu_device_quirks_iter_cb, self);
}
/**
* fu_device_set_firmware_size:
* @self: a #FuDevice
* @size: Size in bytes
*
* Sets the exact allowed size of the firmware blob.
*
* Since: 1.2.6
**/
void
fu_device_set_firmware_size(FuDevice *self, guint64 size)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->size_min = size;
priv->size_max = size;
}
/**
* fu_device_set_firmware_size_min:
* @self: a #FuDevice
* @size_min: Size in bytes
*
* Sets the minimum allowed size of the firmware blob.
*
* Since: 1.1.2
**/
void
fu_device_set_firmware_size_min(FuDevice *self, guint64 size_min)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->size_min = size_min;
}
/**
* fu_device_set_firmware_size_max:
* @self: a #FuDevice
* @size_max: Size in bytes
*
* Sets the maximum allowed size of the firmware blob.
*
* Since: 1.1.2
**/
void
fu_device_set_firmware_size_max(FuDevice *self, guint64 size_max)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->size_max = size_max;
}
/**
* fu_device_get_firmware_size_min:
* @self: a #FuDevice
*
* Gets the minimum size of the firmware blob.
*
* Returns: Size in bytes, or 0 if unset
*
* Since: 1.2.6
**/
guint64
fu_device_get_firmware_size_min(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), 0);
return priv->size_min;
}
/**
* fu_device_get_firmware_size_max:
* @self: a #FuDevice
*
* Gets the maximum size of the firmware blob.
*
* Returns: Size in bytes, or 0 if unset
*
* Since: 1.2.6
**/
guint64
fu_device_get_firmware_size_max(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), 0);
return priv->size_max;
}
static void
fu_device_add_guid_safe(FuDevice *self, const gchar *guid, FuDeviceInstanceFlags flags)
{
/* add the device GUID before adding additional GUIDs from quirks
* to ensure the bootloader GUID is listed after the runtime GUID */
if ((flags & FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS) == 0)
fwupd_device_add_guid(FWUPD_DEVICE(self), guid);
if ((flags & FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS) == 0)
fu_device_add_guid_quirks(self, guid);
}
/**
* fu_device_has_guid:
* @self: a #FuDevice
* @guid: a GUID, e.g. `WacomAES`
*
* Finds out if the device has a specific GUID.
*
* Returns: %TRUE if the GUID is found
*
* Since: 1.2.2
**/
gboolean
fu_device_has_guid(FuDevice *self, const gchar *guid)
{
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(guid != NULL, FALSE);
/* make valid */
if (!fwupd_guid_is_valid(guid)) {
g_autofree gchar *tmp = fwupd_guid_hash_string(guid);
return fwupd_device_has_guid(FWUPD_DEVICE(self), tmp);
}
/* already valid */
return fwupd_device_has_guid(FWUPD_DEVICE(self), guid);
}
/**
* fu_device_add_instance_id_full:
* @self: a #FuDevice
* @instance_id: a instance ID, e.g. `WacomAES`
* @flags: instance ID flags
*
* Adds an instance ID with all parameters set
*
* Since: 1.2.9
**/
void
fu_device_add_instance_id_full(FuDevice *self,
const gchar *instance_id,
FuDeviceInstanceFlags flags)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autofree gchar *guid = NULL;
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(instance_id != NULL);
if (fwupd_guid_is_valid(instance_id)) {
g_warning("use fu_device_add_guid(\"%s\") instead!", instance_id);
fu_device_add_guid_safe(self, instance_id, flags);
return;
}
/* it seems odd adding the instance ID and the GUID quirks and not just
* calling fu_device_add_guid_safe() -- but we want the quirks to match
* so the plugin is set, but not the LVFS metadata to match firmware
* until we're sure the device isn't using _NO_AUTO_INSTANCE_IDS */
guid = fwupd_guid_hash_string(instance_id);
if ((flags & FU_DEVICE_INSTANCE_FLAG_NO_QUIRKS) == 0)
fu_device_add_guid_quirks(self, guid);
if ((flags & FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS) == 0)
fwupd_device_add_instance_id(FWUPD_DEVICE(self), instance_id);
/* already done by ->setup(), so this must be ->registered() */
if (priv->done_setup)
fwupd_device_add_guid(FWUPD_DEVICE(self), guid);
}
/**
* fu_device_add_instance_id:
* @self: a #FuDevice
* @instance_id: the instance ID, e.g. `PCI\VEN_10EC&DEV_525A`
*
* Adds an instance ID to the device. If the @instance_id argument is already a
* valid GUID then fu_device_add_guid() should be used instead.
*
* Since: 1.2.5
**/
void
fu_device_add_instance_id(FuDevice *self, const gchar *instance_id)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(instance_id != NULL);
fu_device_add_instance_id_full(self, instance_id, FU_DEVICE_INSTANCE_FLAG_NONE);
}
/**
* fu_device_add_guid:
* @self: a #FuDevice
* @guid: a GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad`
*
* Adds a GUID to the device. If the @guid argument is not a valid GUID then it
* is converted to a GUID using fwupd_guid_hash_string().
*
* Since: 0.7.2
**/
void
fu_device_add_guid(FuDevice *self, const gchar *guid)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(guid != NULL);
if (!fwupd_guid_is_valid(guid)) {
fu_device_add_instance_id(self, guid);
return;
}
fu_device_add_guid_safe(self, guid, FU_DEVICE_INSTANCE_FLAG_NONE);
}
/**
* fu_device_add_guid_full:
* @self: a #FuDevice
* @guid: a GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad`
* @flags: instance ID flags
*
* Adds a GUID to the device. If the @guid argument is not a valid GUID then it
* is converted to a GUID using fwupd_guid_hash_string().
*
* Since: 1.6.2
**/
void
fu_device_add_guid_full(FuDevice *self, const gchar *guid, FuDeviceInstanceFlags flags)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(guid != NULL);
if (!fwupd_guid_is_valid(guid)) {
fu_device_add_instance_id_full(self, guid, flags);
return;
}
fu_device_add_guid_safe(self, guid, flags);
}
/**
* fu_device_add_counterpart_guid:
* @self: a #FuDevice
* @guid: a GUID, e.g. `2082b5e0-7a64-478a-b1b2-e3404fab6dad`
*
* Adds a GUID to the device. If the @guid argument is not a valid GUID then it
* is converted to a GUID using fwupd_guid_hash_string().
*
* A counterpart GUID is typically the GUID of the same device in bootloader
* or runtime mode, if they have a different device PCI or USB ID. Adding this
* type of GUID does not cause a "cascade" by matching using the quirk database.
*
* Since: 1.1.2
**/
void
fu_device_add_counterpart_guid(FuDevice *self, const gchar *guid)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(guid != NULL);
/* make valid */
if (!fwupd_guid_is_valid(guid)) {
g_autofree gchar *tmp = fwupd_guid_hash_string(guid);
fwupd_device_add_guid(FWUPD_DEVICE(self), tmp);
return;
}
/* already valid */
fwupd_device_add_guid(FWUPD_DEVICE(self), guid);
}
/**
* fu_device_get_guids_as_str:
* @self: a #FuDevice
*
* Gets the device GUIDs as a joined string, which may be useful for error
* messages.
*
* Returns: a string, which may be empty length but not %NULL
*
* Since: 1.0.8
**/
gchar *
fu_device_get_guids_as_str(FuDevice *self)
{
GPtrArray *guids;
g_autofree gchar **tmp = NULL;
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
guids = fu_device_get_guids(self);
tmp = g_new0(gchar *, guids->len + 1);
for (guint i = 0; i < guids->len; i++)
tmp[i] = g_ptr_array_index(guids, i);
return g_strjoinv(",", tmp);
}
/**
* fu_device_get_metadata:
* @self: a #FuDevice
* @key: the key
*
* Gets an item of metadata from the device.
*
* Returns: a string value, or %NULL for unfound.
*
* Since: 0.1.0
**/
const gchar *
fu_device_get_metadata(FuDevice *self, const gchar *key)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->metadata_mutex);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
g_return_val_if_fail(key != NULL, NULL);
g_return_val_if_fail(locker != NULL, NULL);
if (priv->metadata == NULL)
return NULL;
return g_hash_table_lookup(priv->metadata, key);
}
/**
* fu_device_get_metadata_boolean:
* @self: a #FuDevice
* @key: the key
*
* Gets an item of metadata from the device.
*
* Returns: a boolean value, or %FALSE for unfound or failure to parse.
*
* Since: 0.9.7
**/
gboolean
fu_device_get_metadata_boolean(FuDevice *self, const gchar *key)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
const gchar *tmp;
g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->metadata_mutex);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(key != NULL, FALSE);
g_return_val_if_fail(locker != NULL, FALSE);
if (priv->metadata == NULL)
return FALSE;
tmp = g_hash_table_lookup(priv->metadata, key);
if (tmp == NULL)
return FALSE;
return g_strcmp0(tmp, "true") == 0;
}
/**
* fu_device_get_metadata_integer:
* @self: a #FuDevice
* @key: the key
*
* Gets an item of metadata from the device.
*
* Returns: an integer value, or %G_MAXUINT for unfound or failure to parse.
*
* Since: 0.9.7
**/
guint
fu_device_get_metadata_integer(FuDevice *self, const gchar *key)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
const gchar *tmp;
gchar *endptr = NULL;
guint64 val;
g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->metadata_mutex);
g_return_val_if_fail(FU_IS_DEVICE(self), G_MAXUINT);
g_return_val_if_fail(key != NULL, G_MAXUINT);
g_return_val_if_fail(locker != NULL, G_MAXUINT);
if (priv->metadata == NULL)
return G_MAXUINT;
tmp = g_hash_table_lookup(priv->metadata, key);
if (tmp == NULL)
return G_MAXUINT;
val = g_ascii_strtoull(tmp, &endptr, 10);
if (endptr != NULL && endptr[0] != '\0')
return G_MAXUINT;
if (val > G_MAXUINT)
return G_MAXUINT;
return (guint)val;
}
/**
* fu_device_remove_metadata:
* @self: a #FuDevice
* @key: the key
*
* Removes an item of metadata on the device.
*
* Since: 1.3.3
**/
void
fu_device_remove_metadata(FuDevice *self, const gchar *key)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&priv->metadata_mutex);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_return_if_fail(locker != NULL);
if (priv->metadata == NULL)
return;
g_hash_table_remove(priv->metadata, key);
}
/**
* fu_device_set_metadata:
* @self: a #FuDevice
* @key: the key
* @value: the string value
*
* Sets an item of metadata on the device.
*
* Since: 0.1.0
**/
void
fu_device_set_metadata(FuDevice *self, const gchar *key, const gchar *value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(GRWLockWriterLocker) locker = g_rw_lock_writer_locker_new(&priv->metadata_mutex);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_return_if_fail(value != NULL);
g_return_if_fail(locker != NULL);
if (priv->metadata == NULL) {
priv->metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
}
g_hash_table_insert(priv->metadata, g_strdup(key), g_strdup(value));
}
/**
* fu_device_set_metadata_boolean:
* @self: a #FuDevice
* @key: the key
* @value: the boolean value
*
* Sets an item of metadata on the device. When @value is set to %TRUE
* the actual stored value is `true`.
*
* Since: 0.9.7
**/
void
fu_device_set_metadata_boolean(FuDevice *self, const gchar *key, gboolean value)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
fu_device_set_metadata(self, key, value ? "true" : "false");
}
/**
* fu_device_set_metadata_integer:
* @self: a #FuDevice
* @key: the key
* @value: the unsigned integer value
*
* Sets an item of metadata on the device. The integer is stored as a
* base-10 string internally.
*
* Since: 0.9.7
**/
void
fu_device_set_metadata_integer(FuDevice *self, const gchar *key, guint value)
{
g_autofree gchar *tmp = g_strdup_printf("%u", value);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
fu_device_set_metadata(self, key, tmp);
}
/* ensure the name does not have the vendor name as the prefix */
static void
fu_device_fixup_vendor_name(FuDevice *self)
{
const gchar *name = fu_device_get_name(self);
const gchar *vendor = fu_device_get_vendor(self);
if (name != NULL && vendor != NULL) {
g_autofree gchar *name_up = g_utf8_strup(name, -1);
g_autofree gchar *vendor_up = g_utf8_strup(vendor, -1);
if (g_str_has_prefix(name_up, vendor_up)) {
gsize vendor_len = strlen(vendor);
g_autofree gchar *name1 = g_strdup(name + vendor_len);
g_autofree gchar *name2 = fu_strstrip(name1);
g_debug("removing vendor prefix of '%s' from '%s'", vendor, name);
fwupd_device_set_name(FWUPD_DEVICE(self), name2);
}
}
}
/**
* fu_device_set_vendor:
* @self: a #FuDevice
* @vendor: a device vendor
*
* Sets the vendor name on the device.
*
* Since: 1.6.2
**/
void
fu_device_set_vendor(FuDevice *self, const gchar *vendor)
{
g_autofree gchar *vendor_safe = NULL;
/* trim any leading and trailing spaces */
if (vendor != NULL)
vendor_safe = fu_strstrip(vendor);
/* proxy */
fwupd_device_set_vendor(FWUPD_DEVICE(self), vendor_safe);
fu_device_fixup_vendor_name(self);
}
static gchar *
fu_device_sanitize_name(const gchar *value)
{
gboolean last_was_space = FALSE;
guint last_non_space = 0;
g_autoptr(GString) new = g_string_new(NULL);
/* add each printable char with maximum of one whitespace char */
for (guint i = 0; value[i] != '\0'; i++) {
const gchar tmp = value[i];
if (!g_ascii_isprint(tmp))
continue;
if (g_ascii_isspace(tmp) || tmp == '_') {
if (new->len == 0)
continue;
if (last_was_space)
continue;
last_was_space = TRUE;
g_string_append_c(new, ' ');
} else {
last_was_space = FALSE;
g_string_append_c(new, tmp);
last_non_space = new->len;
}
}
g_string_truncate(new, last_non_space);
fu_string_replace(new, "(TM)", "");
fu_string_replace(new, "(R)", "");
if (new->len == 0)
return NULL;
return g_string_free(g_steal_pointer(&new), FALSE);
}
/**
* fu_device_set_name:
* @self: a #FuDevice
* @value: a device name
*
* Sets the name on the device. Any invalid parts will be converted or removed.
*
* Since: 0.7.1
**/
void
fu_device_set_name(FuDevice *self, const gchar *value)
{
g_autofree gchar *value_safe = NULL;
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(value != NULL);
/* overwriting? */
value_safe = fu_device_sanitize_name(value);
if (g_strcmp0(value_safe, fu_device_get_name(self)) == 0)
return;
/* changing */
if (fu_device_get_name(self) != NULL) {
const gchar *id = fu_device_get_id(self);
g_debug("%s device overwriting name value: %s->%s",
id != NULL ? id : "unknown",
fu_device_get_name(self),
value_safe);
}
fwupd_device_set_name(FWUPD_DEVICE(self), value_safe);
fu_device_fixup_vendor_name(self);
}
/**
* fu_device_set_id:
* @self: a #FuDevice
* @id: a string, e.g. `tbt-port1`
*
* Sets the ID on the device. The ID should represent the *connection* of the
* device, so that any similar device plugged into a different slot will
* have a different @id string.
*
* The @id will be converted to a SHA1 hash if required before the device is
* added to the daemon, and plugins should not assume that the ID that is set
* here is the same as what is returned by fu_device_get_id().
*
* Since: 0.7.1
**/
void
fu_device_set_id(FuDevice *self, const gchar *id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
GPtrArray *children;
g_autofree gchar *id_hash = NULL;
g_autofree gchar *id_hash_old = g_strdup(fwupd_device_get_id(FWUPD_DEVICE(self)));
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(id != NULL);
/* allow sane device-id to be set directly */
if (fwupd_device_id_is_valid(id)) {
id_hash = g_strdup(id);
} else {
id_hash = g_compute_checksum_for_string(G_CHECKSUM_SHA1, id, -1);
g_debug("using %s for %s", id_hash, id);
}
fwupd_device_set_id(FWUPD_DEVICE(self), id_hash);
priv->device_id_valid = TRUE;
/* ensure the parent ID is set */
children = fu_device_get_children(self);
for (guint i = 0; i < children->len; i++) {
FuDevice *devtmp = g_ptr_array_index(children, i);
fwupd_device_set_parent_id(FWUPD_DEVICE(devtmp), id_hash);
/* update the composite ID of the child with the new ID if required; this will
* propagate to grandchildren and great-grandchildren as required */
if (id_hash_old != NULL &&
g_strcmp0(fu_device_get_composite_id(devtmp), id_hash_old) == 0)
fu_device_set_composite_id(devtmp, id_hash);
}
}
/**
* fu_device_set_version_format:
* @self: a #FuDevice
* @fmt: the version format, e.g. %FWUPD_VERSION_FORMAT_PLAIN
*
* Sets the device version format.
*
* Since: 1.4.0
**/
void
fu_device_set_version_format(FuDevice *self, FwupdVersionFormat fmt)
{
/* same */
if (fu_device_get_version_format(self) == fmt)
return;
if (fu_device_get_version_format(self) != FWUPD_VERSION_FORMAT_UNKNOWN) {
g_debug("changing verfmt for %s: %s->%s",
fu_device_get_id(self),
fwupd_version_format_to_string(fu_device_get_version_format(self)),
fwupd_version_format_to_string(fmt));
}
fwupd_device_set_version_format(FWUPD_DEVICE(self), fmt);
}
/**
* fu_device_set_version:
* @self: a #FuDevice
* @version: (nullable): a string, e.g. `1.2.3`
*
* Sets the device version, sanitizing the string if required.
*
* Since: 1.2.9
**/
void
fu_device_set_version(FuDevice *self, const gchar *version)
{
g_autofree gchar *version_safe = NULL;
g_autoptr(GError) error = NULL;
g_return_if_fail(FU_IS_DEVICE(self));
/* sanitize if required */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER)) {
version_safe =
fu_version_ensure_semver(version, fu_device_get_version_format(self));
if (g_strcmp0(version, version_safe) != 0)
g_debug("converted '%s' to '%s'", version, version_safe);
} else {
version_safe = g_strdup(version);
}
/* print a console warning for an invalid version, if semver */
if (version_safe != NULL &&
!fu_version_verify_format(version_safe, fu_device_get_version_format(self), &error))
g_warning("%s", error->message);
/* if different */
if (g_strcmp0(fu_device_get_version(self), version_safe) != 0) {
if (fu_device_get_version(self) != NULL) {
g_debug("changing version for %s: %s->%s",
fu_device_get_id(self),
fu_device_get_version(self),
version_safe);
}
fwupd_device_set_version(FWUPD_DEVICE(self), version_safe);
}
}
/**
* fu_device_set_version_lowest:
* @self: a #FuDevice
* @version: (nullable): a string, e.g. `1.2.3`
*
* Sets the device lowest version, sanitizing the string if required.
*
* Since: 1.4.0
**/
void
fu_device_set_version_lowest(FuDevice *self, const gchar *version)
{
g_autofree gchar *version_safe = NULL;
g_autoptr(GError) error = NULL;
g_return_if_fail(FU_IS_DEVICE(self));
/* sanitize if required */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER)) {
version_safe =
fu_version_ensure_semver(version, fu_device_get_version_format(self));
if (g_strcmp0(version, version_safe) != 0)
g_debug("converted '%s' to '%s'", version, version_safe);
} else {
version_safe = g_strdup(version);
}
/* print a console warning for an invalid version, if semver */
if (version_safe != NULL &&
!fu_version_verify_format(version_safe, fu_device_get_version_format(self), &error))
g_warning("%s", error->message);
/* if different */
if (g_strcmp0(fu_device_get_version_lowest(self), version_safe) != 0) {
if (fu_device_get_version_lowest(self) != NULL) {
g_debug("changing version lowest for %s: %s->%s",
fu_device_get_id(self),
fu_device_get_version_lowest(self),
version_safe);
}
fwupd_device_set_version_lowest(FWUPD_DEVICE(self), version_safe);
}
}
/**
* fu_device_set_version_bootloader:
* @self: a #FuDevice
* @version: (nullable): a string, e.g. `1.2.3`
*
* Sets the device bootloader version, sanitizing the string if required.
*
* Since: 1.4.0
**/
void
fu_device_set_version_bootloader(FuDevice *self, const gchar *version)
{
g_autofree gchar *version_safe = NULL;
g_autoptr(GError) error = NULL;
g_return_if_fail(FU_IS_DEVICE(self));
/* sanitize if required */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_ENSURE_SEMVER)) {
version_safe =
fu_version_ensure_semver(version, fu_device_get_version_format(self));
if (g_strcmp0(version, version_safe) != 0)
g_debug("converted '%s' to '%s'", version, version_safe);
} else {
version_safe = g_strdup(version);
}
/* print a console warning for an invalid version, if semver */
if (version_safe != NULL &&
!fu_version_verify_format(version_safe, fu_device_get_version_format(self), &error))
g_warning("%s", error->message);
/* if different */
if (g_strcmp0(fu_device_get_version_bootloader(self), version_safe) != 0) {
if (fu_device_get_version_bootloader(self) != NULL) {
g_debug("changing version for %s: %s->%s",
fu_device_get_id(self),
fu_device_get_version_bootloader(self),
version_safe);
}
fwupd_device_set_version_bootloader(FWUPD_DEVICE(self), version_safe);
}
}
static void
fu_device_inhibit_free(FuDeviceInhibit *inhibit)
{
g_free(inhibit->inhibit_id);
g_free(inhibit->reason);
g_free(inhibit);
}
static void
fu_device_ensure_inhibits(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
FwupdDeviceProblem problems = FWUPD_DEVICE_PROBLEM_NONE;
guint nr_inhibits = g_hash_table_size(priv->inhibits);
/* disable */
if (priv->notify_flags_handler_id != 0)
g_signal_handler_block(self, priv->notify_flags_handler_id);
/* was okay -> not okay */
if (nr_inhibits > 0) {
g_autofree gchar *reasons_str = NULL;
g_autoptr(GList) values = g_hash_table_get_values(priv->inhibits);
g_autoptr(GPtrArray) reasons = g_ptr_array_new();
/* updatable -> updatable-hidden -- which is required as devices might have
* inhibits and *not* be automatically updatable */
if (fu_device_has_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE)) {
fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE);
fu_device_add_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN);
}
/* update update error */
for (GList *l = values; l != NULL; l = l->next) {
FuDeviceInhibit *inhibit = (FuDeviceInhibit *)l->data;
g_ptr_array_add(reasons, inhibit->reason);
problems |= inhibit->problem;
}
reasons_str = fu_strjoin(", ", reasons);
fu_device_set_update_error(self, reasons_str);
} else {
if (fu_device_has_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN)) {
fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN);
fu_device_add_flag(self, FWUPD_DEVICE_FLAG_UPDATABLE);
}
fu_device_set_update_error(self, NULL);
}
/* sync with baseclass */
fwupd_device_set_problems(FWUPD_DEVICE(self), problems);
/* enable */
if (priv->notify_flags_handler_id != 0)
g_signal_handler_unblock(self, priv->notify_flags_handler_id);
}
static gchar *
fu_device_problem_to_inhibit_reason(FuDevice *self, guint64 device_problem)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
if (device_problem == FWUPD_DEVICE_PROBLEM_UNREACHABLE)
return g_strdup("Device is unreachable, or out of wireless range");
if (device_problem == FWUPD_DEVICE_PROBLEM_UPDATE_PENDING)
return g_strdup("Device is waiting for the update to be applied");
if (device_problem == FWUPD_DEVICE_PROBLEM_REQUIRE_AC_POWER)
return g_strdup("Device requires AC power to be connected");
if (device_problem == FWUPD_DEVICE_PROBLEM_LID_IS_CLOSED)
return g_strdup("Device cannot be used while the lid is closed");
if (device_problem == FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW) {
if (priv->ctx == NULL)
return g_strdup("System power is too low to perform the update");
return g_strdup_printf(
"System power is too low to perform the update (%u%%, requires %u%%)",
fu_context_get_battery_level(priv->ctx),
fu_context_get_battery_threshold(priv->ctx));
}
if (device_problem == FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW) {
if (fu_device_get_battery_level(self) == FWUPD_BATTERY_LEVEL_INVALID ||
fu_device_get_battery_threshold(self) == FWUPD_BATTERY_LEVEL_INVALID) {
return g_strdup_printf("Device battery power is too low");
}
return g_strdup_printf("Device battery power is too low (%u%%, requires %u%%)",
fu_device_get_battery_level(self),
fu_device_get_battery_threshold(self));
}
return NULL;
}
static void
fu_device_inhibit_full(FuDevice *self,
FwupdDeviceProblem problem,
const gchar *inhibit_id,
const gchar *reason)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
FuDeviceInhibit *inhibit;
g_return_if_fail(FU_IS_DEVICE(self));
/* lazy create as most devices will not need this */
if (priv->inhibits == NULL) {
priv->inhibits = g_hash_table_new_full(g_str_hash,
g_str_equal,
NULL,
(GDestroyNotify)fu_device_inhibit_free);
}
/* can fallback */
if (inhibit_id == NULL)
inhibit_id = fwupd_device_problem_to_string(problem);
/* already exists */
inhibit = g_hash_table_lookup(priv->inhibits, inhibit_id);
if (inhibit != NULL)
return;
/* create new */
inhibit = g_new0(FuDeviceInhibit, 1);
inhibit->problem = problem;
inhibit->inhibit_id = g_strdup(inhibit_id);
if (reason != NULL) {
inhibit->reason = g_strdup(reason);
} else {
inhibit->reason = fu_device_problem_to_inhibit_reason(self, problem);
}
g_hash_table_insert(priv->inhibits, inhibit->inhibit_id, inhibit);
/* refresh */
fu_device_ensure_inhibits(self);
/* propagate to children */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN)) {
GPtrArray *children = fu_device_get_children(self);
for (guint i = 0; i < children->len; i++) {
FuDevice *child = g_ptr_array_index(children, i);
fu_device_inhibit(child, inhibit_id, reason);
}
}
}
/**
* fu_device_inhibit:
* @self: a #FuDevice
* @inhibit_id: an ID used for uninhibiting, e.g. `low-power`
* @reason: (nullable): a string, e.g. `Cannot update as foo [bar] needs reboot`
*
* Prevent the device from being updated, changing it from %FWUPD_DEVICE_FLAG_UPDATABLE
* to %FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN if not already inhibited.
*
* If the device already has an inhibit with the same @inhibit_id then the request
* is ignored.
*
* Since: 1.6.0
**/
void
fu_device_inhibit(FuDevice *self, const gchar *inhibit_id, const gchar *reason)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(inhibit_id != NULL);
fu_device_inhibit_full(self, FWUPD_DEVICE_PROBLEM_NONE, inhibit_id, reason);
}
/**
* fu_device_has_inhibit:
* @self: a #FuDevice
* @inhibit_id: an ID used for inhibiting, e.g. `low-power`
*
* Check if the device already has an inhibit with a specific ID.
*
* Returns: %TRUE if added
*
* Since: 1.8.0
**/
gboolean
fu_device_has_inhibit(FuDevice *self, const gchar *inhibit_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(inhibit_id != NULL, FALSE);
if (priv->inhibits == NULL)
return FALSE;
return g_hash_table_contains(priv->inhibits, inhibit_id);
}
/**
* fu_device_remove_problem:
* @self: a #FuDevice
* @problem: a #FwupdDeviceProblem, e.g. %FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW
*
* Allow the device from being updated if there are no other inhibitors,
* changing it from %FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN to %FWUPD_DEVICE_FLAG_UPDATABLE.
*
* If the device already has no inhibit with the @inhibit_id then the request
* is ignored.
*
* Since: 1.8.1
**/
void
fu_device_remove_problem(FuDevice *self, FwupdDeviceProblem problem)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(problem != FWUPD_DEVICE_PROBLEM_UNKNOWN);
return fu_device_uninhibit(self, fwupd_device_problem_to_string(problem));
}
/**
* fu_device_add_problem:
* @self: a #FuDevice
* @problem: a #FwupdDeviceProblem, e.g. %FWUPD_DEVICE_PROBLEM_SYSTEM_POWER_TOO_LOW
*
* Prevent the device from being updated, changing it from %FWUPD_DEVICE_FLAG_UPDATABLE
* to %FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN if not already inhibited.
*
* If the device already has an inhibit with the same @problem then the request
* is ignored.
*
* Since: 1.8.1
**/
void
fu_device_add_problem(FuDevice *self, FwupdDeviceProblem problem)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(problem != FWUPD_DEVICE_PROBLEM_UNKNOWN);
fu_device_inhibit_full(self, problem, NULL, NULL);
}
/**
* fu_device_uninhibit:
* @self: a #FuDevice
* @inhibit_id: an ID used for uninhibiting, e.g. `low-power`
*
* Allow the device from being updated if there are no other inhibitors,
* changing it from %FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN to %FWUPD_DEVICE_FLAG_UPDATABLE.
*
* If the device already has no inhibit with the @inhibit_id then the request
* is ignored.
*
* Since: 1.6.0
**/
void
fu_device_uninhibit(FuDevice *self, const gchar *inhibit_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(inhibit_id != NULL);
if (priv->inhibits == NULL)
return;
if (g_hash_table_remove(priv->inhibits, inhibit_id))
fu_device_ensure_inhibits(self);
/* propagate to children */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_INHIBIT_CHILDREN)) {
GPtrArray *children = fu_device_get_children(self);
for (guint i = 0; i < children->len; i++) {
FuDevice *child = g_ptr_array_index(children, i);
fu_device_uninhibit(child, inhibit_id);
}
}
}
/**
* fu_device_ensure_id:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* If not already set, generates a device ID with the optional physical and
* logical IDs.
*
* Returns: %TRUE on success
*
* Since: 1.1.2
**/
gboolean
fu_device_ensure_id(FuDevice *self, GError **error)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autofree gchar *device_id = NULL;
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* already set */
if (priv->device_id_valid)
return TRUE;
/* nothing we can do! */
if (priv->physical_id == NULL) {
g_autofree gchar *tmp = fu_device_to_string(self);
g_set_error(error, G_IO_ERROR, G_IO_ERROR_FAILED, "cannot ensure ID: %s", tmp);
return FALSE;
}
/* logical may be NULL */
device_id =
g_strjoin(":", fu_device_get_physical_id(self), fu_device_get_logical_id(self), NULL);
fu_device_set_id(self, device_id);
return TRUE;
}
/**
* fu_device_get_logical_id:
* @self: a #FuDevice
*
* Gets the logical ID set for the device, which disambiguates devices with the
* same physical ID.
*
* Returns: a string value, or %NULL if never set.
*
* Since: 1.1.2
**/
const gchar *
fu_device_get_logical_id(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->logical_id;
}
/**
* fu_device_set_logical_id:
* @self: a #FuDevice
* @logical_id: a string, e.g. `dev2`
*
* Sets the logical ID on the device. This is designed to disambiguate devices
* with the same physical ID.
*
* Since: 1.1.2
**/
void
fu_device_set_logical_id(FuDevice *self, const gchar *logical_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
/* not changed */
if (g_strcmp0(priv->logical_id, logical_id) == 0)
return;
/* not allowed after ->probe() and ->setup() have completed */
if (priv->done_setup) {
g_warning("cannot change %s logical ID from %s to %s as "
"FuDevice->setup() has already completed",
fu_device_get_id(self),
priv->logical_id,
logical_id);
return;
}
g_free(priv->logical_id);
priv->logical_id = g_strdup(logical_id);
priv->device_id_valid = FALSE;
g_object_notify(G_OBJECT(self), "logical-id");
}
/**
* fu_device_get_backend_id:
* @self: a #FuDevice
*
* Gets the ID set for the device as recognized by the backend. This is typically
* a Linux sysfs path or USB platform ID. If unset, it also falls back to the
* physical ID as this may be the same value.
*
* Returns: a string value, or %NULL if never set.
*
* Since: 1.5.8
**/
const gchar *
fu_device_get_backend_id(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
if (priv->backend_id != NULL)
return priv->backend_id;
return priv->physical_id;
}
/**
* fu_device_set_backend_id:
* @self: a #FuDevice
* @backend_id: a string, e.g. `dev2`
*
* Sets the backend ID on the device. This is designed to disambiguate devices
* with the same physical ID. This is typically a Linux sysfs path or USB
* platform ID.
*
* Since: 1.5.8
**/
void
fu_device_set_backend_id(FuDevice *self, const gchar *backend_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
/* not changed */
if (g_strcmp0(priv->backend_id, backend_id) == 0)
return;
g_free(priv->backend_id);
priv->backend_id = g_strdup(backend_id);
priv->device_id_valid = FALSE;
g_object_notify(G_OBJECT(self), "backend-id");
}
/**
* fu_device_get_proxy_guid:
* @self: a #FuDevice
*
* Gets the proxy GUID device, which which is set to let the engine match up the
* proxy between plugins.
*
* Returns: a string value, or %NULL if never set.
*
* Since: 1.4.1
**/
const gchar *
fu_device_get_proxy_guid(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->proxy_guid;
}
/**
* fu_device_set_proxy_guid:
* @self: a #FuDevice
* @proxy_guid: a string, e.g. `USB\VID_413C&PID_B06E&hub`
*
* Sets the GUID of the proxy device. The proxy device may update @self.
*
* Since: 1.4.1
**/
void
fu_device_set_proxy_guid(FuDevice *self, const gchar *proxy_guid)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
/* not changed */
if (g_strcmp0(priv->proxy_guid, proxy_guid) == 0)
return;
g_free(priv->proxy_guid);
priv->proxy_guid = g_strdup(proxy_guid);
}
/**
* fu_device_set_physical_id:
* @self: a #FuDevice
* @physical_id: a string that identifies the physical device connection
*
* Sets the physical ID on the device which represents the electrical connection
* of the device to the system. Multiple #FuDevices can share a physical ID.
*
* The physical ID is used to remove logical devices when a physical device has
* been removed from the system.
*
* A sysfs or devpath is not a physical ID, but could be something like
* `PCI_SLOT_NAME=0000:3e:00.0`.
*
* Since: 1.1.2
**/
void
fu_device_set_physical_id(FuDevice *self, const gchar *physical_id)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(physical_id != NULL);
/* not changed */
if (g_strcmp0(priv->physical_id, physical_id) == 0)
return;
/* not allowed after ->probe() and ->setup() have completed */
if (priv->done_setup) {
g_warning("cannot change %s physical ID from %s to %s as "
"FuDevice->setup() has already completed",
fu_device_get_id(self),
priv->physical_id,
physical_id);
return;
}
g_free(priv->physical_id);
priv->physical_id = g_strdup(physical_id);
priv->device_id_valid = FALSE;
g_object_notify(G_OBJECT(self), "physical-id");
}
/**
* fu_device_get_physical_id:
* @self: a #FuDevice
*
* Gets the physical ID set for the device, which represents the electrical
* connection used to compare devices.
*
* Multiple #FuDevices can share a single physical ID.
*
* Returns: a string value, or %NULL if never set.
*
* Since: 1.1.2
**/
const gchar *
fu_device_get_physical_id(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->physical_id;
}
/**
* fu_device_remove_flag:
* @self: a #FuDevice
* @flag: a device flag
*
* Removes a device flag from the device.
*
* Since: 1.6.0
**/
void
fu_device_remove_flag(FuDevice *self, FwupdDeviceFlags flag)
{
/* proxy */
fwupd_device_remove_flag(FWUPD_DEVICE(self), flag);
/* allow it to be updatable again */
if (flag & FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)
fu_device_uninhibit(self, "needs-activation");
if (flag & FWUPD_DEVICE_FLAG_UNREACHABLE)
fu_device_uninhibit(self, "unreachable");
}
/**
* fu_device_add_flag:
* @self: a #FuDevice
* @flag: a device flag
*
* Adds a device flag to the device.
*
* Since: 0.1.0
**/
void
fu_device_add_flag(FuDevice *self, FwupdDeviceFlags flag)
{
/* none is not used as an "exported" flag */
if (flag == FWUPD_DEVICE_FLAG_NONE)
return;
/* being both a bootloader and requiring a bootloader is invalid */
if (flag & FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)
fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_IS_BOOTLOADER);
if (flag & FWUPD_DEVICE_FLAG_IS_BOOTLOADER)
fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER);
/* being both a signed and unsigned is invalid */
if (flag & FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD)
fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD);
if (flag & FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD)
fu_device_remove_flag(self, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD);
/* one implies the other */
if (flag & FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE)
flag |= FWUPD_DEVICE_FLAG_CAN_VERIFY;
if (flag & FWUPD_DEVICE_FLAG_INSTALL_ALL_RELEASES)
flag |= FWUPD_DEVICE_FLAG_VERSION_CHECK_REQUIRED;
fwupd_device_add_flag(FWUPD_DEVICE(self), flag);
/* activatable devices shouldn't be allowed to update again until activated */
/* don't let devices be updated until activated */
if (flag & FWUPD_DEVICE_FLAG_NEEDS_ACTIVATION)
fu_device_inhibit(self, "needs-activation", "Pending activation");
/* do not let devices be updated until back in range */
if (flag & FWUPD_DEVICE_FLAG_UNREACHABLE)
fu_device_add_problem(self, FWUPD_DEVICE_PROBLEM_UNREACHABLE);
}
typedef struct {
guint64 value;
gchar *value_str;
} FuDevicePrivateFlagItem;
static void
fu_device_private_flag_item_free(FuDevicePrivateFlagItem *item)
{
g_free(item->value_str);
g_free(item);
}
static FuDevicePrivateFlagItem *
fu_device_private_flag_item_find_by_str(FuDevice *self, const gchar *value_str)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
if (priv->private_flag_items == NULL)
return NULL;
for (guint i = 0; i < priv->private_flag_items->len; i++) {
FuDevicePrivateFlagItem *item = g_ptr_array_index(priv->private_flag_items, i);
if (g_strcmp0(item->value_str, value_str) == 0)
return item;
}
return NULL;
}
static FuDevicePrivateFlagItem *
fu_device_private_flag_item_find_by_val(FuDevice *self, guint64 value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
if (priv->private_flag_items == NULL)
return NULL;
for (guint i = 0; i < priv->private_flag_items->len; i++) {
FuDevicePrivateFlagItem *item = g_ptr_array_index(priv->private_flag_items, i);
if (item->value == value)
return item;
}
return NULL;
}
/**
* fu_device_register_private_flag:
* @self: a #FuDevice
* @value: an integer value
* @value_str: a string that represents @value
*
* Registers a private device flag so that it can be set from quirk files.
*
* Since: 1.6.2
**/
void
fu_device_register_private_flag(FuDevice *self, guint64 value, const gchar *value_str)
{
FuDevicePrivateFlagItem *item;
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(value != 0);
g_return_if_fail(value_str != NULL);
/* ensure exists */
if (priv->private_flag_items == NULL) {
priv->private_flag_items = g_ptr_array_new_with_free_func(
(GDestroyNotify)fu_device_private_flag_item_free);
}
/* sanity check */
item = fu_device_private_flag_item_find_by_val(self, value);
if (item != NULL) {
g_critical("already registered private %s flag with value: %s:0x%x",
G_OBJECT_TYPE_NAME(self),
value_str,
(guint)value);
return;
}
item = fu_device_private_flag_item_find_by_str(self, value_str);
if (item != NULL) {
g_critical("already registered private %s flag with string: %s:0x%x",
G_OBJECT_TYPE_NAME(self),
value_str,
(guint)value);
return;
}
/* add new */
item = g_new0(FuDevicePrivateFlagItem, 1);
item->value = value;
item->value_str = g_strdup(value_str);
g_ptr_array_add(priv->private_flag_items, item);
}
static void
fu_device_set_custom_flag(FuDevice *self, const gchar *hint)
{
FwupdDeviceFlags flag;
FuDevicePrivateFlagItem *item;
FuDeviceInternalFlags internal_flag;
FuDevicePrivate *priv = GET_PRIVATE(self);
/* is this a negated device flag */
if (g_str_has_prefix(hint, "~")) {
flag = fwupd_device_flag_from_string(hint + 1);
if (flag != FWUPD_DEVICE_FLAG_UNKNOWN) {
fu_device_remove_flag(self, flag);
return;
}
internal_flag = fu_device_internal_flag_from_string(hint + 1);
if (internal_flag != FU_DEVICE_INTERNAL_FLAG_UNKNOWN) {
fu_device_remove_internal_flag(self, internal_flag);
return;
}
item = fu_device_private_flag_item_find_by_str(self, hint + 1);
if (item != NULL) {
priv->private_flags &= ~item->value;
return;
}
return;
}
/* is this a known device flag */
flag = fwupd_device_flag_from_string(hint);
if (flag != FWUPD_DEVICE_FLAG_UNKNOWN) {
fu_device_add_flag(self, flag);
return;
}
internal_flag = fu_device_internal_flag_from_string(hint);
if (internal_flag != FU_DEVICE_INTERNAL_FLAG_UNKNOWN) {
fu_device_add_internal_flag(self, internal_flag);
return;
}
item = fu_device_private_flag_item_find_by_str(self, hint);
if (item != NULL) {
priv->private_flags |= item->value;
return;
}
}
/**
* fu_device_set_custom_flags:
* @self: a #FuDevice
* @custom_flags: a string
*
* Sets the custom flags from the quirk system that can be used to
* affect device matching. The actual string format is defined by the plugin.
*
* Since: 1.1.0
**/
void
fu_device_set_custom_flags(FuDevice *self, const gchar *custom_flags)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(custom_flags != NULL);
/* save what was set so we can use it for incorporating a superclass */
g_free(priv->custom_flags);
priv->custom_flags = g_strdup(custom_flags);
/* look for any standard FwupdDeviceFlags */
if (custom_flags != NULL) {
g_auto(GStrv) hints = g_strsplit(custom_flags, ",", -1);
for (guint i = 0; hints[i] != NULL; i++)
fu_device_set_custom_flag(self, hints[i]);
}
}
/**
* fu_device_get_custom_flags:
* @self: a #FuDevice
*
* Gets the custom flags for the device from the quirk system.
*
* Returns: a string value, or %NULL if never set.
*
* Since: 1.1.0
**/
const gchar *
fu_device_get_custom_flags(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->custom_flags;
}
/**
* fu_device_get_remove_delay:
* @self: a #FuDevice
*
* Returns the maximum delay expected when replugging the device going into
* bootloader mode.
*
* Returns: time in milliseconds
*
* Since: 1.0.2
**/
guint
fu_device_get_remove_delay(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), 0);
return priv->remove_delay;
}
/**
* fu_device_set_remove_delay:
* @self: a #FuDevice
* @remove_delay: the value in milliseconds
*
* Sets the amount of time a device is allowed to return in bootloader mode.
*
* NOTE: this should be less than 3000ms for devices that just have to reset
* and automatically re-enumerate, but significantly longer if it involves a
* user removing a cable, pressing several buttons and removing a cable.
* A suggested value for this would be 10,000ms.
*
* Since: 1.0.2
**/
void
fu_device_set_remove_delay(FuDevice *self, guint remove_delay)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->remove_delay = remove_delay;
}
/**
* fu_device_set_update_state:
* @self: a #FuDevice
* @update_state: the state, e.g. %FWUPD_UPDATE_STATE_PENDING
*
* Sets the update state, clearing the update error as required.
*
* Since: 1.6.2
**/
void
fu_device_set_update_state(FuDevice *self, FwupdUpdateState update_state)
{
g_return_if_fail(FU_IS_DEVICE(self));
if (update_state == FWUPD_UPDATE_STATE_SUCCESS ||
update_state == FWUPD_UPDATE_STATE_PENDING ||
update_state == FWUPD_UPDATE_STATE_NEEDS_REBOOT)
fu_device_set_update_error(self, NULL);
fwupd_device_set_update_state(FWUPD_DEVICE(self), update_state);
}
static void
fu_device_ensure_battery_inhibit(FuDevice *self)
{
if (fu_device_get_battery_level(self) == FWUPD_BATTERY_LEVEL_INVALID ||
fu_device_get_battery_level(self) >= fu_device_get_battery_threshold(self)) {
fu_device_remove_problem(self, FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW);
return;
}
fu_device_add_problem(self, FWUPD_DEVICE_PROBLEM_POWER_TOO_LOW);
}
/**
* fu_device_get_battery_level:
* @self: a #FuDevice
*
* Returns the battery level.
*
* Returns: value in percent
*
* Since: 1.5.8
**/
guint
fu_device_get_battery_level(FuDevice *self)
{
g_return_val_if_fail(FU_IS_DEVICE(self), FWUPD_BATTERY_LEVEL_INVALID);
/* use the parent if the child is unset */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY) &&
fu_device_get_battery_level(self) == FWUPD_BATTERY_LEVEL_INVALID) {
FuDevice *parent = fu_device_get_parent(self);
if (parent != NULL)
return fu_device_get_battery_level(parent);
}
return fwupd_device_get_battery_level(FWUPD_DEVICE(self));
}
/**
* fu_device_set_battery_level:
* @self: a #FuDevice
* @battery_level: the percentage value
*
* Sets the battery level, or %FWUPD_BATTERY_LEVEL_INVALID.
*
* Setting this allows fwupd to show a warning if the device change is too low
* to perform the update.
*
* Since: 1.5.8
**/
void
fu_device_set_battery_level(FuDevice *self, guint battery_level)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(battery_level <= FWUPD_BATTERY_LEVEL_INVALID);
fwupd_device_set_battery_level(FWUPD_DEVICE(self), battery_level);
fu_device_ensure_battery_inhibit(self);
}
/**
* fu_device_get_battery_threshold:
* @self: a #FuDevice
*
* Returns the battery threshold under which a firmware update cannot be
* performed.
*
* If fu_device_set_battery_threshold() has not been used, a default value is
* used instead.
*
* Returns: value in percent
*
* Since: 1.6.0
**/
guint
fu_device_get_battery_threshold(FuDevice *self)
{
g_return_val_if_fail(FU_IS_DEVICE(self), FWUPD_BATTERY_LEVEL_INVALID);
/* use the parent if the child is unset */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_BATTERY) &&
fwupd_device_get_battery_threshold(FWUPD_DEVICE(self)) == FWUPD_BATTERY_LEVEL_INVALID) {
FuDevice *parent = fu_device_get_parent(self);
if (parent != NULL)
return fu_device_get_battery_threshold(parent);
}
return fwupd_device_get_battery_threshold(FWUPD_DEVICE(self));
}
/**
* fu_device_set_battery_threshold:
* @self: a #FuDevice
* @battery_threshold: the percentage value
*
* Sets the battery level, or %FWUPD_BATTERY_LEVEL_INVALID for the default.
*
* Setting this allows fwupd to show a warning if the device change is too low
* to perform the update.
*
* Since: 1.6.0
**/
void
fu_device_set_battery_threshold(FuDevice *self, guint battery_threshold)
{
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(battery_threshold <= FWUPD_BATTERY_LEVEL_INVALID);
fwupd_device_set_battery_threshold(FWUPD_DEVICE(self), battery_threshold);
fu_device_ensure_battery_inhibit(self);
}
/**
* fu_device_add_string:
* @self: a #FuDevice
* @idt: indent level
* @str: a string to append to
*
* Add daemon-specific device metadata to an existing string.
*
* Since: 1.7.1
**/
void
fu_device_add_string(FuDevice *self, guint idt, GString *str)
{
GPtrArray *children;
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autofree gchar *tmp = NULL;
g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&priv->metadata_mutex);
g_return_if_fail(locker != NULL);
tmp = fwupd_device_to_string(FWUPD_DEVICE(self));
if (tmp != NULL && tmp[0] != '\0')
g_string_append(str, tmp);
if (priv->alternate_id != NULL)
fu_string_append(str, idt + 1, "AlternateId", priv->alternate_id);
if (priv->equivalent_id != NULL)
fu_string_append(str, idt + 1, "EquivalentId", priv->equivalent_id);
if (priv->physical_id != NULL)
fu_string_append(str, idt + 1, "PhysicalId", priv->physical_id);
if (priv->logical_id != NULL)
fu_string_append(str, idt + 1, "LogicalId", priv->logical_id);
if (priv->backend_id != NULL)
fu_string_append(str, idt + 1, "BackendId", priv->backend_id);
if (priv->proxy != NULL)
fu_string_append(str, idt + 1, "ProxyId", fu_device_get_id(priv->proxy));
if (priv->proxy_guid != NULL)
fu_string_append(str, idt + 1, "ProxyGuid", priv->proxy_guid);
if (priv->remove_delay != 0)
fu_string_append_ku(str, idt + 1, "RemoveDelay", priv->remove_delay);
if (priv->custom_flags != NULL)
fu_string_append(str, idt + 1, "CustomFlags", priv->custom_flags);
if (priv->firmware_gtype != G_TYPE_INVALID) {
fu_string_append(str, idt + 1, "FirmwareGType", g_type_name(priv->firmware_gtype));
}
if (priv->size_min > 0) {
g_autofree gchar *sz = g_strdup_printf("%" G_GUINT64_FORMAT, priv->size_min);
fu_string_append(str, idt + 1, "FirmwareSizeMin", sz);
}
if (priv->size_max > 0) {
g_autofree gchar *sz = g_strdup_printf("%" G_GUINT64_FORMAT, priv->size_max);
fu_string_append(str, idt + 1, "FirmwareSizeMax", sz);
}
if (priv->order != G_MAXINT) {
g_autofree gchar *order = g_strdup_printf("%i", priv->order);
fu_string_append(str, idt + 1, "Order", order);
}
if (priv->priority > 0)
fu_string_append_ku(str, idt + 1, "Priority", priv->priority);
if (priv->metadata != NULL) {
g_autoptr(GList) keys = g_hash_table_get_keys(priv->metadata);
for (GList *l = keys; l != NULL; l = l->next) {
const gchar *key = l->data;
const gchar *value = g_hash_table_lookup(priv->metadata, key);
fu_string_append(str, idt + 1, key, value);
}
}
for (guint i = 0; i < priv->possible_plugins->len; i++) {
const gchar *name = g_ptr_array_index(priv->possible_plugins, i);
fu_string_append(str, idt + 1, "PossiblePlugin", name);
}
if (priv->parent_physical_ids != NULL && priv->parent_physical_ids->len > 0) {
g_autofree gchar *flags = fu_strjoin(",", priv->parent_physical_ids);
fu_string_append(str, idt + 1, "ParentPhysicalIds", flags);
}
if (priv->internal_flags != FU_DEVICE_INTERNAL_FLAG_NONE) {
g_autoptr(GString) tmp2 = g_string_new("");
for (guint i = 0; i < 64; i++) {
if ((priv->internal_flags & ((guint64)1 << i)) == 0)
continue;
g_string_append_printf(tmp2,
"%s|",
fu_device_internal_flag_to_string((guint64)1 << i));
}
if (tmp2->len > 0)
g_string_truncate(tmp2, tmp2->len - 1);
fu_string_append(str, idt + 1, "InternalFlags", tmp2->str);
}
if (priv->private_flags > 0) {
g_autoptr(GPtrArray) tmpv = g_ptr_array_new();
g_autofree gchar *tmps = NULL;
for (guint64 i = 0; i < 64; i++) {
FuDevicePrivateFlagItem *item;
guint64 value = 1ull << i;
if ((priv->private_flags & value) == 0)
continue;
item = fu_device_private_flag_item_find_by_val(self, value);
if (item == NULL)
continue;
g_ptr_array_add(tmpv, item->value_str);
}
tmps = fu_strjoin(",", tmpv);
fu_string_append(str, idt + 1, "PrivateFlags", tmps);
}
if (priv->inhibits != NULL) {
g_autoptr(GList) values = g_hash_table_get_values(priv->inhibits);
for (GList *l = values; l != NULL; l = l->next) {
FuDeviceInhibit *inhibit = (FuDeviceInhibit *)l->data;
g_autofree gchar *val =
g_strdup_printf("[%s] %s", inhibit->inhibit_id, inhibit->reason);
fu_string_append(str, idt + 1, "Inhibit", val);
}
}
/* subclassed */
if (klass->to_string != NULL)
klass->to_string(self, idt + 1, str);
/* print children also */
children = fu_device_get_children(self);
for (guint i = 0; i < children->len; i++) {
FuDevice *child = g_ptr_array_index(children, i);
fu_device_add_string(child, idt + 1, str);
}
}
/**
* fu_device_to_string:
* @self: a #FuDevice
*
* This allows us to easily print the device, the release and the
* daemon-specific metadata.
*
* Returns: a string value, or %NULL for invalid.
*
* Since: 0.9.8
**/
gchar *
fu_device_to_string(FuDevice *self)
{
GString *str = g_string_new(NULL);
fu_device_add_string(self, 0, str);
return g_string_free(str, FALSE);
}
/**
* fu_device_set_context:
* @self: a #FuDevice
* @ctx: (nullable): optional #FuContext
*
* Sets the optional context which may be useful to this device.
* This is typically set after the device has been created, but before
* the device has been opened or probed.
*
* Since: 1.6.0
**/
void
fu_device_set_context(FuDevice *self, FuContext *ctx)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(FU_IS_CONTEXT(ctx) || ctx == NULL);
#ifndef SUPPORTED_BUILD
if (priv->ctx != NULL && ctx == NULL) {
g_critical("clearing device context for %s [%s]",
fu_device_get_name(self),
fu_device_get_id(self));
return;
}
if (priv->ctx != NULL && priv->ctx == ctx) {
g_critical("re-setting device context for %s [%s]",
fu_device_get_name(self),
fu_device_get_id(self));
return;
}
#endif
if (g_set_object(&priv->ctx, ctx))
g_object_notify(G_OBJECT(self), "context");
}
/**
* fu_device_get_context:
* @self: a #FuDevice
*
* Gets the context assigned for this device.
*
* Returns: (transfer none): the #FuContext object, or %NULL
*
* Since: 1.6.0
**/
FuContext *
fu_device_get_context(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
return priv->ctx;
}
/**
* fu_device_get_release_default:
* @self: a #FuDevice
*
* Gets the default release for the device, creating one if not found.
*
* Returns: (transfer none): the #FwupdRelease object
*
* Since: 1.0.5
**/
FwupdRelease *
fu_device_get_release_default(FuDevice *self)
{
g_autoptr(FwupdRelease) rel = NULL;
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
if (fwupd_device_get_release_default(FWUPD_DEVICE(self)) != NULL)
return fwupd_device_get_release_default(FWUPD_DEVICE(self));
rel = fwupd_release_new();
fwupd_device_add_release(FWUPD_DEVICE(self), rel);
return rel;
}
/**
* fu_device_get_results:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Gets the results of the last update operation on the device by calling a vfunc.
*
* Returns: %TRUE on success
*
* Since: 1.6.2
**/
gboolean
fu_device_get_results(FuDevice *self, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->get_results == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"getting results not supported by device");
return FALSE;
}
/* call vfunc */
return klass->get_results(self, error);
}
/**
* fu_device_write_firmware:
* @self: a #FuDevice
* @fw: firmware blob
* @progress: a #FuProgress
* @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE
* @error: (nullable): optional return location for an error
*
* Writes firmware to the device by calling a plugin-specific vfunc.
*
* Returns: %TRUE on success
*
* Since: 1.0.8
**/
gboolean
fu_device_write_firmware(FuDevice *self,
GBytes *fw,
FuProgress *progress,
FwupdInstallFlags flags,
GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(FuFirmware) firmware = NULL;
g_autofree gchar *str = NULL;
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->write_firmware == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"writing firmware not supported by device");
return FALSE;
}
/* prepare (e.g. decompress) firmware */
fu_progress_set_status(progress, FWUPD_STATUS_DECOMPRESSING);
firmware = fu_device_prepare_firmware(self, fw, flags, error);
if (firmware == NULL)
return FALSE;
str = fu_firmware_to_string(firmware);
g_debug("installing onto %s:\n%s", fu_device_get_id(self), str);
/* call vfunc */
if (!klass->write_firmware(self, firmware, progress, flags, error))
return FALSE;
/* the device set an UpdateMessage (possibly from a quirk, or XML file)
* but did not do an event; guess something */
if (priv->request_cnts[FWUPD_REQUEST_KIND_POST] == 0 &&
fu_device_get_update_message(self) != NULL) {
g_autoptr(FwupdRequest) request = fwupd_request_new();
fwupd_request_set_kind(request, FWUPD_REQUEST_KIND_POST);
fwupd_request_set_id(request, FWUPD_REQUEST_ID_REMOVE_REPLUG);
fwupd_request_set_message(request, fu_device_get_update_message(self));
fwupd_request_set_image(request, fu_device_get_update_image(self));
fu_device_emit_request(self, request);
}
/* success */
return TRUE;
}
/**
* fu_device_prepare_firmware:
* @self: a #FuDevice
* @fw: firmware blob
* @flags: install flags, e.g. %FWUPD_INSTALL_FLAG_FORCE
* @error: (nullable): optional return location for an error
*
* Prepares the firmware by calling an optional device-specific vfunc for the
* device, which can do things like decompressing or parsing of the firmware
* data.
*
* For all firmware, this checks the size of the firmware if limits have been
* set using fu_device_set_firmware_size_min(), fu_device_set_firmware_size_max()
* or using a quirk entry.
*
* Returns: (transfer full): a new #GBytes, or %NULL for error
*
* Since: 1.1.2
**/
FuFirmware *
fu_device_prepare_firmware(FuDevice *self, GBytes *fw, FwupdInstallFlags flags, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
FuDevicePrivate *priv = GET_PRIVATE(self);
g_autoptr(FuFirmware) firmware = NULL;
g_autoptr(GBytes) fw_def = NULL;
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
g_return_val_if_fail(fw != NULL, NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
/* optionally subclassed */
if (klass->prepare_firmware != NULL) {
firmware = klass->prepare_firmware(self, fw, flags, error);
if (firmware == NULL)
return NULL;
} else if (priv->firmware_gtype != G_TYPE_INVALID) {
firmware = g_object_new(priv->firmware_gtype, NULL);
if (!fu_firmware_parse(firmware, fw, flags, error))
return NULL;
} else {
firmware = fu_firmware_new_from_bytes(fw);
}
/* check size */
fw_def = fu_firmware_get_bytes(firmware, NULL);
if (fw_def != NULL) {
guint64 fw_sz = (guint64)g_bytes_get_size(fw_def);
if (priv->size_max > 0 && fw_sz > priv->size_max) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"firmware is 0x%04x bytes larger than the allowed "
"maximum size of 0x%04x bytes",
(guint)(fw_sz - priv->size_max),
(guint)priv->size_max);
return NULL;
}
if (priv->size_min > 0 && fw_sz < priv->size_min) {
g_set_error(error,
FWUPD_ERROR,
FWUPD_ERROR_INVALID_FILE,
"firmware is %04x bytes smaller than the allowed "
"minimum size of %04x bytes",
(guint)(priv->size_min - fw_sz),
(guint)priv->size_max);
return NULL;
}
}
/* success */
return g_steal_pointer(&firmware);
}
/**
* fu_device_read_firmware:
* @self: a #FuDevice
* @progress: a #FuProgress
* @error: (nullable): optional return location for an error
*
* Reads firmware from the device by calling a plugin-specific vfunc.
* The device subclass should try to ensure the firmware does not contain any
* serial numbers or user-configuration values and can be used to calculate the
* device checksum.
*
* The return value can be converted to a blob of memory using fu_firmware_write().
*
* Returns: (transfer full): a #FuFirmware, or %NULL for error
*
* Since: 1.0.8
**/
FuFirmware *
fu_device_read_firmware(FuDevice *self, FuProgress *progress, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_autoptr(GBytes) fw = NULL;
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
g_return_val_if_fail(FU_IS_PROGRESS(progress), NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
/* device does not support reading for verification CRCs */
if (!fu_device_has_flag(self, FWUPD_DEVICE_FLAG_CAN_VERIFY_IMAGE)) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"reading firmware is not supported by device");
return NULL;
}
/* call vfunc */
if (klass->read_firmware != NULL)
return klass->read_firmware(self, progress, error);
/* use the default FuFirmware when only ->dump_firmware is provided */
fw = fu_device_dump_firmware(self, progress, error);
if (fw == NULL)
return NULL;
return fu_firmware_new_from_bytes(fw);
}
/**
* fu_device_dump_firmware:
* @self: a #FuDevice
* @progress: a #FuProgress
* @error: (nullable): optional return location for an error
*
* Reads the raw firmware image from the device by calling a plugin-specific
* vfunc. This raw firmware image may contain serial numbers or device-specific
* configuration but should be a byte-for-byte match compared to using an
* external SPI programmer.
*
* Returns: (transfer full): a #GBytes, or %NULL for error
*
* Since: 1.5.0
**/
GBytes *
fu_device_dump_firmware(FuDevice *self, FuProgress *progress, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
g_return_val_if_fail(FU_IS_PROGRESS(progress), NULL);
g_return_val_if_fail(error == NULL || *error == NULL, NULL);
/* use the default FuFirmware when only ->dump_firmware is provided */
if (klass->dump_firmware == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"dumping firmware is not supported by device");
return NULL;
}
/* proxy */
return klass->dump_firmware(self, progress, error);
}
/**
* fu_device_detach:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Detaches a device from the application into bootloader mode.
*
* Returns: %TRUE on success
*
* Since: 1.0.8
**/
gboolean
fu_device_detach(FuDevice *self, GError **error)
{
g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
return fu_device_detach_full(self, progress, error);
}
/**
* fu_device_detach_full:
* @self: a #FuDevice
* @progress: a #FuProgress
* @error: (nullable): optional return location for an error
*
* Detaches a device from the application into bootloader mode.
*
* Returns: %TRUE on success
*
* Since: 1.7.0
**/
gboolean
fu_device_detach_full(FuDevice *self, FuProgress *progress, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->detach == NULL)
return TRUE;
/* call vfunc */
return klass->detach(self, progress, error);
}
/**
* fu_device_attach:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Attaches a device from the bootloader into application mode.
*
* Returns: %TRUE on success
*
* Since: 1.0.8
**/
gboolean
fu_device_attach(FuDevice *self, GError **error)
{
g_autoptr(FuProgress) progress = fu_progress_new(G_STRLOC);
return fu_device_attach_full(self, progress, error);
}
/**
* fu_device_attach_full:
* @self: a #FuDevice
* @progress: a #FuProgress
* @error: (nullable): optional return location for an error
*
* Attaches a device from the bootloader into application mode.
*
* Returns: %TRUE on success
*
* Since: 1.7.0
**/
gboolean
fu_device_attach_full(FuDevice *self, FuProgress *progress, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->attach == NULL)
return TRUE;
/* call vfunc */
return klass->attach(self, progress, error);
}
/**
* fu_device_reload:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Reloads a device that has just gone from bootloader into application mode.
*
* Returns: %TRUE on success
*
* Since: 1.3.3
**/
gboolean
fu_device_reload(FuDevice *self, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->reload == NULL)
return TRUE;
/* call vfunc */
return klass->reload(self, error);
}
/**
* fu_device_prepare:
* @self: a #FuDevice
* @progress: a #FuProgress
* @flags: install flags
* @error: (nullable): optional return location for an error
*
* Prepares a device for update. A different plugin can handle each of
* FuDevice->prepare(), FuDevice->detach() and FuDevice->write_firmware().
*
* Returns: %TRUE on success
*
* Since: 1.3.3
**/
gboolean
fu_device_prepare(FuDevice *self, FuProgress *progress, FwupdInstallFlags flags, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->prepare == NULL)
return TRUE;
/* call vfunc */
return klass->prepare(self, progress, flags, error);
}
/**
* fu_device_cleanup:
* @self: a #FuDevice
* @progress: a #FuProgress
* @flags: install flags
* @error: (nullable): optional return location for an error
*
* Cleans up a device after an update. A different plugin can handle each of
* FuDevice->write_firmware(), FuDevice->attach() and FuDevice->cleanup().
*
* Returns: %TRUE on success
*
* Since: 1.3.3
**/
gboolean
fu_device_cleanup(FuDevice *self, FuProgress *progress, FwupdInstallFlags flags, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->cleanup == NULL)
return TRUE;
/* call vfunc */
return klass->cleanup(self, progress, flags, error);
}
static gboolean
fu_device_open_cb(FuDevice *self, gpointer user_data, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
return klass->open(self, error);
}
static gboolean
fu_device_open_internal(FuDevice *self, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
FuDevicePrivate *priv = GET_PRIVATE(self);
/* already open */
g_atomic_int_inc(&priv->open_refcount);
if (priv->open_refcount > 1)
return TRUE;
/* probe */
if (!fu_device_probe(self, error))
return FALSE;
/* ensure the device ID is already setup */
if (!fu_device_ensure_id(self, error))
return FALSE;
/* subclassed */
if (klass->open != NULL) {
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_RETRY_OPEN)) {
if (!fu_device_retry_full(self,
fu_device_open_cb,
FU_DEVICE_RETRY_OPEN_COUNT,
FU_DEVICE_RETRY_OPEN_DELAY,
NULL,
error))
return FALSE;
} else {
if (!klass->open(self, error))
return FALSE;
}
}
/* setup */
if (!fu_device_setup(self, error))
return FALSE;
/* ensure the device ID is still valid */
if (!fu_device_ensure_id(self, error))
return FALSE;
/* success */
fu_device_add_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_IS_OPEN);
return TRUE;
}
/**
* fu_device_open:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Opens a device, optionally running a object-specific vfunc.
*
* Plugins can call fu_device_open() multiple times without calling
* fu_device_close(), but only the first call will actually invoke the vfunc.
*
* It is expected that plugins issue the same number of fu_device_open() and
* fu_device_close() methods when using a specific @self.
*
* If the `->probe()`, `->open()` and `->setup()` actions all complete
* successfully the internal device flag %FU_DEVICE_INTERNAL_FLAG_IS_OPEN will
* be set.
*
* NOTE: It is important to still call fu_device_close() even if this function
* fails as the device may still be partially initialized.
*
* Returns: %TRUE for success
*
* Since: 1.1.2
**/
gboolean
fu_device_open(FuDevice *self, GError **error)
{
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* use parent */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN)) {
FuDevice *parent = fu_device_get_parent(self);
if (parent == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no parent device");
return FALSE;
}
return fu_device_open_internal(parent, error);
}
return fu_device_open_internal(self, error);
}
static gboolean
fu_device_close_internal(FuDevice *self, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
FuDevicePrivate *priv = GET_PRIVATE(self);
/* not yet open */
if (priv->open_refcount == 0) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOTHING_TO_DO,
"cannot close device, refcount already zero");
return FALSE;
}
if (!g_atomic_int_dec_and_test(&priv->open_refcount))
return TRUE;
/* subclassed */
if (klass->close != NULL) {
if (!klass->close(self, error))
return FALSE;
}
/* success */
fu_device_remove_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_IS_OPEN);
return TRUE;
}
/**
* fu_device_close:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Closes a device, optionally running a object-specific vfunc.
*
* Plugins can call fu_device_close() multiple times without calling
* fu_device_open(), but only the last call will actually invoke the vfunc.
*
* It is expected that plugins issue the same number of fu_device_open() and
* fu_device_close() methods when using a specific @self.
*
* An error is returned if this method is called without having used the
* fu_device_open() method beforehand.
*
* If the close action completed successfully the internal device flag
* %FU_DEVICE_INTERNAL_FLAG_IS_OPEN will be cleared.
*
* Returns: %TRUE for success
*
* Since: 1.1.2
**/
gboolean
fu_device_close(FuDevice *self, GError **error)
{
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* use parent */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_USE_PARENT_FOR_OPEN)) {
FuDevice *parent = fu_device_get_parent(self);
if (parent == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"no parent device");
return FALSE;
}
return fu_device_close_internal(parent, error);
}
return fu_device_close_internal(self, error);
}
/**
* fu_device_probe:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Probes a device, setting parameters on the object that does not need
* the device open or the interface claimed.
* If the device is not compatible then an error should be returned.
*
* Returns: %TRUE for success
*
* Since: 1.1.2
**/
gboolean
fu_device_probe(FuDevice *self, GError **error)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* already done */
if (priv->done_probe)
return TRUE;
/* device self-assigned */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_PROBE)) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not probing");
return FALSE;
}
/* subclassed */
if (klass->probe != NULL) {
if (!klass->probe(self, error))
return FALSE;
}
/* vfunc skipped device */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_PROBE)) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not probing");
return FALSE;
}
/* success */
priv->done_probe = TRUE;
return TRUE;
}
/**
* fu_device_rescan:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Rescans a device, re-adding GUIDs or flags based on some hardware change.
*
* Returns: %TRUE for success
*
* Since: 1.3.1
**/
gboolean
fu_device_rescan(FuDevice *self, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* remove all GUIDs */
g_ptr_array_set_size(fu_device_get_instance_ids(self), 0);
g_ptr_array_set_size(fu_device_get_guids(self), 0);
/* subclassed */
if (klass->rescan != NULL) {
if (!klass->rescan(self, error)) {
fu_device_convert_instance_ids(self);
return FALSE;
}
}
fu_device_convert_instance_ids(self);
return TRUE;
}
/**
* fu_device_set_progress:
* @self: a #FuDevice
* @progress: a #FuProgress
*
* Sets steps on the progress object used to write firmware.
*
* Since: 1.7.0
**/
void
fu_device_set_progress(FuDevice *self, FuProgress *progress)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(FU_IS_PROGRESS(progress));
g_return_if_fail(FU_IS_PROGRESS(progress));
/* subclassed */
if (klass->set_progress == NULL)
return;
klass->set_progress(self, progress);
}
/**
* fu_device_convert_instance_ids:
* @self: a #FuDevice
*
* Converts all the Device instance IDs added using fu_device_add_instance_id()
* into actual GUIDs, **unless** %FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS has
* been set.
*
* Plugins will only need to need to call this manually when adding child
* devices, as fu_device_setup() automatically calls this after the
* fu_device_probe() and fu_device_setup() virtual functions have been run.
*
* Since: 1.2.5
**/
void
fu_device_convert_instance_ids(FuDevice *self)
{
GPtrArray *instance_ids;
/* OEM specific hardware */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_AUTO_INSTANCE_IDS))
return;
instance_ids = fwupd_device_get_instance_ids(FWUPD_DEVICE(self));
for (guint i = 0; i < instance_ids->len; i++) {
const gchar *instance_id = g_ptr_array_index(instance_ids, i);
g_autofree gchar *guid = fwupd_guid_hash_string(instance_id);
fwupd_device_add_guid(FWUPD_DEVICE(self), guid);
}
}
/**
* fu_device_setup:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Sets up a device, setting parameters on the object that requires
* the device to be open and have the interface claimed.
* If the device is not compatible then an error should be returned.
*
* Returns: %TRUE for success
*
* Since: 1.1.2
**/
gboolean
fu_device_setup(FuDevice *self, GError **error)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
GPtrArray *children;
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* should have already been called */
if (!fu_device_probe(self, error))
return FALSE;
/* already done */
if (priv->done_setup)
return TRUE;
/* subclassed */
if (klass->setup != NULL) {
if (!klass->setup(self, error))
return FALSE;
}
/* vfunc skipped device */
if (fu_device_has_internal_flag(self, FU_DEVICE_INTERNAL_FLAG_NO_PROBE)) {
g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "not probing");
return FALSE;
}
/* run setup on the children too (unless done already) */
children = fu_device_get_children(self);
for (guint i = 0; i < children->len; i++) {
FuDevice *child_tmp = g_ptr_array_index(children, i);
if (!fu_device_setup(child_tmp, error))
return FALSE;
}
/* convert the instance IDs to GUIDs */
fu_device_convert_instance_ids(self);
/* subclassed */
if (klass->ready != NULL) {
if (!klass->ready(self, error))
return FALSE;
}
priv->done_setup = TRUE;
return TRUE;
}
/**
* fu_device_activate:
* @self: a #FuDevice
* @progress: a #FuProgress
* @error: (nullable): optional return location for an error
*
* Activates up a device, which normally means the device switches to a new
* firmware version. This should only be called when data loss cannot occur.
*
* Returns: %TRUE for success
*
* Since: 1.2.6
**/
gboolean
fu_device_activate(FuDevice *self, FuProgress *progress, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(FU_IS_PROGRESS(progress), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* subclassed */
if (klass->activate != NULL) {
if (!klass->activate(self, progress, error))
return FALSE;
}
return TRUE;
}
/**
* fu_device_probe_invalidate:
* @self: a #FuDevice
*
* Normally when calling fu_device_probe() multiple times it is only done once.
* Calling this method causes the next requests to fu_device_probe() and
* fu_device_setup() actually probe the hardware.
*
* This should be done in case the backing device has changed, for instance if
* a USB device has been replugged.
*
* Since: 1.1.2
**/
void
fu_device_probe_invalidate(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
priv->done_probe = FALSE;
priv->done_setup = FALSE;
}
/**
* fu_device_report_metadata_pre:
* @self: a #FuDevice
*
* Collects metadata that would be useful for debugging a failed update report.
*
* Returns: (transfer full) (nullable): a #GHashTable, or %NULL if there is no data
*
* Since: 1.5.0
**/
GHashTable *
fu_device_report_metadata_pre(FuDevice *self)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_autoptr(GHashTable) metadata = NULL;
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
/* not implemented */
if (klass->report_metadata_pre == NULL)
return NULL;
/* metadata for all devices */
metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
klass->report_metadata_pre(self, metadata);
return g_steal_pointer(&metadata);
}
/**
* fu_device_report_metadata_post:
* @self: a #FuDevice
*
* Collects metadata that would be useful for debugging a failed update report.
*
* Returns: (transfer full) (nullable): a #GHashTable, or %NULL if there is no data
*
* Since: 1.5.0
**/
GHashTable *
fu_device_report_metadata_post(FuDevice *self)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_autoptr(GHashTable) metadata = NULL;
g_return_val_if_fail(FU_IS_DEVICE(self), NULL);
/* not implemented */
if (klass->report_metadata_post == NULL)
return NULL;
/* metadata for all devices */
metadata = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
klass->report_metadata_post(self, metadata);
return g_steal_pointer(&metadata);
}
/**
* fu_device_add_security_attrs:
* @self: a #FuDevice
* @attrs: a security attribute
*
* Adds HSI security attributes.
*
* Since: 1.6.0
**/
void
fu_device_add_security_attrs(FuDevice *self, FuSecurityAttrs *attrs)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_if_fail(FU_IS_DEVICE(self));
/* optional */
if (klass->add_security_attrs != NULL)
return klass->add_security_attrs(self, attrs);
}
/**
* fu_device_bind_driver:
* @self: a #FuDevice
* @subsystem: a subsystem string, e.g. `pci`
* @driver: a kernel module name, e.g. `tg3`
* @error: (nullable): optional return location for an error
*
* Binds a driver to the device, which normally means the kernel driver takes
* control of the hardware.
*
* Returns: %TRUE if driver was bound.
*
* Since: 1.5.0
**/
gboolean
fu_device_bind_driver(FuDevice *self, const gchar *subsystem, const gchar *driver, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(subsystem != NULL, FALSE);
g_return_val_if_fail(driver != NULL, FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* not implemented */
if (klass->bind_driver == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"binding drivers is not supported by device");
return FALSE;
}
/* subclass */
return klass->bind_driver(self, subsystem, driver, error);
}
/**
* fu_device_unbind_driver:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
*
* Unbinds the driver from the device, which normally means the kernel releases
* the hardware so it can be used from userspace.
*
* If there is no driver bound then this function will return with success
* without actually doing anything.
*
* Returns: %TRUE if driver was unbound.
*
* Since: 1.5.0
**/
gboolean
fu_device_unbind_driver(FuDevice *self, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
/* not implemented */
if (klass->unbind_driver == NULL) {
g_set_error_literal(error,
FWUPD_ERROR,
FWUPD_ERROR_NOT_SUPPORTED,
"unbinding drivers is not supported by device");
return FALSE;
}
/* subclass */
return klass->unbind_driver(self, error);
}
static const gchar *
fu_device_instance_lookup(FuDevice *self, const gchar *key)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
return g_hash_table_lookup(priv->instance_hash, key);
}
/**
* fu_device_incorporate:
* @self: a #FuDevice
* @donor: Another #FuDevice
*
* Copy all properties from the donor object if they have not already been set.
*
* Since: 1.1.0
**/
void
fu_device_incorporate(FuDevice *self, FuDevice *donor)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS(self);
FuDevicePrivate *priv = GET_PRIVATE(self);
FuDevicePrivate *priv_donor = GET_PRIVATE(donor);
GPtrArray *instance_ids = fu_device_get_instance_ids(donor);
GPtrArray *parent_guids = fu_device_get_parent_guids(donor);
GPtrArray *parent_physical_ids = fu_device_get_parent_physical_ids(donor);
GHashTableIter iter;
gpointer key, value;
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(FU_IS_DEVICE(donor));
/* copy from donor FuDevice if has not already been set */
if (priv->alternate_id == NULL)
fu_device_set_alternate_id(self, fu_device_get_alternate_id(donor));
if (priv->equivalent_id == NULL)
fu_device_set_equivalent_id(self, fu_device_get_equivalent_id(donor));
if (priv->physical_id == NULL && priv_donor->physical_id != NULL)
fu_device_set_physical_id(self, priv_donor->physical_id);
if (priv->logical_id == NULL && priv_donor->logical_id != NULL)
fu_device_set_logical_id(self, priv_donor->logical_id);
if (priv->backend_id == NULL && priv_donor->backend_id != NULL)
fu_device_set_backend_id(self, priv_donor->backend_id);
if (priv->proxy == NULL && priv_donor->proxy != NULL)
fu_device_set_proxy(self, priv_donor->proxy);
if (priv->proxy_guid == NULL && priv_donor->proxy_guid != NULL)
fu_device_set_proxy_guid(self, priv_donor->proxy_guid);
if (priv->custom_flags == NULL && priv_donor->custom_flags != NULL)
fu_device_set_custom_flags(self, priv_donor->custom_flags);
if (priv->ctx == NULL)
fu_device_set_context(self, fu_device_get_context(donor));
g_rw_lock_reader_lock(&priv_donor->parent_guids_mutex);
for (guint i = 0; i < parent_guids->len; i++)
fu_device_add_parent_guid(self, g_ptr_array_index(parent_guids, i));
g_rw_lock_reader_unlock(&priv_donor->parent_guids_mutex);
if (parent_physical_ids != NULL) {
for (guint i = 0; i < parent_physical_ids->len; i++) {
const gchar *tmp = g_ptr_array_index(parent_physical_ids, i);
fu_device_add_parent_physical_id(self, tmp);
}
}
g_rw_lock_reader_lock(&priv_donor->metadata_mutex);
if (priv->metadata != NULL) {
g_hash_table_iter_init(&iter, priv_donor->metadata);
while (g_hash_table_iter_next(&iter, &key, &value)) {
if (fu_device_get_metadata(self, key) == NULL)
fu_device_set_metadata(self, key, value);
}
}
g_rw_lock_reader_unlock(&priv_donor->metadata_mutex);
/* probably not required, but seems safer */
for (guint i = 0; i < priv_donor->possible_plugins->len; i++) {
const gchar *possible_plugin = g_ptr_array_index(priv_donor->possible_plugins, i);
fu_device_add_possible_plugin(self, possible_plugin);
}
/* copy all instance ID keys if not already set */
g_hash_table_iter_init(&iter, priv_donor->instance_hash);
while (g_hash_table_iter_next(&iter, &key, &value)) {
if (fu_device_instance_lookup(self, key) == NULL)
fu_device_add_instance_str(self, key, value);
}
/* now the base class, where all the interesting bits are */
fwupd_device_incorporate(FWUPD_DEVICE(self), FWUPD_DEVICE(donor));
/* set by the superclass */
if (fu_device_get_id(self) != NULL)
priv->device_id_valid = TRUE;
/* optional subclass */
if (klass->incorporate != NULL)
klass->incorporate(self, donor);
/* call the set_quirk_kv() vfunc for the superclassed object */
for (guint i = 0; i < instance_ids->len; i++) {
const gchar *instance_id = g_ptr_array_index(instance_ids, i);
g_autofree gchar *guid = fwupd_guid_hash_string(instance_id);
fu_device_add_guid_quirks(self, guid);
}
}
/**
* fu_device_incorporate_flag:
* @self: a #FuDevice
* @donor: another device
* @flag: device flags
*
* Copy the value of a specific flag from the donor object.
*
* Since: 1.3.5
**/
void
fu_device_incorporate_flag(FuDevice *self, FuDevice *donor, FwupdDeviceFlags flag)
{
if (fu_device_has_flag(donor, flag) && !fu_device_has_flag(self, flag)) {
g_debug("donor set %s", fwupd_device_flag_to_string(flag));
fu_device_add_flag(self, flag);
} else if (!fu_device_has_flag(donor, flag) && fu_device_has_flag(self, flag)) {
g_debug("donor unset %s", fwupd_device_flag_to_string(flag));
fu_device_remove_flag(self, flag);
}
}
/**
* fu_device_incorporate_from_component: (skip):
* @device: a device
* @component: a Xmlb node
*
* Copy all properties from the donor AppStream component.
*
* Since: 1.2.4
**/
void
fu_device_incorporate_from_component(FuDevice *self, XbNode *component)
{
const gchar *tmp;
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(XB_IS_NODE(component));
tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateMessage']", NULL);
if (tmp != NULL)
fwupd_device_set_update_message(FWUPD_DEVICE(self), tmp);
tmp = xb_node_query_text(component, "custom/value[@key='LVFS::UpdateImage']", NULL);
if (tmp != NULL)
fwupd_device_set_update_image(FWUPD_DEVICE(self), tmp);
}
/**
* fu_device_emit_request:
* @self: a device
* @request: a request
*
* Emit a request from a plugin to the client.
*
* Since: 1.6.2
**/
void
fu_device_emit_request(FuDevice *self, FwupdRequest *request)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(FWUPD_IS_REQUEST(request));
/* sanity check */
if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_UNKNOWN) {
g_critical("a request must have an assigned kind");
return;
}
if (fwupd_request_get_id(request) == NULL) {
g_critical("a request must have an assigned ID");
return;
}
if (fwupd_request_get_kind(request) >= FWUPD_REQUEST_KIND_LAST) {
g_critical("invalid request kind");
return;
}
/* ensure set */
fwupd_request_set_device_id(request, fu_device_get_id(self));
/* for compatibility with older clients */
if (fwupd_request_get_kind(request) == FWUPD_REQUEST_KIND_POST) {
fu_device_set_update_message(self, fwupd_request_get_message(request));
fu_device_set_update_image(self, fwupd_request_get_image(request));
}
/* proxy to the engine */
g_signal_emit(self, signals[SIGNAL_REQUEST], 0, request);
priv->request_cnts[fwupd_request_get_kind(request)]++;
}
static void
fu_device_flags_notify_cb(FuDevice *self, GParamSpec *pspec, gpointer user_data)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
/* we only inhibit when the flags contains UPDATABLE, and that might be discovered by
* probing the hardware *after* the battery level has been set */
if (priv->inhibits != NULL)
fu_device_ensure_inhibits(self);
}
/**
* fu_device_add_instance_str:
* @self: a #FuDevice
* @key: (not nullable): string
* @value: (nullable): value
*
* Assign a value for the @key.
*
* Since: 1.7.7
**/
void
fu_device_add_instance_str(FuDevice *self, const gchar *key, const gchar *value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup(value));
}
static gboolean
fu_strsafe_instance_id_is_valid_char(gchar c)
{
if (c == ' ')
return FALSE;
if (c == '_')
return FALSE;
if (c == '&')
return FALSE;
if (c == '/')
return FALSE;
if (c == '\\')
return FALSE;
return g_ascii_isprint(c);
}
/* NOTE: we can't use fu_strsafe as this behavior is now effectively ABI */
static gchar *
fu_common_instance_id_strsafe(const gchar *str)
{
g_autoptr(GString) tmp = g_string_new(NULL);
gboolean has_content = FALSE;
/* sanity check */
if (str == NULL)
return NULL;
/* use - to replace problematic chars -- but only once per section */
for (guint i = 0; str[i] != '\0'; i++) {
gchar c = str[i];
if (!fu_strsafe_instance_id_is_valid_char(c)) {
if (has_content) {
g_string_append_c(tmp, '-');
has_content = FALSE;
}
} else {
g_string_append_c(tmp, c);
has_content = TRUE;
}
}
/* remove any trailing replacements */
if (tmp->len > 0 && tmp->str[tmp->len - 1] == '-')
g_string_truncate(tmp, tmp->len - 1);
/* nothing left! */
if (tmp->len == 0)
return NULL;
/* success */
return g_string_free(g_steal_pointer(&tmp), FALSE);
}
/**
* fu_device_add_instance_strsafe:
* @self: a #FuDevice
* @key: (not nullable): string
* @value: (nullable): value
*
* Assign a sanitized value for the @key.
*
* Since: 1.7.7
**/
void
fu_device_add_instance_strsafe(FuDevice *self, const gchar *key, const gchar *value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->instance_hash,
g_strdup(key),
fu_common_instance_id_strsafe(value));
}
/**
* fu_device_add_instance_strup:
* @self: a #FuDevice
* @key: (not nullable): string
* @value: (nullable): value
*
* Assign a uppercase value for the @key.
*
* Since: 1.7.7
**/
void
fu_device_add_instance_strup(FuDevice *self, const gchar *key, const gchar *value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->instance_hash,
g_strdup(key),
value != NULL ? g_utf8_strup(value, -1) : NULL);
}
/**
* fu_device_add_instance_u4:
* @self: a #FuDevice
* @key: (not nullable): string
* @value: value
*
* Assign a value to the @key, which is padded as %1X.
*
* Since: 1.7.7
**/
void
fu_device_add_instance_u4(FuDevice *self, const gchar *key, guint8 value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup_printf("%01X", value));
}
/**
* fu_device_add_instance_u8:
* @self: a #FuDevice
* @key: (not nullable): string
* @value: value
*
* Assign a value to the @key, which is padded as %2X.
*
* Since: 1.7.7
**/
void
fu_device_add_instance_u8(FuDevice *self, const gchar *key, guint8 value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup_printf("%02X", value));
}
/**
* fu_device_add_instance_u16:
* @self: a #FuDevice
* @key: (not nullable): string
* @value: value
*
* Assign a value to the @key, which is padded as %4X.
*
* Since: 1.7.7
**/
void
fu_device_add_instance_u16(FuDevice *self, const gchar *key, guint16 value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup_printf("%04X", value));
}
/**
* fu_device_add_instance_u32:
* @self: a #FuDevice
* @key: (not nullable): string
* @value: value
*
* Assign a value to the @key, which is padded as %8X.
*
* Since: 1.7.7
**/
void
fu_device_add_instance_u32(FuDevice *self, const gchar *key, guint32 value)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
g_return_if_fail(FU_IS_DEVICE(self));
g_return_if_fail(key != NULL);
g_hash_table_insert(priv->instance_hash, g_strdup(key), g_strdup_printf("%08X", value));
}
/**
* fu_device_build_instance_id:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
* @subsystem: (not nullable): subsystem, e.g. `NVME`
* @...: pairs of string key values, ending with %NULL
*
* Creates an instance ID from a prefix and some key values.
* If the key value cannot be found, the parent and then proxy is also queried.
*
* If any of the key values remain unset then no instance ID is added.
*
* fu_device_add_instance_str(dev, "VID", "1234");
* fu_device_add_instance_u16(dev, "PID", 5678);
* if (!fu_device_build_instance_id(dev, &error, "NVME", "VID", "PID", NULL))
* g_warning("failed to add ID: %s", error->message);
*
* Returns: %TRUE if the instance ID was added.
*
* Since: 1.7.7
**/
gboolean
fu_device_build_instance_id(FuDevice *self, GError **error, const gchar *subsystem, ...)
{
FuDevice *parent = fu_device_get_parent(self);
FuDevicePrivate *priv = GET_PRIVATE(self);
gboolean ret = TRUE;
va_list args;
g_autoptr(GString) str = g_string_new(subsystem);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(subsystem != NULL, FALSE);
va_start(args, subsystem);
for (guint i = 0;; i++) {
const gchar *key = va_arg(args, const gchar *);
const gchar *value;
if (key == NULL)
break;
value = fu_device_instance_lookup(self, key);
if (value == NULL && parent != NULL)
value = fu_device_instance_lookup(parent, key);
if (value == NULL && priv->proxy != NULL)
value = fu_device_instance_lookup(priv->proxy, key);
if (value == NULL) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"no value for %s",
key);
ret = FALSE;
break;
}
g_string_append(str, i == 0 ? "\\" : "&");
g_string_append_printf(str, "%s_%s", key, value);
}
va_end(args);
/* we set an error above */
if (!ret)
return FALSE;
/* success */
fu_device_add_instance_id(self, str->str);
return TRUE;
}
/**
* fu_device_build_instance_id_quirk:
* @self: a #FuDevice
* @error: (nullable): optional return location for an error
* @subsystem: (not nullable): subsystem, e.g. `NVME`
* @...: pairs of string key values, ending with %NULL
*
* Creates an quirk-only instance ID from a prefix and some key values. If any of the key values
* are unset then no instance ID is added.
*
* Returns: %TRUE if the instance ID was added.
*
* Since: 1.7.7
**/
gboolean
fu_device_build_instance_id_quirk(FuDevice *self, GError **error, const gchar *subsystem, ...)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
gboolean ret = TRUE;
va_list args;
g_autoptr(GString) str = g_string_new(subsystem);
g_return_val_if_fail(FU_IS_DEVICE(self), FALSE);
g_return_val_if_fail(subsystem != NULL, FALSE);
va_start(args, subsystem);
for (guint i = 0;; i++) {
const gchar *key = va_arg(args, const gchar *);
const gchar *value;
if (key == NULL)
break;
value = g_hash_table_lookup(priv->instance_hash, key);
if (value == NULL) {
g_set_error(error,
G_IO_ERROR,
G_IO_ERROR_INVALID_DATA,
"no value for %s",
key);
ret = FALSE;
break;
}
g_string_append(str, i == 0 ? "\\" : "&");
g_string_append_printf(str, "%s_%s", key, value);
}
va_end(args);
/* we set an error above */
if (!ret)
return FALSE;
/* success */
fu_device_add_instance_id_full(self, str->str, FU_DEVICE_INSTANCE_FLAG_ONLY_QUIRKS);
return TRUE;
}
static void
fu_device_class_init(FuDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
GParamSpec *pspec;
object_class->finalize = fu_device_finalize;
object_class->get_property = fu_device_get_property;
object_class->set_property = fu_device_set_property;
/**
* FuDevice::child-added:
* @self: the #FuDevice instance that emitted the signal
* @device: the #FuDevice child
*
* The ::child-added signal is emitted when a device has been added as a child.
*
* Since: 1.0.8
**/
signals[SIGNAL_CHILD_ADDED] = g_signal_new("child-added",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(FuDeviceClass, child_added),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
FU_TYPE_DEVICE);
/**
* FuDevice::child-removed:
* @self: the #FuDevice instance that emitted the signal
* @device: the #FuDevice child
*
* The ::child-removed signal is emitted when a device has been removed as a child.
*
* Since: 1.0.8
**/
signals[SIGNAL_CHILD_REMOVED] = g_signal_new("child-removed",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(FuDeviceClass, child_removed),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
FU_TYPE_DEVICE);
/**
* FuDevice::request:
* @self: the #FuDevice instance that emitted the signal
* @request: the #FwupdRequest
*
* The ::request signal is emitted when the device needs interactive action from the user.
*
* Since: 1.6.2
**/
signals[SIGNAL_REQUEST] = g_signal_new("request",
G_TYPE_FROM_CLASS(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(FuDeviceClass, request),
NULL,
NULL,
g_cclosure_marshal_VOID__OBJECT,
G_TYPE_NONE,
1,
FWUPD_TYPE_REQUEST);
/**
* FuDevice:physical-id:
*
* The device physical ID.
*
* Since: 1.1.2
*/
pspec = g_param_spec_string("physical-id",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_PHYSICAL_ID, pspec);
/**
* FuDevice:logical-id:
*
* The device logical ID.
*
* Since: 1.1.2
*/
pspec = g_param_spec_string("logical-id",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_LOGICAL_ID, pspec);
/**
* FuDevice:backend-id:
*
* The device backend ID.
*
* Since: 1.5.8
*/
pspec = g_param_spec_string("backend-id",
NULL,
NULL,
NULL,
G_PARAM_READWRITE | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_BACKEND_ID, pspec);
/**
* FuDevice:context:
*
* The #FuContext to use.
*
* Since: 1.6.0
*/
pspec = g_param_spec_object("context",
NULL,
NULL,
FU_TYPE_CONTEXT,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_CONTEXT, pspec);
/**
* FuDevice:proxy:
*
* The device proxy to use.
*
* Since: 1.4.1
*/
pspec = g_param_spec_object("proxy",
NULL,
NULL,
FU_TYPE_DEVICE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_PROXY, pspec);
/**
* FuDevice:parent:
*
* The device parent.
*
* Since: 1.0.8
*/
pspec = g_param_spec_object("parent",
NULL,
NULL,
FU_TYPE_DEVICE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT | G_PARAM_STATIC_NAME);
g_object_class_install_property(object_class, PROP_PARENT, pspec);
}
static void
fu_device_init(FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE(self);
priv->order = G_MAXINT;
priv->parent_guids = g_ptr_array_new_with_free_func(g_free);
priv->possible_plugins = g_ptr_array_new_with_free_func(g_free);
priv->retry_recs = g_ptr_array_new_with_free_func(g_free);
priv->instance_hash = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
g_rw_lock_init(&priv->parent_guids_mutex);
g_rw_lock_init(&priv->metadata_mutex);
priv->notify_flags_handler_id = g_signal_connect(FWUPD_DEVICE(self),
"notify::flags",
G_CALLBACK(fu_device_flags_notify_cb),
NULL);
}
static void
fu_device_finalize(GObject *object)
{
FuDevice *self = FU_DEVICE(object);
FuDevicePrivate *priv = GET_PRIVATE(self);
g_rw_lock_clear(&priv->metadata_mutex);
g_rw_lock_clear(&priv->parent_guids_mutex);
if (priv->alternate != NULL)
g_object_unref(priv->alternate);
if (priv->proxy != NULL)
g_object_remove_weak_pointer(G_OBJECT(priv->proxy), (gpointer *)&priv->proxy);
if (priv->ctx != NULL)
g_object_unref(priv->ctx);
if (priv->poll_id != 0)
g_source_remove(priv->poll_id);
if (priv->metadata != NULL)
g_hash_table_unref(priv->metadata);
if (priv->inhibits != NULL)
g_hash_table_unref(priv->inhibits);
if (priv->parent_physical_ids != NULL)
g_ptr_array_unref(priv->parent_physical_ids);
if (priv->private_flag_items != NULL)
g_ptr_array_unref(priv->private_flag_items);
g_ptr_array_unref(priv->parent_guids);
g_ptr_array_unref(priv->possible_plugins);
g_ptr_array_unref(priv->retry_recs);
g_free(priv->alternate_id);
g_free(priv->equivalent_id);
g_free(priv->physical_id);
g_free(priv->logical_id);
g_free(priv->backend_id);
g_free(priv->proxy_guid);
g_free(priv->custom_flags);
g_hash_table_unref(priv->instance_hash);
G_OBJECT_CLASS(fu_device_parent_class)->finalize(object);
}
/**
* fu_device_new:
*
* Creates a new #Fudevice
*
* Since: 1.8.2
**/
FuDevice *
fu_device_new(FuContext *ctx)
{
FuDevice *self = g_object_new(FU_TYPE_DEVICE, "context", ctx, NULL);
return FU_DEVICE(self);
}