summary refs log tree commit diff
path: root/src/core/nm-rfkill-manager.c
diff options
context:
space:
mode:
authorMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
committerMichael Biebl <biebl@debian.org>2021-02-11 18:11:46 +0100
commit80ec1decc49c72efec2a8b87c06245c92c0ab807 (patch)
treee3b229aa94e8dcf0590f2317664176e7b8f7607b /src/core/nm-rfkill-manager.c
parent65f86e8f56267192d42f2b629fc6b0c99fb9cd0c (diff)
New upstream version 1.29.90 upstream/1.29.90
Diffstat (limited to 'src/core/nm-rfkill-manager.c')
-rw-r--r--src/core/nm-rfkill-manager.c431
1 files changed, 431 insertions, 0 deletions
diff --git a/src/core/nm-rfkill-manager.c b/src/core/nm-rfkill-manager.c
new file mode 100644
index 00000000..f032851e
--- /dev/null
+++ b/src/core/nm-rfkill-manager.c
@@ -0,0 +1,431 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ * Copyright (C) 2009 - 2013 Red Hat, Inc.
+ */
+
+#include "src/core/nm-default-daemon.h"
+
+#include "nm-rfkill-manager.h"
+
+#include <libudev.h>
+
+#include "nm-udev-aux/nm-udev-utils.h"
+
+/*****************************************************************************/
+
+enum {
+    RFKILL_CHANGED,
+    LAST_SIGNAL,
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+typedef struct {
+    NMUdevClient *udev_client;
+
+    /* Authoritative rfkill state (RFKILL_* enum) */
+    RfKillState rfkill_states[RFKILL_TYPE_MAX];
+    GSList *    killswitches;
+} NMRfkillManagerPrivate;
+
+struct _NMRfkillManager {
+    GObject                parent;
+    NMRfkillManagerPrivate _priv;
+};
+
+struct _NMRfkillManagerClass {
+    GObjectClass parent;
+};
+
+G_DEFINE_TYPE(NMRfkillManager, nm_rfkill_manager, G_TYPE_OBJECT)
+
+#define NM_RFKILL_MANAGER_GET_PRIVATE(self) \
+    _NM_GET_PRIVATE(self, NMRfkillManager, NM_IS_RFKILL_MANAGER)
+
+/*****************************************************************************/
+
+typedef struct {
+    char *     name;
+    guint64    seqnum;
+    char *     path;
+    char *     driver;
+    RfKillType rtype;
+    int        state;
+    gboolean   platform;
+} Killswitch;
+
+RfKillState
+nm_rfkill_manager_get_rfkill_state(NMRfkillManager *self, RfKillType rtype)
+{
+    g_return_val_if_fail(self != NULL, RFKILL_UNBLOCKED);
+    g_return_val_if_fail(rtype < RFKILL_TYPE_MAX, RFKILL_UNBLOCKED);
+
+    return NM_RFKILL_MANAGER_GET_PRIVATE(self)->rfkill_states[rtype];
+}
+
+static const char *
+rfkill_type_to_desc(RfKillType rtype)
+{
+    if (rtype == 0)
+        return "Wi-Fi";
+    else if (rtype == 1)
+        return "WWAN";
+    else if (rtype == 2)
+        return "WiMAX";
+    return "unknown";
+}
+
+static const char *
+rfkill_state_to_desc(RfKillState rstate)
+{
+    if (rstate == 0)
+        return "unblocked";
+    else if (rstate == 1)
+        return "soft-blocked";
+    else if (rstate == 2)
+        return "hard-blocked";
+    return "unknown";
+}
+
+static Killswitch *
+killswitch_new(struct udev_device *device, RfKillType rtype)
+{
+    Killswitch *        ks;
+    struct udev_device *parent = NULL, *grandparent = NULL;
+    const char *        driver, *subsys, *parent_subsys = NULL;
+
+    ks         = g_malloc0(sizeof(Killswitch));
+    ks->name   = g_strdup(udev_device_get_sysname(device));
+    ks->seqnum = udev_device_get_seqnum(device);
+    ks->path   = g_strdup(udev_device_get_syspath(device));
+    ks->rtype  = rtype;
+
+    driver = udev_device_get_property_value(device, "DRIVER");
+    subsys = udev_device_get_subsystem(device);
+
+    /* Check parent for various attributes */
+    parent = udev_device_get_parent(device);
+    if (parent) {
+        parent_subsys = udev_device_get_subsystem(parent);
+        if (!driver)
+            driver = udev_device_get_property_value(parent, "DRIVER");
+        if (!driver) {
+            /* Sigh; try the grandparent */
+            grandparent = udev_device_get_parent(parent);
+            if (grandparent)
+                driver = udev_device_get_property_value(grandparent, "DRIVER");
+        }
+    }
+
+    if (!driver)
+        driver = "(unknown)";
+    ks->driver = g_strdup(driver);
+
+    if (g_strcmp0(subsys, "platform") == 0 || g_strcmp0(parent_subsys, "platform") == 0
+        || g_strcmp0(subsys, "acpi") == 0 || g_strcmp0(parent_subsys, "acpi") == 0)
+        ks->platform = TRUE;
+
+    return ks;
+}
+
+static void
+killswitch_destroy(Killswitch *ks)
+{
+    g_return_if_fail(ks != NULL);
+
+    g_free(ks->name);
+    g_free(ks->path);
+    g_free(ks->driver);
+    memset(ks, 0, sizeof(Killswitch));
+    g_free(ks);
+}
+
+static RfKillState
+sysfs_state_to_nm_state(int sysfs_state)
+{
+    switch (sysfs_state) {
+    case 0:
+        return RFKILL_SOFT_BLOCKED;
+    case 1:
+        return RFKILL_UNBLOCKED;
+    case 2:
+        return RFKILL_HARD_BLOCKED;
+    default:
+        nm_log_warn(LOGD_RFKILL, "unhandled rfkill state %d", sysfs_state);
+        break;
+    }
+    return RFKILL_UNBLOCKED;
+}
+
+static void
+recheck_killswitches(NMRfkillManager *self)
+{
+    NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self);
+    GSList *                iter;
+    RfKillState             poll_states[RFKILL_TYPE_MAX];
+    RfKillState             platform_states[RFKILL_TYPE_MAX];
+    gboolean                platform_checked[RFKILL_TYPE_MAX];
+    int                     i;
+
+    /* Default state is unblocked */
+    for (i = 0; i < RFKILL_TYPE_MAX; i++) {
+        poll_states[i]      = RFKILL_UNBLOCKED;
+        platform_states[i]  = RFKILL_UNBLOCKED;
+        platform_checked[i] = FALSE;
+    }
+
+    /* Poll the states of all killswitches */
+    for (iter = priv->killswitches; iter; iter = g_slist_next(iter)) {
+        Killswitch *        ks = iter->data;
+        struct udev_device *device;
+        RfKillState         dev_state;
+        int                 sysfs_state;
+
+        device = udev_device_new_from_subsystem_sysname(nm_udev_client_get_udev(priv->udev_client),
+                                                        "rfkill",
+                                                        ks->name);
+        if (!device)
+            continue;
+        sysfs_state =
+            _nm_utils_ascii_str_to_int64(udev_device_get_property_value(device, "RFKILL_STATE"),
+                                         10,
+                                         G_MININT,
+                                         G_MAXINT,
+                                         -1);
+        dev_state = sysfs_state_to_nm_state(sysfs_state);
+
+        nm_log_dbg(LOGD_RFKILL,
+                   "%s rfkill%s switch %s state now %d/%u",
+                   rfkill_type_to_desc(ks->rtype),
+                   ks->platform ? " platform" : "",
+                   ks->name,
+                   sysfs_state,
+                   dev_state);
+
+        if (ks->platform == FALSE) {
+            if (dev_state > poll_states[ks->rtype])
+                poll_states[ks->rtype] = dev_state;
+        } else {
+            platform_checked[ks->rtype] = TRUE;
+            if (dev_state > platform_states[ks->rtype])
+                platform_states[ks->rtype] = dev_state;
+        }
+        udev_device_unref(device);
+    }
+
+    /* Log and emit change signal for final rfkill states */
+    for (i = 0; i < RFKILL_TYPE_MAX; i++) {
+        if (platform_checked[i] == TRUE) {
+            /* blocked platform switch state overrides device state, otherwise
+             * let the device state stand. (bgo #655773)
+             */
+            if (platform_states[i] != RFKILL_UNBLOCKED)
+                poll_states[i] = platform_states[i];
+        }
+
+        if (poll_states[i] != priv->rfkill_states[i]) {
+            nm_log_dbg(LOGD_RFKILL,
+                       "%s rfkill state now '%s'",
+                       rfkill_type_to_desc(i),
+                       rfkill_state_to_desc(poll_states[i]));
+
+            priv->rfkill_states[i] = poll_states[i];
+            g_signal_emit(self, signals[RFKILL_CHANGED], 0, i, priv->rfkill_states[i]);
+        }
+    }
+}
+
+static Killswitch *
+killswitch_find_by_name(NMRfkillManager *self, const char *name)
+{
+    NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self);
+    GSList *                iter;
+
+    g_return_val_if_fail(name != NULL, NULL);
+
+    for (iter = priv->killswitches; iter; iter = g_slist_next(iter)) {
+        Killswitch *candidate = iter->data;
+
+        if (!strcmp(name, candidate->name))
+            return candidate;
+    }
+    return NULL;
+}
+
+static RfKillType
+rfkill_type_to_enum(const char *str)
+{
+    g_return_val_if_fail(str != NULL, RFKILL_TYPE_UNKNOWN);
+
+    if (!strcmp(str, "wlan"))
+        return RFKILL_TYPE_WLAN;
+    else if (!strcmp(str, "wwan"))
+        return RFKILL_TYPE_WWAN;
+
+    return RFKILL_TYPE_UNKNOWN;
+}
+
+static void
+add_one_killswitch(NMRfkillManager *self, struct udev_device *device)
+{
+    NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self);
+    const char *            str_type;
+    RfKillType              rtype;
+    Killswitch *            ks;
+
+    str_type = udev_device_get_property_value(device, "RFKILL_TYPE");
+    rtype    = rfkill_type_to_enum(str_type);
+    if (rtype == RFKILL_TYPE_UNKNOWN)
+        return;
+
+    ks                 = killswitch_new(device, rtype);
+    priv->killswitches = g_slist_prepend(priv->killswitches, ks);
+
+    nm_log_info(LOGD_RFKILL,
+                "%s: found %s radio killswitch (at %s) (%sdriver %s)",
+                ks->name,
+                rfkill_type_to_desc(rtype),
+                ks->path,
+                ks->platform ? "platform " : "",
+                ks->driver ?: "<unknown>");
+}
+
+static void
+rfkill_add(NMRfkillManager *self, struct udev_device *device)
+{
+    const char *name;
+
+    g_return_if_fail(device != NULL);
+    name = udev_device_get_sysname(device);
+    g_return_if_fail(name != NULL);
+
+    if (!killswitch_find_by_name(self, name))
+        add_one_killswitch(self, device);
+}
+
+static void
+rfkill_remove(NMRfkillManager *self, struct udev_device *device)
+{
+    NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self);
+    GSList *                iter;
+    const char *            name;
+
+    g_return_if_fail(device != NULL);
+    name = udev_device_get_sysname(device);
+    g_return_if_fail(name != NULL);
+
+    for (iter = priv->killswitches; iter; iter = g_slist_next(iter)) {
+        Killswitch *ks = iter->data;
+
+        if (!strcmp(ks->name, name)) {
+            nm_log_info(LOGD_RFKILL, "radio killswitch %s disappeared", ks->path);
+            priv->killswitches = g_slist_remove(priv->killswitches, ks);
+            killswitch_destroy(ks);
+            break;
+        }
+    }
+}
+
+static void
+handle_uevent(NMUdevClient *client, struct udev_device *device, gpointer user_data)
+{
+    NMRfkillManager *self = NM_RFKILL_MANAGER(user_data);
+    const char *     subsys;
+    const char *     action;
+
+    action = udev_device_get_action(device);
+
+    g_return_if_fail(action != NULL);
+
+    /* A bit paranoid */
+    subsys = udev_device_get_subsystem(device);
+    g_return_if_fail(!g_strcmp0(subsys, "rfkill"));
+
+    nm_log_dbg(LOGD_PLATFORM,
+               "udev rfkill event: action '%s' device '%s'",
+               action,
+               udev_device_get_sysname(device));
+
+    if (!strcmp(action, "add"))
+        rfkill_add(self, device);
+    else if (!strcmp(action, "remove"))
+        rfkill_remove(self, device);
+
+    recheck_killswitches(self);
+}
+
+/*****************************************************************************/
+
+static void
+nm_rfkill_manager_init(NMRfkillManager *self)
+{
+    NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self);
+    struct udev_enumerate * enumerate;
+    struct udev_list_entry *iter;
+    guint                   i;
+
+    for (i = 0; i < RFKILL_TYPE_MAX; i++)
+        priv->rfkill_states[i] = RFKILL_UNBLOCKED;
+
+    priv->udev_client = nm_udev_client_new(NM_MAKE_STRV("rfkill"), handle_uevent, self);
+
+    enumerate = nm_udev_client_enumerate_new(priv->udev_client);
+    udev_enumerate_scan_devices(enumerate);
+    iter = udev_enumerate_get_list_entry(enumerate);
+    for (; iter; iter = udev_list_entry_get_next(iter)) {
+        struct udev_device *udevice;
+
+        udevice = udev_device_new_from_syspath(udev_enumerate_get_udev(enumerate),
+                                               udev_list_entry_get_name(iter));
+        if (!udevice)
+            continue;
+
+        add_one_killswitch(self, udevice);
+        udev_device_unref(udevice);
+    }
+    udev_enumerate_unref(enumerate);
+
+    recheck_killswitches(self);
+}
+
+NMRfkillManager *
+nm_rfkill_manager_new(void)
+{
+    return g_object_new(NM_TYPE_RFKILL_MANAGER, NULL);
+}
+
+static void
+dispose(GObject *object)
+{
+    NMRfkillManager *       self = NM_RFKILL_MANAGER(object);
+    NMRfkillManagerPrivate *priv = NM_RFKILL_MANAGER_GET_PRIVATE(self);
+
+    if (priv->killswitches) {
+        g_slist_free_full(priv->killswitches, (GDestroyNotify) killswitch_destroy);
+        priv->killswitches = NULL;
+    }
+
+    priv->udev_client = nm_udev_client_destroy(priv->udev_client);
+
+    G_OBJECT_CLASS(nm_rfkill_manager_parent_class)->dispose(object);
+}
+
+static void
+nm_rfkill_manager_class_init(NMRfkillManagerClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS(klass);
+
+    object_class->dispose = dispose;
+
+    signals[RFKILL_CHANGED] = g_signal_new(NM_RFKILL_MANAGER_SIGNAL_RFKILL_CHANGED,
+                                           G_OBJECT_CLASS_TYPE(object_class),
+                                           G_SIGNAL_RUN_FIRST,
+                                           0,
+                                           NULL,
+                                           NULL,
+                                           NULL,
+                                           G_TYPE_NONE,
+                                           2,
+                                           G_TYPE_UINT,
+                                           G_TYPE_UINT);
+}