/* NetworkManager -- Network link manager * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2009 - 2013 Red Hat, Inc. */ #include "nm-default.h" #include "nm-rfkill-manager.h" #include #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 ?: ""); } 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 ((const char *[]) { "rfkill", NULL }, 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 NM_RFKILL_MANAGER (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_unref (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); }