/
githubmirror
/
fwupd
Обзор
Документация
Войти
/
githubmirror
/
fwupd
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
src/fu-device-list.c
1 243 строки
39 KB
Richard Hughes
Add fu_context_timeout_add_seconds() that uses the GMainContext for idle
07 авг 2026, 13:05
07 авг 2026, 13:05
a43aaa1
Код
Авторство
О чём код?
/* * Copyright 2017 Richard Hughes <richard@hughsie.com> * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define G_LOG_DOMAIN "FuDeviceList" #include "config.h" #include <string.h> #include "fu-device-list.h" #include "fu-device-private.h" /** * FuDeviceList: * * This list of devices provides a way to find a device using either the * device-id or a GUID. * * The device list will emit ::added and ::removed signals when the device list * has been changed. If the #FuDevice has changed during a device replug then * the ::changed signal will be emitted instead of ::added and then ::removed. * * See also: [class@FuDevice] */ static void fu_device_list_finalize(GObject *obj); struct _FuDeviceList { GObject parent_instance; FuContext *ctx; GPtrArray *devices; /* of FuDeviceItem */ GRWLock devices_mutex; }; enum { SIGNAL_ADDED, SIGNAL_REMOVED, SIGNAL_CHANGED, SIGNAL_LAST }; static guint signals[SIGNAL_LAST] = {0}; enum { QUARK_UNCONNECTED, QUARK_LAST, }; static guint quarks[QUARK_LAST] = {0}; typedef struct { FuDevice *device; FuDevice *device_old; FuDeviceList *self; /* no ref */ GSource *remove_source; } FuDeviceItem; static void fu_device_list_codec_iface_init(FwupdCodecInterface *iface); G_DEFINE_TYPE_EXTENDED(FuDeviceList, fu_device_list, G_TYPE_OBJECT, 0, G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC, fu_device_list_codec_iface_init)); static void fu_device_list_emit_device_added(FuDeviceList *self, FuDevice *device) { g_autofree gchar *id_display = fu_device_get_id_display(device); g_info("::added %s", id_display); g_signal_emit(self, signals[SIGNAL_ADDED], 0, device); } static void fu_device_list_emit_device_removed(FuDeviceList *self, FuDevice *device) { g_autofree gchar *id_display = fu_device_get_id_display(device); g_info("::removed %s", id_display); g_signal_emit(self, signals[SIGNAL_REMOVED], 0, device); } static void fu_device_list_emit_device_changed(FuDeviceList *self, FuDevice *device) { g_autofree gchar *id_display = fu_device_get_id_display(device); g_info("::changed %s", id_display); g_signal_emit(self, signals[SIGNAL_CHANGED], 0, device); } static void fu_device_list_add_json(FwupdCodec *codec, FwupdJsonObject *json_obj, FwupdCodecFlags flags) { FuDeviceList *self = FU_DEVICE_LIST(codec); g_autoptr(FwupdJsonArray) json_array = fwupd_json_array_new(); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); g_autoptr(FwupdJsonObject) json_device = fwupd_json_object_new(); fwupd_codec_to_json(FWUPD_CODEC(item->device), json_device, flags); fwupd_json_array_add_object(json_array, json_device); } fwupd_json_object_add_array(json_obj, "Devices", json_array); g_rw_lock_reader_unlock(&self->devices_mutex); } static void fu_device_list_add_string(FwupdCodec *codec, guint idt, GString *str) { FuDeviceList *self = FU_DEVICE_LIST(codec); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); gboolean wfr; g_string_append_printf(str, "%u [%p] %s\n", i, item, item->remove_source != 0 ? "IN_TIMEOUT" : ""); wfr = fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); g_string_append_printf(str, "new: %s [%p] %s\n", fu_device_get_id(item->device), item->device, wfr ? "WAIT_FOR_REPLUG" : ""); if (item->device_old != NULL) { wfr = fu_device_has_flag(item->device_old, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); g_string_append_printf(str, "old: %s [%p] %s\n", fu_device_get_id(item->device_old), item->device_old, wfr ? "WAIT_FOR_REPLUG" : ""); } } g_rw_lock_reader_unlock(&self->devices_mutex); } /* we cannot use fu_device_get_children() as this will not find "parent-only" * logical relationships added using fu_device_add_parent_guid() */ static GPtrArray * fu_device_list_get_children(FuDeviceList *self, FuDevice *device) { GPtrArray *devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (device == fu_device_get_parent_internal(item->device)) g_ptr_array_add(devices, g_object_ref(item->device)); } g_rw_lock_reader_unlock(&self->devices_mutex); return devices; } static void fu_device_list_depsolve_order_full(FuDeviceList *self, FuDevice *device, guint depth) { g_autoptr(GPtrArray) children = NULL; /* ourself */ fu_device_set_order(device, depth); /* optional children */ children = fu_device_list_get_children(self, device); for (guint i = 0; i < children->len; i++) { FuDevice *child = g_ptr_array_index(children, i); if (fu_device_has_private_flag(child, FU_DEVICE_PRIVATE_FLAG_INSTALL_PARENT_FIRST) || fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_INSTALL_PARENT_FIRST)) { fu_device_list_depsolve_order_full(self, child, depth + 1); } else { fu_device_list_depsolve_order_full(self, child, depth - 1); } } } /** * fu_device_list_depsolve_order: * @self: a device list * @device: a device * * Sets the device order using the logical parent->child relationships -- by default * the child is updated first, unless the device has set private flag * %FU_DEVICE_PRIVATE_FLAG_INSTALL_PARENT_FIRST. * * Since: 1.5.0 **/ void fu_device_list_depsolve_order(FuDeviceList *self, FuDevice *device) { g_autoptr(FuDevice) root = fu_device_get_root(device); if (fu_device_has_private_flag(root, FU_DEVICE_PRIVATE_FLAG_EXPLICIT_ORDER)) return; fu_device_list_depsolve_order_full(self, root, 0); } /** * fu_device_list_get_all: * @self: a device list * * Returns all the devices that have been added to the device list. * This includes devices that are no longer active, for instance where a * different plugin has taken over responsibility of the #FuDevice. * * Returns: (transfer container) (element-type FuDevice): the devices * * Since: 1.0.2 **/ GPtrArray * fu_device_list_get_all(FuDeviceList *self) { GPtrArray *devices; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), NULL); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); g_ptr_array_add(devices, g_object_ref(item->device)); } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device_old == NULL) continue; g_ptr_array_add(devices, g_object_ref(item->device_old)); } g_rw_lock_reader_unlock(&self->devices_mutex); return devices; } /** * fu_device_list_get_active: * @self: a device list * * Returns all the active devices that have been added to the device list. * An active device is defined as a device that is currently connected and has * is owned by a plugin. * * Returns: (transfer container) (element-type FuDevice): the devices * * Since: 1.0.2 **/ GPtrArray * fu_device_list_get_active(FuDeviceList *self) { GPtrArray *devices; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), NULL); devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (fu_device_has_private_flag_quark(item->device, quarks[QUARK_UNCONNECTED])) continue; if (fu_device_has_inhibit(item->device, "hidden")) continue; g_ptr_array_add(devices, g_object_ref(item->device)); } g_rw_lock_reader_unlock(&self->devices_mutex); return devices; } static FuDeviceItem * fu_device_list_find_by_device(FuDeviceList *self, FuDevice *device) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&self->devices_mutex); g_return_val_if_fail(locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device == device) return item; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device_old == device) return item; } return NULL; } static FuDeviceItem * fu_device_list_find_by_guid(FuDeviceList *self, const gchar *guid) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&self->devices_mutex); g_return_val_if_fail(locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (fu_device_has_guid(item->device, guid)) return item; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device_old == NULL) continue; if (fu_device_has_guid(item->device_old, guid)) return item; } return NULL; } static FuDeviceItem * fu_device_list_find_by_connection(FuDeviceList *self, const gchar *physical_id, const gchar *logical_id) { g_autoptr(GRWLockReaderLocker) locker = NULL; if (physical_id == NULL) return NULL; locker = g_rw_lock_reader_locker_new(&self->devices_mutex); g_return_val_if_fail(locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); FuDevice *device = item_tmp->device; if (device != NULL && g_strcmp0(fu_device_get_physical_id(device), physical_id) == 0 && g_strcmp0(fu_device_get_logical_id(device), logical_id) == 0) return item_tmp; } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); FuDevice *device = item_tmp->device_old; if (device != NULL && g_strcmp0(fu_device_get_physical_id(device), physical_id) == 0 && g_strcmp0(fu_device_get_logical_id(device), logical_id) == 0) return item_tmp; } return NULL; } static gint fu_device_list_item_sort_by_priority_cb(gconstpointer a, gconstpointer b) { const FuDeviceItem *item1 = *((FuDeviceItem **)a); const FuDeviceItem *item2 = *((FuDeviceItem **)b); if (fu_device_get_priority(item1->device) < fu_device_get_priority(item2->device)) return 1; if (fu_device_get_priority(item1->device) > fu_device_get_priority(item2->device)) return -1; return 0; } static GPtrArray * fu_device_list_filter_by_id(FuDeviceList *self, const gchar *device_id, GError **error) { gsize device_id_len; g_autoptr(GPtrArray) items = g_ptr_array_new(); g_return_val_if_fail(device_id != NULL, NULL); /* support abbreviated hashes */ device_id_len = strlen(device_id); if (device_id_len < 4) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_INTERNAL, "invalid device ID: %s", device_id); return NULL; } g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); const gchar *ids[] = {fu_device_get_id(item_tmp->device), fu_device_get_equivalent_id(item_tmp->device), NULL}; for (guint j = 0; ids[j] != NULL; j++) { if (strncmp(ids[j], device_id, device_id_len) == 0) { g_ptr_array_add(items, item_tmp); break; } } } g_rw_lock_reader_unlock(&self->devices_mutex); if (items->len > 0) { g_ptr_array_sort(items, fu_device_list_item_sort_by_priority_cb); return g_steal_pointer(&items); } /* only search old devices if we didn't find the active device */ g_rw_lock_reader_lock(&self->devices_mutex); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); const gchar *ids[3] = {NULL}; if (item_tmp->device_old == NULL) continue; ids[0] = fu_device_get_id(item_tmp->device_old); ids[1] = fu_device_get_equivalent_id(item_tmp->device_old); for (guint j = 0; ids[j] != NULL; j++) { if (strncmp(ids[j], device_id, device_id_len) == 0) { g_ptr_array_add(items, item_tmp); break; } } } g_rw_lock_reader_unlock(&self->devices_mutex); if (items->len > 0) { g_ptr_array_sort(items, fu_device_list_item_sort_by_priority_cb); return g_steal_pointer(&items); } /* failed */ g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device ID %s was not found", device_id); return NULL; } static FuDeviceItem * fu_device_list_find_by_id(FuDeviceList *self, const gchar *device_id, GError **error) { FuDeviceItem *item0; g_autoptr(GPtrArray) items = NULL; items = fu_device_list_filter_by_id(self, device_id, error); if (items == NULL) return NULL; /* check there are not more devices that have the same priority */ item0 = g_ptr_array_index(items, 0); for (guint i = 1; i < items->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(items, i); if (fu_device_get_priority(item_tmp->device) == fu_device_get_priority(item0->device)) { g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED, "device ID %s was not unique", device_id); return NULL; } } return item0; } /** * fu_device_list_get_old: * @self: a device list * @device: a device * * Returns the old device associated with the currently active device. * * Returns: (transfer full): the device, or %NULL if not found * * Since: 1.0.3 **/ FuDevice * fu_device_list_get_old(FuDeviceList *self, FuDevice *device) { FuDeviceItem *item = fu_device_list_find_by_device(self, device); if (item == NULL) return NULL; if (item->device_old == NULL) return NULL; return g_object_ref(item->device_old); } static FuDeviceItem * fu_device_list_get_by_guids_removed(FuDeviceList *self, GPtrArray *guids) { g_autoptr(GRWLockReaderLocker) locker = g_rw_lock_reader_locker_new(&self->devices_mutex); g_return_val_if_fail(locker != NULL, NULL); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->remove_source == 0) continue; for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index(guids, j); if (fu_device_has_guid(item->device, guid) || fu_device_has_instance_id(item->device, guid, FU_DEVICE_INSTANCE_FLAG_COUNTERPART | FU_DEVICE_INSTANCE_FLAG_VISIBLE)) return item; } } for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item = g_ptr_array_index(self->devices, i); if (item->device_old == NULL) continue; if (item->remove_source == 0) continue; for (guint j = 0; j < guids->len; j++) { const gchar *guid = g_ptr_array_index(guids, j); if (fu_device_has_guid(item->device_old, guid) || fu_device_has_instance_id(item->device_old, guid, FU_DEVICE_INSTANCE_FLAG_COUNTERPART | FU_DEVICE_INSTANCE_FLAG_VISIBLE)) return item; } } return NULL; } static gboolean fu_device_list_device_delayed_remove_cb(gpointer user_data) { FuDeviceItem *item = (FuDeviceItem *)user_data; FuDeviceList *self = FU_DEVICE_LIST(item->self); /* no longer valid */ g_clear_pointer(&item->remove_source, g_source_unref); /* remove any children associated with device */ if (!fu_device_has_private_flag(item->device, FU_DEVICE_PRIVATE_FLAG_NO_AUTO_REMOVE_CHILDREN)) { GPtrArray *children = fu_device_get_children(item->device); for (guint j = 0; j < children->len; j++) { FuDevice *child = g_ptr_array_index(children, j); FuDeviceItem *child_item; child_item = fu_device_list_find_by_id(self, fu_device_get_id(child), NULL); if (child_item == NULL) { g_info("device %s not found", fu_device_get_id(child)); continue; } fu_device_list_emit_device_removed(self, child); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, child_item); g_rw_lock_writer_unlock(&self->devices_mutex); } } /* just remove now */ g_info("doing delayed removal"); fu_device_list_emit_device_removed(self, item->device); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, item); g_rw_lock_writer_unlock(&self->devices_mutex); return G_SOURCE_REMOVE; } static void fu_device_list_remove_with_delay(FuDeviceList *self, FuDeviceItem *item) { g_autofree gchar *id_display = fu_device_get_id_display(item->device); /* give the hardware time to re-enumerate or the user time to * re-insert the device with a magic button pressed */ g_info("waiting %ums for %s device removal", fu_device_get_remove_delay(item->device), id_display); item->remove_source = fu_context_add_timeout(self->ctx, fu_device_get_remove_delay(item->device), fu_device_list_device_delayed_remove_cb, item); } /* nocheck:name */ static gboolean fu_device_list_should_remove_with_delay(FuDevice *device) { if (fu_device_get_remove_delay(device) == 0) return FALSE; if (!fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_DELAYED_REMOVAL) && !fu_device_has_flag(device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) return FALSE; return TRUE; } /** * fu_device_list_remove: * @self: a device list * @device: a device * * Removes a specific device from the list if it exists. * * If the @device has a remove-delay set then a timeout will be started. If * the exact same #FuDevice is added to the list with fu_device_list_add() * within the timeout then only a ::changed signal will be emitted. * * If there is no remove-delay set, the ::removed signal will be emitted * straight away. * * Since: 1.0.2 **/ void fu_device_list_remove(FuDeviceList *self, FuDevice *device) { FuDeviceItem *item; g_return_if_fail(FU_IS_DEVICE_LIST(self)); g_return_if_fail(FU_IS_DEVICE(device)); /* check the device already exists */ item = fu_device_list_find_by_id(self, fu_device_get_id(device), NULL); if (item == NULL) { g_info("device %s not found", fu_device_get_id(device)); return; } /* we can't do anything with an unconnected device */ fu_device_add_private_flag(item->device, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED); /* ensure never fired if the remove delay is changed */ if (item->remove_source != NULL) { g_source_destroy(item->remove_source); g_clear_pointer(&item->remove_source, g_source_unref); } /* delay the removal and check for replug */ if (fu_device_list_should_remove_with_delay(item->device)) { fu_device_list_remove_with_delay(self, item); return; } /* remove any children associated with device */ if (!fu_device_has_private_flag(item->device, FU_DEVICE_PRIVATE_FLAG_NO_AUTO_REMOVE_CHILDREN)) { GPtrArray *children = fu_device_get_children(device); for (guint j = 0; j < children->len; j++) { FuDevice *child = g_ptr_array_index(children, j); FuDeviceItem *child_item; child_item = fu_device_list_find_by_id(self, fu_device_get_id(child), NULL); if (child_item == NULL) { g_info("device %s not found", fu_device_get_id(child)); continue; } fu_device_list_emit_device_removed(self, child); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, child_item); g_rw_lock_writer_unlock(&self->devices_mutex); } } /* remove right now */ fu_device_list_emit_device_removed(self, item->device); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, item); g_rw_lock_writer_unlock(&self->devices_mutex); } /** * fu_device_list_remove_all: * @self: a device list * * Removes all devices from the list. * * Since: 2.0.0 **/ void fu_device_list_remove_all(FuDeviceList *self) { g_return_if_fail(FU_IS_DEVICE_LIST(self)); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_set_size(self->devices, 0); g_rw_lock_writer_unlock(&self->devices_mutex); } static void fu_device_list_add_missing_guids(FuDeviceList *self, FuDevice *device_new, FuDevice *device_old) { GPtrArray *guids_old = fu_device_get_guids(device_old); for (guint i = 0; i < guids_old->len; i++) { const gchar *guid_tmp = g_ptr_array_index(guids_old, i); if (!fu_device_has_guid(device_new, guid_tmp) && !fu_device_has_instance_id(device_new, guid_tmp, FU_DEVICE_INSTANCE_FLAG_COUNTERPART)) { if (fu_device_has_private_flag( device_new, FU_DEVICE_PRIVATE_FLAG_ADD_COUNTERPART_GUIDS)) { g_info("adding GUID %s to device", guid_tmp); fu_device_add_instance_id_full(device_new, guid_tmp, FU_DEVICE_INSTANCE_FLAG_COUNTERPART); } else { g_info("not adding GUID %s to device, use " "FU_DEVICE_PRIVATE_FLAG_ADD_COUNTERPART_GUIDS if required", guid_tmp); } } } } static void fu_device_list_item_finalized_cb(gpointer data, GObject *where_the_object_was) { FuDeviceItem *item = (FuDeviceItem *)data; FuDeviceList *self = FU_DEVICE_LIST(item->self); g_warning("FuDevice %p was finalized without being removed from " "FuDeviceList, removing item!", where_the_object_was); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_remove(self->devices, item); g_rw_lock_writer_unlock(&self->devices_mutex); } static void fu_device_list_item_set_device_old(FuDeviceItem *item, FuDevice *device) { fu_device_set_parent(device, NULL); fu_device_remove_children(device); g_set_object(&item->device_old, device); } /* this should never be required, and yet here we are */ static void fu_device_list_item_set_device(FuDeviceItem *item, FuDevice *device) { if (item->device != NULL) { g_object_weak_unref(G_OBJECT(item->device), fu_device_list_item_finalized_cb, item); } if (device != NULL) { g_object_weak_ref(G_OBJECT(device), fu_device_list_item_finalized_cb, item); } g_set_object(&item->device, device); } static void fu_device_list_clear_wait_for_replug(FuDeviceList *self, FuDeviceItem *item) { g_autofree gchar *str = NULL; /* clear timeout if scheduled */ if (item->remove_source != NULL) { g_source_destroy(item->remove_source); item->remove_source = NULL; } /* remove flag on both old and new devices */ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_info("%s device came back, clearing flag", fu_device_get_id(item->device)); fu_device_remove_flag(item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } if (item->device_old != NULL) { if (fu_device_has_flag(item->device_old, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG)) { g_info("%s old device came back, clearing flag", fu_device_get_id(item->device_old)); fu_device_remove_flag(item->device_old, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); } } fu_device_remove_private_flag(item->device, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED); /* debug */ str = fwupd_codec_to_string(FWUPD_CODEC(self)); g_debug("%s", str); } /* nocheck:name */ static void _fu_device_incorporate_problem_update_in_progress(FuDevice *self, FuDevice *donor) { if (fu_device_has_problem(donor, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS)) { g_info("moving inhibit update-in-progress to active device"); fu_device_add_problem(self, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS); fu_device_remove_problem(donor, FWUPD_DEVICE_PROBLEM_UPDATE_IN_PROGRESS); } } static void fu_device_list_replace(FuDeviceList *self, FuDeviceItem *item, FuDevice *device) { g_autofree gchar *str = NULL; /* run the optional device-specific subclass */ fu_device_replace(device, item->device); /* copy over any GUIDs that used to exist */ fu_device_list_add_missing_guids(self, device, item->device); /* incorporate properties from the old device */ fu_device_incorporate(device, item->device, FU_DEVICE_INCORPORATE_FLAG_VENDOR_IDS | FU_DEVICE_INCORPORATE_FLAG_UPDATE_ERROR | FU_DEVICE_INCORPORATE_FLAG_UPDATE_STATE); /* copy inhibit */ _fu_device_incorporate_problem_update_in_progress(item->device, device); /* copy over the version strings if not set */ if (fu_device_get_version(item->device) != NULL && fu_device_get_version(device) == NULL) { const gchar *version = fu_device_get_version(item->device); guint64 raw = fu_device_get_version_raw(item->device); g_info("copying old version %s to new device", version); fu_device_set_version_format(device, fu_device_get_version_format(item->device)); fu_device_set_version(device, version); /* nocheck:set-version */ fu_device_set_version_raw(device, raw); } /* always use the runtime version */ if (fu_device_has_private_flag(item->device, FU_DEVICE_PRIVATE_FLAG_USE_RUNTIME_VERSION) && fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_NEEDS_BOOTLOADER)) { const gchar *version = fu_device_get_version(item->device); guint64 raw = fu_device_get_version_raw(item->device); g_info("forcing runtime version %s to new device", version); fu_device_set_version_format(device, fu_device_get_version_format(item->device)); fu_device_set_version(device, version); /* nocheck:set-version */ fu_device_set_version_raw(device, raw); } /* allow another plugin to handle the write too */ fu_device_incorporate_flag(device, item->device, FWUPD_DEVICE_FLAG_ANOTHER_WRITE_REQUIRED); /* seems like a sane assumption if we've tagged the runtime mode as signed */ fu_device_incorporate_flag(device, item->device, FWUPD_DEVICE_FLAG_SIGNED_PAYLOAD); fu_device_incorporate_flag(device, item->device, FWUPD_DEVICE_FLAG_UNSIGNED_PAYLOAD); /* never unset */ if (fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_EMULATION_TAG)) fu_device_add_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG); /* device won't come back in right mode */ fu_device_incorporate_flag(device, item->device, FWUPD_DEVICE_FLAG_WILL_DISAPPEAR); /* assign the new device */ fu_device_list_item_set_device_old(item, item->device); fu_device_list_item_set_device(item, device); fu_device_list_emit_device_changed(self, device); /* debug */ str = fwupd_codec_to_string(FWUPD_CODEC(self)); g_debug("%s", str); /* we were waiting for this... */ fu_device_list_clear_wait_for_replug(self, item); } /** * fu_device_list_add: * @self: a device list * @device: a device * * Adds a specific device to the device list if not already present. * * If the @device (or a compatible @device) has been previously removed within * the remove-timeout then only the ::changed signal will be emitted on calling * this function. Otherwise the ::added signal will be emitted straight away. * * Compatible devices are defined as #FuDevice objects that share at least one * device GUID. If a compatible device is matched then the vendor ID and * version will be copied to the new object if they are not already set. * * Any GUIDs present on the old device and not on the new device will be * inherited and do not have to be copied over by plugins manually. * * Returns: (transfer none): a device, or %NULL if not found * * Since: 1.0.2 **/ void fu_device_list_add(FuDeviceList *self, FuDevice *device) { FuDeviceItem *item; g_return_if_fail(FU_IS_DEVICE_LIST(self)); g_return_if_fail(FU_IS_DEVICE(device)); /* make tests easier */ fu_device_convert_instance_ids(device); /* is the device waiting to be replugged? */ item = fu_device_list_find_by_id(self, fu_device_get_id(device), NULL); if (item != NULL) { /* literally the same object */ if (device == item->device) { g_info("found existing device %s", fu_device_get_id(device)); fu_device_list_clear_wait_for_replug(self, item); fu_device_list_emit_device_changed(self, device); return; } /* the old device again */ if (item->device_old != NULL && device == item->device_old) { g_info("found old device %s, swapping", fu_device_get_id(device)); fu_device_remove_private_flag(item->device, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED); _fu_device_incorporate_problem_update_in_progress(device, item->device); fu_device_incorporate(item->device, device, FU_DEVICE_INCORPORATE_FLAG_UPDATE_ERROR | FU_DEVICE_INCORPORATE_FLAG_UPDATE_STATE); g_set_object(&item->device_old, item->device); fu_device_list_item_set_device(item, device); fu_device_list_clear_wait_for_replug(self, item); fu_device_list_emit_device_changed(self, device); return; } /* adding a device with a lower priority */ if (!fu_device_has_flag(item->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) && fu_device_get_priority(item->device) > fu_device_get_priority(device) && g_strcmp0(fu_device_get_plugin(item->device), fu_device_get_plugin(device)) != 0) { g_info("ignoring %s from %s as a higher prio device from %s already exists", fu_device_get_id(device), fu_device_get_plugin(device), fu_device_get_plugin(item->device)); fu_device_remove_flag(device, FWUPD_DEVICE_FLAG_EMULATION_TAG); return; } /* same ID, different object */ g_info("found existing device %s, reusing item", fu_device_get_id(item->device)); fu_device_list_replace(self, item, device); fu_device_remove_private_flag(device, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED); return; } /* verify a device with same connection does not already exist */ item = fu_device_list_find_by_connection(self, fu_device_get_physical_id(device), fu_device_get_logical_id(device)); if (item != NULL && item->remove_source != NULL) { g_info("found physical device %s recently removed, reusing " "item from plugin %s for plugin %s", fu_device_get_id(item->device), fu_device_get_plugin(item->device), fu_device_get_plugin(device)); fu_device_list_replace(self, item, device); fu_device_remove_private_flag(device, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED); return; } /* verify a compatible device does not already exist */ item = fu_device_list_get_by_guids_removed(self, fu_device_get_guids(device)); if (item == NULL) { g_autoptr(GPtrArray) guids = fu_device_get_counterpart_guids(device); item = fu_device_list_get_by_guids_removed(self, guids); } if (item != NULL) { if (fu_device_has_private_flag(device, FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID)) { g_info("found compatible device %s recently removed, reusing " "item from plugin %s for plugin %s", fu_device_get_id(item->device), fu_device_get_plugin(item->device), fu_device_get_plugin(device)); fu_device_list_replace(self, item, device); fu_device_remove_private_flag(device, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED); return; } g_info("not adding matching %s for device add, use " "FU_DEVICE_PRIVATE_FLAG_REPLUG_MATCH_GUID if required", fu_device_get_id(item->device)); } /* this can never be true */ fu_device_remove_private_flag(device, FU_DEVICE_PRIVATE_FLAG_UNCONNECTED); /* add helper */ item = g_new0(FuDeviceItem, 1); item->self = self; /* no ref */ fu_device_list_item_set_device(item, device); g_rw_lock_writer_lock(&self->devices_mutex); g_ptr_array_add(self->devices, item); g_rw_lock_writer_unlock(&self->devices_mutex); fu_device_list_emit_device_added(self, device); } /** * fu_device_list_get_by_guid: * @self: a device list * @guid: a device GUID * @error: (nullable): optional return location for an error * * Finds a specific device that has the matching GUID. * * Returns: (transfer full): a device, or %NULL if not found * * Since: 1.0.2 **/ FuDevice * fu_device_list_get_by_guid(FuDeviceList *self, const gchar *guid, GError **error) { FuDeviceItem *item; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), NULL); g_return_val_if_fail(guid != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); item = fu_device_list_find_by_guid(self, guid); if (item != NULL) return g_object_ref(item->device); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "GUID %s was not found", guid); return NULL; } static GPtrArray * fu_device_list_get_wait_for_replug(FuDeviceList *self) { GPtrArray *devices = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref); for (guint i = 0; i < self->devices->len; i++) { FuDeviceItem *item_tmp = g_ptr_array_index(self->devices, i); if (fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG) && !fu_device_has_flag(item_tmp->device, FWUPD_DEVICE_FLAG_EMULATED)) g_ptr_array_add(devices, g_object_ref(item_tmp->device)); } return devices; } /** * fu_device_list_wait_for_replug: * @self: a device list * @error: (nullable): optional return location for an error * * Waits for all the devices with %FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG to replug. * * If the device does not exist this function returns without an error. * * Returns: %TRUE for success * * Since: 1.1.2 **/ gboolean fu_device_list_wait_for_replug(FuDeviceList *self, GError **error) { guint remove_delay = 0; g_autoptr(GTimer) timer = g_timer_new(); g_autoptr(GPtrArray) devices_wfr1 = NULL; g_autoptr(GPtrArray) devices_wfr2 = NULL; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), FALSE); g_return_val_if_fail(error == NULL || *error == NULL, FALSE); /* not required, or possibly literally just happened */ devices_wfr1 = fu_device_list_get_wait_for_replug(self); if (devices_wfr1->len == 0) { g_info("no replug or re-enumerate required"); return TRUE; } /* use the maximum of all the devices */ for (guint i = 0; i < devices_wfr1->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices_wfr1, i); if (fu_device_get_remove_delay(device_tmp) > remove_delay) remove_delay = fu_device_get_remove_delay(device_tmp); } /* plugin did not specify */ if (remove_delay == 0) { remove_delay = FU_DEVICE_REMOVE_DELAY_RE_ENUMERATE; g_warning("plugin did not specify a remove delay, " "so guessing we should wait %ums for replug", remove_delay); } else { g_info("waiting %ums for replug", remove_delay); } /* time to unplug and then re-plug */ do { g_autoptr(GPtrArray) devices_wfr_tmp = NULL; g_usleep(1000); while (g_main_context_iteration(fu_context_get_main_context(self->ctx), FALSE)) { /* nothing needs to be done here */ }; devices_wfr_tmp = fu_device_list_get_wait_for_replug(self); if (devices_wfr_tmp->len == 0) break; } while (g_timer_elapsed(timer, NULL) * 1000.f < remove_delay); /* check that no other devices are still waiting for replug */ devices_wfr2 = fu_device_list_get_wait_for_replug(self); if (devices_wfr2->len > 0) { g_autoptr(GPtrArray) device_ids = g_ptr_array_new_with_free_func(g_free); g_autofree gchar *device_ids_str = NULL; g_autofree gchar *str = NULL; /* dump to console */ str = fwupd_codec_to_string(FWUPD_CODEC(self)); g_debug("%s", str); /* unset and build error string */ for (guint i = 0; i < devices_wfr2->len; i++) { FuDevice *device_tmp = g_ptr_array_index(devices_wfr2, i); fu_device_remove_flag(device_tmp, FWUPD_DEVICE_FLAG_WAIT_FOR_REPLUG); g_ptr_array_add(device_ids, g_strdup(fu_device_get_id(device_tmp))); } device_ids_str = fu_strjoin(",", device_ids); g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "device %s did not come back", device_ids_str); return FALSE; } /* the loop was quit without the timer */ g_info("waited for replug"); return TRUE; } /** * fu_device_list_get_by_id: * @self: a device list * @device_id: a device ID, typically a SHA1 hash * @error: (nullable): optional return location for an error * * Finds a specific device using the ID string. This function also supports * using abbreviated hashes. * * Returns: (transfer full): a device, or %NULL if not found * * Since: 1.0.2 **/ FuDevice * fu_device_list_get_by_id(FuDeviceList *self, const gchar *device_id, GError **error) { FuDeviceItem *item; g_return_val_if_fail(FU_IS_DEVICE_LIST(self), NULL); g_return_val_if_fail(device_id != NULL, NULL); g_return_val_if_fail(error == NULL || *error == NULL, NULL); /* multiple things matched */ item = fu_device_list_find_by_id(self, device_id, error); if (item == NULL) return NULL; /* something found */ return g_object_ref(item->device); } static void fu_device_list_item_free(FuDeviceItem *item) { if (item->remove_source != NULL) g_source_destroy(item->remove_source); if (item->device_old != NULL) g_object_unref(item->device_old); fu_device_list_item_set_device(item, NULL); g_free(item); } static void fu_device_list_codec_iface_init(FwupdCodecInterface *iface) { iface->add_string = fu_device_list_add_string; iface->add_json = fu_device_list_add_json; } static void fu_device_list_dispose(GObject *obj) { FuDeviceList *self = FU_DEVICE_LIST(obj); if (self->devices != NULL) g_ptr_array_set_size(self->devices, 0); G_OBJECT_CLASS(fu_device_list_parent_class)->dispose(obj); } static void fu_device_list_class_init(FuDeviceListClass *klass) { GObjectClass *object_class = G_OBJECT_CLASS(klass); object_class->dispose = fu_device_list_dispose; object_class->finalize = fu_device_list_finalize; /* used as device flags */ quarks[QUARK_UNCONNECTED] = g_quark_from_static_string(FU_DEVICE_PRIVATE_FLAG_UNCONNECTED); /** * FuDeviceList::added: * @self: the #FuDeviceList instance that emitted the signal * @device: the #FuDevice * * The ::added signal is emitted when a device has been added to the list. **/ signals[SIGNAL_ADDED] = g_signal_new("added", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuDeviceList::removed: * @self: the #FuDeviceList instance that emitted the signal * @device: the #FuDevice * * The ::removed signal is emitted when a device has been removed from the list. **/ signals[SIGNAL_REMOVED] = g_signal_new("removed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); /** * FuDeviceList::changed: * @self: the #FuDeviceList instance that emitted the signal * @device: the #FuDevice * * The ::changed signal is emitted when a device has changed. **/ signals[SIGNAL_CHANGED] = g_signal_new("changed", G_TYPE_FROM_CLASS(object_class), G_SIGNAL_RUN_LAST, 0, NULL, NULL, g_cclosure_marshal_VOID__OBJECT, G_TYPE_NONE, 1, FU_TYPE_DEVICE); } static void fu_device_list_init(FuDeviceList *self) { self->devices = g_ptr_array_new_with_free_func((GDestroyNotify)fu_device_list_item_free); g_rw_lock_init(&self->devices_mutex); } static void fu_device_list_finalize(GObject *obj) { FuDeviceList *self = FU_DEVICE_LIST(obj); g_clear_object(&self->ctx); g_rw_lock_clear(&self->devices_mutex); g_ptr_array_unref(self->devices); G_OBJECT_CLASS(fu_device_list_parent_class)->finalize(obj); } /** * fu_device_list_new: * @ctx: a #FuContext * * Creates a new device list. * * Returns: (transfer full): a device list **/ FuDeviceList * fu_device_list_new(FuContext *ctx) { FuDeviceList *self; g_return_val_if_fail(FU_IS_CONTEXT(ctx), NULL); self = g_object_new(FU_TYPE_DEVICE_LIST, NULL); self->ctx = g_object_ref(ctx); return FU_DEVICE_LIST(self); }