Add a ->udev_device_changed plugin vfunc

This allows plugins to rescan hardware based on uevents of any device class
registered with fu_plugin_add_udev_subsystem().

Additionally, the events are rate limited to avoid causing lots of extra plugin
processing when replugging hardware.
This commit is contained in:
Richard Hughes 2019-08-26 11:09:46 +01:00
parent 751e125634
commit 5e952ce35f
4 changed files with 117 additions and 1 deletions

View File

@ -70,6 +70,7 @@ struct _FuEngine
FuPluginList *plugin_list;
GPtrArray *plugin_filter;
GPtrArray *udev_subsystems;
GHashTable *udev_changed_ids; /* sysfs:FuEngineUdevChangedHelper */
FuSmbios *smbios;
FuHwids *hwids;
FuQuirks *quirks;
@ -3938,10 +3939,69 @@ fu_engine_udev_device_remove (FuEngine *self, GUdevDevice *udev_device)
}
}
typedef struct {
FuEngine *self;
GUdevDevice *udev_device;
guint idle_id;
} FuEngineUdevChangedHelper;
static void
fu_engine_udev_changed_helper_free (FuEngineUdevChangedHelper *helper)
{
if (helper->idle_id != 0)
g_source_remove (helper->idle_id);
g_object_unref (helper->self);
g_object_unref (helper->udev_device);
g_free (helper);
}
static FuEngineUdevChangedHelper *
fu_engine_udev_changed_helper_new (FuEngine *self, GUdevDevice *udev_device)
{
FuEngineUdevChangedHelper *helper = g_new0 (FuEngineUdevChangedHelper, 1);
helper->self = g_object_ref (self);
helper->udev_device = g_object_ref (udev_device);
return helper;
}
static gboolean
fu_engine_udev_changed_cb (gpointer user_data)
{
FuEngineUdevChangedHelper *helper = (FuEngineUdevChangedHelper *) user_data;
GPtrArray *plugins = fu_plugin_list_get_all (helper->self->plugin_list);
g_autoptr(FuUdevDevice) device = fu_udev_device_new (helper->udev_device);
/* run all plugins */
for (guint j = 0; j < plugins->len; j++) {
FuPlugin *plugin_tmp = g_ptr_array_index (plugins, j);
g_autoptr(GError) error = NULL;
if (!fu_plugin_runner_udev_device_changed (plugin_tmp, device, &error)) {
if (g_error_matches (error, FWUPD_ERROR, FWUPD_ERROR_NOT_SUPPORTED)) {
g_debug ("%s ignoring: %s",
fu_plugin_get_name (plugin_tmp),
error->message);
continue;
}
g_warning ("%s failed to change udev device %s: %s",
fu_plugin_get_name (plugin_tmp),
g_udev_device_get_sysfs_path (helper->udev_device),
error->message);
}
}
/* device done, so remove ref */
helper->idle_id = 0;
g_hash_table_remove (helper->self->udev_changed_ids,
g_udev_device_get_sysfs_path (helper->udev_device));
return FALSE;
}
static void
fu_engine_udev_device_changed (FuEngine *self, GUdevDevice *udev_device)
{
const gchar *sysfs_path = g_udev_device_get_sysfs_path (udev_device);
g_autoptr(GPtrArray) devices = NULL;
FuEngineUdevChangedHelper *helper;
/* emit changed on any that match */
devices = fu_device_list_get_all (self->device_list);
@ -3950,10 +4010,20 @@ fu_engine_udev_device_changed (FuEngine *self, GUdevDevice *udev_device)
if (!FU_IS_UDEV_DEVICE (device))
continue;
if (g_strcmp0 (fu_udev_device_get_sysfs_path (FU_UDEV_DEVICE (device)),
g_udev_device_get_sysfs_path (udev_device)) == 0) {
sysfs_path) == 0) {
fu_udev_device_emit_changed (FU_UDEV_DEVICE (device));
}
}
/* run all plugins, with per-device rate limiting */
if (g_hash_table_remove (self->udev_changed_ids, sysfs_path)) {
g_debug ("re-adding rate-limited timeout for %s", sysfs_path);
} else {
g_debug ("adding rate-limited timeout for %s", sysfs_path);
}
helper = fu_engine_udev_changed_helper_new (self, udev_device);
helper->idle_id = g_timeout_add (500, fu_engine_udev_changed_cb, helper);
g_hash_table_insert (self->udev_changed_ids, g_strdup (sysfs_path), helper);
}
static void
@ -4676,6 +4746,8 @@ fu_engine_init (FuEngine *self)
self->plugin_list = fu_plugin_list_new ();
self->plugin_filter = g_ptr_array_new_with_free_func (g_free);
self->udev_subsystems = g_ptr_array_new_with_free_func (g_free);
self->udev_changed_ids = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) fu_engine_udev_changed_helper_free);
self->runtime_versions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
self->compile_versions = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
self->approved_firmware = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
@ -4733,6 +4805,7 @@ fu_engine_finalize (GObject *obj)
g_object_unref (self->device_list);
g_ptr_array_unref (self->plugin_filter);
g_ptr_array_unref (self->udev_subsystems);
g_hash_table_unref (self->udev_changed_ids);
g_hash_table_unref (self->runtime_versions);
g_hash_table_unref (self->compile_versions);
g_hash_table_unref (self->approved_firmware);

