diff --git a/src/nm-manager.c b/src/nm-manager.c index 678cdc2891..568ff2ae2d 100644 --- a/src/nm-manager.c +++ b/src/nm-manager.c @@ -22,6 +22,9 @@ #include #include +#include +#include +#include #include #include #include @@ -1007,22 +1010,25 @@ write_value_to_state_file (const char *filename, } static gboolean -radio_enabled_for_rstate (RadioState *rstate, gboolean check_daemon_enabled) +radio_enabled_for_rstate (RadioState *rstate, gboolean check_changeable) { gboolean enabled; - enabled = rstate->user_enabled && rstate->sw_enabled && rstate->hw_enabled; - if (rstate->daemon_enabled_func && check_daemon_enabled) - enabled &= rstate->daemon_enabled; + enabled = rstate->user_enabled && rstate->hw_enabled; + if (check_changeable) { + enabled &= rstate->sw_enabled; + if (rstate->daemon_enabled_func) + enabled &= rstate->daemon_enabled; + } return enabled; } static gboolean -radio_enabled_for_type (NMManager *self, RfKillType rtype, gboolean check_daemon_enabled) +radio_enabled_for_type (NMManager *self, RfKillType rtype, gboolean check_changeable) { NMManagerPrivate *priv = NM_MANAGER_GET_PRIVATE (self); - return radio_enabled_for_rstate (&priv->radio_states[rtype], check_daemon_enabled); + return radio_enabled_for_rstate (&priv->radio_states[rtype], check_changeable); } static void @@ -3250,6 +3256,58 @@ dispose (GObject *object) G_OBJECT_CLASS (nm_manager_parent_class)->dispose (object); } +#define KERN_RFKILL_OP_CHANGE_ALL 3 +#define KERN_RFKILL_TYPE_WLAN 1 +struct rfkill_event { + __u32 idx; + __u8 type; + __u8 op; + __u8 soft, hard; +} __attribute__((packed)); + +static void +rfkill_change_wifi (const char *desc, gboolean enabled) +{ + int fd; + struct rfkill_event event; + ssize_t len; + + errno = 0; + fd = open ("/dev/rfkill", O_RDWR); + if (fd < 0) { + if (errno == EACCES) + nm_log_warn (LOGD_RFKILL, "(%s): failed to open killswitch device " + "for WiFi radio control", desc); + return; + } + + if (fcntl (fd, F_SETFL, O_NONBLOCK) < 0) { + nm_log_warn (LOGD_RFKILL, "(%s): failed to set killswitch device for " + "non-blocking operation", desc); + close (fd); + return; + } + + memset (&event, 0, sizeof (event)); + event.op = KERN_RFKILL_OP_CHANGE_ALL; + event.type = KERN_RFKILL_TYPE_WLAN; + event.soft = enabled ? 0 : 1; + + len = write (fd, &event, sizeof (event)); + if (len < 0) { + nm_log_warn (LOGD_RFKILL, "(%s): failed to change WiFi killswitch state: (%d) %s", + desc, errno, g_strerror (errno)); + } else if (len == sizeof (event)) { + nm_log_info (LOGD_RFKILL, "%s hardware radio set %s", + desc, enabled ? "enabled" : "disabled"); + } else { + /* Failed to write full structure */ + nm_log_warn (LOGD_RFKILL, "(%s): failed to change WiFi killswitch state", desc); + } + + close (fd); +} + static void manager_radio_user_toggled (NMManager *self, RadioState *rstate, @@ -3291,8 +3349,13 @@ manager_radio_user_toggled (NMManager *self, old_enabled = radio_enabled_for_rstate (rstate, TRUE); rstate->user_enabled = enabled; new_enabled = radio_enabled_for_rstate (rstate, FALSE); - if (new_enabled != old_enabled) + if (new_enabled != old_enabled) { manager_update_radio_enabled (self, rstate, new_enabled); + + /* For WiFi only (for now) set the actual kernel rfkill state */ + if (rstate->rtype == RFKILL_TYPE_WLAN) + rfkill_change_wifi (rstate->desc, new_enabled); + } } static void diff --git a/src/nm-udev-manager.c b/src/nm-udev-manager.c index 45bdf7db26..0b0e6fb298 100644 --- a/src/nm-udev-manager.c +++ b/src/nm-udev-manager.c @@ -74,6 +74,7 @@ typedef struct { char *driver; RfKillType rtype; gint state; + gboolean platform; } Killswitch; RfKillState @@ -113,8 +114,8 @@ static Killswitch * killswitch_new (GUdevDevice *device, RfKillType rtype) { Killswitch *ks; - GUdevDevice *parent = NULL; - const char *driver; + GUdevDevice *parent = NULL, *grandparent = NULL; + const char *driver, *subsys, *parent_subsys = NULL; ks = g_malloc0 (sizeof (Killswitch)); ks->name = g_strdup (g_udev_device_get_name (device)); @@ -123,17 +124,33 @@ killswitch_new (GUdevDevice *device, RfKillType rtype) ks->rtype = rtype; driver = g_udev_device_get_property (device, "DRIVER"); - if (!driver) { - parent = g_udev_device_get_parent (device); - if (parent) - driver = g_udev_device_get_property (parent, "DRIVER"); - } - if (driver) - ks->driver = g_strdup (driver); + subsys = g_udev_device_get_subsystem (device); + /* Check parent for various attributes */ + parent = g_udev_device_get_parent (device); + if (parent) { + parent_subsys = g_udev_device_get_subsystem (parent); + if (!driver) + driver = g_udev_device_get_property (parent, "DRIVER"); + if (!driver) { + /* Sigh; try the grandparent */ + grandparent = g_udev_device_get_parent (parent); + if (grandparent) + driver = g_udev_device_get_property (parent, "DRIVER"); + } + } + + if (!driver) + driver = "(unknown)"; + ks->driver = g_strdup (driver); + + if (g_strcmp0 (subsys, "platform") == 0 || g_strcmp0 (parent_subsys, "platform") == 0) + ks->platform = TRUE; + + if (grandparent) + g_object_unref (grandparent); if (parent) g_object_unref (parent); - return ks; } @@ -178,28 +195,77 @@ recheck_killswitches (NMUdevManager *self) NMUdevManagerPrivate *priv = NM_UDEV_MANAGER_GET_PRIVATE (self); GSList *iter; RfKillState poll_states[RFKILL_TYPE_MAX]; + gboolean platform_checked[RFKILL_TYPE_MAX]; int i; /* Default state is unblocked */ - for (i = 0; i < RFKILL_TYPE_MAX; i++) + for (i = 0; i < RFKILL_TYPE_MAX; i++) { poll_states[i] = RFKILL_UNBLOCKED; + platform_checked[i] = FALSE; + } + /* Perform two passes here; the first pass is for non-platform switches, + * which typically if hardkilled cannot be changed except by a physical + * hardware switch. The second pass checks platform killswitches, which + * take precedence over device killswitches, because typically platform + * killswitches control device killswitches. That is, a hardblocked device + * switch can often be unblocked by a platform switch. Thus if we have + * a hardblocked device switch and a softblocked platform switch, the + * combined state should be softblocked since the platform switch can be + * unblocked to change the device switch. + */ + + /* Device switches first */ for (iter = priv->killswitches; iter; iter = g_slist_next (iter)) { Killswitch *ks = iter->data; GUdevDevice *device; RfKillState dev_state; + int sysfs_state; - device = g_udev_client_query_by_subsystem_and_name (priv->client, "rfkill", ks->name); - if (!device) - continue; - - dev_state = sysfs_state_to_nm_state (g_udev_device_get_property_as_int (device, "RFKILL_STATE")); - if (dev_state > poll_states[ks->rtype]) - poll_states[ks->rtype] = dev_state; - - g_object_unref (device); + if (ks->platform == FALSE) { + device = g_udev_client_query_by_subsystem_and_name (priv->client, "rfkill", ks->name); + if (device) { + sysfs_state = g_udev_device_get_property_as_int (device, "RFKILL_STATE"); + dev_state = sysfs_state_to_nm_state (sysfs_state); + if (dev_state > poll_states[ks->rtype]) + poll_states[ks->rtype] = dev_state; + g_object_unref (device); + } + } } + /* Platform switches next; their state overwrites device state */ + for (iter = priv->killswitches; iter; iter = g_slist_next (iter)) { + Killswitch *ks = iter->data; + GUdevDevice *device; + RfKillState dev_state; + int sysfs_state; + + if (ks->platform == TRUE) { + device = g_udev_client_query_by_subsystem_and_name (priv->client, "rfkill", ks->name); + if (device) { + sysfs_state = g_udev_device_get_property_as_int (device, "RFKILL_STATE"); + dev_state = sysfs_state_to_nm_state (sysfs_state); + + if (platform_checked[ks->rtype] == FALSE) { + /* Overwrite device state with platform state for first + * platform switch found. + */ + poll_states[ks->rtype] = dev_state; + platform_checked[ks->rtype] = TRUE; + } else { + /* If there are multiple platform switches of the same type, + * take the "worst" state for all of that type. + */ + if (dev_state > poll_states[ks->rtype]) + poll_states[ks->rtype] = dev_state; + } + g_object_unref (device); + } + } + } + + /* Log and emit change signal for final rfkill states */ for (i = 0; i < RFKILL_TYPE_MAX; i++) { if (poll_states[i] != priv->rfkill_states[i]) { nm_log_dbg (LOGD_RFKILL, "%s rfkill state now '%s'",