core: better handling of rfkill for WiMAX and WiFi (bgo #629589) (rh #599002)

This commit changes rfkill state handling slightly in the following
ways:

- when checking whether a user toggle request can change radio state,
ignore states we can change in radio_enabled_for_rstate() as a result
of the toggle; this fixes WiMAX enable/disable because a softblock
can be changed by telling wimaxd to enable the radio.  As a side-effect
this also fixes handling of WiFi when altering the rfkill state as well.

- make WiFi user toggle requests change wifi killswitch state; this has
been long requested and on the TODO list for a while and it turns out
to be a lot easier to do these days.  This provides the expected
behavior when disabling wireless from user agent menus since there's
not an easy way to do this other than dropping to shell and running
rfkill.
This commit is contained in:
Dan Williams
2011-04-22 14:56:31 -05:00
parent 8cce42f2a5
commit 67e092abcb
2 changed files with 156 additions and 27 deletions

View File

@@ -22,6 +22,9 @@
#include <config.h> #include <config.h>
#include <netinet/ether.h> #include <netinet/ether.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h> #include <string.h>
#include <dbus/dbus-glib-lowlevel.h> #include <dbus/dbus-glib-lowlevel.h>
#include <dbus/dbus-glib.h> #include <dbus/dbus-glib.h>
@@ -1007,22 +1010,25 @@ write_value_to_state_file (const char *filename,
} }
static gboolean static gboolean
radio_enabled_for_rstate (RadioState *rstate, gboolean check_daemon_enabled) radio_enabled_for_rstate (RadioState *rstate, gboolean check_changeable)
{ {
gboolean enabled; gboolean enabled;
enabled = rstate->user_enabled && rstate->sw_enabled && rstate->hw_enabled; enabled = rstate->user_enabled && rstate->hw_enabled;
if (rstate->daemon_enabled_func && check_daemon_enabled) if (check_changeable) {
enabled &= rstate->sw_enabled;
if (rstate->daemon_enabled_func)
enabled &= rstate->daemon_enabled; enabled &= rstate->daemon_enabled;
}
return enabled; return enabled;
} }
static gboolean 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); 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 static void
@@ -3250,6 +3256,58 @@ dispose (GObject *object)
G_OBJECT_CLASS (nm_manager_parent_class)->dispose (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 static void
manager_radio_user_toggled (NMManager *self, manager_radio_user_toggled (NMManager *self,
RadioState *rstate, RadioState *rstate,
@@ -3291,8 +3349,13 @@ manager_radio_user_toggled (NMManager *self,
old_enabled = radio_enabled_for_rstate (rstate, TRUE); old_enabled = radio_enabled_for_rstate (rstate, TRUE);
rstate->user_enabled = enabled; rstate->user_enabled = enabled;
new_enabled = radio_enabled_for_rstate (rstate, FALSE); 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); 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 static void

View File

@@ -74,6 +74,7 @@ typedef struct {
char *driver; char *driver;
RfKillType rtype; RfKillType rtype;
gint state; gint state;
gboolean platform;
} Killswitch; } Killswitch;
RfKillState RfKillState
@@ -113,8 +114,8 @@ static Killswitch *
killswitch_new (GUdevDevice *device, RfKillType rtype) killswitch_new (GUdevDevice *device, RfKillType rtype)
{ {
Killswitch *ks; Killswitch *ks;
GUdevDevice *parent = NULL; GUdevDevice *parent = NULL, *grandparent = NULL;
const char *driver; const char *driver, *subsys, *parent_subsys = NULL;
ks = g_malloc0 (sizeof (Killswitch)); ks = g_malloc0 (sizeof (Killswitch));
ks->name = g_strdup (g_udev_device_get_name (device)); ks->name = g_strdup (g_udev_device_get_name (device));
@@ -123,17 +124,33 @@ killswitch_new (GUdevDevice *device, RfKillType rtype)
ks->rtype = rtype; ks->rtype = rtype;
driver = g_udev_device_get_property (device, "DRIVER"); driver = g_udev_device_get_property (device, "DRIVER");
if (!driver) { subsys = g_udev_device_get_subsystem (device);
/* Check parent for various attributes */
parent = g_udev_device_get_parent (device); parent = g_udev_device_get_parent (device);
if (parent) 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"); driver = g_udev_device_get_property (parent, "DRIVER");
} }
if (driver) }
if (!driver)
driver = "(unknown)";
ks->driver = g_strdup (driver); 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) if (parent)
g_object_unref (parent); g_object_unref (parent);
return ks; return ks;
} }
@@ -178,28 +195,77 @@ recheck_killswitches (NMUdevManager *self)
NMUdevManagerPrivate *priv = NM_UDEV_MANAGER_GET_PRIVATE (self); NMUdevManagerPrivate *priv = NM_UDEV_MANAGER_GET_PRIVATE (self);
GSList *iter; GSList *iter;
RfKillState poll_states[RFKILL_TYPE_MAX]; RfKillState poll_states[RFKILL_TYPE_MAX];
gboolean platform_checked[RFKILL_TYPE_MAX];
int i; int i;
/* Default state is unblocked */ /* 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; 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)) { for (iter = priv->killswitches; iter; iter = g_slist_next (iter)) {
Killswitch *ks = iter->data; Killswitch *ks = iter->data;
GUdevDevice *device; GUdevDevice *device;
RfKillState dev_state; RfKillState dev_state;
int sysfs_state;
if (ks->platform == FALSE) {
device = g_udev_client_query_by_subsystem_and_name (priv->client, "rfkill", ks->name); device = g_udev_client_query_by_subsystem_and_name (priv->client, "rfkill", ks->name);
if (!device) if (device) {
continue; sysfs_state = g_udev_device_get_property_as_int (device, "RFKILL_STATE");
dev_state = sysfs_state_to_nm_state (sysfs_state);
dev_state = sysfs_state_to_nm_state (g_udev_device_get_property_as_int (device, "RFKILL_STATE"));
if (dev_state > poll_states[ks->rtype]) if (dev_state > poll_states[ks->rtype])
poll_states[ks->rtype] = dev_state; poll_states[ks->rtype] = dev_state;
g_object_unref (device); 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++) { for (i = 0; i < RFKILL_TYPE_MAX; i++) {
if (poll_states[i] != priv->rfkill_states[i]) { if (poll_states[i] != priv->rfkill_states[i]) {
nm_log_dbg (LOGD_RFKILL, "%s rfkill state now '%s'", nm_log_dbg (LOGD_RFKILL, "%s rfkill state now '%s'",