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:
@@ -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
|
||||||
|
@@ -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'",
|
||||||
|
Reference in New Issue
Block a user