blob: 56eb9b02725f1ebf7ecf8ec98b570eb62570f575 [file] [log] [blame]
/*
* Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
*
* SPDX-License-Identifier: LGPL-2.1+
*/
#define G_LOG_DOMAIN "FuBackend"
#include "config.h"
#include <gudev/gudev.h>
#include "fu-udev-device.h"
#include "fu-udev-backend.h"
struct _FuUdevBackend {
FuBackend parent_instance;
GUdevClient *gudev_client;
GHashTable *changed_idle_ids; /* sysfs:FuUdevBackendHelper */
GPtrArray *subsystems;
};
G_DEFINE_TYPE (FuUdevBackend, fu_udev_backend, FU_TYPE_BACKEND)
static void
fu_udev_backend_device_add (FuUdevBackend *self, GUdevDevice *udev_device)
{
g_autoptr(FuUdevDevice) device = fu_udev_device_new (udev_device);
/* success */
fu_backend_device_added (FU_BACKEND (self), FU_DEVICE (device));
}
static void
fu_udev_backend_device_remove (FuUdevBackend *self, GUdevDevice *udev_device)
{
FuDevice *device_tmp;
/* find the device we enumerated */
device_tmp = fu_backend_lookup_by_id (FU_BACKEND (self),
g_udev_device_get_sysfs_path (udev_device));
if (device_tmp != NULL) {
if (g_getenv ("FWUPD_PROBE_VERBOSE") != NULL) {
g_debug ("UDEV %s removed",
g_udev_device_get_sysfs_path (udev_device));
}
fu_backend_device_removed (FU_BACKEND (self), device_tmp);
}
}
typedef struct {
FuUdevBackend *self;
FuDevice *device;
guint idle_id;
} FuUdevBackendHelper;
static void
fu_udev_backend_changed_helper_free (FuUdevBackendHelper *helper)
{
if (helper->idle_id != 0)
g_source_remove (helper->idle_id);
g_object_unref (helper->self);
g_object_unref (helper->device);
g_free (helper);
}
static FuUdevBackendHelper *
fu_udev_backend_changed_helper_new (FuUdevBackend *self, FuDevice *device)
{
FuUdevBackendHelper *helper = g_new0 (FuUdevBackendHelper, 1);
helper->self = g_object_ref (self);
helper->device = g_object_ref (device);
return helper;
}
static gboolean
fu_udev_backend_device_changed_cb (gpointer user_data)
{
FuUdevBackendHelper *helper = (FuUdevBackendHelper *) user_data;
fu_backend_device_changed (FU_BACKEND (helper->self), helper->device);
helper->idle_id = 0;
g_hash_table_remove (helper->self->changed_idle_ids,
fu_udev_device_get_sysfs_path (FU_UDEV_DEVICE (helper->device)));
return FALSE;
}
static void
fu_udev_backend_device_changed (FuUdevBackend *self, GUdevDevice *udev_device)
{
const gchar *sysfs_path = g_udev_device_get_sysfs_path (udev_device);
FuUdevBackendHelper *helper;
FuDevice *device_tmp;
/* not a device we enumerated */
device_tmp = fu_backend_lookup_by_id (FU_BACKEND (self), sysfs_path);
if (device_tmp == NULL)
return;
/* run all plugins, with per-device rate limiting */
if (g_hash_table_remove (self->changed_idle_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_udev_backend_changed_helper_new (self, device_tmp);
helper->idle_id = g_timeout_add (500, fu_udev_backend_device_changed_cb, helper);
g_hash_table_insert (self->changed_idle_ids, g_strdup (sysfs_path), helper);
}
static void
fu_udev_backend_uevent_cb (GUdevClient *gudev_client,
const gchar *action,
GUdevDevice *udev_device,
FuUdevBackend *self)
{
if (g_strcmp0 (action, "add") == 0) {
fu_udev_backend_device_add (self, udev_device);
return;
}
if (g_strcmp0 (action, "remove") == 0) {
fu_udev_backend_device_remove (self, udev_device);
return;
}
if (g_strcmp0 (action, "change") == 0) {
fu_udev_backend_device_changed (self, udev_device);
return;
}
}
static gboolean
fu_udev_backend_coldplug (FuBackend *backend, GError **error)
{
FuUdevBackend *self = FU_UDEV_BACKEND (backend);
/* udev watches can only be set up in _init() so set up client now */
if (self->subsystems->len > 0) {
g_auto(GStrv) subsystems = g_new0 (gchar *, self->subsystems->len + 1);
for (guint i = 0; i < self->subsystems->len; i++) {
const gchar *subsystem = g_ptr_array_index (self->subsystems, i);
subsystems[i] = g_strdup (subsystem);
}
self->gudev_client = g_udev_client_new ((const gchar * const *) subsystems);
g_signal_connect (self->gudev_client, "uevent",
G_CALLBACK (fu_udev_backend_uevent_cb), self);
}
/* get all devices of class */
for (guint i = 0; i < self->subsystems->len; i++) {
const gchar *subsystem = g_ptr_array_index (self->subsystems, i);
GList *devices = g_udev_client_query_by_subsystem (self->gudev_client,
subsystem);
if (g_getenv ("FWUPD_PROBE_VERBOSE") != NULL) {
g_debug ("%u devices with subsystem %s",
g_list_length (devices), subsystem);
}
for (GList *l = devices; l != NULL; l = l->next) {
GUdevDevice *udev_device = l->data;
fu_udev_backend_device_add (self, udev_device);
}
g_list_foreach (devices, (GFunc) g_object_unref, NULL);
g_list_free (devices);
}
return TRUE;
}
static void
fu_udev_backend_finalize (GObject *object)
{
FuUdevBackend *self = FU_UDEV_BACKEND (object);
if (self->gudev_client != NULL)
g_object_unref (self->gudev_client);
if (self->subsystems != NULL)
g_ptr_array_unref (self->subsystems);
g_hash_table_unref (self->changed_idle_ids);
G_OBJECT_CLASS (fu_udev_backend_parent_class)->finalize (object);
}
static void
fu_udev_backend_init (FuUdevBackend *self)
{
self->changed_idle_ids = g_hash_table_new_full (g_str_hash, g_str_equal,
g_free, (GDestroyNotify) fu_udev_backend_changed_helper_free);
}
static void
fu_udev_backend_class_init (FuUdevBackendClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
FuBackendClass *klass_backend = FU_BACKEND_CLASS (klass);
object_class->finalize = fu_udev_backend_finalize;
klass_backend->coldplug = fu_udev_backend_coldplug;
}
FuBackend *
fu_udev_backend_new (GPtrArray *subsystems)
{
FuUdevBackend *self;
self = FU_UDEV_BACKEND (g_object_new (FU_TYPE_UDEV_BACKEND, "name", "udev", NULL));
if (subsystems != NULL)
self->subsystems = g_ptr_array_ref (subsystems);
return FU_BACKEND (self);
}