View File

@ -86,6 +86,9 @@ gboolean fu_plugin_runner_usb_device_added (FuPlugin *self,
gboolean fu_plugin_runner_udev_device_added (FuPlugin *self,
FuUdevDevice *device,
GError **error);
gboolean fu_plugin_runner_udev_device_changed (FuPlugin *self,
FuUdevDevice *device,
GError **error);
void fu_plugin_runner_device_removed (FuPlugin *self,
FuDevice *device);
void fu_plugin_runner_device_register (FuPlugin *self,

View File

@ -80,6 +80,9 @@ gboolean fu_plugin_usb_device_added (FuPlugin *plugin,
gboolean fu_plugin_udev_device_added (FuPlugin *plugin,
FuUdevDevice *device,
GError **error);
gboolean fu_plugin_udev_device_changed (FuPlugin *plugin,
FuUdevDevice *device,
GError **error);
gboolean fu_plugin_device_removed (FuPlugin *plugin,
FuDevice *device,
GError **error);

View File

@ -1305,6 +1305,43 @@ fu_plugin_runner_udev_device_added (FuPlugin *self, FuUdevDevice *device, GError
return TRUE;
}
gboolean
fu_plugin_runner_udev_device_changed (FuPlugin *self, FuUdevDevice *device, GError **error)
{
FuPluginPrivate *priv = GET_PRIVATE (self);
FuPluginUdevDeviceAddedFunc func = NULL;
g_autoptr(GError) error_local = NULL;
/* not enabled */
if (!priv->enabled)
return TRUE;
/* no object loaded */
if (priv->module == NULL)
return TRUE;
/* optional */
g_module_symbol (priv->module, "fu_plugin_udev_device_changed", (gpointer *) &func);
if (func == NULL)
return TRUE;
g_debug ("performing udev_device_changed() on %s", priv->name);
if (!func (self, device, &error_local)) {
if (error_local == NULL) {
g_critical ("unset error in plugin %s for udev_device_changed()",
priv->name);
g_set_error_literal (&error_local,
FWUPD_ERROR,
FWUPD_ERROR_INTERNAL,
"unspecified error");
}
g_propagate_prefixed_error (error, g_steal_pointer (&error_local),
"failed to change device on %s: ",
priv->name);
return FALSE;
}
return TRUE;
}
void
fu_plugin_runner_device_removed (FuPlugin *self, FuDevice *device)
{