fwupd/libfwupdplugin/fu-device.c
Richard Hughes aaa77c6f51 Allow adding and removing custom flags on devices
The CustomFlags feature is a bit of a hack where we just join the flags
and store in the device metadata section as a string. This makes it
inefficient to check if just one flag exists as we have to split the
string to a temporary array each time.

Rather than adding to the hack by splitting, appending (if not exists)
then joining again, store the flags in the plugin privdata directly.

This allows us to support negating custom properties (e.g. ~hint) and
also allows quirks to append custom values without duplicating them on
each GUID match, e.g.

[USB\VID_17EF&PID_307F]
Plugin = customflag1
[USB\VID_17EF&PID_307F&HUB_0002]
Flags = customflag2

...would result in customflag1,customflag2 which is the same as you'd
get from an enumerated device flag doing the same thing.
2021-06-23 07:59:15 +01:00

4395 lines
117 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 <string.h>
#include <glib-object.h>
#include <gio/gio.h>
#include "fu-common.h"
#include "fu-common-version.h"
#include "fu-device-private.h"
#include "fu-mutex.h"
#include "fu-quirks.h"
#include "fwupd-common.h"
#include "fwupd-device-private.h"
#define FU_DEVICE_RETRY_OPEN_COUNT 5
#define FU_DEVICE_RETRY_OPEN_DELAY 500 /* ms */
#define FU_DEVICE_DEFAULT_BATTERY_THRESHOLD 10 /* % */
/**
* 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);
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 progress;
guint battery_level;
guint battery_threshold;
gint order;
guint priority;
guint poll_id;
gboolean done_probe;
gboolean done_setup;
gboolean device_id_valid;
guint64 size_min;
guint64 size_max;
gint open_refcount; /* atomic */
GType specialized_gtype;
GPtrArray *possible_plugins;
GPtrArray *retry_recs; /* of FuDeviceRetryRecovery */
guint retry_delay;
FuDeviceInternalFlags internal_flags;
guint64 private_flags;
GPtrArray *private_flag_items; /* (nullable) */
} FuDevicePrivate;
typedef struct {
GQuark domain;
gint code;
FuDeviceRetryFunc recovery_func;
} FuDeviceRetryRecovery;
enum {
PROP_0,
PROP_PROGRESS,
PROP_BATTERY_LEVEL,
PROP_BATTERY_THRESHOLD,
PROP_PHYSICAL_ID,
PROP_LOGICAL_ID,
PROP_BACKEND_ID,
PROP_CONTEXT,
PROP_PROXY,
PROP_PARENT,
PROP_LAST
};
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_PROGRESS:
g_value_set_uint (value, priv->progress);
break;
case PROP_BATTERY_LEVEL:
g_value_set_uint (value, priv->battery_level);
break;
case PROP_BATTERY_THRESHOLD:
g_value_set_uint (value, priv->battery_threshold);
break;
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_PROGRESS:
fu_device_set_progress (self, g_value_get_uint (value));
break;
case PROP_BATTERY_LEVEL:
fu_device_set_battery_level (self, g_value_get_uint (value));
break;
case PROP_BATTERY_THRESHOLD:
fu_device_set_battery_threshold (self, g_value_get_uint (value));
break;
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";
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;
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_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_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, 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);
}
/**
* 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;
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));
}
/**
* 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));
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;
}
/**
* 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_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);
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));
/* 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 %u as child is greater than %u",
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 (fu_device_get_vendor (child) == NULL)
fu_device_set_vendor (child, fu_device_get_vendor (self));
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);
}
/**
* 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_kv (FuDevice *self,
const gchar *key,
const gchar *value,
GError **error)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self);
if (g_strcmp0 (key, FU_QUIRKS_PLUGIN) == 0) {
fu_device_add_possible_plugin (self, value);
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) {
fu_device_add_vendor_id (self, value);
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_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) {
fu_device_add_icon (self, value);
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_GUID) == 0) {
fu_device_add_guid (self, value);
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_COUNTERPART_GUID) == 0) {
fu_device_add_counterpart_guid (self, value);
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_PARENT_GUID) == 0) {
fu_device_add_parent_guid (self, value);
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) {
fu_device_set_firmware_size_min (self, fu_common_strtoull (value));
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_FIRMWARE_SIZE_MAX) == 0) {
fu_device_set_firmware_size_max (self, fu_common_strtoull (value));
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_FIRMWARE_SIZE) == 0) {
fu_device_set_firmware_size (self, fu_common_strtoull (value));
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_INSTALL_DURATION) == 0) {
fu_device_set_install_duration (self, fu_common_strtoull (value));
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_PRIORITY) == 0) {
fu_device_set_priority (self, fu_common_strtoull (value));
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_BATTERY_THRESHOLD) == 0) {
fu_device_set_battery_threshold (self, fu_common_strtoull (value));
return TRUE;
}
if (g_strcmp0 (key, FU_QUIRKS_REMOVE_DELAY) == 0) {
fu_device_set_remove_delay (self, fu_common_strtoull (value));
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) {
if (value != NULL)
fu_device_inhibit (self, "quirk", value);
else
fu_device_uninhibit (self, "quirk");
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_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;
}
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)
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)
{
/* add the device GUID before adding additional GUIDs from quirks
* to ensure the bootloader GUID is listed after the runtime GUID */
fwupd_device_add_guid (FWUPD_DEVICE (self), guid);
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);
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);
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 InstanceID, 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_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: a string 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);
}
/**
* 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_autoptr(GString) new = g_string_new (value);
g_return_if_fail (FU_IS_DEVICE (self));
g_return_if_fail (value != NULL);
/* overwriting? */
if (g_strcmp0 (value, fu_device_get_name (self)) == 0) {
const gchar *id = fu_device_get_id (self);
g_debug ("%s device overwriting same name value: %s",
id != NULL ? id : "unknown", value);
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);
}
g_strdelimit (new->str, "_", ' ');
fu_common_string_replace (new, "(TM)", "");
fwupd_device_set_name (FWUPD_DEVICE (self), new->str);
}
/**
* 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_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);
}
}
/**
* 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_common_version_ensure_semver (version);
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_common_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_common_version_ensure_semver (version);
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_common_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_common_version_ensure_semver (version);
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_common_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);
}
}
typedef struct {
gchar *inhibit_id;
gchar *reason;
} FuDeviceInhibit;
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);
guint nr_inhibits = g_hash_table_size (priv->inhibits);
/* was okay -> not okay */
if (fu_device_has_flag (self, FWUPD_DEVICE_FLAG_UPDATABLE) &&
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 ();
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);
}
reasons_str = fu_common_strjoin_array (", ", reasons);
fu_device_set_update_error (self, reasons_str);
}
/* not okay -> is okay */
if (fu_device_has_flag (self, FWUPD_DEVICE_FLAG_UPDATABLE_HIDDEN) &&
nr_inhibits == 0) {
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);
}
}
/**
* 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)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
FuDeviceInhibit *inhibit;
g_return_if_fail (FU_IS_DEVICE (self));
g_return_if_fail (inhibit_id != NULL);
/* 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);
}
/* already exists */
inhibit = g_hash_table_lookup (priv->inhibits, inhibit_id);
if (inhibit != NULL)
return;
/* create new */
inhibit = g_new0 (FuDeviceInhibit, 1);
inhibit->inhibit_id = g_strdup (inhibit_id);
inhibit->reason = g_strdup (reason);
g_hash_table_insert (priv->inhibits, inhibit->inhibit_id, inhibit);
/* refresh */
fu_device_ensure_inhibits (self);
}
/**
* 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);
}
/**
* 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");
}
/**
* 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);
/* 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");
}
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);
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;
}
g_debug ("failed to find registered custom flag %s", hint + 1);
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;
}
g_debug ("failed to find registered custom flag %s", hint);
}
/**
* 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)
{
g_return_if_fail (FU_IS_DEVICE (self));
g_return_if_fail (custom_flags != NULL);
/* display what was set when converting to a string */
fu_device_set_metadata (self, "CustomFlags", 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)
{
g_return_val_if_fail (FU_IS_DEVICE (self), NULL);
return fu_device_get_metadata (self, "CustomFlags");
}
/**
* fu_device_has_custom_flag:
* @self: a #FuDevice
* @hint: a string, e.g. `bootloader`
*
* Checks if the custom flag exists for the device from the quirk system.
*
* It may be more efficient to call fu_device_get_custom_flags() and split the
* string locally if checking for lots of different flags.
*
* Returns: %TRUE if the hint exists
*
* Since: 1.1.0
**/
gboolean
fu_device_has_custom_flag (FuDevice *self, const gchar *hint)
{
const gchar *hint_str;
g_auto(GStrv) hints = NULL;
g_return_val_if_fail (FU_IS_DEVICE (self), FALSE);
g_return_val_if_fail (hint != NULL, FALSE);
/* no hint is perfectly valid */
hint_str = fu_device_get_custom_flags (self);
if (hint_str == NULL)
return FALSE;
hints = g_strsplit (hint_str, ",", -1);
return g_strv_contains ((const gchar * const *) hints, hint);
}
/**
* 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 delay value
*
* 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_get_status:
* @self: a #FuDevice
*
* Returns what the device is currently doing.
*
* Returns: the status value, e.g. %FWUPD_STATUS_DEVICE_WRITE
*
* Since: 1.0.3
**/
FwupdStatus
fu_device_get_status (FuDevice *self)
{
g_return_val_if_fail (FU_IS_DEVICE (self), 0);
return fwupd_device_get_status (FWUPD_DEVICE (self));
}
/**
* fu_device_set_status:
* @self: a #FuDevice
* @status: the status value, e.g. %FWUPD_STATUS_DEVICE_WRITE
*
* Sets what the device is currently doing.
*
* Since: 1.0.3
**/
void
fu_device_set_status (FuDevice *self, FwupdStatus status)
{
g_return_if_fail (FU_IS_DEVICE (self));
fwupd_device_set_status (FWUPD_DEVICE (self), status);
}
/**
* fu_device_get_progress:
* @self: a #FuDevice
*
* Returns the progress completion.
*
* Returns: value in percent
*
* Since: 1.0.3
**/
guint
fu_device_get_progress (FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_DEVICE (self), 0);
return priv->progress;
}
/**
* fu_device_set_progress:
* @self: a #FuDevice
* @progress: the progress percentage value
*
* Sets the progress completion.
*
* Since: 1.0.3
**/
void
fu_device_set_progress (FuDevice *self, guint progress)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_DEVICE (self));
if (priv->progress == progress)
return;
priv->progress = progress;
g_object_notify (G_OBJECT (self), "progress");
}
/**
* fu_device_set_progress_full:
* @self: a #FuDevice
* @progress_done: the bytes already done
* @progress_total: the total number of bytes
*
* Sets the progress completion using the raw progress values.
*
* Since: 1.0.3
**/
void
fu_device_set_progress_full (FuDevice *self, gsize progress_done, gsize progress_total)
{
gdouble percentage = 0.f;
g_return_if_fail (FU_IS_DEVICE (self));
if (progress_total > 0)
percentage = (100.f * (gdouble) progress_done) / (gdouble) progress_total;
fu_device_set_progress (self, (guint) percentage);
}
/**
* fu_device_sleep_with_progress:
* @self: a #FuDevice
* @delay_secs: the delay in seconds
*
* Sleeps, setting the device progress from 0..100% as time continues.
* The value is gven in whole seconds as it does not make sense to show the
* progressbar advancing so quickly for durations of less than one second.
*
* Since: 1.5.0
**/
void
fu_device_sleep_with_progress (FuDevice *self, guint delay_secs)
{
gulong delay_us_pc = (delay_secs * G_USEC_PER_SEC) / 100;
g_return_if_fail (FU_IS_DEVICE (self));
g_return_if_fail (delay_secs > 0);
fu_device_set_progress (self, 0);
for (guint i = 0; i < 100; i++) {
g_usleep (delay_us_pc);
fu_device_set_progress (self, i + 1);
}
}
static void
fu_device_ensure_battery_inhibit (FuDevice *self)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
if (priv->battery_level == FU_BATTERY_VALUE_INVALID ||
priv->battery_level >= fu_device_get_battery_threshold (self)) {
fu_device_uninhibit (self, "battery");
return;
}
fu_device_inhibit (self, "battery", "Battery level is 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)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_DEVICE (self), FU_BATTERY_VALUE_INVALID);
return priv->battery_level;
}
/**
* fu_device_set_battery_level:
* @self: a #FuDevice
* @battery_level: the percentage value
*
* Sets the battery level, or %FU_BATTERY_VALUE_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)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_DEVICE (self));
g_return_if_fail (battery_level <= FU_BATTERY_VALUE_INVALID);
if (priv->battery_level == battery_level)
return;
priv->battery_level = battery_level;
g_object_notify (G_OBJECT (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)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_DEVICE (self), FU_BATTERY_VALUE_INVALID);
/* default value */
if (priv->battery_threshold == FU_BATTERY_VALUE_INVALID)
return FU_DEVICE_DEFAULT_BATTERY_THRESHOLD;
return priv->battery_threshold;
}
/**
* fu_device_set_battery_threshold:
* @self: a #FuDevice
* @battery_threshold: the percentage value
*
* Sets the battery level, or %FU_BATTERY_VALUE_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)
{
FuDevicePrivate *priv = GET_PRIVATE (self);
g_return_if_fail (FU_IS_DEVICE (self));
g_return_if_fail (battery_threshold <= FU_BATTERY_VALUE_INVALID);
if (priv->battery_threshold == battery_threshold)
return;
priv->battery_threshold = battery_threshold;
g_object_notify (G_OBJECT (self), "battery-threshold");
fu_device_ensure_battery_inhibit (self);
}
static 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);
/* subclassed type */
fu_common_string_append_kv (str, idt, G_OBJECT_TYPE_NAME (self), 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_common_string_append_kv (str, idt + 1, "AlternateId", priv->alternate_id);
if (priv->equivalent_id != NULL)
fu_common_string_append_kv (str, idt + 1, "EquivalentId", priv->equivalent_id);
if (priv->physical_id != NULL)
fu_common_string_append_kv (str, idt + 1, "PhysicalId", priv->physical_id);
if (priv->logical_id != NULL)
fu_common_string_append_kv (str, idt + 1, "LogicalId", priv->logical_id);
if (priv->backend_id != NULL)
fu_common_string_append_kv (str, idt + 1, "BackendId", priv->backend_id);
if (priv->proxy != NULL)
fu_common_string_append_kv (str, idt + 1, "ProxyId", fu_device_get_id (priv->proxy));
if (priv->proxy_guid != NULL)
fu_common_string_append_kv (str, idt + 1, "ProxyGuid", priv->proxy_guid);
if (priv->battery_level != FU_BATTERY_VALUE_INVALID)
fu_common_string_append_ku (str, idt + 1, "BatteryLevel", priv->battery_level);
if (priv->battery_threshold != FU_BATTERY_VALUE_INVALID)
fu_common_string_append_ku (str, idt + 1, "BatteryThreshold", priv->battery_threshold);
if (priv->size_min > 0) {
g_autofree gchar *sz = g_strdup_printf ("%" G_GUINT64_FORMAT, priv->size_min);
fu_common_string_append_kv (str, idt + 1, "FirmwareSizeMin", sz);
}
if (priv->size_max > 0) {
g_autofree gchar *sz = g_strdup_printf ("%" G_GUINT64_FORMAT, priv->size_max);
fu_common_string_append_kv (str, idt + 1, "FirmwareSizeMax", sz);
}
if (priv->order != G_MAXINT)
fu_common_string_append_ku (str, idt + 1, "Order", priv->order);
if (priv->priority > 0)
fu_common_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_common_string_append_kv (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_common_string_append_kv (str, idt + 1, "PossiblePlugin", name);
}
if (priv->parent_physical_ids != NULL && priv->parent_physical_ids->len > 0) {
g_autofree gchar *flags = fu_common_strjoin_array (",", priv->parent_physical_ids);
fu_common_string_append_kv (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_common_string_append_kv (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_common_strjoin_array (",", tmpv);
fu_common_string_append_kv (str, idt + 1, "PrivateFlags", tmps);
}
/* 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));
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_write_firmware:
* @self: a #FuDevice
* @fw: firmware blob
* @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,
FwupdInstallFlags flags,
GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS (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 (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,
"not supported");
return FALSE;
}
/* prepare (e.g. decompress) firmware */
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 */
return klass->write_firmware (self, firmware, flags, error);
}
/**
* 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) {
fu_device_set_status (self, FWUPD_STATUS_DECOMPRESSING);
firmware = klass->prepare_firmware (self, fw, flags, error);
if (firmware == NULL)
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 %04x bytes larger than the allowed "
"maximum size of %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
* @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, 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 (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,
"not supported");
return NULL;
}
/* call vfunc */
if (klass->read_firmware != NULL)
return klass->read_firmware (self, error);
/* use the default FuFirmware when only ->dump_firmware is provided */
fw = fu_device_dump_firmware (self, error);
if (fw == NULL)
return NULL;
return fu_firmware_new_from_bytes (fw);
}
/**
* fu_device_dump_firmware:
* @self: a #FuDevice
* @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, GError **error)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self);
g_return_val_if_fail (FU_IS_DEVICE (self), 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,
"not supported");
return NULL;
}
/* proxy */
return klass->dump_firmware (self, 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)
{
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->detach == NULL)
return TRUE;
/* call vfunc */
return klass->detach (self, 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)
{
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->attach == NULL)
return TRUE;
/* call vfunc */
return klass->attach (self, 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
* @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, 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 (error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->prepare == NULL)
return TRUE;
/* call vfunc */
return klass->prepare (self, flags, error);
}
/**
* fu_device_cleanup:
* @self: a #FuDevice
* @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, 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 (error == NULL || *error == NULL, FALSE);
/* no plugin-specific method */
if (klass->cleanup == NULL)
return TRUE;
/* call vfunc */
return klass->cleanup (self, 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);
}
/**
* 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)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self);
FuDevicePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_DEVICE (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* 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_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)
{
FuDeviceClass *klass = FU_DEVICE_GET_CLASS (self);
FuDevicePrivate *priv = GET_PRIVATE (self);
g_return_val_if_fail (FU_IS_DEVICE (self), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
/* not yet open */
if (priv->open_refcount == 0) {
g_set_error_literal (error,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"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_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;
/* subclassed */
if (klass->probe != NULL) {
if (!klass->probe (self, error))
return FALSE;
}
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_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;
}
/* 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
* @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, 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->activate != NULL) {
if (!klass->activate (self, 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,
"not supported");
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,
"not supported");
return FALSE;
}
/* subclass */
return klass->unbind_driver (self, error);
}
/**
* 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);
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->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_autoptr(GList) keys = g_hash_table_get_keys (priv_donor->metadata);
for (GList *l = keys; l != NULL; l = l->next) {
const gchar *key = l->data;
if (g_hash_table_lookup (priv->metadata, key) == NULL) {
const gchar *value = g_hash_table_lookup (priv_donor->metadata, key);
fu_device_set_metadata (self, key, value);
}
}
}
g_rw_lock_reader_unlock (&priv_donor->metadata_mutex);
/* 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);
}
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;
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);
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);
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);
pspec = g_param_spec_uint ("progress", NULL, NULL,
0, 100, 0,
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_PROGRESS, pspec);
pspec = g_param_spec_uint ("battery-level", NULL, NULL,
0,
FU_BATTERY_VALUE_INVALID,
FU_BATTERY_VALUE_INVALID,
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_BATTERY_LEVEL, pspec);
pspec = g_param_spec_uint ("battery-threshold", NULL, NULL,
0,
FU_BATTERY_VALUE_INVALID,
FU_BATTERY_VALUE_INVALID,
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_BATTERY_THRESHOLD, pspec);
pspec = g_param_spec_object ("context", NULL, NULL,
FU_TYPE_CONTEXT,
G_PARAM_READWRITE |
G_PARAM_STATIC_NAME);
g_object_class_install_property (object_class, PROP_CONTEXT, pspec);
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);
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->battery_level = FU_BATTERY_VALUE_INVALID;
priv->battery_threshold = FU_BATTERY_VALUE_INVALID;
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);
g_rw_lock_init (&priv->parent_guids_mutex);
g_rw_lock_init (&priv->metadata_mutex);
}
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_OBJECT_CLASS (fu_device_parent_class)->finalize (object);
}
/**
* fu_device_new:
*
* Creates a new #Fudevice
*
* Since: 0.1.0
**/
FuDevice *
fu_device_new (void)
{
FuDevice *self = g_object_new (FU_TYPE_DEVICE, NULL);
return FU_DEVICE (self);
}