The new code added by this patch will make rfkill create
a misc character device /dev/rfkill that userspace can use
to control rfkill soft blocks and get status of devices as
well as events when the status changes.
Using it is very simple -- when you open it you can read
a number of times to get the initial state, and every
further read blocks (you can poll) on getting the next
event from the kernel. The same structure you read is
also used when writing to it to change the soft block of
a given device, all devices of a given type, or all
devices.
This also makes CONFIG_RFKILL_INPUT selectable again in
order to be able to test without it present since its
functionality can now be replaced by userspace entirely
and distros and users may not want the input part of
rfkill interfering with their userspace code. We will
also write a userspace daemon to handle all that and
consequently add the input code to the feature removal
schedule.
Signed-off-by: Johannes Berg <[email protected]>
---
Documentation/feature-removal-schedule.txt | 7
include/linux/rfkill.h | 79 ++++++--
net/rfkill/Kconfig | 4
net/rfkill/core.c | 265 ++++++++++++++++++++++++++++-
4 files changed, 324 insertions(+), 31 deletions(-)
--- wireless-testing.orig/include/linux/rfkill.h 2009-05-28 16:56:30.000000000 +0200
+++ wireless-testing/include/linux/rfkill.h 2009-05-28 16:57:14.000000000 +0200
@@ -22,34 +22,17 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#include <linux/types.h>
/* define userspace visible states */
#define RFKILL_STATE_SOFT_BLOCKED 0
#define RFKILL_STATE_UNBLOCKED 1
#define RFKILL_STATE_HARD_BLOCKED 2
-/* and that's all userspace gets */
-#ifdef __KERNEL__
-/* don't allow anyone to use these in the kernel */
-enum rfkill_user_states {
- RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
- RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
- RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
-};
-#undef RFKILL_STATE_SOFT_BLOCKED
-#undef RFKILL_STATE_UNBLOCKED
-#undef RFKILL_STATE_HARD_BLOCKED
-
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/list.h>
-#include <linux/mutex.h>
-#include <linux/device.h>
-#include <linux/leds.h>
-
/**
* enum rfkill_type - type of rfkill switch.
*
+ * @RFKILL_TYPE_ALL: toggles all switches (userspace only)
* @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device.
* @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device.
* @RFKILL_TYPE_UWB: switch is on a ultra wideband device.
@@ -58,6 +41,7 @@ enum rfkill_user_states {
* @NUM_RFKILL_TYPES: number of defined rfkill types
*/
enum rfkill_type {
+ RFKILL_TYPE_ALL = 0,
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
@@ -66,6 +50,57 @@ enum rfkill_type {
NUM_RFKILL_TYPES,
};
+/**
+ * enum rfkill_operation - operation types
+ * @RFKILL_OP_ADD: a device was added
+ * @RFKILL_OP_DEL: a device was removed
+ * @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device
+ * @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all)
+ */
+enum rfkill_operation {
+ RFKILL_OP_ADD = 0,
+ RFKILL_OP_DEL,
+ RFKILL_OP_CHANGE,
+ RFKILL_OP_CHANGE_ALL,
+};
+
+/**
+ * struct rfkill_event - events for userspace on /dev/rfkill
+ * @idx: index of dev rfkill
+ * @type: type of the rfkill struct
+ * @op: operation code
+ * @hard: hard state (0/1)
+ * @soft: soft state (0/1)
+ *
+ * Structure used for userspace communication on /dev/rfkill,
+ * used for events from the kernel and control to the kernel.
+ */
+struct rfkill_event {
+ __u32 idx;
+ __u32 type;
+ __u8 op;
+ __u8 soft, hard;
+} __packed;
+
+/* and that's all userspace gets */
+#ifdef __KERNEL__
+/* don't allow anyone to use these in the kernel */
+enum rfkill_user_states {
+ RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
+ RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
+ RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
+};
+#undef RFKILL_STATE_SOFT_BLOCKED
+#undef RFKILL_STATE_UNBLOCKED
+#undef RFKILL_STATE_HARD_BLOCKED
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+
/* this is opaque */
struct rfkill;
@@ -84,11 +119,7 @@ struct rfkill;
* the rfkill core query your driver before setting a requested
* block.
* @set_block: turn the transmitter on (blocked == false) or off
- * (blocked == true) -- this is called only while the transmitter
- * is not hard-blocked, but note that the core's view of whether
- * the transmitter is hard-blocked might differ from your driver's
- * view due to race conditions, so it is possible that it is still
- * called at the same time as you are calling rfkill_set_hw_state().
+ * (blocked == true) -- ignore and return 0 when hard blocked.
* This callback must be assigned.
*/
struct rfkill_ops {
--- wireless-testing.orig/net/rfkill/core.c 2009-05-28 16:56:30.000000000 +0200
+++ wireless-testing/net/rfkill/core.c 2009-05-28 17:18:43.000000000 +0200
@@ -28,6 +28,10 @@
#include <linux/mutex.h>
#include <linux/rfkill.h>
#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
#include "rfkill.h"
@@ -49,6 +53,8 @@ struct rfkill {
unsigned long state;
+ u32 idx;
+
bool registered;
bool suspended;
@@ -69,6 +75,17 @@ struct rfkill {
};
#define to_rfkill(d) container_of(d, struct rfkill, dev)
+struct rfkill_int_event {
+ struct list_head list;
+ struct rfkill_event ev;
+};
+
+struct rfkill_data {
+ struct list_head list;
+ struct list_head events;
+ struct mutex mtx;
+ wait_queue_head_t read_wait;
+};
MODULE_AUTHOR("Ivo van Doorn <[email protected]>");
@@ -90,6 +107,7 @@ MODULE_LICENSE("GPL");
*/
static LIST_HEAD(rfkill_list); /* list of registered rf switches */
static DEFINE_MUTEX(rfkill_global_mutex);
+static LIST_HEAD(rfkill_fds); /* list of open fds of /dev/rfkill */
static unsigned int rfkill_default_state = 1;
module_param_named(default_state, rfkill_default_state, uint, 0444);
@@ -171,12 +189,48 @@ static inline void rfkill_led_trigger_un
}
#endif /* CONFIG_RFKILL_LEDS */
-static void rfkill_uevent(struct rfkill *rfkill)
+static void rfkill_fill_event(struct rfkill_event *ev, struct rfkill *rfkill,
+ enum rfkill_operation op)
+{
+ unsigned long flags;
+
+ ev->idx = rfkill->idx;
+ ev->type = rfkill->type;
+ ev->op = op;
+
+ spin_lock_irqsave(&rfkill->lock, flags);
+ ev->hard = !!(rfkill->state & RFKILL_BLOCK_HW);
+ ev->soft = !!(rfkill->state & (RFKILL_BLOCK_SW |
+ RFKILL_BLOCK_SW_PREV));
+ spin_unlock_irqrestore(&rfkill->lock, flags);
+}
+
+static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op)
+{
+ struct rfkill_data *data;
+ struct rfkill_int_event *ev;
+
+ list_for_each_entry(data, &rfkill_fds, list) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ continue;
+ rfkill_fill_event(&ev->ev, rfkill, op);
+ mutex_lock(&data->mtx);
+ list_add_tail(&ev->list, &data->events);
+ mutex_unlock(&data->mtx);
+ wake_up_interruptible(&data->read_wait);
+ }
+}
+
+static void rfkill_event(struct rfkill *rfkill)
{
if (!rfkill->registered || rfkill->suspended)
return;
kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);
+
+ /* also send event to /dev/rfkill */
+ rfkill_send_events(rfkill, RFKILL_OP_CHANGE);
}
static bool __rfkill_set_hw_state(struct rfkill *rfkill,
@@ -260,9 +314,10 @@ static void rfkill_set_block(struct rfki
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
- rfkill_uevent(rfkill);
+ rfkill_event(rfkill);
}
+#ifdef CONFIG_RFKILL_INPUT
/**
* __rfkill_switch_all - Toggle state of all switches of given type
* @type: type of interfaces to be affected
@@ -331,6 +386,7 @@ void rfkill_epo(void)
rfkill_global_states[i].def = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
+ out:
mutex_unlock(&rfkill_global_mutex);
}
@@ -391,6 +447,7 @@ bool rfkill_get_global_sw_state(const en
{
return rfkill_global_states[type].cur;
}
+#endif
void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
{
@@ -537,6 +594,15 @@ static ssize_t rfkill_type_show(struct d
return sprintf(buf, "%s\n", rfkill_get_type_str(rfkill->type));
}
+static ssize_t rfkill_idx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rfkill *rfkill = to_rfkill(dev);
+
+ return sprintf(buf, "%d\n", rfkill->idx);
+}
+
static u8 user_state_from_blocked(unsigned long state)
{
if (state & RFKILL_BLOCK_HW)
@@ -594,6 +660,7 @@ static ssize_t rfkill_claim_store(struct
static struct device_attribute rfkill_dev_attrs[] = {
__ATTR(name, S_IRUGO, rfkill_name_show, NULL),
__ATTR(type, S_IRUGO, rfkill_type_show, NULL),
+ __ATTR(index, S_IRUGO, rfkill_idx_show, NULL),
__ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store),
__ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store),
__ATTR_NULL
@@ -708,7 +775,7 @@ struct rfkill * __must_check rfkill_allo
if (WARN_ON(!name))
return NULL;
- if (WARN_ON(type >= NUM_RFKILL_TYPES))
+ if (WARN_ON(type == RFKILL_TYPE_ALL || type >= NUM_RFKILL_TYPES))
return NULL;
rfkill = kzalloc(sizeof(*rfkill), GFP_KERNEL);
@@ -754,7 +821,9 @@ static void rfkill_uevent_work(struct wo
rfkill = container_of(work, struct rfkill, uevent_work);
- rfkill_uevent(rfkill);
+ mutex_lock(&rfkill_global_mutex);
+ rfkill_event(rfkill);
+ mutex_unlock(&rfkill_global_mutex);
}
static void rfkill_sync_work(struct work_struct *work)
@@ -785,6 +854,7 @@ int __must_check rfkill_register(struct
goto unlock;
}
+ rfkill->idx = rfkill_no;
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
@@ -819,6 +889,7 @@ int __must_check rfkill_register(struct
INIT_WORK(&rfkill->sync_work, rfkill_sync_work);
schedule_work(&rfkill->sync_work);
+ rfkill_send_events(rfkill, RFKILL_OP_ADD);
mutex_unlock(&rfkill_global_mutex);
return 0;
@@ -848,6 +919,7 @@ void rfkill_unregister(struct rfkill *rf
device_del(&rfkill->dev);
mutex_lock(&rfkill_global_mutex);
+ rfkill_send_events(rfkill, RFKILL_OP_DEL);
list_del_init(&rfkill->node);
mutex_unlock(&rfkill_global_mutex);
@@ -875,6 +947,179 @@ bool rfkill_blocked(struct rfkill *rfkil
}
EXPORT_SYMBOL(rfkill_blocked);
+static int rfkill_fop_open(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data;
+ struct rfkill *rfkill;
+ struct rfkill_int_event *ev, *tmp;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&data->events);
+ mutex_init(&data->mtx);
+ init_waitqueue_head(&data->read_wait);
+
+ mutex_lock(&rfkill_global_mutex);
+ mutex_lock(&data->mtx);
+ /*
+ * start getting events from elsewhere but hold mtx to get
+ * startup events added first
+ */
+ list_add(&data->list, &rfkill_fds);
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ goto free;
+ rfkill_fill_event(&ev->ev, rfkill, RFKILL_OP_ADD);
+ list_add_tail(&ev->list, &data->events);
+ }
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+
+ file->private_data = data;
+
+ return nonseekable_open(inode, file);
+
+ free:
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+ kfree(data);
+ return -ENOMEM;
+}
+
+static unsigned int rfkill_fop_poll(struct file *file, poll_table *wait)
+{
+ struct rfkill_data *data = file->private_data;
+ unsigned int res = POLLOUT | POLLWRNORM;
+
+ poll_wait(file, &data->read_wait, wait);
+
+ mutex_lock(&data->mtx);
+ if (!list_empty(&data->events))
+ res = POLLIN | POLLRDNORM;
+ mutex_unlock(&data->mtx);
+
+ return res;
+}
+
+static bool rfkill_readable(struct rfkill_data *data)
+{
+ bool r;
+
+ mutex_lock(&data->mtx);
+ r = !list_empty(&data->events);
+ mutex_unlock(&data->mtx);
+
+ return r;
+}
+
+static ssize_t rfkill_fop_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev;
+ unsigned long sz;
+ int ret;
+
+ mutex_lock(&data->mtx);
+
+ while (list_empty(&data->events)) {
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ goto out;
+ }
+ mutex_unlock(&data->mtx);
+ ret = wait_event_interruptible(data->read_wait,
+ rfkill_readable(data));
+ mutex_lock(&data->mtx);
+
+ if (ret)
+ goto out;
+ }
+
+ ev = list_first_entry(&data->events, struct rfkill_int_event,
+ list);
+
+ sz = min_t(unsigned long, sizeof(ev->ev), count);
+ ret = sz;
+ if (copy_to_user(buf, &ev->ev, sz))
+ ret = -EFAULT;
+
+ list_del(&ev->list);
+ kfree(ev);
+ out:
+ mutex_unlock(&data->mtx);
+ return ret;
+}
+
+static ssize_t rfkill_fop_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill *rfkill;
+ struct rfkill_event ev;
+
+ /* we don't need the 'hard' variable but accept it */
+ if (count < sizeof(ev) - 1)
+ return -EINVAL;
+
+ if (copy_from_user(&ev, buf, sizeof(ev) - 1))
+ return -EFAULT;
+
+ if (ev.op != RFKILL_OP_CHANGE && ev.op != RFKILL_OP_CHANGE_ALL)
+ return -EINVAL;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ if (rfkill->idx != ev.idx && ev.op != RFKILL_OP_CHANGE_ALL)
+ continue;
+
+ if (rfkill->type != ev.type && ev.type != RFKILL_TYPE_ALL)
+ continue;
+
+ rfkill_set_block(rfkill, ev.soft);
+ }
+ mutex_unlock(&rfkill_global_mutex);
+
+ return count;
+}
+
+static int rfkill_fop_release(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev, *tmp;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_del(&data->list);
+ mutex_unlock(&rfkill_global_mutex);
+
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+
+ kfree(data);
+
+ return 0;
+}
+
+static const struct file_operations rfkill_fops = {
+ .open = rfkill_fop_open,
+ .read = rfkill_fop_read,
+ .write = rfkill_fop_write,
+ .poll = rfkill_fop_poll,
+ .release = rfkill_fop_release,
+};
+
+static struct miscdevice rfkill_miscdev = {
+ .name = "rfkill",
+ .fops = &rfkill_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
static int __init rfkill_init(void)
{
@@ -888,10 +1133,19 @@ static int __init rfkill_init(void)
if (error)
goto out;
+ error = misc_register(&rfkill_miscdev);
+ if (error) {
+ class_unregister(&rfkill_class);
+ goto out;
+ }
+
#ifdef CONFIG_RFKILL_INPUT
error = rfkill_handler_init();
- if (error)
+ if (error) {
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
+ goto out;
+ }
#endif
out:
@@ -904,6 +1158,7 @@ static void __exit rfkill_exit(void)
#ifdef CONFIG_RFKILL_INPUT
rfkill_handler_exit();
#endif
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
}
module_exit(rfkill_exit);
--- wireless-testing.orig/net/rfkill/Kconfig 2009-05-28 16:56:30.000000000 +0200
+++ wireless-testing/net/rfkill/Kconfig 2009-05-28 17:14:39.000000000 +0200
@@ -18,7 +18,7 @@ config RFKILL_LEDS
default y
config RFKILL_INPUT
- bool
+ bool "RF switch input support"
depends on RFKILL
depends on INPUT = y || RFKILL = INPUT
- default y
+ default y if !EMBEDDED
--- wireless-testing.orig/Documentation/feature-removal-schedule.txt 2009-05-28 17:11:56.000000000 +0200
+++ wireless-testing/Documentation/feature-removal-schedule.txt 2009-05-28 17:12:58.000000000 +0200
@@ -437,3 +437,10 @@ Why: Superseded by tdfxfb. I2C/DDC suppo
driver but this caused driver conflicts.
Who: Jean Delvare <[email protected]>
Krzysztof Helt <[email protected]>
+
+---------------------------
+
+What: CONFIG_RFKILL_INPUT
+When: 2.6.33
+Why: Should be implemented in userspace, policy daemon.
+Who: Johannes Berg <[email protected]>
Johannes Berg wrote:
> On Sun, 2009-05-31 at 10:13 +0100, Alan Jenkins wrote:
>
>
>> If I read correctly, userspace can write to the global states, but
>> can't read them? I think it's awkward to implement rfkill-input in
>> userspace without being able to read the global states. The daemon
>> would have to save the states in a file, in case it is restarted.
>>
>
> You have a point there, but I'm not sure it even cares? When restarted
> it will probably want to impose its current policy anyway? It would be
> easy to add that we send the global default value for newly added ones
> too but I'm not sure it's necessary -- Marcel?
>
> johannes
This applies equally to the type-specific values; I think you'd get
multiple "global default values".
I don't think restarting the daemon should necessarily impose a policy
default state, because "whatever the kernel initialized it to" is
actually a good default, when platform devices have persistent rfkill state.
Actually, I suppose it would meet my expectations if the daemon simply
uses the state of an arbitrary rfkill device of each type, because the
kernel initializes them all to the same value. If you don't mind the
slightly arbitrary looking code, it should be fine.
alan
The new code added by this patch will make rfkill create
a misc character device /dev/rfkill that userspace can use
to control rfkill soft blocks and get status of devices as
well as events when the status changes.
Using it is very simple -- when you open it you can read
a number of times to get the initial state, and every
further read blocks (you can poll) on getting the next
event from the kernel. The same structure you read is
also used when writing to it to change the soft block of
a given device, all devices of a given type, or all
devices.
This also makes CONFIG_RFKILL_INPUT selectable again in
order to be able to test without it present since its
functionality can now be replaced by userspace entirely
and distros and users may not want the input part of
rfkill interfering with their userspace code. We will
also write a userspace daemon to handle all that and
consequently add the input code to the feature removal
schedule.
Signed-off-by: Johannes Berg <[email protected]>
---
Documentation/feature-removal-schedule.txt | 7
include/linux/rfkill.h | 79 ++++++--
net/rfkill/Kconfig | 4
net/rfkill/core.c | 265 ++++++++++++++++++++++++++++-
4 files changed, 324 insertions(+), 31 deletions(-)
--- wireless-testing.orig/include/linux/rfkill.h 2009-05-28 17:32:20.000000000 +0200
+++ wireless-testing/include/linux/rfkill.h 2009-05-28 17:39:26.000000000 +0200
@@ -22,34 +22,17 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#include <linux/types.h>
/* define userspace visible states */
#define RFKILL_STATE_SOFT_BLOCKED 0
#define RFKILL_STATE_UNBLOCKED 1
#define RFKILL_STATE_HARD_BLOCKED 2
-/* and that's all userspace gets */
-#ifdef __KERNEL__
-/* don't allow anyone to use these in the kernel */
-enum rfkill_user_states {
- RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
- RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
- RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
-};
-#undef RFKILL_STATE_SOFT_BLOCKED
-#undef RFKILL_STATE_UNBLOCKED
-#undef RFKILL_STATE_HARD_BLOCKED
-
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/list.h>
-#include <linux/mutex.h>
-#include <linux/device.h>
-#include <linux/leds.h>
-
/**
* enum rfkill_type - type of rfkill switch.
*
+ * @RFKILL_TYPE_ALL: toggles all switches (userspace only)
* @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device.
* @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device.
* @RFKILL_TYPE_UWB: switch is on a ultra wideband device.
@@ -58,6 +41,7 @@ enum rfkill_user_states {
* @NUM_RFKILL_TYPES: number of defined rfkill types
*/
enum rfkill_type {
+ RFKILL_TYPE_ALL = 0,
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
@@ -66,6 +50,57 @@ enum rfkill_type {
NUM_RFKILL_TYPES,
};
+/**
+ * enum rfkill_operation - operation types
+ * @RFKILL_OP_ADD: a device was added
+ * @RFKILL_OP_DEL: a device was removed
+ * @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device
+ * @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all)
+ */
+enum rfkill_operation {
+ RFKILL_OP_ADD = 0,
+ RFKILL_OP_DEL,
+ RFKILL_OP_CHANGE,
+ RFKILL_OP_CHANGE_ALL,
+};
+
+/**
+ * struct rfkill_event - events for userspace on /dev/rfkill
+ * @idx: index of dev rfkill
+ * @type: type of the rfkill struct
+ * @op: operation code
+ * @hard: hard state (0/1)
+ * @soft: soft state (0/1)
+ *
+ * Structure used for userspace communication on /dev/rfkill,
+ * used for events from the kernel and control to the kernel.
+ */
+struct rfkill_event {
+ __u32 idx;
+ __u8 type;
+ __u8 op;
+ __u8 soft, hard;
+} __packed;
+
+/* and that's all userspace gets */
+#ifdef __KERNEL__
+/* don't allow anyone to use these in the kernel */
+enum rfkill_user_states {
+ RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
+ RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
+ RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
+};
+#undef RFKILL_STATE_SOFT_BLOCKED
+#undef RFKILL_STATE_UNBLOCKED
+#undef RFKILL_STATE_HARD_BLOCKED
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+
/* this is opaque */
struct rfkill;
@@ -84,11 +119,7 @@ struct rfkill;
* the rfkill core query your driver before setting a requested
* block.
* @set_block: turn the transmitter on (blocked == false) or off
- * (blocked == true) -- this is called only while the transmitter
- * is not hard-blocked, but note that the core's view of whether
- * the transmitter is hard-blocked might differ from your driver's
- * view due to race conditions, so it is possible that it is still
- * called at the same time as you are calling rfkill_set_hw_state().
+ * (blocked == true) -- ignore and return 0 when hard blocked.
* This callback must be assigned.
*/
struct rfkill_ops {
--- wireless-testing.orig/net/rfkill/core.c 2009-05-28 17:32:31.000000000 +0200
+++ wireless-testing/net/rfkill/core.c 2009-05-28 17:32:33.000000000 +0200
@@ -28,6 +28,10 @@
#include <linux/mutex.h>
#include <linux/rfkill.h>
#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
#include "rfkill.h"
@@ -49,6 +53,8 @@ struct rfkill {
unsigned long state;
+ u32 idx;
+
bool registered;
bool suspended;
@@ -69,6 +75,17 @@ struct rfkill {
};
#define to_rfkill(d) container_of(d, struct rfkill, dev)
+struct rfkill_int_event {
+ struct list_head list;
+ struct rfkill_event ev;
+};
+
+struct rfkill_data {
+ struct list_head list;
+ struct list_head events;
+ struct mutex mtx;
+ wait_queue_head_t read_wait;
+};
MODULE_AUTHOR("Ivo van Doorn <[email protected]>");
@@ -90,6 +107,7 @@ MODULE_LICENSE("GPL");
*/
static LIST_HEAD(rfkill_list); /* list of registered rf switches */
static DEFINE_MUTEX(rfkill_global_mutex);
+static LIST_HEAD(rfkill_fds); /* list of open fds of /dev/rfkill */
static unsigned int rfkill_default_state = 1;
module_param_named(default_state, rfkill_default_state, uint, 0444);
@@ -171,12 +189,48 @@ static inline void rfkill_led_trigger_un
}
#endif /* CONFIG_RFKILL_LEDS */
-static void rfkill_uevent(struct rfkill *rfkill)
+static void rfkill_fill_event(struct rfkill_event *ev, struct rfkill *rfkill,
+ enum rfkill_operation op)
+{
+ unsigned long flags;
+
+ ev->idx = rfkill->idx;
+ ev->type = rfkill->type;
+ ev->op = op;
+
+ spin_lock_irqsave(&rfkill->lock, flags);
+ ev->hard = !!(rfkill->state & RFKILL_BLOCK_HW);
+ ev->soft = !!(rfkill->state & (RFKILL_BLOCK_SW |
+ RFKILL_BLOCK_SW_PREV));
+ spin_unlock_irqrestore(&rfkill->lock, flags);
+}
+
+static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op)
+{
+ struct rfkill_data *data;
+ struct rfkill_int_event *ev;
+
+ list_for_each_entry(data, &rfkill_fds, list) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ continue;
+ rfkill_fill_event(&ev->ev, rfkill, op);
+ mutex_lock(&data->mtx);
+ list_add_tail(&ev->list, &data->events);
+ mutex_unlock(&data->mtx);
+ wake_up_interruptible(&data->read_wait);
+ }
+}
+
+static void rfkill_event(struct rfkill *rfkill)
{
if (!rfkill->registered || rfkill->suspended)
return;
kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);
+
+ /* also send event to /dev/rfkill */
+ rfkill_send_events(rfkill, RFKILL_OP_CHANGE);
}
static bool __rfkill_set_hw_state(struct rfkill *rfkill,
@@ -260,9 +314,10 @@ static void rfkill_set_block(struct rfki
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
- rfkill_uevent(rfkill);
+ rfkill_event(rfkill);
}
+#ifdef CONFIG_RFKILL_INPUT
/**
* __rfkill_switch_all - Toggle state of all switches of given type
* @type: type of interfaces to be affected
@@ -331,6 +386,7 @@ void rfkill_epo(void)
rfkill_global_states[i].def = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
+ out:
mutex_unlock(&rfkill_global_mutex);
}
@@ -391,6 +447,7 @@ bool rfkill_get_global_sw_state(const en
{
return rfkill_global_states[type].cur;
}
+#endif
void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
{
@@ -537,6 +594,15 @@ static ssize_t rfkill_type_show(struct d
return sprintf(buf, "%s\n", rfkill_get_type_str(rfkill->type));
}
+static ssize_t rfkill_idx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rfkill *rfkill = to_rfkill(dev);
+
+ return sprintf(buf, "%d\n", rfkill->idx);
+}
+
static u8 user_state_from_blocked(unsigned long state)
{
if (state & RFKILL_BLOCK_HW)
@@ -594,6 +660,7 @@ static ssize_t rfkill_claim_store(struct
static struct device_attribute rfkill_dev_attrs[] = {
__ATTR(name, S_IRUGO, rfkill_name_show, NULL),
__ATTR(type, S_IRUGO, rfkill_type_show, NULL),
+ __ATTR(index, S_IRUGO, rfkill_idx_show, NULL),
__ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store),
__ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store),
__ATTR_NULL
@@ -708,7 +775,7 @@ struct rfkill * __must_check rfkill_allo
if (WARN_ON(!name))
return NULL;
- if (WARN_ON(type >= NUM_RFKILL_TYPES))
+ if (WARN_ON(type == RFKILL_TYPE_ALL || type >= NUM_RFKILL_TYPES))
return NULL;
rfkill = kzalloc(sizeof(*rfkill), GFP_KERNEL);
@@ -754,7 +821,9 @@ static void rfkill_uevent_work(struct wo
rfkill = container_of(work, struct rfkill, uevent_work);
- rfkill_uevent(rfkill);
+ mutex_lock(&rfkill_global_mutex);
+ rfkill_event(rfkill);
+ mutex_unlock(&rfkill_global_mutex);
}
static void rfkill_sync_work(struct work_struct *work)
@@ -785,6 +854,7 @@ int __must_check rfkill_register(struct
goto unlock;
}
+ rfkill->idx = rfkill_no;
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
@@ -819,6 +889,7 @@ int __must_check rfkill_register(struct
INIT_WORK(&rfkill->sync_work, rfkill_sync_work);
schedule_work(&rfkill->sync_work);
+ rfkill_send_events(rfkill, RFKILL_OP_ADD);
mutex_unlock(&rfkill_global_mutex);
return 0;
@@ -848,6 +919,7 @@ void rfkill_unregister(struct rfkill *rf
device_del(&rfkill->dev);
mutex_lock(&rfkill_global_mutex);
+ rfkill_send_events(rfkill, RFKILL_OP_DEL);
list_del_init(&rfkill->node);
mutex_unlock(&rfkill_global_mutex);
@@ -875,6 +947,179 @@ bool rfkill_blocked(struct rfkill *rfkil
}
EXPORT_SYMBOL(rfkill_blocked);
+static int rfkill_fop_open(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data;
+ struct rfkill *rfkill;
+ struct rfkill_int_event *ev, *tmp;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&data->events);
+ mutex_init(&data->mtx);
+ init_waitqueue_head(&data->read_wait);
+
+ mutex_lock(&rfkill_global_mutex);
+ mutex_lock(&data->mtx);
+ /*
+ * start getting events from elsewhere but hold mtx to get
+ * startup events added first
+ */
+ list_add(&data->list, &rfkill_fds);
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ goto free;
+ rfkill_fill_event(&ev->ev, rfkill, RFKILL_OP_ADD);
+ list_add_tail(&ev->list, &data->events);
+ }
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+
+ file->private_data = data;
+
+ return nonseekable_open(inode, file);
+
+ free:
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+ kfree(data);
+ return -ENOMEM;
+}
+
+static unsigned int rfkill_fop_poll(struct file *file, poll_table *wait)
+{
+ struct rfkill_data *data = file->private_data;
+ unsigned int res = POLLOUT | POLLWRNORM;
+
+ poll_wait(file, &data->read_wait, wait);
+
+ mutex_lock(&data->mtx);
+ if (!list_empty(&data->events))
+ res = POLLIN | POLLRDNORM;
+ mutex_unlock(&data->mtx);
+
+ return res;
+}
+
+static bool rfkill_readable(struct rfkill_data *data)
+{
+ bool r;
+
+ mutex_lock(&data->mtx);
+ r = !list_empty(&data->events);
+ mutex_unlock(&data->mtx);
+
+ return r;
+}
+
+static ssize_t rfkill_fop_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev;
+ unsigned long sz;
+ int ret;
+
+ mutex_lock(&data->mtx);
+
+ while (list_empty(&data->events)) {
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ goto out;
+ }
+ mutex_unlock(&data->mtx);
+ ret = wait_event_interruptible(data->read_wait,
+ rfkill_readable(data));
+ mutex_lock(&data->mtx);
+
+ if (ret)
+ goto out;
+ }
+
+ ev = list_first_entry(&data->events, struct rfkill_int_event,
+ list);
+
+ sz = min_t(unsigned long, sizeof(ev->ev), count);
+ ret = sz;
+ if (copy_to_user(buf, &ev->ev, sz))
+ ret = -EFAULT;
+
+ list_del(&ev->list);
+ kfree(ev);
+ out:
+ mutex_unlock(&data->mtx);
+ return ret;
+}
+
+static ssize_t rfkill_fop_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill *rfkill;
+ struct rfkill_event ev;
+
+ /* we don't need the 'hard' variable but accept it */
+ if (count < sizeof(ev) - 1)
+ return -EINVAL;
+
+ if (copy_from_user(&ev, buf, sizeof(ev) - 1))
+ return -EFAULT;
+
+ if (ev.op != RFKILL_OP_CHANGE && ev.op != RFKILL_OP_CHANGE_ALL)
+ return -EINVAL;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ if (rfkill->idx != ev.idx && ev.op != RFKILL_OP_CHANGE_ALL)
+ continue;
+
+ if (rfkill->type != ev.type && ev.type != RFKILL_TYPE_ALL)
+ continue;
+
+ rfkill_set_block(rfkill, ev.soft);
+ }
+ mutex_unlock(&rfkill_global_mutex);
+
+ return count;
+}
+
+static int rfkill_fop_release(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev, *tmp;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_del(&data->list);
+ mutex_unlock(&rfkill_global_mutex);
+
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+
+ kfree(data);
+
+ return 0;
+}
+
+static const struct file_operations rfkill_fops = {
+ .open = rfkill_fop_open,
+ .read = rfkill_fop_read,
+ .write = rfkill_fop_write,
+ .poll = rfkill_fop_poll,
+ .release = rfkill_fop_release,
+};
+
+static struct miscdevice rfkill_miscdev = {
+ .name = "rfkill",
+ .fops = &rfkill_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
static int __init rfkill_init(void)
{
@@ -888,10 +1133,19 @@ static int __init rfkill_init(void)
if (error)
goto out;
+ error = misc_register(&rfkill_miscdev);
+ if (error) {
+ class_unregister(&rfkill_class);
+ goto out;
+ }
+
#ifdef CONFIG_RFKILL_INPUT
error = rfkill_handler_init();
- if (error)
+ if (error) {
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
+ goto out;
+ }
#endif
out:
@@ -904,6 +1158,7 @@ static void __exit rfkill_exit(void)
#ifdef CONFIG_RFKILL_INPUT
rfkill_handler_exit();
#endif
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
}
module_exit(rfkill_exit);
--- wireless-testing.orig/net/rfkill/Kconfig 2009-05-28 17:32:20.000000000 +0200
+++ wireless-testing/net/rfkill/Kconfig 2009-05-28 17:32:33.000000000 +0200
@@ -18,7 +18,7 @@ config RFKILL_LEDS
default y
config RFKILL_INPUT
- bool
+ bool "RF switch input support"
depends on RFKILL
depends on INPUT = y || RFKILL = INPUT
- default y
+ default y if !EMBEDDED
--- wireless-testing.orig/Documentation/feature-removal-schedule.txt 2009-05-28 17:32:20.000000000 +0200
+++ wireless-testing/Documentation/feature-removal-schedule.txt 2009-05-28 17:32:33.000000000 +0200
@@ -437,3 +437,10 @@ Why: Superseded by tdfxfb. I2C/DDC suppo
driver but this caused driver conflicts.
Who: Jean Delvare <[email protected]>
Krzysztof Helt <[email protected]>
+
+---------------------------
+
+What: CONFIG_RFKILL_INPUT
+When: 2.6.33
+Why: Should be implemented in userspace, policy daemon.
+Who: Johannes Berg <[email protected]>
Johannes Berg wrote:
>> How should userspace test CONFIG_RFKILL_INPUT to determine whether
>> it's safe to start the daemon? With the old core, debian-eeepc
>> scripts check if the module rfkill-input exists (which should work
>> even if it's built in). If it exists, the scripts don't perform any
>> rfkill actions. (Yeah, according to the doc this is not allowed
>> because the scripts don't use "claim", but you can see how it's
>> useful).
>>
>> The new rfkill-input isn't a module, so I'm not sure how your daemon
>> would test for it.
>>
>
> Maybe we should add an ioctl that disables rfkill-input if present.
>
> johannes
>
Sounds good.
On 5/28/09, Johannes Berg <[email protected]> wrote:
> The new code added by this patch will make rfkill create
> a misc character device /dev/rfkill that userspace can use
> to control rfkill soft blocks and get status of devices as
> well as events when the status changes.
>
> Using it is very simple -- when you open it you can read
> a number of times to get the initial state, and every
> further read blocks (you can poll) on getting the next
> event from the kernel. The same structure you read is
> also used when writing to it to change the soft block of
> a given device, all devices of a given type, or all
> devices.
>
> This also makes CONFIG_RFKILL_INPUT selectable again in
> order to be able to test without it present since its
> functionality can now be replaced by userspace entirely
> and distros and users may not want the input part of
> rfkill interfering with their userspace code. We will
> also write a userspace daemon to handle all that and
> consequently add the input code to the feature removal
> schedule.
>
> Signed-off-by: Johannes Berg <[email protected]>
How should userspace test CONFIG_RFKILL_INPUT to determine whether
it's safe to start the daemon? With the old core, debian-eeepc
scripts check if the module rfkill-input exists (which should work
even if it's built in). If it exists, the scripts don't perform any
rfkill actions. (Yeah, according to the doc this is not allowed
because the scripts don't use "claim", but you can see how it's
useful).
The new rfkill-input isn't a module, so I'm not sure how your daemon
would test for it.
Thanks
Alan
Hi Johannes,
> > If I read correctly, userspace can write to the global states, but
> > can't read them? I think it's awkward to implement rfkill-input in
> > userspace without being able to read the global states. The daemon
> > would have to save the states in a file, in case it is restarted.
>
> You have a point there, but I'm not sure it even cares? When restarted
> it will probably want to impose its current policy anyway? It would be
> easy to add that we send the global default value for newly added ones
> too but I'm not sure it's necessary -- Marcel?
we can be smart and send an additional CHANGE_ALL when opening the
control device if it is set. We can also just send these anyway. Doesn't
really matter? Does it?
Regards
Marcel
On 5/28/09, Johannes Berg <[email protected]> wrote:
> The new code added by this patch will make rfkill create
> a misc character device /dev/rfkill that userspace can use
> to control rfkill soft blocks and get status of devices as
> well as events when the status changes.
>
> Using it is very simple -- when you open it you can read
> a number of times to get the initial state, and every
> further read blocks (you can poll) on getting the next
> event from the kernel. The same structure you read is
> also used when writing to it to change the soft block of
> a given device, all devices of a given type, or all
> devices.
>
> This also makes CONFIG_RFKILL_INPUT selectable again in
> order to be able to test without it present since its
> functionality can now be replaced by userspace entirely
> and distros and users may not want the input part of
> rfkill interfering with their userspace code. We will
> also write a userspace daemon to handle all that and
> consequently add the input code to the feature removal
> schedule.
>
> Signed-off-by: Johannes Berg <[email protected]>
If I read correctly, userspace can write to the global states, but
can't read them? I think it's awkward to implement rfkill-input in
userspace without being able to read the global states. The daemon
would have to save the states in a file, in case it is restarted.
Thanks
Alan
On Sun, 2009-05-31 at 10:13 +0100, Alan Jenkins wrote:
> If I read correctly, userspace can write to the global states, but
> can't read them? I think it's awkward to implement rfkill-input in
> userspace without being able to read the global states. The daemon
> would have to save the states in a file, in case it is restarted.
You have a point there, but I'm not sure it even cares? When restarted
it will probably want to impose its current policy anyway? It would be
easy to add that we send the global default value for newly added ones
too but I'm not sure it's necessary -- Marcel?
johannes
The new code added by this patch will make rfkill create
a misc character device /dev/rfkill that userspace can use
to control rfkill soft blocks and get status of devices as
well as events when the status changes.
Using it is very simple -- when you open it you can read
a number of times to get the initial state, and every
further read blocks (you can poll) on getting the next
event from the kernel. The same structure you read is
also used when writing to it to change the soft block of
a given device, all devices of a given type, or all
devices.
This also makes CONFIG_RFKILL_INPUT selectable again in
order to be able to test without it present since its
functionality can now be replaced by userspace entirely
and distros and users may not want the input part of
rfkill interfering with their userspace code. We will
also write a userspace daemon to handle all that and
consequently add the input code to the feature removal
schedule.
Signed-off-by: Johannes Berg <[email protected]>
---
v4: set default global state from userspace for rfkill hotplug
(pointed out by Marcel)
Documentation/feature-removal-schedule.txt | 7
include/linux/rfkill.h | 79 +++++---
net/rfkill/Kconfig | 4
net/rfkill/core.c | 279 ++++++++++++++++++++++++++++-
4 files changed, 338 insertions(+), 31 deletions(-)
--- wireless-testing.orig/include/linux/rfkill.h 2009-05-28 17:56:41.000000000 +0200
+++ wireless-testing/include/linux/rfkill.h 2009-05-29 10:28:02.000000000 +0200
@@ -22,34 +22,17 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#include <linux/types.h>
/* define userspace visible states */
#define RFKILL_STATE_SOFT_BLOCKED 0
#define RFKILL_STATE_UNBLOCKED 1
#define RFKILL_STATE_HARD_BLOCKED 2
-/* and that's all userspace gets */
-#ifdef __KERNEL__
-/* don't allow anyone to use these in the kernel */
-enum rfkill_user_states {
- RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
- RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
- RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
-};
-#undef RFKILL_STATE_SOFT_BLOCKED
-#undef RFKILL_STATE_UNBLOCKED
-#undef RFKILL_STATE_HARD_BLOCKED
-
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/list.h>
-#include <linux/mutex.h>
-#include <linux/device.h>
-#include <linux/leds.h>
-
/**
* enum rfkill_type - type of rfkill switch.
*
+ * @RFKILL_TYPE_ALL: toggles all switches (userspace only)
* @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device.
* @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device.
* @RFKILL_TYPE_UWB: switch is on a ultra wideband device.
@@ -58,6 +41,7 @@ enum rfkill_user_states {
* @NUM_RFKILL_TYPES: number of defined rfkill types
*/
enum rfkill_type {
+ RFKILL_TYPE_ALL = 0,
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
@@ -66,6 +50,57 @@ enum rfkill_type {
NUM_RFKILL_TYPES,
};
+/**
+ * enum rfkill_operation - operation types
+ * @RFKILL_OP_ADD: a device was added
+ * @RFKILL_OP_DEL: a device was removed
+ * @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device
+ * @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all)
+ */
+enum rfkill_operation {
+ RFKILL_OP_ADD = 0,
+ RFKILL_OP_DEL,
+ RFKILL_OP_CHANGE,
+ RFKILL_OP_CHANGE_ALL,
+};
+
+/**
+ * struct rfkill_event - events for userspace on /dev/rfkill
+ * @idx: index of dev rfkill
+ * @type: type of the rfkill struct
+ * @op: operation code
+ * @hard: hard state (0/1)
+ * @soft: soft state (0/1)
+ *
+ * Structure used for userspace communication on /dev/rfkill,
+ * used for events from the kernel and control to the kernel.
+ */
+struct rfkill_event {
+ __u32 idx;
+ __u8 type;
+ __u8 op;
+ __u8 soft, hard;
+} __packed;
+
+/* and that's all userspace gets */
+#ifdef __KERNEL__
+/* don't allow anyone to use these in the kernel */
+enum rfkill_user_states {
+ RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
+ RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
+ RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
+};
+#undef RFKILL_STATE_SOFT_BLOCKED
+#undef RFKILL_STATE_UNBLOCKED
+#undef RFKILL_STATE_HARD_BLOCKED
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+
/* this is opaque */
struct rfkill;
@@ -84,11 +119,7 @@ struct rfkill;
* the rfkill core query your driver before setting a requested
* block.
* @set_block: turn the transmitter on (blocked == false) or off
- * (blocked == true) -- this is called only while the transmitter
- * is not hard-blocked, but note that the core's view of whether
- * the transmitter is hard-blocked might differ from your driver's
- * view due to race conditions, so it is possible that it is still
- * called at the same time as you are calling rfkill_set_hw_state().
+ * (blocked == true) -- ignore and return 0 when hard blocked.
* This callback must be assigned.
*/
struct rfkill_ops {
--- wireless-testing.orig/net/rfkill/core.c 2009-05-28 17:56:41.000000000 +0200
+++ wireless-testing/net/rfkill/core.c 2009-05-29 10:32:17.000000000 +0200
@@ -28,6 +28,10 @@
#include <linux/mutex.h>
#include <linux/rfkill.h>
#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
#include "rfkill.h"
@@ -49,6 +53,8 @@ struct rfkill {
unsigned long state;
+ u32 idx;
+
bool registered;
bool suspended;
@@ -69,6 +75,17 @@ struct rfkill {
};
#define to_rfkill(d) container_of(d, struct rfkill, dev)
+struct rfkill_int_event {
+ struct list_head list;
+ struct rfkill_event ev;
+};
+
+struct rfkill_data {
+ struct list_head list;
+ struct list_head events;
+ struct mutex mtx;
+ wait_queue_head_t read_wait;
+};
MODULE_AUTHOR("Ivo van Doorn <[email protected]>");
@@ -90,6 +107,7 @@ MODULE_LICENSE("GPL");
*/
static LIST_HEAD(rfkill_list); /* list of registered rf switches */
static DEFINE_MUTEX(rfkill_global_mutex);
+static LIST_HEAD(rfkill_fds); /* list of open fds of /dev/rfkill */
static unsigned int rfkill_default_state = 1;
module_param_named(default_state, rfkill_default_state, uint, 0444);
@@ -171,12 +189,48 @@ static inline void rfkill_led_trigger_un
}
#endif /* CONFIG_RFKILL_LEDS */
-static void rfkill_uevent(struct rfkill *rfkill)
+static void rfkill_fill_event(struct rfkill_event *ev, struct rfkill *rfkill,
+ enum rfkill_operation op)
+{
+ unsigned long flags;
+
+ ev->idx = rfkill->idx;
+ ev->type = rfkill->type;
+ ev->op = op;
+
+ spin_lock_irqsave(&rfkill->lock, flags);
+ ev->hard = !!(rfkill->state & RFKILL_BLOCK_HW);
+ ev->soft = !!(rfkill->state & (RFKILL_BLOCK_SW |
+ RFKILL_BLOCK_SW_PREV));
+ spin_unlock_irqrestore(&rfkill->lock, flags);
+}
+
+static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op)
+{
+ struct rfkill_data *data;
+ struct rfkill_int_event *ev;
+
+ list_for_each_entry(data, &rfkill_fds, list) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ continue;
+ rfkill_fill_event(&ev->ev, rfkill, op);
+ mutex_lock(&data->mtx);
+ list_add_tail(&ev->list, &data->events);
+ mutex_unlock(&data->mtx);
+ wake_up_interruptible(&data->read_wait);
+ }
+}
+
+static void rfkill_event(struct rfkill *rfkill)
{
if (!rfkill->registered || rfkill->suspended)
return;
kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);
+
+ /* also send event to /dev/rfkill */
+ rfkill_send_events(rfkill, RFKILL_OP_CHANGE);
}
static bool __rfkill_set_hw_state(struct rfkill *rfkill,
@@ -260,9 +314,10 @@ static void rfkill_set_block(struct rfki
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
- rfkill_uevent(rfkill);
+ rfkill_event(rfkill);
}
+#ifdef CONFIG_RFKILL_INPUT
/**
* __rfkill_switch_all - Toggle state of all switches of given type
* @type: type of interfaces to be affected
@@ -331,6 +386,7 @@ void rfkill_epo(void)
rfkill_global_states[i].def = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
+
mutex_unlock(&rfkill_global_mutex);
}
@@ -391,6 +447,7 @@ bool rfkill_get_global_sw_state(const en
{
return rfkill_global_states[type].cur;
}
+#endif
void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
{
@@ -537,6 +594,15 @@ static ssize_t rfkill_type_show(struct d
return sprintf(buf, "%s\n", rfkill_get_type_str(rfkill->type));
}
+static ssize_t rfkill_idx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rfkill *rfkill = to_rfkill(dev);
+
+ return sprintf(buf, "%d\n", rfkill->idx);
+}
+
static u8 user_state_from_blocked(unsigned long state)
{
if (state & RFKILL_BLOCK_HW)
@@ -594,6 +660,7 @@ static ssize_t rfkill_claim_store(struct
static struct device_attribute rfkill_dev_attrs[] = {
__ATTR(name, S_IRUGO, rfkill_name_show, NULL),
__ATTR(type, S_IRUGO, rfkill_type_show, NULL),
+ __ATTR(index, S_IRUGO, rfkill_idx_show, NULL),
__ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store),
__ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store),
__ATTR_NULL
@@ -708,7 +775,7 @@ struct rfkill * __must_check rfkill_allo
if (WARN_ON(!name))
return NULL;
- if (WARN_ON(type >= NUM_RFKILL_TYPES))
+ if (WARN_ON(type == RFKILL_TYPE_ALL || type >= NUM_RFKILL_TYPES))
return NULL;
rfkill = kzalloc(sizeof(*rfkill), GFP_KERNEL);
@@ -754,7 +821,9 @@ static void rfkill_uevent_work(struct wo
rfkill = container_of(work, struct rfkill, uevent_work);
- rfkill_uevent(rfkill);
+ mutex_lock(&rfkill_global_mutex);
+ rfkill_event(rfkill);
+ mutex_unlock(&rfkill_global_mutex);
}
static void rfkill_sync_work(struct work_struct *work)
@@ -785,6 +854,7 @@ int __must_check rfkill_register(struct
goto unlock;
}
+ rfkill->idx = rfkill_no;
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
@@ -819,6 +889,7 @@ int __must_check rfkill_register(struct
INIT_WORK(&rfkill->sync_work, rfkill_sync_work);
schedule_work(&rfkill->sync_work);
+ rfkill_send_events(rfkill, RFKILL_OP_ADD);
mutex_unlock(&rfkill_global_mutex);
return 0;
@@ -848,6 +919,7 @@ void rfkill_unregister(struct rfkill *rf
device_del(&rfkill->dev);
mutex_lock(&rfkill_global_mutex);
+ rfkill_send_events(rfkill, RFKILL_OP_DEL);
list_del_init(&rfkill->node);
mutex_unlock(&rfkill_global_mutex);
@@ -862,6 +934,193 @@ void rfkill_destroy(struct rfkill *rfkil
}
EXPORT_SYMBOL(rfkill_destroy);
+static int rfkill_fop_open(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data;
+ struct rfkill *rfkill;
+ struct rfkill_int_event *ev, *tmp;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&data->events);
+ mutex_init(&data->mtx);
+ init_waitqueue_head(&data->read_wait);
+
+ mutex_lock(&rfkill_global_mutex);
+ mutex_lock(&data->mtx);
+ /*
+ * start getting events from elsewhere but hold mtx to get
+ * startup events added first
+ */
+ list_add(&data->list, &rfkill_fds);
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ goto free;
+ rfkill_fill_event(&ev->ev, rfkill, RFKILL_OP_ADD);
+ list_add_tail(&ev->list, &data->events);
+ }
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+
+ file->private_data = data;
+
+ return nonseekable_open(inode, file);
+
+ free:
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+ kfree(data);
+ return -ENOMEM;
+}
+
+static unsigned int rfkill_fop_poll(struct file *file, poll_table *wait)
+{
+ struct rfkill_data *data = file->private_data;
+ unsigned int res = POLLOUT | POLLWRNORM;
+
+ poll_wait(file, &data->read_wait, wait);
+
+ mutex_lock(&data->mtx);
+ if (!list_empty(&data->events))
+ res = POLLIN | POLLRDNORM;
+ mutex_unlock(&data->mtx);
+
+ return res;
+}
+
+static bool rfkill_readable(struct rfkill_data *data)
+{
+ bool r;
+
+ mutex_lock(&data->mtx);
+ r = !list_empty(&data->events);
+ mutex_unlock(&data->mtx);
+
+ return r;
+}
+
+static ssize_t rfkill_fop_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev;
+ unsigned long sz;
+ int ret;
+
+ mutex_lock(&data->mtx);
+
+ while (list_empty(&data->events)) {
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ goto out;
+ }
+ mutex_unlock(&data->mtx);
+ ret = wait_event_interruptible(data->read_wait,
+ rfkill_readable(data));
+ mutex_lock(&data->mtx);
+
+ if (ret)
+ goto out;
+ }
+
+ ev = list_first_entry(&data->events, struct rfkill_int_event,
+ list);
+
+ sz = min_t(unsigned long, sizeof(ev->ev), count);
+ ret = sz;
+ if (copy_to_user(buf, &ev->ev, sz))
+ ret = -EFAULT;
+
+ list_del(&ev->list);
+ kfree(ev);
+ out:
+ mutex_unlock(&data->mtx);
+ return ret;
+}
+
+static ssize_t rfkill_fop_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill *rfkill;
+ struct rfkill_event ev;
+
+ /* we don't need the 'hard' variable but accept it */
+ if (count < sizeof(ev) - 1)
+ return -EINVAL;
+
+ if (copy_from_user(&ev, buf, sizeof(ev) - 1))
+ return -EFAULT;
+
+ if (ev.op != RFKILL_OP_CHANGE && ev.op != RFKILL_OP_CHANGE_ALL)
+ return -EINVAL;
+
+ if (ev.type >= NUM_RFKILL_TYPES)
+ return -EINVAL;
+
+ mutex_lock(&rfkill_global_mutex);
+
+ if (ev.op == RFKILL_OP_CHANGE_ALL) {
+ if (ev.type == RFKILL_TYPE_ALL) {
+ enum rfkill_type i;
+ for (i = 0; i < NUM_RFKILL_TYPES; i++)
+ rfkill_global_states[i].cur = ev.soft;
+ } else {
+ rfkill_global_states[ev.type].cur = ev.soft;
+ }
+ }
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ if (rfkill->idx != ev.idx && ev.op != RFKILL_OP_CHANGE_ALL)
+ continue;
+
+ if (rfkill->type != ev.type && ev.type != RFKILL_TYPE_ALL)
+ continue;
+
+ rfkill_set_block(rfkill, ev.soft);
+ }
+ mutex_unlock(&rfkill_global_mutex);
+
+ return count;
+}
+
+static int rfkill_fop_release(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev, *tmp;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_del(&data->list);
+ mutex_unlock(&rfkill_global_mutex);
+
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+
+ kfree(data);
+
+ return 0;
+}
+
+static const struct file_operations rfkill_fops = {
+ .open = rfkill_fop_open,
+ .read = rfkill_fop_read,
+ .write = rfkill_fop_write,
+ .poll = rfkill_fop_poll,
+ .release = rfkill_fop_release,
+};
+
+static struct miscdevice rfkill_miscdev = {
+ .name = "rfkill",
+ .fops = &rfkill_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
static int __init rfkill_init(void)
{
@@ -875,10 +1134,19 @@ static int __init rfkill_init(void)
if (error)
goto out;
+ error = misc_register(&rfkill_miscdev);
+ if (error) {
+ class_unregister(&rfkill_class);
+ goto out;
+ }
+
#ifdef CONFIG_RFKILL_INPUT
error = rfkill_handler_init();
- if (error)
+ if (error) {
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
+ goto out;
+ }
#endif
out:
@@ -891,6 +1159,7 @@ static void __exit rfkill_exit(void)
#ifdef CONFIG_RFKILL_INPUT
rfkill_handler_exit();
#endif
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
}
module_exit(rfkill_exit);
--- wireless-testing.orig/net/rfkill/Kconfig 2009-05-28 17:56:32.000000000 +0200
+++ wireless-testing/net/rfkill/Kconfig 2009-05-28 17:56:47.000000000 +0200
@@ -18,7 +18,7 @@ config RFKILL_LEDS
default y
config RFKILL_INPUT
- bool
+ bool "RF switch input support"
depends on RFKILL
depends on INPUT = y || RFKILL = INPUT
- default y
+ default y if !EMBEDDED
--- wireless-testing.orig/Documentation/feature-removal-schedule.txt 2009-05-28 17:56:32.000000000 +0200
+++ wireless-testing/Documentation/feature-removal-schedule.txt 2009-05-28 17:56:47.000000000 +0200
@@ -437,3 +437,10 @@ Why: Superseded by tdfxfb. I2C/DDC suppo
driver but this caused driver conflicts.
Who: Jean Delvare <[email protected]>
Krzysztof Helt <[email protected]>
+
+---------------------------
+
+What: CONFIG_RFKILL_INPUT
+When: 2.6.33
+Why: Should be implemented in userspace, policy daemon.
+Who: Johannes Berg <[email protected]>
> How should userspace test CONFIG_RFKILL_INPUT to determine whether
> it's safe to start the daemon? With the old core, debian-eeepc
> scripts check if the module rfkill-input exists (which should work
> even if it's built in). If it exists, the scripts don't perform any
> rfkill actions. (Yeah, according to the doc this is not allowed
> because the scripts don't use "claim", but you can see how it's
> useful).
>
> The new rfkill-input isn't a module, so I'm not sure how your daemon
> would test for it.
Maybe we should add an ioctl that disables rfkill-input if present.
johannes
Hi Johannes,
> > How should userspace test CONFIG_RFKILL_INPUT to determine whether
> > it's safe to start the daemon? With the old core, debian-eeepc
> > scripts check if the module rfkill-input exists (which should work
> > even if it's built in). If it exists, the scripts don't perform any
> > rfkill actions. (Yeah, according to the doc this is not allowed
> > because the scripts don't use "claim", but you can see how it's
> > useful).
> >
> > The new rfkill-input isn't a module, so I'm not sure how your daemon
> > would test for it.
>
> Maybe we should add an ioctl that disables rfkill-input if present.
I am against it. Can we just add a module parameter that allows us to
disable it. I am against cluttering a new interface with legacy stuff
since we are removing rfkill-input and replacing it by rfkilld anyway in
a near future (meaning when I am back from vacation).
Regards
Marcel
Hi Johannes,
> The new code added by this patch will make rfkill create
> a misc character device /dev/rfkill that userspace can use
> to control rfkill soft blocks and get status of devices as
> well as events when the status changes.
>
> Using it is very simple -- when you open it you can read
> a number of times to get the initial state, and every
> further read blocks (you can poll) on getting the next
> event from the kernel. The same structure you read is
> also used when writing to it to change the soft block of
> a given device, all devices of a given type, or all
> devices.
>
> This also makes CONFIG_RFKILL_INPUT selectable again in
> order to be able to test without it present since its
> functionality can now be replaced by userspace entirely
> and distros and users may not want the input part of
> rfkill interfering with their userspace code. We will
> also write a userspace daemon to handle all that and
> consequently add the input code to the feature removal
> schedule.
>
> Signed-off-by: Johannes Berg <[email protected]>
> ---
> v4: set default global state from userspace for rfkill hotplug
> (pointed out by Marcel)
I have tested this patch with a prototype code for ConnMan and it works
fine here.
Acked-by: Marcel Holtmann <[email protected]>
Tested-by: Marcel Holtmann <[email protected]>
Regards
Marcel
On Sun, 2009-05-31 at 21:03 +0200, Marcel Holtmann wrote:
> Hi Johannes,
>
> > > How should userspace test CONFIG_RFKILL_INPUT to determine whether
> > > it's safe to start the daemon? With the old core, debian-eeepc
> > > scripts check if the module rfkill-input exists (which should work
> > > even if it's built in). If it exists, the scripts don't perform any
> > > rfkill actions. (Yeah, according to the doc this is not allowed
> > > because the scripts don't use "claim", but you can see how it's
> > > useful).
> > >
> > > The new rfkill-input isn't a module, so I'm not sure how your daemon
> > > would test for it.
> >
> > Maybe we should add an ioctl that disables rfkill-input if present.
>
> I am against it. Can we just add a module parameter that allows us to
> disable it. I am against cluttering a new interface with legacy stuff
> since we are removing rfkill-input and replacing it by rfkilld anyway in
> a near future (meaning when I am back from vacation).
Module parameter to what? Module parameters are almost always the wrong
thing to do. Or, just don't built rfkill_input it into your kernel
Dan
The new code added by this patch will make rfkill create
a misc character device /dev/rfkill that userspace can use
to control rfkill soft blocks and get status of devices as
well as events when the status changes.
Using it is very simple -- when you open it you can read
a number of times to get the initial state, and every
further read blocks (you can poll) on getting the next
event from the kernel. The same structure you read is
also used when writing to it to change the soft block of
a given device, all devices of a given type, or all
devices.
This also makes CONFIG_RFKILL_INPUT selectable again in
order to be able to test without it present since its
functionality can now be replaced by userspace entirely
and distros and users may not want the input part of
rfkill interfering with their userspace code. We will
also write a userspace daemon to handle all that and
consequently add the input code to the feature removal
schedule.
Signed-off-by: Johannes Berg <[email protected]>
---
Sorry, had the patch at the wrong spot in the queue.
Documentation/feature-removal-schedule.txt | 7
include/linux/rfkill.h | 79 ++++++--
net/rfkill/Kconfig | 4
net/rfkill/core.c | 265 ++++++++++++++++++++++++++++-
4 files changed, 324 insertions(+), 31 deletions(-)
--- wireless-testing.orig/include/linux/rfkill.h 2009-05-28 17:56:41.000000000 +0200
+++ wireless-testing/include/linux/rfkill.h 2009-05-28 17:56:47.000000000 +0200
@@ -22,34 +22,17 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#include <linux/types.h>
/* define userspace visible states */
#define RFKILL_STATE_SOFT_BLOCKED 0
#define RFKILL_STATE_UNBLOCKED 1
#define RFKILL_STATE_HARD_BLOCKED 2
-/* and that's all userspace gets */
-#ifdef __KERNEL__
-/* don't allow anyone to use these in the kernel */
-enum rfkill_user_states {
- RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
- RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
- RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
-};
-#undef RFKILL_STATE_SOFT_BLOCKED
-#undef RFKILL_STATE_UNBLOCKED
-#undef RFKILL_STATE_HARD_BLOCKED
-
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/list.h>
-#include <linux/mutex.h>
-#include <linux/device.h>
-#include <linux/leds.h>
-
/**
* enum rfkill_type - type of rfkill switch.
*
+ * @RFKILL_TYPE_ALL: toggles all switches (userspace only)
* @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device.
* @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device.
* @RFKILL_TYPE_UWB: switch is on a ultra wideband device.
@@ -58,6 +41,7 @@ enum rfkill_user_states {
* @NUM_RFKILL_TYPES: number of defined rfkill types
*/
enum rfkill_type {
+ RFKILL_TYPE_ALL = 0,
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
@@ -66,6 +50,57 @@ enum rfkill_type {
NUM_RFKILL_TYPES,
};
+/**
+ * enum rfkill_operation - operation types
+ * @RFKILL_OP_ADD: a device was added
+ * @RFKILL_OP_DEL: a device was removed
+ * @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device
+ * @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all)
+ */
+enum rfkill_operation {
+ RFKILL_OP_ADD = 0,
+ RFKILL_OP_DEL,
+ RFKILL_OP_CHANGE,
+ RFKILL_OP_CHANGE_ALL,
+};
+
+/**
+ * struct rfkill_event - events for userspace on /dev/rfkill
+ * @idx: index of dev rfkill
+ * @type: type of the rfkill struct
+ * @op: operation code
+ * @hard: hard state (0/1)
+ * @soft: soft state (0/1)
+ *
+ * Structure used for userspace communication on /dev/rfkill,
+ * used for events from the kernel and control to the kernel.
+ */
+struct rfkill_event {
+ __u32 idx;
+ __u8 type;
+ __u8 op;
+ __u8 soft, hard;
+} __packed;
+
+/* and that's all userspace gets */
+#ifdef __KERNEL__
+/* don't allow anyone to use these in the kernel */
+enum rfkill_user_states {
+ RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
+ RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
+ RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
+};
+#undef RFKILL_STATE_SOFT_BLOCKED
+#undef RFKILL_STATE_UNBLOCKED
+#undef RFKILL_STATE_HARD_BLOCKED
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+
/* this is opaque */
struct rfkill;
@@ -84,11 +119,7 @@ struct rfkill;
* the rfkill core query your driver before setting a requested
* block.
* @set_block: turn the transmitter on (blocked == false) or off
- * (blocked == true) -- this is called only while the transmitter
- * is not hard-blocked, but note that the core's view of whether
- * the transmitter is hard-blocked might differ from your driver's
- * view due to race conditions, so it is possible that it is still
- * called at the same time as you are calling rfkill_set_hw_state().
+ * (blocked == true) -- ignore and return 0 when hard blocked.
* This callback must be assigned.
*/
struct rfkill_ops {
--- wireless-testing.orig/net/rfkill/core.c 2009-05-28 17:56:41.000000000 +0200
+++ wireless-testing/net/rfkill/core.c 2009-05-28 17:57:32.000000000 +0200
@@ -28,6 +28,10 @@
#include <linux/mutex.h>
#include <linux/rfkill.h>
#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
#include "rfkill.h"
@@ -49,6 +53,8 @@ struct rfkill {
unsigned long state;
+ u32 idx;
+
bool registered;
bool suspended;
@@ -69,6 +75,17 @@ struct rfkill {
};
#define to_rfkill(d) container_of(d, struct rfkill, dev)
+struct rfkill_int_event {
+ struct list_head list;
+ struct rfkill_event ev;
+};
+
+struct rfkill_data {
+ struct list_head list;
+ struct list_head events;
+ struct mutex mtx;
+ wait_queue_head_t read_wait;
+};
MODULE_AUTHOR("Ivo van Doorn <[email protected]>");
@@ -90,6 +107,7 @@ MODULE_LICENSE("GPL");
*/
static LIST_HEAD(rfkill_list); /* list of registered rf switches */
static DEFINE_MUTEX(rfkill_global_mutex);
+static LIST_HEAD(rfkill_fds); /* list of open fds of /dev/rfkill */
static unsigned int rfkill_default_state = 1;
module_param_named(default_state, rfkill_default_state, uint, 0444);
@@ -171,12 +189,48 @@ static inline void rfkill_led_trigger_un
}
#endif /* CONFIG_RFKILL_LEDS */
-static void rfkill_uevent(struct rfkill *rfkill)
+static void rfkill_fill_event(struct rfkill_event *ev, struct rfkill *rfkill,
+ enum rfkill_operation op)
+{
+ unsigned long flags;
+
+ ev->idx = rfkill->idx;
+ ev->type = rfkill->type;
+ ev->op = op;
+
+ spin_lock_irqsave(&rfkill->lock, flags);
+ ev->hard = !!(rfkill->state & RFKILL_BLOCK_HW);
+ ev->soft = !!(rfkill->state & (RFKILL_BLOCK_SW |
+ RFKILL_BLOCK_SW_PREV));
+ spin_unlock_irqrestore(&rfkill->lock, flags);
+}
+
+static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op)
+{
+ struct rfkill_data *data;
+ struct rfkill_int_event *ev;
+
+ list_for_each_entry(data, &rfkill_fds, list) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ continue;
+ rfkill_fill_event(&ev->ev, rfkill, op);
+ mutex_lock(&data->mtx);
+ list_add_tail(&ev->list, &data->events);
+ mutex_unlock(&data->mtx);
+ wake_up_interruptible(&data->read_wait);
+ }
+}
+
+static void rfkill_event(struct rfkill *rfkill)
{
if (!rfkill->registered || rfkill->suspended)
return;
kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);
+
+ /* also send event to /dev/rfkill */
+ rfkill_send_events(rfkill, RFKILL_OP_CHANGE);
}
static bool __rfkill_set_hw_state(struct rfkill *rfkill,
@@ -260,9 +314,10 @@ static void rfkill_set_block(struct rfki
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
- rfkill_uevent(rfkill);
+ rfkill_event(rfkill);
}
+#ifdef CONFIG_RFKILL_INPUT
/**
* __rfkill_switch_all - Toggle state of all switches of given type
* @type: type of interfaces to be affected
@@ -331,6 +386,7 @@ void rfkill_epo(void)
rfkill_global_states[i].def = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
+
mutex_unlock(&rfkill_global_mutex);
}
@@ -391,6 +447,7 @@ bool rfkill_get_global_sw_state(const en
{
return rfkill_global_states[type].cur;
}
+#endif
void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
{
@@ -537,6 +594,15 @@ static ssize_t rfkill_type_show(struct d
return sprintf(buf, "%s\n", rfkill_get_type_str(rfkill->type));
}
+static ssize_t rfkill_idx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rfkill *rfkill = to_rfkill(dev);
+
+ return sprintf(buf, "%d\n", rfkill->idx);
+}
+
static u8 user_state_from_blocked(unsigned long state)
{
if (state & RFKILL_BLOCK_HW)
@@ -594,6 +660,7 @@ static ssize_t rfkill_claim_store(struct
static struct device_attribute rfkill_dev_attrs[] = {
__ATTR(name, S_IRUGO, rfkill_name_show, NULL),
__ATTR(type, S_IRUGO, rfkill_type_show, NULL),
+ __ATTR(index, S_IRUGO, rfkill_idx_show, NULL),
__ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store),
__ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store),
__ATTR_NULL
@@ -708,7 +775,7 @@ struct rfkill * __must_check rfkill_allo
if (WARN_ON(!name))
return NULL;
- if (WARN_ON(type >= NUM_RFKILL_TYPES))
+ if (WARN_ON(type == RFKILL_TYPE_ALL || type >= NUM_RFKILL_TYPES))
return NULL;
rfkill = kzalloc(sizeof(*rfkill), GFP_KERNEL);
@@ -754,7 +821,9 @@ static void rfkill_uevent_work(struct wo
rfkill = container_of(work, struct rfkill, uevent_work);
- rfkill_uevent(rfkill);
+ mutex_lock(&rfkill_global_mutex);
+ rfkill_event(rfkill);
+ mutex_unlock(&rfkill_global_mutex);
}
static void rfkill_sync_work(struct work_struct *work)
@@ -785,6 +854,7 @@ int __must_check rfkill_register(struct
goto unlock;
}
+ rfkill->idx = rfkill_no;
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
@@ -819,6 +889,7 @@ int __must_check rfkill_register(struct
INIT_WORK(&rfkill->sync_work, rfkill_sync_work);
schedule_work(&rfkill->sync_work);
+ rfkill_send_events(rfkill, RFKILL_OP_ADD);
mutex_unlock(&rfkill_global_mutex);
return 0;
@@ -848,6 +919,7 @@ void rfkill_unregister(struct rfkill *rf
device_del(&rfkill->dev);
mutex_lock(&rfkill_global_mutex);
+ rfkill_send_events(rfkill, RFKILL_OP_DEL);
list_del_init(&rfkill->node);
mutex_unlock(&rfkill_global_mutex);
@@ -862,6 +934,179 @@ void rfkill_destroy(struct rfkill *rfkil
}
EXPORT_SYMBOL(rfkill_destroy);
+static int rfkill_fop_open(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data;
+ struct rfkill *rfkill;
+ struct rfkill_int_event *ev, *tmp;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&data->events);
+ mutex_init(&data->mtx);
+ init_waitqueue_head(&data->read_wait);
+
+ mutex_lock(&rfkill_global_mutex);
+ mutex_lock(&data->mtx);
+ /*
+ * start getting events from elsewhere but hold mtx to get
+ * startup events added first
+ */
+ list_add(&data->list, &rfkill_fds);
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ goto free;
+ rfkill_fill_event(&ev->ev, rfkill, RFKILL_OP_ADD);
+ list_add_tail(&ev->list, &data->events);
+ }
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+
+ file->private_data = data;
+
+ return nonseekable_open(inode, file);
+
+ free:
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+ kfree(data);
+ return -ENOMEM;
+}
+
+static unsigned int rfkill_fop_poll(struct file *file, poll_table *wait)
+{
+ struct rfkill_data *data = file->private_data;
+ unsigned int res = POLLOUT | POLLWRNORM;
+
+ poll_wait(file, &data->read_wait, wait);
+
+ mutex_lock(&data->mtx);
+ if (!list_empty(&data->events))
+ res = POLLIN | POLLRDNORM;
+ mutex_unlock(&data->mtx);
+
+ return res;
+}
+
+static bool rfkill_readable(struct rfkill_data *data)
+{
+ bool r;
+
+ mutex_lock(&data->mtx);
+ r = !list_empty(&data->events);
+ mutex_unlock(&data->mtx);
+
+ return r;
+}
+
+static ssize_t rfkill_fop_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev;
+ unsigned long sz;
+ int ret;
+
+ mutex_lock(&data->mtx);
+
+ while (list_empty(&data->events)) {
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ goto out;
+ }
+ mutex_unlock(&data->mtx);
+ ret = wait_event_interruptible(data->read_wait,
+ rfkill_readable(data));
+ mutex_lock(&data->mtx);
+
+ if (ret)
+ goto out;
+ }
+
+ ev = list_first_entry(&data->events, struct rfkill_int_event,
+ list);
+
+ sz = min_t(unsigned long, sizeof(ev->ev), count);
+ ret = sz;
+ if (copy_to_user(buf, &ev->ev, sz))
+ ret = -EFAULT;
+
+ list_del(&ev->list);
+ kfree(ev);
+ out:
+ mutex_unlock(&data->mtx);
+ return ret;
+}
+
+static ssize_t rfkill_fop_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill *rfkill;
+ struct rfkill_event ev;
+
+ /* we don't need the 'hard' variable but accept it */
+ if (count < sizeof(ev) - 1)
+ return -EINVAL;
+
+ if (copy_from_user(&ev, buf, sizeof(ev) - 1))
+ return -EFAULT;
+
+ if (ev.op != RFKILL_OP_CHANGE && ev.op != RFKILL_OP_CHANGE_ALL)
+ return -EINVAL;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ if (rfkill->idx != ev.idx && ev.op != RFKILL_OP_CHANGE_ALL)
+ continue;
+
+ if (rfkill->type != ev.type && ev.type != RFKILL_TYPE_ALL)
+ continue;
+
+ rfkill_set_block(rfkill, ev.soft);
+ }
+ mutex_unlock(&rfkill_global_mutex);
+
+ return count;
+}
+
+static int rfkill_fop_release(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev, *tmp;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_del(&data->list);
+ mutex_unlock(&rfkill_global_mutex);
+
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+
+ kfree(data);
+
+ return 0;
+}
+
+static const struct file_operations rfkill_fops = {
+ .open = rfkill_fop_open,
+ .read = rfkill_fop_read,
+ .write = rfkill_fop_write,
+ .poll = rfkill_fop_poll,
+ .release = rfkill_fop_release,
+};
+
+static struct miscdevice rfkill_miscdev = {
+ .name = "rfkill",
+ .fops = &rfkill_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
static int __init rfkill_init(void)
{
@@ -875,10 +1120,19 @@ static int __init rfkill_init(void)
if (error)
goto out;
+ error = misc_register(&rfkill_miscdev);
+ if (error) {
+ class_unregister(&rfkill_class);
+ goto out;
+ }
+
#ifdef CONFIG_RFKILL_INPUT
error = rfkill_handler_init();
- if (error)
+ if (error) {
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
+ goto out;
+ }
#endif
out:
@@ -891,6 +1145,7 @@ static void __exit rfkill_exit(void)
#ifdef CONFIG_RFKILL_INPUT
rfkill_handler_exit();
#endif
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
}
module_exit(rfkill_exit);
--- wireless-testing.orig/net/rfkill/Kconfig 2009-05-28 17:56:32.000000000 +0200
+++ wireless-testing/net/rfkill/Kconfig 2009-05-28 17:56:47.000000000 +0200
@@ -18,7 +18,7 @@ config RFKILL_LEDS
default y
config RFKILL_INPUT
- bool
+ bool "RF switch input support"
depends on RFKILL
depends on INPUT = y || RFKILL = INPUT
- default y
+ default y if !EMBEDDED
--- wireless-testing.orig/Documentation/feature-removal-schedule.txt 2009-05-28 17:56:32.000000000 +0200
+++ wireless-testing/Documentation/feature-removal-schedule.txt 2009-05-28 17:56:47.000000000 +0200
@@ -437,3 +437,10 @@ Why: Superseded by tdfxfb. I2C/DDC suppo
driver but this caused driver conflicts.
Who: Jean Delvare <[email protected]>
Krzysztof Helt <[email protected]>
+
+---------------------------
+
+What: CONFIG_RFKILL_INPUT
+When: 2.6.33
+Why: Should be implemented in userspace, policy daemon.
+Who: Johannes Berg <[email protected]>
Hi Johannes,
> The new code added by this patch will make rfkill create
> a misc character device /dev/rfkill that userspace can use
> to control rfkill soft blocks and get status of devices as
> well as events when the status changes.
>
> Using it is very simple -- when you open it you can read
> a number of times to get the initial state, and every
> further read blocks (you can poll) on getting the next
> event from the kernel. The same structure you read is
> also used when writing to it to change the soft block of
> a given device, all devices of a given type, or all
> devices.
>
> This also makes CONFIG_RFKILL_INPUT selectable again in
> order to be able to test without it present since its
> functionality can now be replaced by userspace entirely
> and distros and users may not want the input part of
> rfkill interfering with their userspace code. We will
> also write a userspace daemon to handle all that and
> consequently add the input code to the feature removal
> schedule.
patch looks really good and the interface is nice and simple :)
> Signed-off-by: Johannes Berg <[email protected]>
Acked-by: Marcel Holtmann <[email protected]>
Regards
Marcel
On Sun, 31 May 2009, Johannes Berg wrote:
> You have a point there, but I'm not sure it even cares? When restarted
Yes, it does. Some platforms retain state across shutdown, ThinkPads
included...
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Thu, 04 Jun 2009, Marcel Holtmann wrote:
> > > > > Forget about this EPO crap. That is just a stupid concept anyway.
> > > >
> > > > Is it? So, if I use the _hardware switch_ on my laptop to kill all
> > > > internal radios, it shouldn't be enforced by the OS on extra radios I
> > > > plugged? Or on shitty internal WLAN cards that doesn't tie properly to
> > > > the mini-pci and mini-pcie hardware kill lines?
> > > >
> > > > And any userspace PoS program can decide to bring up such radios that
> > > > are not hardware-killed even if I am clearly trying to disable them all?
> > >
> > > You hardkilled. Of course it should bring down everything. If you
> > > don't want to hardkill, then disable specific radios via /sys or some
> > > UI. The hardswitch is the "eject" button.
> >
> > That's exactly my point. I want EPO to mean "no, you cannot turn this crap
> > on, GO AWAY" for anyone but root (or whomever SELinux allowed to do it,
> > etc).
>
> let me repeat, this is what you want and that is policy. Feel free to
> implement that in userspace. Leave such policy out of the kernel.
EPO is not policy, it is functionality. Entering EPO and going out of
EPO state would be policy, though.
What happens while in EPO state is NOT policy. The very definition of
EPO is that, once enabled, it cannot be overriden until it is disabled
(and it is usual to have strong rules for how it can be disabled, but
this is not important right now). EPO is a term from real world
engineering, its meaning is set in stone.
You can remove the EPO functionality and add a "switch all radios off"
and "switch all radios on" accell commands. But that's all you can do
if you depend on userspace. It will -not- be EPO.
You can have EPO in the kernel, and have userspace command the kernel to
enter and exit EPO state.
I have more to say on that, and will do so in a separate email, but I
don't have the time to properly compose it right now.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Mon, 2009-06-01 at 16:47 +0200, Marcel Holtmann wrote:
> > This adds the two following things to /dev/rfkill:
> > 1) notification to userspace with a new operation
> > RFKILL_OP_NVS_REPORT about default states restored
> > from platform non-volatile storage
>
> I really don't understand why this is needed. What benefit does it give
> us compared to just sent OP_CHANGE and OP_CHANGE as an update. My X200
> for example does this anyway on suspend/resume.
>
> So what is rfkilld suppose to be doing when receiving this report? What
> is the expected behavior? Why do we bother with multi-OS crap here? I am
> really unclear what are we trying to solve here.
Well, apparently people want to use the BIOS to store the current rfkill
state -- this lets them do that. Now, I don't care if you implement it
or not, but now they could.
The point really is that when rfkilld starts up it has to impose a
policy, and the question is what that policy is and where it is stored,
if desired, across shutdowns.
johannes
Johannes Berg wrote:
> On Sun, 2009-05-31 at 21:03 +0200, Marcel Holtmann wrote:
>
>
>>> Maybe we should add an ioctl that disables rfkill-input if present.
>>>
>> I am against it. Can we just add a module parameter that allows us to
>> disable it. I am against cluttering a new interface with legacy stuff
>> since we are removing rfkill-input and replacing it by rfkilld anyway in
>> a near future (meaning when I am back from vacation).
>>
>
> Right. But you'll get a bunch of people get it mixed up. I think I would
> prefer adding the ioctl, but having it only when rfkill-input is
> compiled in, so userspace would always just get -ENOSYS if there's no
> rfkill-input present, which means the "cluttering" of the interface
> occurs only for as long as we have rfkill-input and at some point you
> would just remove that one line of code from rfkilld.
>
> johannes
>
Yeah, that's much better. If you had a module parameter then you would
have to keep it around, even after the removal of the kernel
rfkill-input. Otherwise people have to change modprobe.conf _again_
when they upgrade the kernel, because rfkill-as-module will refuse to load.
Thanks
Alan
On Sun, 07 Jun 2009, Marcel Holtmann wrote:
> > I don't think we should expect userspace to know whether or not a device
> > has a persistent state. Yes, it _could_ maintain whitelists, but why
> > should it have to if the driver already knows?
>
> If you want that, then the best approach seems an extra sysfs attribute
> for this. It is not intrusive on the event API and lets udev etc. have
> these information, too.
I have no problems with either approach. As long as the information of
which devices have restored their initial state from NVS is available to
userspace, it is enough.
Do note that this information also needs to be available for resume (state
should be checkpointed to NVS on sleep, and restored from NVS on resume. I
believe tpacpi does this, but if it doesn't, I will fix it eventually).
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Hi Henrique,
> > > > Forget about this EPO crap. That is just a stupid concept anyway.
> > >
> > > Is it? So, if I use the _hardware switch_ on my laptop to kill all
> > > internal radios, it shouldn't be enforced by the OS on extra radios I
> > > plugged? Or on shitty internal WLAN cards that doesn't tie properly to
> > > the mini-pci and mini-pcie hardware kill lines?
> > >
> > > And any userspace PoS program can decide to bring up such radios that
> > > are not hardware-killed even if I am clearly trying to disable them all?
> >
> > You hardkilled. Of course it should bring down everything. If you
> > don't want to hardkill, then disable specific radios via /sys or some
> > UI. The hardswitch is the "eject" button.
>
> That's exactly my point. I want EPO to mean "no, you cannot turn this crap
> on, GO AWAY" for anyone but root (or whomever SELinux allowed to do it,
> etc).
let me repeat, this is what you want and that is policy. Feel free to
implement that in userspace. Leave such policy out of the kernel.
Regards
Marcel
On Sun, 2009-05-31 at 21:01 +0200, Marcel Holtmann wrote:
> > You have a point there, but I'm not sure it even cares? When restarted
> > it will probably want to impose its current policy anyway? It would be
> > easy to add that we send the global default value for newly added ones
> > too but I'm not sure it's necessary -- Marcel?
>
> we can be smart and send an additional CHANGE_ALL when opening the
> control device if it is set. We can also just send these anyway. Doesn't
> really matter? Does it?
Well, I don't think we want/need this.
See, Henrique says that the use case is Thinkpads which store the
previous state in the BIOS. But that matters to you only if you use a
mixture of operation systems, which we don't have to support.
On the other hand, people on machines that don't store the rfkill state
in the BIOS might care about having their machine boot up with the same
rfkill state(s) as they shut down with, so the sane thing to do would be
to have rfkilld store the state persistently, and then recover it at
boot. At which point the BIOS state becomes irrelevant and a detail we
actually end up not even _wanting_ to support because it means we need
to be aware of machine differences in userspace.
johannes
Hi Johannes,
> > > Maybe we should add an ioctl that disables rfkill-input if present.
> >
> > I am against it. Can we just add a module parameter that allows us to
> > disable it. I am against cluttering a new interface with legacy stuff
> > since we are removing rfkill-input and replacing it by rfkilld anyway in
> > a near future (meaning when I am back from vacation).
>
> Right. But you'll get a bunch of people get it mixed up. I think I would
> prefer adding the ioctl, but having it only when rfkill-input is
> compiled in, so userspace would always just get -ENOSYS if there's no
> rfkill-input present, which means the "cluttering" of the interface
> occurs only for as long as we have rfkill-input and at some point you
> would just remove that one line of code from rfkilld.
I can agree to your reasoning here. So that is fine with me.
Regards
Marcel
On Sun, 07 Jun 2009, Alan Jenkins wrote:
> 1) remove rfkill_set_global_sw_state()
> 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
> registration, setting a flag.
> 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
> events, tho it may as well be set in all events)
> 4) rfkill-input preserves existing behaviour - *if enabled* - by
> initializing the global state from individual devices which have NVS.
> (As before, each _type_ of rfkill device has its own global state).
> 5) rfkill devices with NVS will have their current state preserved,
> so long as the global state has not yet been set (by userspace or by
> rfkill-input). Of course userspace can change the state in response
> to the device being added.
I can agree to that, it will avoid the regression I have been
complaining about, and seems to address the major complaints against
what we have now...
I think it should probably be enhanced (it doesn't have to be enhanced
_now_, however) to let the core call back into NVS-providing drivers to
checkpoint NVS state. But that's not something I feel strongly about.
Otherwise, the driver will checkpoint to NVS whatever is the state of
its rfkill device, which could have been changed outside of the global
scope. While that's exacly what we do right now, it is arguably not the
best way to go about it (i.e. it is broken).
> Comments?
Let's do it :-)
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
rfkill_set_global_sw_state() (previously rfkill_set_default()) will no
longer be exported by the rewritten rfkill core.
Instead, platform drivers which can provide persistent soft-rfkill state
across power-down/reboot should indicate their initial state by calling
rfkill_set_sw_state() before registration. Otherwise, they will be
initialized to a default value during registration by a set_block call.
We remove existing calls to rfkill_set_sw_state() which happen before
registration, since these had no effect in the old model. If these
drivers do have persistent state, the calls can be put back (subject
to testing :-). This affects hp-wmi and acer-wmi.
Drivers with persistent state will affect the global state only if
rfkill-input is enabled. This is required, otherwise booting with
wireless soft-blocked and pressing the wireless-toggle key once would
have no apparent effect. This special case will be removed in future
along with rfkill-input, in favour of a more flexible userspace daemon
(see Documentation/feature-removal-schedule.txt).
Now rfkill_global_states[n].def is only used to preserve global states
over EPO, it is renamed to ".sav".
Signed-off-by: Alan Jenkins <[email protected]>
---
v2:
- fix thinkpad-acpi message to reflect altered error-case behaviour
- remove !CONFIG_RFKILL stub for rfkill_set_global_sw_state()
drivers/platform/x86/acer-wmi.c | 3 -
drivers/platform/x86/eeepc-laptop.c | 8 ++--
drivers/platform/x86/hp-wmi.c | 4 --
drivers/platform/x86/thinkpad_acpi.c | 31 ++++++-------
include/linux/rfkill.h | 23 +++------
net/rfkill/core.c | 81 ++++++++++++---------------------
6 files changed, 56 insertions(+), 94 deletions(-)
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index b618fa5..09a503e 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -988,7 +988,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
char *name, u32 cap)
{
int err;
- u32 state;
struct rfkill *rfkill_dev;
rfkill_dev = rfkill_alloc(name, dev, type,
@@ -996,8 +995,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
(void *)(unsigned long)cap);
if (!rfkill_dev)
return ERR_PTR(-ENOMEM);
- get_u32(&state, cap);
- rfkill_set_sw_state(rfkill_dev, !state);
err = rfkill_register(rfkill_dev);
if (err) {
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
index 1208d0c..03bf522 100644
--- a/drivers/platform/x86/eeepc-laptop.c
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -675,8 +675,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_wlan_rfkill)
goto wlan_fail;
- rfkill_set_global_sw_state(RFKILL_TYPE_WLAN,
- get_acpi(CM_ASL_WLAN) != 1);
+ rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill,
+ get_acpi(CM_ASL_WLAN) != 1);
result = rfkill_register(ehotk->eeepc_wlan_rfkill);
if (result)
goto wlan_fail;
@@ -693,8 +693,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_bluetooth_rfkill)
goto bluetooth_fail;
- rfkill_set_global_sw_state(RFKILL_TYPE_BLUETOOTH,
- get_acpi(CM_ASL_BLUETOOTH) != 1);
+ rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill,
+ get_acpi(CM_ASL_BLUETOOTH) != 1);
result = rfkill_register(ehotk->eeepc_bluetooth_rfkill);
if (result)
goto bluetooth_fail;
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
index 8d93114..16fffe4 100644
--- a/drivers/platform/x86/hp-wmi.c
+++ b/drivers/platform/x86/hp-wmi.c
@@ -422,7 +422,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_WLAN,
&hp_wmi_rfkill_ops,
(void *) 0);
- rfkill_set_sw_state(wifi_rfkill, hp_wmi_wifi_state());
err = rfkill_register(wifi_rfkill);
if (err)
goto register_wifi_error;
@@ -433,8 +432,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_BLUETOOTH,
&hp_wmi_rfkill_ops,
(void *) 1);
- rfkill_set_sw_state(bluetooth_rfkill,
- hp_wmi_bluetooth_state());
err = rfkill_register(bluetooth_rfkill);
if (err)
goto register_bluetooth_error;
@@ -445,7 +442,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_WWAN,
&hp_wmi_rfkill_ops,
(void *) 2);
- rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state());
err = rfkill_register(wwan_rfkill);
if (err)
goto register_wwan_err;
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index cfcafa4..ab6968a 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -1168,21 +1168,6 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
- initial_sw_status = (tp_rfkops->get_status)();
- if (initial_sw_status < 0) {
- printk(TPACPI_ERR
- "failed to read initial state for %s, error %d; "
- "will turn radio off\n", name, initial_sw_status);
- } else {
- initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
- if (set_default) {
- /* try to set the initial state as the default for the
- * rfkill type, since we ask the firmware to preserve
- * it across S5 in NVRAM */
- rfkill_set_global_sw_state(rfktype, initial_sw_state);
- }
- }
-
atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
if (atp_rfk)
atp_rfk->rfkill = rfkill_alloc(name,
@@ -1200,8 +1185,20 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
atp_rfk->id = id;
atp_rfk->ops = tp_rfkops;
- rfkill_set_states(atp_rfk->rfkill, initial_sw_state,
- tpacpi_rfk_check_hwblock_state());
+ initial_sw_status = (tp_rfkops->get_status)();
+ if (initial_sw_status < 0) {
+ printk(TPACPI_ERR
+ "failed to read initial state for %s, error %d; "
+ "will turn radio off\n", name, initial_sw_status);
+ } else {
+ initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
+ if (set_default) {
+ /* try to keep the initial state, since we ask the
+ * firmware to preserve it across S5 in NVRAM */
+ rfkill_set_sw_state(atp_rfk->rfkill, initial_sw_state);
+ }
+ }
+ rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state());
res = rfkill_register(atp_rfk->rfkill);
if (res < 0) {
diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h
index ee3edde..f8852bb 100644
--- a/include/linux/rfkill.h
+++ b/include/linux/rfkill.h
@@ -156,8 +156,14 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
* @rfkill: rfkill structure to be registered
*
* This function should be called by the transmitter driver to register
- * the rfkill structure needs to be registered. Before calling this function
- * the driver needs to be ready to service method calls from rfkill.
+ * the rfkill structure. Before calling this function the driver needs
+ * to be ready to service method calls from rfkill.
+ *
+ * If the software blocked state is not set before registration,
+ * set_block will be called to initialize it to a default value.
+ *
+ * If the hardware blocked state is not set before registration,
+ * it is assumed to be unblocked.
*/
int __must_check rfkill_register(struct rfkill *rfkill);
@@ -250,19 +256,6 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked);
void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw);
/**
- * rfkill_set_global_sw_state - set global sw block default
- * @type: rfkill type to set default for
- * @blocked: default to set
- *
- * This function sets the global default -- use at boot if your platform has
- * an rfkill switch. If not early enough this call may be ignored.
- *
- * XXX: instead of ignoring -- how about just updating all currently
- * registered drivers?
- */
-void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked);
-
-/**
* rfkill_blocked - query rfkill block
*
* @rfkill: rfkill struct to query
diff --git a/net/rfkill/core.c b/net/rfkill/core.c
index 11b7314..d14493f 100644
--- a/net/rfkill/core.c
+++ b/net/rfkill/core.c
@@ -57,6 +57,7 @@ struct rfkill {
bool registered;
bool suspended;
+ bool persistent;
const struct rfkill_ops *ops;
void *data;
@@ -116,11 +117,9 @@ MODULE_PARM_DESC(default_state,
"Default initial state for all radio types, 0 = radio off");
static struct {
- bool cur, def;
+ bool cur, sav;
} rfkill_global_states[NUM_RFKILL_TYPES];
-static unsigned long rfkill_states_default_locked;
-
static bool rfkill_epo_lock_active;
@@ -392,7 +391,7 @@ void rfkill_epo(void)
rfkill_set_block(rfkill, true);
for (i = 0; i < NUM_RFKILL_TYPES; i++) {
- rfkill_global_states[i].def = rfkill_global_states[i].cur;
+ rfkill_global_states[i].sav = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
@@ -417,7 +416,7 @@ void rfkill_restore_states(void)
rfkill_epo_lock_active = false;
for (i = 0; i < NUM_RFKILL_TYPES; i++)
- __rfkill_switch_all(i, rfkill_global_states[i].def);
+ __rfkill_switch_all(i, rfkill_global_states[i].sav);
mutex_unlock(&rfkill_global_mutex);
}
@@ -464,29 +463,6 @@ bool rfkill_get_global_sw_state(const enum rfkill_type type)
}
#endif
-void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
-{
- BUG_ON(type == RFKILL_TYPE_ALL);
-
- mutex_lock(&rfkill_global_mutex);
-
- /* don't allow unblock when epo */
- if (rfkill_epo_lock_active && !blocked)
- goto out;
-
- /* too late */
- if (rfkill_states_default_locked & BIT(type))
- goto out;
-
- rfkill_states_default_locked |= BIT(type);
-
- rfkill_global_states[type].cur = blocked;
- rfkill_global_states[type].def = blocked;
- out:
- mutex_unlock(&rfkill_global_mutex);
-}
-EXPORT_SYMBOL(rfkill_set_global_sw_state);
-
bool rfkill_set_hw_state(struct rfkill *rfkill, bool blocked)
{
@@ -532,13 +508,14 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
blocked = blocked || hwblock;
spin_unlock_irqrestore(&rfkill->lock, flags);
- if (!rfkill->registered)
- return blocked;
+ if (!rfkill->registered) {
+ rfkill->persistent = true;
+ } else {
+ if (prev != blocked && !hwblock)
+ schedule_work(&rfkill->uevent_work);
- if (prev != blocked && !hwblock)
- schedule_work(&rfkill->uevent_work);
-
- rfkill_led_trigger_event(rfkill);
+ rfkill_led_trigger_event(rfkill);
+ }
return blocked;
}
@@ -563,13 +540,14 @@ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
spin_unlock_irqrestore(&rfkill->lock, flags);
- if (!rfkill->registered)
- return;
-
- if (swprev != sw || hwprev != hw)
- schedule_work(&rfkill->uevent_work);
+ if (!rfkill->registered) {
+ rfkill->persistent = true;
+ } else {
+ if (swprev != sw || hwprev != hw)
+ schedule_work(&rfkill->uevent_work);
- rfkill_led_trigger_event(rfkill);
+ rfkill_led_trigger_event(rfkill);
+ }
}
EXPORT_SYMBOL(rfkill_set_states);
@@ -888,15 +866,6 @@ int __must_check rfkill_register(struct rfkill *rfkill)
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
- if (!(rfkill_states_default_locked & BIT(rfkill->type))) {
- /* first of its kind */
- BUILD_BUG_ON(NUM_RFKILL_TYPES >
- sizeof(rfkill_states_default_locked) * 8);
- rfkill_states_default_locked |= BIT(rfkill->type);
- rfkill_global_states[rfkill->type].cur =
- rfkill_global_states[rfkill->type].def;
- }
-
list_add_tail(&rfkill->node, &rfkill_list);
error = device_add(dev);
@@ -916,7 +885,17 @@ int __must_check rfkill_register(struct rfkill *rfkill)
if (rfkill->ops->poll)
schedule_delayed_work(&rfkill->poll_work,
round_jiffies_relative(POLL_INTERVAL));
- schedule_work(&rfkill->sync_work);
+
+ if (!rfkill->persistent || rfkill_epo_lock_active) {
+ schedule_work(&rfkill->sync_work);
+ } else {
+#ifdef CONFIG_RFKILL_INPUT
+ bool soft_blocked = !!(rfkill->state & RFKILL_BLOCK_SW);
+
+ if (!atomic_read(&rfkill_input_disabled))
+ __rfkill_switch_all(rfkill->type, soft_blocked);
+#endif
+ }
rfkill_send_events(rfkill, RFKILL_OP_ADD);
@@ -1191,7 +1170,7 @@ static int __init rfkill_init(void)
int i;
for (i = 0; i < NUM_RFKILL_TYPES; i++)
- rfkill_global_states[i].def = !rfkill_default_state;
+ rfkill_global_states[i].cur = !rfkill_default_state;
error = class_register(&rfkill_class);
if (error)
--
1.5.4.3
On Mon, 2009-06-01 at 12:36 -0300, Henrique de Moraes Holschuh wrote:
> > > On a related note, Johannes, would you be opposed to exporting something I
> > > could call from thinkpad-acpi to request a radio state change on a rfkill
> > > struct? This would allow me to remove some of the mess in thinkpad-acpi.
> >
> > I don't think I understand what you want? What do you mean by "request a
> > radio state change"?
>
> As in "please try to block this _specific_ radio", and "please try to
> unblock this _specific_ radio".
>
> I.e. what one would do by a write to the "state" attribute of the _old_
> rfkill sysfs (and which would get overriden the next time someone changed
> the global state, etc).
Ok, but where would you want that exported to? It's available
on /dev/rfkill, why would you want that for the driver, it can just call
itself, no?
johannes
On Sun, 07 Jun 2009, Marcel Holtmann wrote:
> > You can remove the EPO functionality and add a "switch all radios off"
> > and "switch all radios on" accell commands. But that's all you can do
> > if you depend on userspace. It will -not- be EPO.
> >
> > You can have EPO in the kernel, and have userspace command the kernel to
> > enter and exit EPO state.
>
> I fully disagree with you here and your concept of EPO and how it is
> suppose to work is fully flawed. EPO is a policy and if you define it as
Just rename it to something else.
Whether the current code can achieve what EPO is suposed to be is beside
the point. Just rename that functionality to something else, as the
direction you want to go is clearly NOT one suitable for something named
"EPO".
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Hi Henrique,
> > > You can remove the EPO functionality and add a "switch all radios off"
> > > and "switch all radios on" accell commands. But that's all you can do
> > > if you depend on userspace. It will -not- be EPO.
> > >
> > > You can have EPO in the kernel, and have userspace command the kernel to
> > > enter and exit EPO state.
> >
> > I fully disagree with you here and your concept of EPO and how it is
> > suppose to work is fully flawed. EPO is a policy and if you define it as
>
> Just rename it to something else.
>
> Whether the current code can achieve what EPO is suposed to be is beside
> the point. Just rename that functionality to something else, as the
> direction you want to go is clearly NOT one suitable for something named
> "EPO".
I don't care about the name. You can name it whatever you want. The
point here is that it is a policy and so we trigger it from userspace.
And we are finally fully capable of doing so with almost all subsystems
from a simple daemon. Only exception is most external 3G (as mentioned
in my other email) and my WiMAX is behaving a little bit quirky, but I
am looking into that.
Regards
Marcel
On Mon, 01 Jun 2009, Johannes Berg wrote:
> On Mon, 2009-06-01 at 12:36 -0300, Henrique de Moraes Holschuh wrote:
> > > > On a related note, Johannes, would you be opposed to exporting something I
> > > > could call from thinkpad-acpi to request a radio state change on a rfkill
> > > > struct? This would allow me to remove some of the mess in thinkpad-acpi.
> > >
> > > I don't think I understand what you want? What do you mean by "request a
> > > radio state change"?
> >
> > As in "please try to block this _specific_ radio", and "please try to
> > unblock this _specific_ radio".
> >
> > I.e. what one would do by a write to the "state" attribute of the _old_
> > rfkill sysfs (and which would get overriden the next time someone changed
> > the global state, etc).
>
> Ok, but where would you want that exported to? It's available
> on /dev/rfkill, why would you want that for the driver, it can just call
> itself, no?
Yes, it "calls itself" right now... so I can certainly do that :)
However, the in-driver shortcut means I give rfkill a kick and say "the soft
state has changed, deal with it", instead of "please change the state" which
it might deny due to EPO, etc.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Mon, 01 Jun 2009, Alan Jenkins wrote:
>> See, Henrique says that the use case is Thinkpads which store the
>> previous state in the BIOS. But that matters to you only if you use a
>> mixture of operation systems, which we don't have to support.
Well, if you do all in userspace, how do you propose to avoid the usual race
conditions of the sort "radio starts on, but it should have started off",
etc? You'd have to kick the radios off on rfkill module load for safety,
and that will also cause nastyness (it kicks my built-in wlan
(eeepcargh)/bluetooth(most)/wwan(most) off bus, then hotplugs it again!).
That is just nasty for no good reason. Just have this thing be read/write.
You will have to sync states at every device open otherwise, in the end it
is just plain more useful to have it read/write to begin with.
> "Other OS's" also includes the BIOS. My BIOS setup screen has an option
> to toggle the wireless state. It's great to have this just work, and
> annoying to have regressions.
Well, thinkpads don't let you toggle anything on BIOS. They allow you to
outright lock down things in BIOS, and in that case, the radio becomes
hardware-locked and cannot be turned on no matter what you do, or just plain
doesn't show up anywhere...
>> On the other hand, people on machines that don't store the rfkill state
>> in the BIOS might care about having their machine boot up with the same
>> rfkill state(s) as they shut down with, so the sane thing to do would be
Supporting this kind of hardware is fine. Lowering the bar to support them,
isn't.
>> to have rfkilld store the state persistently, and then recover it at
>> boot. At which point the BIOS state becomes irrelevant and a detail we
And have the lights flicker? Not nice.
Just give us the full interface. Don't do things half-way, it is much worse
on the long road. And it is a LOT easier on the users of your subsystem as
well.
>> actually end up not even _wanting_ to support because it means we need
>> to be aware of machine differences in userspace.
Expect resistence down this path. I, for one, won't agree with that.
Others might not as well. I am open to ideas on how to make functionality
generic, and I am not even putting much of a fuss over the total disregard
of the stable ABI rules shown here, but I will _not_ stand for reduced
functionality.
> I like one of the solutions Marcels suggested, that /dev/rfkill should
> report the "global default values" only when they have been set - either
> by userspace or by a platform driver.
It can have default values just fine. And you can't wait for userspace or
platform drivers to register a default, it just doesn't work, you cannot
expect that all relevant drivers are loaded before "rfkilld".
Just don't expose a rfkill type until the first rfkill structure of that
type gets registered. THAT closes all holes in a sensible,
principle-of-least-suprise way. The current code (including the rewrite)
already deals with defaults and firmware-backed state storage just fine in
that case. All you need is a full interface that deals with global state
hotplug (which ain't difficult, that's one or two more notifications only).
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
rfkill_set_global_sw_state() (previously rfkill_set_default()) will no
longer be exported by the rewritten rfkill core.
Instead, platform drivers which can provide persistent soft-rfkill state
across power-down/reboot should indicate their initial state by calling
rfkill_set_sw_state() before registration. Otherwise, they will be
initialized to a default value during registration by a set_block call.
We remove existing calls to rfkill_set_sw_state() which happen before
registration, since these had no effect in the old model. If these
drivers do have persistent state, the calls can be put back (subject
to testing :-). This affects hp-wmi and acer-wmi.
Drivers with persistent state will affect the global state only if
rfkill-input is enabled. This is required, otherwise booting with
wireless soft-blocked and pressing the wireless-toggle key once would
have no apparent effect. This special case will be removed in future
along with rfkill-input, in favour of a more flexible userspace daemon
(see Documentation/feature-removal-schedule.txt).
Now rfkill_global_states[n].def is only used to preserve global states
over EPO, it is renamed to ".sav".
Signed-off-by: Alan Jenkins <[email protected]>
---
v2: nothing (wrong patch)
v3:
- fix thinkpad-acpi message to reflect altered error-case behaviour
- remove !CONFIG_RFKILL stub for rfkill_set_global_sw_state()
v4:
fix build error (sorry for the noise)
drivers/platform/x86/acer-wmi.c | 3 -
drivers/platform/x86/eeepc-laptop.c | 8 ++--
drivers/platform/x86/hp-wmi.c | 4 --
drivers/platform/x86/thinkpad_acpi.c | 31 ++++++-------
include/linux/rfkill.h | 28 +++--------
net/rfkill/core.c | 81 ++++++++++++---------------------
6 files changed, 56 insertions(+), 99 deletions(-)
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index b618fa5..09a503e 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -988,7 +988,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
char *name, u32 cap)
{
int err;
- u32 state;
struct rfkill *rfkill_dev;
rfkill_dev = rfkill_alloc(name, dev, type,
@@ -996,8 +995,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
(void *)(unsigned long)cap);
if (!rfkill_dev)
return ERR_PTR(-ENOMEM);
- get_u32(&state, cap);
- rfkill_set_sw_state(rfkill_dev, !state);
err = rfkill_register(rfkill_dev);
if (err) {
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
index 1208d0c..03bf522 100644
--- a/drivers/platform/x86/eeepc-laptop.c
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -675,8 +675,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_wlan_rfkill)
goto wlan_fail;
- rfkill_set_global_sw_state(RFKILL_TYPE_WLAN,
- get_acpi(CM_ASL_WLAN) != 1);
+ rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill,
+ get_acpi(CM_ASL_WLAN) != 1);
result = rfkill_register(ehotk->eeepc_wlan_rfkill);
if (result)
goto wlan_fail;
@@ -693,8 +693,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_bluetooth_rfkill)
goto bluetooth_fail;
- rfkill_set_global_sw_state(RFKILL_TYPE_BLUETOOTH,
- get_acpi(CM_ASL_BLUETOOTH) != 1);
+ rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill,
+ get_acpi(CM_ASL_BLUETOOTH) != 1);
result = rfkill_register(ehotk->eeepc_bluetooth_rfkill);
if (result)
goto bluetooth_fail;
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
index 8d93114..16fffe4 100644
--- a/drivers/platform/x86/hp-wmi.c
+++ b/drivers/platform/x86/hp-wmi.c
@@ -422,7 +422,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_WLAN,
&hp_wmi_rfkill_ops,
(void *) 0);
- rfkill_set_sw_state(wifi_rfkill, hp_wmi_wifi_state());
err = rfkill_register(wifi_rfkill);
if (err)
goto register_wifi_error;
@@ -433,8 +432,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_BLUETOOTH,
&hp_wmi_rfkill_ops,
(void *) 1);
- rfkill_set_sw_state(bluetooth_rfkill,
- hp_wmi_bluetooth_state());
err = rfkill_register(bluetooth_rfkill);
if (err)
goto register_bluetooth_error;
@@ -445,7 +442,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_WWAN,
&hp_wmi_rfkill_ops,
(void *) 2);
- rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state());
err = rfkill_register(wwan_rfkill);
if (err)
goto register_wwan_err;
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index cfcafa4..86e9585 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -1168,21 +1168,6 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
- initial_sw_status = (tp_rfkops->get_status)();
- if (initial_sw_status < 0) {
- printk(TPACPI_ERR
- "failed to read initial state for %s, error %d; "
- "will turn radio off\n", name, initial_sw_status);
- } else {
- initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
- if (set_default) {
- /* try to set the initial state as the default for the
- * rfkill type, since we ask the firmware to preserve
- * it across S5 in NVRAM */
- rfkill_set_global_sw_state(rfktype, initial_sw_state);
- }
- }
-
atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
if (atp_rfk)
atp_rfk->rfkill = rfkill_alloc(name,
@@ -1200,8 +1185,20 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
atp_rfk->id = id;
atp_rfk->ops = tp_rfkops;
- rfkill_set_states(atp_rfk->rfkill, initial_sw_state,
- tpacpi_rfk_check_hwblock_state());
+ initial_sw_status = (tp_rfkops->get_status)();
+ if (initial_sw_status < 0) {
+ printk(TPACPI_ERR
+ "failed to read initial state for %s, error %d\n",
+ name, initial_sw_status);
+ } else {
+ initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
+ if (set_default) {
+ /* try to keep the initial state, since we ask the
+ * firmware to preserve it across S5 in NVRAM */
+ rfkill_set_sw_state(atp_rfk->rfkill, initial_sw_state);
+ }
+ }
+ rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state());
res = rfkill_register(atp_rfk->rfkill);
if (res < 0) {
diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h
index ee3edde..e98a4b3 100644
--- a/include/linux/rfkill.h
+++ b/include/linux/rfkill.h
@@ -156,8 +156,14 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
* @rfkill: rfkill structure to be registered
*
* This function should be called by the transmitter driver to register
- * the rfkill structure needs to be registered. Before calling this function
- * the driver needs to be ready to service method calls from rfkill.
+ * the rfkill structure. Before calling this function the driver needs
+ * to be ready to service method calls from rfkill.
+ *
+ * If the software blocked state is not set before registration,
+ * set_block will be called to initialize it to a default value.
+ *
+ * If the hardware blocked state is not set before registration,
+ * it is assumed to be unblocked.
*/
int __must_check rfkill_register(struct rfkill *rfkill);
@@ -250,19 +256,6 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked);
void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw);
/**
- * rfkill_set_global_sw_state - set global sw block default
- * @type: rfkill type to set default for
- * @blocked: default to set
- *
- * This function sets the global default -- use at boot if your platform has
- * an rfkill switch. If not early enough this call may be ignored.
- *
- * XXX: instead of ignoring -- how about just updating all currently
- * registered drivers?
- */
-void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked);
-
-/**
* rfkill_blocked - query rfkill block
*
* @rfkill: rfkill struct to query
@@ -316,11 +309,6 @@ static inline void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
{
}
-static inline void rfkill_set_global_sw_state(const enum rfkill_type type,
- bool blocked)
-{
-}
-
static inline bool rfkill_blocked(struct rfkill *rfkill)
{
return false;
diff --git a/net/rfkill/core.c b/net/rfkill/core.c
index 11b7314..d14493f 100644
--- a/net/rfkill/core.c
+++ b/net/rfkill/core.c
@@ -57,6 +57,7 @@ struct rfkill {
bool registered;
bool suspended;
+ bool persistent;
const struct rfkill_ops *ops;
void *data;
@@ -116,11 +117,9 @@ MODULE_PARM_DESC(default_state,
"Default initial state for all radio types, 0 = radio off");
static struct {
- bool cur, def;
+ bool cur, sav;
} rfkill_global_states[NUM_RFKILL_TYPES];
-static unsigned long rfkill_states_default_locked;
-
static bool rfkill_epo_lock_active;
@@ -392,7 +391,7 @@ void rfkill_epo(void)
rfkill_set_block(rfkill, true);
for (i = 0; i < NUM_RFKILL_TYPES; i++) {
- rfkill_global_states[i].def = rfkill_global_states[i].cur;
+ rfkill_global_states[i].sav = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
@@ -417,7 +416,7 @@ void rfkill_restore_states(void)
rfkill_epo_lock_active = false;
for (i = 0; i < NUM_RFKILL_TYPES; i++)
- __rfkill_switch_all(i, rfkill_global_states[i].def);
+ __rfkill_switch_all(i, rfkill_global_states[i].sav);
mutex_unlock(&rfkill_global_mutex);
}
@@ -464,29 +463,6 @@ bool rfkill_get_global_sw_state(const enum rfkill_type type)
}
#endif
-void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
-{
- BUG_ON(type == RFKILL_TYPE_ALL);
-
- mutex_lock(&rfkill_global_mutex);
-
- /* don't allow unblock when epo */
- if (rfkill_epo_lock_active && !blocked)
- goto out;
-
- /* too late */
- if (rfkill_states_default_locked & BIT(type))
- goto out;
-
- rfkill_states_default_locked |= BIT(type);
-
- rfkill_global_states[type].cur = blocked;
- rfkill_global_states[type].def = blocked;
- out:
- mutex_unlock(&rfkill_global_mutex);
-}
-EXPORT_SYMBOL(rfkill_set_global_sw_state);
-
bool rfkill_set_hw_state(struct rfkill *rfkill, bool blocked)
{
@@ -532,13 +508,14 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
blocked = blocked || hwblock;
spin_unlock_irqrestore(&rfkill->lock, flags);
- if (!rfkill->registered)
- return blocked;
+ if (!rfkill->registered) {
+ rfkill->persistent = true;
+ } else {
+ if (prev != blocked && !hwblock)
+ schedule_work(&rfkill->uevent_work);
- if (prev != blocked && !hwblock)
- schedule_work(&rfkill->uevent_work);
-
- rfkill_led_trigger_event(rfkill);
+ rfkill_led_trigger_event(rfkill);
+ }
return blocked;
}
@@ -563,13 +540,14 @@ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
spin_unlock_irqrestore(&rfkill->lock, flags);
- if (!rfkill->registered)
- return;
-
- if (swprev != sw || hwprev != hw)
- schedule_work(&rfkill->uevent_work);
+ if (!rfkill->registered) {
+ rfkill->persistent = true;
+ } else {
+ if (swprev != sw || hwprev != hw)
+ schedule_work(&rfkill->uevent_work);
- rfkill_led_trigger_event(rfkill);
+ rfkill_led_trigger_event(rfkill);
+ }
}
EXPORT_SYMBOL(rfkill_set_states);
@@ -888,15 +866,6 @@ int __must_check rfkill_register(struct rfkill *rfkill)
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
- if (!(rfkill_states_default_locked & BIT(rfkill->type))) {
- /* first of its kind */
- BUILD_BUG_ON(NUM_RFKILL_TYPES >
- sizeof(rfkill_states_default_locked) * 8);
- rfkill_states_default_locked |= BIT(rfkill->type);
- rfkill_global_states[rfkill->type].cur =
- rfkill_global_states[rfkill->type].def;
- }
-
list_add_tail(&rfkill->node, &rfkill_list);
error = device_add(dev);
@@ -916,7 +885,17 @@ int __must_check rfkill_register(struct rfkill *rfkill)
if (rfkill->ops->poll)
schedule_delayed_work(&rfkill->poll_work,
round_jiffies_relative(POLL_INTERVAL));
- schedule_work(&rfkill->sync_work);
+
+ if (!rfkill->persistent || rfkill_epo_lock_active) {
+ schedule_work(&rfkill->sync_work);
+ } else {
+#ifdef CONFIG_RFKILL_INPUT
+ bool soft_blocked = !!(rfkill->state & RFKILL_BLOCK_SW);
+
+ if (!atomic_read(&rfkill_input_disabled))
+ __rfkill_switch_all(rfkill->type, soft_blocked);
+#endif
+ }
rfkill_send_events(rfkill, RFKILL_OP_ADD);
@@ -1191,7 +1170,7 @@ static int __init rfkill_init(void)
int i;
for (i = 0; i < NUM_RFKILL_TYPES; i++)
- rfkill_global_states[i].def = !rfkill_default_state;
+ rfkill_global_states[i].cur = !rfkill_default_state;
error = class_register(&rfkill_class);
if (error)
On Mon, 08 Jun 2009, Alan Jenkins wrote:
> --- a/drivers/platform/x86/thinkpad_acpi.c
> +++ b/drivers/platform/x86/thinkpad_acpi.c
> @@ -1168,21 +1168,6 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
>
> BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
>
> - initial_sw_status = (tp_rfkops->get_status)();
> - if (initial_sw_status < 0) {
> - printk(TPACPI_ERR
> - "failed to read initial state for %s, error %d; "
> - "will turn radio off\n", name, initial_sw_status);
> - } else {
> - initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
> - if (set_default) {
> - /* try to set the initial state as the default for the
> - * rfkill type, since we ask the firmware to preserve
> - * it across S5 in NVRAM */
> - rfkill_set_global_sw_state(rfktype, initial_sw_state);
> - }
> - }
> -
> atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
> if (atp_rfk)
> atp_rfk->rfkill = rfkill_alloc(name,
> @@ -1200,8 +1185,20 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
> atp_rfk->id = id;
> atp_rfk->ops = tp_rfkops;
>
> - rfkill_set_states(atp_rfk->rfkill, initial_sw_state,
> - tpacpi_rfk_check_hwblock_state());
> + initial_sw_status = (tp_rfkops->get_status)();
> + if (initial_sw_status < 0) {
> + printk(TPACPI_ERR
> + "failed to read initial state for %s, error %d\n",
> + name, initial_sw_status);
> + } else {
> + initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
> + if (set_default) {
> + /* try to keep the initial state, since we ask the
> + * firmware to preserve it across S5 in NVRAM */
> + rfkill_set_sw_state(atp_rfk->rfkill, initial_sw_state);
> + }
> + }
> + rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state());
>
> res = rfkill_register(atp_rfk->rfkill);
> if (res < 0) {
Acked-by: Henrique de Moraes Holschuh <[email protected]>
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Sun, 2009-06-07 at 09:57 -0300, Henrique de Moraes Holschuh wrote:
> > 1) remove rfkill_set_global_sw_state()
> > 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
> > registration, setting a flag.
> > 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
> > events, tho it may as well be set in all events)
> > 4) rfkill-input preserves existing behaviour - *if enabled* - by
> > initializing the global state from individual devices which have NVS.
> > (As before, each _type_ of rfkill device has its own global state).
> > 5) rfkill devices with NVS will have their current state preserved,
> > so long as the global state has not yet been set (by userspace or by
> > rfkill-input). Of course userspace can change the state in response
> > to the device being added.
> Let's do it :-)
Alan, you're right in that we currently overwrite devices with the
global state upon registration. My suggestion would be to not do that
when set_sw_state() has been called on the device before it was
registered, by simply keeping track of that. Anyone want to propose a
patch?
johannes
Alan Jenkins wrote:
> rfkill_set_global_sw_state() (previously rfkill_set_default()) will no
> longer be exported by the rewritten rfkill core.
>
> Instead, platform drivers which can provide persistent soft-rfkill state
> across power-down/reboot should indicate their initial state by calling
> rfkill_set_sw_state() before registration. Otherwise, they will be
> initialized to a default value during registration by a set_block call.
>
> We remove existing calls to rfkill_set_sw_state() which happen before
> registration, since these had no effect in the old model. If these
> drivers do have persistent state, the calls can be put back (subject
> to testing :-). This affects hp-wmi and acer-wmi.
>
>
> Drivers with persistent state will affect the global state only if
> rfkill-input is enabled. This is required, otherwise booting with
> wireless soft-blocked and pressing the wireless-toggle key once would
> have no apparent effect. This special case will be removed in future
> along with rfkill-input, in favour of a more flexible userspace daemon
> (see Documentation/feature-removal-schedule.txt).
>
>
> Now rfkill_global_states[n].def is only used to preserve global states
> over EPO, it is renamed to ".sav".
>
> Signed-off-by: Alan Jenkins <[email protected]>
> ---
> v2:
> - fix thinkpad-acpi message to reflect altered error-case behaviour
> - remove !CONFIG_RFKILL stub for rfkill_set_global_sw_state()
>
Sorry, wrong patch, try again
Hi Johannes,
> The new code added by this patch will make rfkill create
> a misc character device /dev/rfkill that userspace can use
> to control rfkill soft blocks and get status of devices as
> well as events when the status changes.
>
> Using it is very simple -- when you open it you can read
> a number of times to get the initial state, and every
> further read blocks (you can poll) on getting the next
> event from the kernel. The same structure you read is
> also used when writing to it to change the soft block of
> a given device, all devices of a given type, or all
> devices.
>
> This also makes CONFIG_RFKILL_INPUT selectable again in
> order to be able to test without it present since its
> functionality can now be replaced by userspace entirely
> and distros and users may not want the input part of
> rfkill interfering with their userspace code. We will
> also write a userspace daemon to handle all that and
> consequently add the input code to the feature removal
> schedule.
>
> In order to have rfkilld support both kernels with and
> without CONFIG_RFKILL_INPUT (or new kernels after its
> eventual removal) we also add an ioctl (that only exists
> if rfkill-input is present) to disable rfkill-input.
> It is not very efficient, but at least gives the correct
> behaviour in all cases.
looks all good to me. As I said, before this makes sense and now we have
to start working on creating rfkilld for RFKILL policy and input
handling.
John, I would prefer if we start merging this into wireless-testing to
get proper user exposure and so we can start fixing fallouts (if there
are any). We then should merge this into 2.6.31 quickly to finally have
a proper RFKILL subsystem.
> Signed-off-by: Johannes Berg <[email protected]>
Acked-by: Marcel Holtmann <[email protected]>
Regards
Marcel
On Wed, 03 Jun 2009, Marcel Holtmann wrote:
> any user program with proper rights (remember that /dev/rfkill can now
> be controlled by Unix permissions and SELinux) can bring up a specific
> device. That is policy and it belongs in userspace.
If I hardkill (EPO) the devices, I want them to stay hardkilled, and only a
system daemon (if that) should be able to mess with that.
I very much doubt I am the only one who see things that way :-)
I'd like to keep working towards that goal (no, we're not there yet), and
not away from it.
> This of course only works on soft blocked devices. The hard blocked
> devices stay off. And in case of ThinkPads where the button does the
> hard block, you can't bring it back from software.
Yes. But the rfkill core is also meant to bring some band-aid help to the
devices that the hardware can't kill by itself. That's good usability.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Marcel Holtmann wrote:
> Hi Henrique,
>
>
>>>> I don't think we should expect userspace to know whether or not a device
>>>> has a persistent state. Yes, it _could_ maintain whitelists, but why
>>>> should it have to if the driver already knows?
>>>>
>>> If you want that, then the best approach seems an extra sysfs attribute
>>> for this. It is not intrusive on the event API and lets udev etc. have
>>> these information, too.
>>>
>> I have no problems with either approach. As long as the information of
>> which devices have restored their initial state from NVS is available to
>> userspace, it is enough.
>>
>
> just to get the semantic right here. We are not telling userspace if a
> state has been restored or not. We are telling userspace that this
> specific RFKILL switch is capable of storing something in a persistent
> state over boot. There is a difference here.
>
> If a RFKILL driver claims it is capable of persistent storage then it
> better work or it should not claims it. Either it does it all the time
> or doesn't do it at all. Otherwise we end up in policy again and that is
> not the job of the kernel.
>
>
>> Do note that this information also needs to be available for resume (state
>> should be checkpointed to NVS on sleep, and restored from NVS on resume. I
>> believe tpacpi does this, but if it doesn't, I will fix it eventually).
>>
>
> Correct. That is the job of the driver. If it is broken, that needs
> fixing.
>
The core needs fixing too, currently it restores state for all devices
on resume.
This is my fault again, for coming up with scenarios you probably don't
care about :-). The problem is that suspend to disk provides a
possibility that the state will change for _any_ driver. It's more
obvious with rfkill-input and NVS, if the wireless toggle key is pressed
when the disk image is being written out. But you can also contrive it
with no NVS and rfkilld, if rfkilld gets started in the initramfs of the
resume kernel. We took the easy way out, rather than adding resume
handlers to all drivers, or trying to work out what the real design
problem was :-).
I hope that explains the issue. I agree with your logic, I just want to
be clear that it needs more work on the rfkill core. Drivers with NVS
should have a resume handler to call rfkill_set_sw_state(), but for this
to work the core will need to stop restoring their state (for NVS
drivers only). As a detail, I think this behaviour difference with NVS
means it should be flagged with a more explicit API, e.g.
rfkill_init_persistent_sw_state().
rfkill-input would like another (even more intrusive) hack here to set
the global state on resume. But I for one can live without it for the
transition. I think NVS state change over suspend is much more of a
corner case. At least on eeepc-laptop it only seems to happen if the
user does something relatively odd. And the worst that will happen is
they have to press the wireless-toggle key a second time before it
starts working.
alan
Hi Henrique,
> > We just need to fix the platform drivers then. They should not set
> > global states since that is not what they are controlling. They control
>
> We should change things, yes. So that the platform stores the global
> state. That was half-broken on the old core (the platform stored the
> state of its own device, which could be out of sync with the global
> state), but the part of it setting the global state is correct.
>
> That needs a new in-kernel API to tie the core to platform drivers
> capable of storing global states without causing problems when drivers
> are unloaded, but it is not hard.
>
> As for NVS events, they have a clear use case: to let rfkilld know which
> global states it could leave alone the first time it loads, and which
> ones have to be restored...
show me an example of a platform device that stores the global state. I
think you are confusing the word platform as in system with a platform
device. The ThinkPad Bluetooth and WWAN switches are platform devices
and control each one specific device. Same goes for the EeePC. They are
not controlling a global state.
Regards
Marcel
On Wed, 2009-06-03 at 01:10 -0300, Henrique de Moraes Holschuh wrote:
> On Tue, 02 Jun 2009, Marcel Holtmann wrote:
> > Forget about this EPO crap. That is just a stupid concept anyway.
>
> Is it? So, if I use the _hardware switch_ on my laptop to kill all
> internal radios, it shouldn't be enforced by the OS on extra radios I
> plugged? Or on shitty internal WLAN cards that doesn't tie properly to
> the mini-pci and mini-pcie hardware kill lines?
>
> And any userspace PoS program can decide to bring up such radios that
> are not hardware-killed even if I am clearly trying to disable them all?
You hardkilled. Of course it should bring down everything. If you
don't want to hardkill, then disable specific radios via /sys or some
UI. The hardswitch is the "eject" button.
Other types like pushbutton switches (Fn+F5, HP laptop touchbuttons,
etc) that *aren't* hardswitches connected to a GPIO on the wifi device
should probably just bring up some window letting you pick what gets
rfkilled.
Dan
The new code added by this patch will make rfkill create
a misc character device /dev/rfkill that userspace can use
to control rfkill soft blocks and get status of devices as
well as events when the status changes.
Using it is very simple -- when you open it you can read
a number of times to get the initial state, and every
further read blocks (you can poll) on getting the next
event from the kernel. The same structure you read is
also used when writing to it to change the soft block of
a given device, all devices of a given type, or all
devices.
This also makes CONFIG_RFKILL_INPUT selectable again in
order to be able to test without it present since its
functionality can now be replaced by userspace entirely
and distros and users may not want the input part of
rfkill interfering with their userspace code. We will
also write a userspace daemon to handle all that and
consequently add the input code to the feature removal
schedule.
In order to have rfkilld support both kernels with and
without CONFIG_RFKILL_INPUT (or new kernels after its
eventual removal) we also add an ioctl (that only exists
if rfkill-input is present) to disable rfkill-input.
It is not very efficient, but at least gives the correct
behaviour in all cases.
Signed-off-by: Johannes Berg <[email protected]>
Acked-by: Marcel Holtmann <[email protected]>
---
v4: set default global state from userspace for rfkill hotplug
(pointed out by Marcel)
v5: add ioctl
v6: hook up compat_ioctl
Documentation/feature-removal-schedule.txt | 7
include/linux/rfkill.h | 84 +++++--
net/rfkill/Kconfig | 4
net/rfkill/core.c | 330 ++++++++++++++++++++++++++++-
4 files changed, 394 insertions(+), 31 deletions(-)
--- wireless-testing.orig/include/linux/rfkill.h 2009-06-01 22:34:04.000000000 +0200
+++ wireless-testing/include/linux/rfkill.h 2009-06-02 11:39:11.000000000 +0200
@@ -22,34 +22,17 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#include <linux/types.h>
/* define userspace visible states */
#define RFKILL_STATE_SOFT_BLOCKED 0
#define RFKILL_STATE_UNBLOCKED 1
#define RFKILL_STATE_HARD_BLOCKED 2
-/* and that's all userspace gets */
-#ifdef __KERNEL__
-/* don't allow anyone to use these in the kernel */
-enum rfkill_user_states {
- RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
- RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
- RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
-};
-#undef RFKILL_STATE_SOFT_BLOCKED
-#undef RFKILL_STATE_UNBLOCKED
-#undef RFKILL_STATE_HARD_BLOCKED
-
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/list.h>
-#include <linux/mutex.h>
-#include <linux/device.h>
-#include <linux/leds.h>
-
/**
* enum rfkill_type - type of rfkill switch.
*
+ * @RFKILL_TYPE_ALL: toggles all switches (userspace only)
* @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device.
* @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device.
* @RFKILL_TYPE_UWB: switch is on a ultra wideband device.
@@ -58,6 +41,7 @@ enum rfkill_user_states {
* @NUM_RFKILL_TYPES: number of defined rfkill types
*/
enum rfkill_type {
+ RFKILL_TYPE_ALL = 0,
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
@@ -66,6 +50,62 @@ enum rfkill_type {
NUM_RFKILL_TYPES,
};
+/**
+ * enum rfkill_operation - operation types
+ * @RFKILL_OP_ADD: a device was added
+ * @RFKILL_OP_DEL: a device was removed
+ * @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device
+ * @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all)
+ */
+enum rfkill_operation {
+ RFKILL_OP_ADD = 0,
+ RFKILL_OP_DEL,
+ RFKILL_OP_CHANGE,
+ RFKILL_OP_CHANGE_ALL,
+};
+
+/**
+ * struct rfkill_event - events for userspace on /dev/rfkill
+ * @idx: index of dev rfkill
+ * @type: type of the rfkill struct
+ * @op: operation code
+ * @hard: hard state (0/1)
+ * @soft: soft state (0/1)
+ *
+ * Structure used for userspace communication on /dev/rfkill,
+ * used for events from the kernel and control to the kernel.
+ */
+struct rfkill_event {
+ __u32 idx;
+ __u8 type;
+ __u8 op;
+ __u8 soft, hard;
+} __packed;
+
+/* ioctl for turning off rfkill-input (if present) */
+#define RFKILL_IOC_MAGIC 'R'
+#define RFKILL_IOC_NOINPUT 1
+#define RFKILL_IOCTL_NOINPUT _IO(RFKILL_IOC_MAGIC, RFKILL_IOC_NOINPUT)
+
+/* and that's all userspace gets */
+#ifdef __KERNEL__
+/* don't allow anyone to use these in the kernel */
+enum rfkill_user_states {
+ RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
+ RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
+ RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
+};
+#undef RFKILL_STATE_SOFT_BLOCKED
+#undef RFKILL_STATE_UNBLOCKED
+#undef RFKILL_STATE_HARD_BLOCKED
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+
/* this is opaque */
struct rfkill;
@@ -84,11 +124,7 @@ struct rfkill;
* the rfkill core query your driver before setting a requested
* block.
* @set_block: turn the transmitter on (blocked == false) or off
- * (blocked == true) -- this is called only while the transmitter
- * is not hard-blocked, but note that the core's view of whether
- * the transmitter is hard-blocked might differ from your driver's
- * view due to race conditions, so it is possible that it is still
- * called at the same time as you are calling rfkill_set_hw_state().
+ * (blocked == true) -- ignore and return 0 when hard blocked.
* This callback must be assigned.
*/
struct rfkill_ops {
--- wireless-testing.orig/net/rfkill/core.c 2009-06-01 22:34:04.000000000 +0200
+++ wireless-testing/net/rfkill/core.c 2009-06-02 11:39:29.000000000 +0200
@@ -28,6 +28,10 @@
#include <linux/mutex.h>
#include <linux/rfkill.h>
#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
#include "rfkill.h"
@@ -49,6 +53,8 @@ struct rfkill {
unsigned long state;
+ u32 idx;
+
bool registered;
bool suspended;
@@ -69,6 +75,18 @@ struct rfkill {
};
#define to_rfkill(d) container_of(d, struct rfkill, dev)
+struct rfkill_int_event {
+ struct list_head list;
+ struct rfkill_event ev;
+};
+
+struct rfkill_data {
+ struct list_head list;
+ struct list_head events;
+ struct mutex mtx;
+ wait_queue_head_t read_wait;
+ bool input_handler;
+};
MODULE_AUTHOR("Ivo van Doorn <[email protected]>");
@@ -90,6 +108,7 @@ MODULE_LICENSE("GPL");
*/
static LIST_HEAD(rfkill_list); /* list of registered rf switches */
static DEFINE_MUTEX(rfkill_global_mutex);
+static LIST_HEAD(rfkill_fds); /* list of open fds of /dev/rfkill */
static unsigned int rfkill_default_state = 1;
module_param_named(default_state, rfkill_default_state, uint, 0444);
@@ -171,12 +190,48 @@ static inline void rfkill_led_trigger_un
}
#endif /* CONFIG_RFKILL_LEDS */
-static void rfkill_uevent(struct rfkill *rfkill)
+static void rfkill_fill_event(struct rfkill_event *ev, struct rfkill *rfkill,
+ enum rfkill_operation op)
+{
+ unsigned long flags;
+
+ ev->idx = rfkill->idx;
+ ev->type = rfkill->type;
+ ev->op = op;
+
+ spin_lock_irqsave(&rfkill->lock, flags);
+ ev->hard = !!(rfkill->state & RFKILL_BLOCK_HW);
+ ev->soft = !!(rfkill->state & (RFKILL_BLOCK_SW |
+ RFKILL_BLOCK_SW_PREV));
+ spin_unlock_irqrestore(&rfkill->lock, flags);
+}
+
+static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op)
+{
+ struct rfkill_data *data;
+ struct rfkill_int_event *ev;
+
+ list_for_each_entry(data, &rfkill_fds, list) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ continue;
+ rfkill_fill_event(&ev->ev, rfkill, op);
+ mutex_lock(&data->mtx);
+ list_add_tail(&ev->list, &data->events);
+ mutex_unlock(&data->mtx);
+ wake_up_interruptible(&data->read_wait);
+ }
+}
+
+static void rfkill_event(struct rfkill *rfkill)
{
if (!rfkill->registered || rfkill->suspended)
return;
kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);
+
+ /* also send event to /dev/rfkill */
+ rfkill_send_events(rfkill, RFKILL_OP_CHANGE);
}
static bool __rfkill_set_hw_state(struct rfkill *rfkill,
@@ -260,9 +315,12 @@ static void rfkill_set_block(struct rfki
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
- rfkill_uevent(rfkill);
+ rfkill_event(rfkill);
}
+#ifdef CONFIG_RFKILL_INPUT
+static atomic_t rfkill_input_disabled = ATOMIC_INIT(0);
+
/**
* __rfkill_switch_all - Toggle state of all switches of given type
* @type: type of interfaces to be affected
@@ -299,6 +357,9 @@ static void __rfkill_switch_all(const en
*/
void rfkill_switch_all(enum rfkill_type type, bool blocked)
{
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
if (!rfkill_epo_lock_active)
@@ -321,6 +382,9 @@ void rfkill_epo(void)
struct rfkill *rfkill;
int i;
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = true;
@@ -331,6 +395,7 @@ void rfkill_epo(void)
rfkill_global_states[i].def = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
+
mutex_unlock(&rfkill_global_mutex);
}
@@ -345,6 +410,9 @@ void rfkill_restore_states(void)
{
int i;
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = false;
@@ -361,6 +429,9 @@ void rfkill_restore_states(void)
*/
void rfkill_remove_epo_lock(void)
{
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = false;
mutex_unlock(&rfkill_global_mutex);
@@ -391,9 +462,12 @@ bool rfkill_get_global_sw_state(const en
{
return rfkill_global_states[type].cur;
}
+#endif
void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
{
+ BUG_ON(type == RFKILL_TYPE_ALL);
+
mutex_lock(&rfkill_global_mutex);
/* don't allow unblock when epo */
@@ -537,6 +611,15 @@ static ssize_t rfkill_type_show(struct d
return sprintf(buf, "%s\n", rfkill_get_type_str(rfkill->type));
}
+static ssize_t rfkill_idx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rfkill *rfkill = to_rfkill(dev);
+
+ return sprintf(buf, "%d\n", rfkill->idx);
+}
+
static u8 user_state_from_blocked(unsigned long state)
{
if (state & RFKILL_BLOCK_HW)
@@ -594,6 +677,7 @@ static ssize_t rfkill_claim_store(struct
static struct device_attribute rfkill_dev_attrs[] = {
__ATTR(name, S_IRUGO, rfkill_name_show, NULL),
__ATTR(type, S_IRUGO, rfkill_type_show, NULL),
+ __ATTR(index, S_IRUGO, rfkill_idx_show, NULL),
__ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store),
__ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store),
__ATTR_NULL
@@ -708,7 +792,7 @@ struct rfkill * __must_check rfkill_allo
if (WARN_ON(!name))
return NULL;
- if (WARN_ON(type >= NUM_RFKILL_TYPES))
+ if (WARN_ON(type == RFKILL_TYPE_ALL || type >= NUM_RFKILL_TYPES))
return NULL;
rfkill = kzalloc(sizeof(*rfkill), GFP_KERNEL);
@@ -754,7 +838,9 @@ static void rfkill_uevent_work(struct wo
rfkill = container_of(work, struct rfkill, uevent_work);
- rfkill_uevent(rfkill);
+ mutex_lock(&rfkill_global_mutex);
+ rfkill_event(rfkill);
+ mutex_unlock(&rfkill_global_mutex);
}
static void rfkill_sync_work(struct work_struct *work)
@@ -785,6 +871,7 @@ int __must_check rfkill_register(struct
goto unlock;
}
+ rfkill->idx = rfkill_no;
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
@@ -819,6 +906,7 @@ int __must_check rfkill_register(struct
INIT_WORK(&rfkill->sync_work, rfkill_sync_work);
schedule_work(&rfkill->sync_work);
+ rfkill_send_events(rfkill, RFKILL_OP_ADD);
mutex_unlock(&rfkill_global_mutex);
return 0;
@@ -848,6 +936,7 @@ void rfkill_unregister(struct rfkill *rf
device_del(&rfkill->dev);
mutex_lock(&rfkill_global_mutex);
+ rfkill_send_events(rfkill, RFKILL_OP_DEL);
list_del_init(&rfkill->node);
mutex_unlock(&rfkill_global_mutex);
@@ -862,6 +951,227 @@ void rfkill_destroy(struct rfkill *rfkil
}
EXPORT_SYMBOL(rfkill_destroy);
+static int rfkill_fop_open(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data;
+ struct rfkill *rfkill;
+ struct rfkill_int_event *ev, *tmp;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&data->events);
+ mutex_init(&data->mtx);
+ init_waitqueue_head(&data->read_wait);
+
+ mutex_lock(&rfkill_global_mutex);
+ mutex_lock(&data->mtx);
+ /*
+ * start getting events from elsewhere but hold mtx to get
+ * startup events added first
+ */
+ list_add(&data->list, &rfkill_fds);
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ goto free;
+ rfkill_fill_event(&ev->ev, rfkill, RFKILL_OP_ADD);
+ list_add_tail(&ev->list, &data->events);
+ }
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+
+ file->private_data = data;
+
+ return nonseekable_open(inode, file);
+
+ free:
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+ kfree(data);
+ return -ENOMEM;
+}
+
+static unsigned int rfkill_fop_poll(struct file *file, poll_table *wait)
+{
+ struct rfkill_data *data = file->private_data;
+ unsigned int res = POLLOUT | POLLWRNORM;
+
+ poll_wait(file, &data->read_wait, wait);
+
+ mutex_lock(&data->mtx);
+ if (!list_empty(&data->events))
+ res = POLLIN | POLLRDNORM;
+ mutex_unlock(&data->mtx);
+
+ return res;
+}
+
+static bool rfkill_readable(struct rfkill_data *data)
+{
+ bool r;
+
+ mutex_lock(&data->mtx);
+ r = !list_empty(&data->events);
+ mutex_unlock(&data->mtx);
+
+ return r;
+}
+
+static ssize_t rfkill_fop_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev;
+ unsigned long sz;
+ int ret;
+
+ mutex_lock(&data->mtx);
+
+ while (list_empty(&data->events)) {
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ goto out;
+ }
+ mutex_unlock(&data->mtx);
+ ret = wait_event_interruptible(data->read_wait,
+ rfkill_readable(data));
+ mutex_lock(&data->mtx);
+
+ if (ret)
+ goto out;
+ }
+
+ ev = list_first_entry(&data->events, struct rfkill_int_event,
+ list);
+
+ sz = min_t(unsigned long, sizeof(ev->ev), count);
+ ret = sz;
+ if (copy_to_user(buf, &ev->ev, sz))
+ ret = -EFAULT;
+
+ list_del(&ev->list);
+ kfree(ev);
+ out:
+ mutex_unlock(&data->mtx);
+ return ret;
+}
+
+static ssize_t rfkill_fop_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill *rfkill;
+ struct rfkill_event ev;
+
+ /* we don't need the 'hard' variable but accept it */
+ if (count < sizeof(ev) - 1)
+ return -EINVAL;
+
+ if (copy_from_user(&ev, buf, sizeof(ev) - 1))
+ return -EFAULT;
+
+ if (ev.op != RFKILL_OP_CHANGE && ev.op != RFKILL_OP_CHANGE_ALL)
+ return -EINVAL;
+
+ if (ev.type >= NUM_RFKILL_TYPES)
+ return -EINVAL;
+
+ mutex_lock(&rfkill_global_mutex);
+
+ if (ev.op == RFKILL_OP_CHANGE_ALL) {
+ if (ev.type == RFKILL_TYPE_ALL) {
+ enum rfkill_type i;
+ for (i = 0; i < NUM_RFKILL_TYPES; i++)
+ rfkill_global_states[i].cur = ev.soft;
+ } else {
+ rfkill_global_states[ev.type].cur = ev.soft;
+ }
+ }
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ if (rfkill->idx != ev.idx && ev.op != RFKILL_OP_CHANGE_ALL)
+ continue;
+
+ if (rfkill->type != ev.type && ev.type != RFKILL_TYPE_ALL)
+ continue;
+
+ rfkill_set_block(rfkill, ev.soft);
+ }
+ mutex_unlock(&rfkill_global_mutex);
+
+ return count;
+}
+
+static int rfkill_fop_release(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev, *tmp;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_del(&data->list);
+ mutex_unlock(&rfkill_global_mutex);
+
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+
+#ifdef CONFIG_RFKILL_INPUT
+ if (data->input_handler)
+ atomic_dec(&rfkill_input_disabled);
+#endif
+
+ kfree(data);
+
+ return 0;
+}
+
+#ifdef CONFIG_RFKILL_INPUT
+static long rfkill_fop_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rfkill_data *data = file->private_data;
+
+ if (_IOC_TYPE(cmd) != RFKILL_IOC_MAGIC)
+ return -ENOSYS;
+
+ if (_IOC_NR(cmd) != RFKILL_IOC_NOINPUT)
+ return -ENOSYS;
+
+ mutex_lock(&data->mtx);
+
+ if (!data->input_handler) {
+ atomic_inc(&rfkill_input_disabled);
+ data->input_handler = true;
+ }
+
+ mutex_unlock(&data->mtx);
+
+ return 0;
+}
+#endif
+
+static const struct file_operations rfkill_fops = {
+ .open = rfkill_fop_open,
+ .read = rfkill_fop_read,
+ .write = rfkill_fop_write,
+ .poll = rfkill_fop_poll,
+ .release = rfkill_fop_release,
+#ifdef CONFIG_RFKILL_INPUT
+ .unlocked_ioctl = rfkill_fop_ioctl,
+ .compat_ioctl = rfkill_fop_ioctl,
+#endif
+};
+
+static struct miscdevice rfkill_miscdev = {
+ .name = "rfkill",
+ .fops = &rfkill_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
static int __init rfkill_init(void)
{
@@ -875,10 +1185,19 @@ static int __init rfkill_init(void)
if (error)
goto out;
+ error = misc_register(&rfkill_miscdev);
+ if (error) {
+ class_unregister(&rfkill_class);
+ goto out;
+ }
+
#ifdef CONFIG_RFKILL_INPUT
error = rfkill_handler_init();
- if (error)
+ if (error) {
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
+ goto out;
+ }
#endif
out:
@@ -891,6 +1210,7 @@ static void __exit rfkill_exit(void)
#ifdef CONFIG_RFKILL_INPUT
rfkill_handler_exit();
#endif
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
}
module_exit(rfkill_exit);
--- wireless-testing.orig/net/rfkill/Kconfig 2009-06-01 22:34:04.000000000 +0200
+++ wireless-testing/net/rfkill/Kconfig 2009-06-01 22:36:50.000000000 +0200
@@ -18,7 +18,7 @@ config RFKILL_LEDS
default y
config RFKILL_INPUT
- bool
+ bool "RF switch input support"
depends on RFKILL
depends on INPUT = y || RFKILL = INPUT
- default y
+ default y if !EMBEDDED
--- wireless-testing.orig/Documentation/feature-removal-schedule.txt 2009-06-01 22:32:03.000000000 +0200
+++ wireless-testing/Documentation/feature-removal-schedule.txt 2009-06-01 22:36:50.000000000 +0200
@@ -437,3 +437,10 @@ Why: Superseded by tdfxfb. I2C/DDC suppo
driver but this caused driver conflicts.
Who: Jean Delvare <[email protected]>
Krzysztof Helt <[email protected]>
+
+---------------------------
+
+What: CONFIG_RFKILL_INPUT
+When: 2.6.33
+Why: Should be implemented in userspace, policy daemon.
+Who: Johannes Berg <[email protected]>
On Tue, 02 Jun 2009, Marcel Holtmann wrote:
> We just need to fix the platform drivers then. They should not set
> global states since that is not what they are controlling. They control
We should change things, yes. So that the platform stores the global
state. That was half-broken on the old core (the platform stored the
state of its own device, which could be out of sync with the global
state), but the part of it setting the global state is correct.
That needs a new in-kernel API to tie the core to platform drivers
capable of storing global states without causing problems when drivers
are unloaded, but it is not hard.
As for NVS events, they have a clear use case: to let rfkilld know which
global states it could leave alone the first time it loads, and which
ones have to be restored...
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Johannes Berg wrote:
> Would everybody be happy with this rolled in?
>
> johannes
>
> Subject: rfkill: userspace API improvements
>
> This adds the two following things to /dev/rfkill:
> 1) notification to userspace with a new operation
> RFKILL_OP_NVS_REPORT about default states restored
> from platform non-volatile storage
> 2) the ability to ignore input events in the kernel
> while a handler daemon is connected, if the input
> part is compiled in.
>
> Signed-off-by: Johannes Berg <[email protected]>
>
Sounds reasonable. Thanks for responding to these concerns.
Alan
On Mon, 01 Jun 2009, Johannes Berg wrote:
> On Mon, 2009-06-01 at 10:33 -0300, Henrique de Moraes Holschuh wrote:
>
> > I don't know if I have any gripes with your latest patch ;) Can't test it
> > right now. I was reacting to the comments in the thread.
>
> Well could you read it and see if the API does what you require? A
> discussion about that would be so much more useful than handwaving about
> LED blinking and security issues...
LEDs? I mean the device being rfkilled/unrfkilled (which often means the
entire device is kicked off the USB bus and hotplugged back)... I must cut
down on comparisons and analogies.
I will look at your patch as soon as I can, I just can't do it right now.
> > On a related note, Johannes, would you be opposed to exporting something I
> > could call from thinkpad-acpi to request a radio state change on a rfkill
> > struct? This would allow me to remove some of the mess in thinkpad-acpi.
>
> I don't think I understand what you want? What do you mean by "request a
> radio state change"?
As in "please try to block this _specific_ radio", and "please try to
unblock this _specific_ radio".
I.e. what one would do by a write to the "state" attribute of the _old_
rfkill sysfs (and which would get overriden the next time someone changed
the global state, etc).
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Hi Alan,
> >>>> I don't think we should expect userspace to know whether or not a device
> >>>> has a persistent state. Yes, it _could_ maintain whitelists, but why
> >>>> should it have to if the driver already knows?
> >>>>
> >>> If you want that, then the best approach seems an extra sysfs attribute
> >>> for this. It is not intrusive on the event API and lets udev etc. have
> >>> these information, too.
> >>>
> >> I have no problems with either approach. As long as the information of
> >> which devices have restored their initial state from NVS is available to
> >> userspace, it is enough.
> >>
> >
> > just to get the semantic right here. We are not telling userspace if a
> > state has been restored or not. We are telling userspace that this
> > specific RFKILL switch is capable of storing something in a persistent
> > state over boot. There is a difference here.
> >
> > If a RFKILL driver claims it is capable of persistent storage then it
> > better work or it should not claims it. Either it does it all the time
> > or doesn't do it at all. Otherwise we end up in policy again and that is
> > not the job of the kernel.
> >
> >
> >> Do note that this information also needs to be available for resume (state
> >> should be checkpointed to NVS on sleep, and restored from NVS on resume. I
> >> believe tpacpi does this, but if it doesn't, I will fix it eventually).
> >>
> >
> > Correct. That is the job of the driver. If it is broken, that needs
> > fixing.
> >
>
> The core needs fixing too, currently it restores state for all devices
> on resume.
>
> This is my fault again, for coming up with scenarios you probably don't
> care about :-). The problem is that suspend to disk provides a
> possibility that the state will change for _any_ driver. It's more
> obvious with rfkill-input and NVS, if the wireless toggle key is pressed
> when the disk image is being written out. But you can also contrive it
> with no NVS and rfkilld, if rfkilld gets started in the initramfs of the
> resume kernel. We took the easy way out, rather than adding resume
> handlers to all drivers, or trying to work out what the real design
> problem was :-).
>
> I hope that explains the issue. I agree with your logic, I just want to
> be clear that it needs more work on the rfkill core. Drivers with NVS
> should have a resume handler to call rfkill_set_sw_state(), but for this
> to work the core will need to stop restoring their state (for NVS
> drivers only). As a detail, I think this behaviour difference with NVS
> means it should be flagged with a more explicit API, e.g.
> rfkill_init_persistent_sw_state().
I don't see any problem here. If the driver needs extra help from the
RFKILL subsystem to express its states, that is just fine with me.
> rfkill-input would like another (even more intrusive) hack here to set
> the global state on resume. But I for one can live without it for the
> transition. I think NVS state change over suspend is much more of a
> corner case. At least on eeepc-laptop it only seems to happen if the
> user does something relatively odd. And the worst that will happen is
> they have to press the wireless-toggle key a second time before it
> starts working.
So one think is NVS states and the other are the HW switches. For the
NVS ones we need extra support for suspend/resume details, but that is
as mentioned above just fine. Also please keep in mind that NVS states
are per device and not global. If a system wants to treat them as global
that is userspace policy and not our concern from the kernel side.
For the HW states, I think they gonna store its state pretty obvious and
if we need a resume callback to poll its current state, then that seems
logical to me too.
Regards
Marcel
Henrique de Moraes Holschuh wrote:
> On Sun, 07 Jun 2009, Alan Jenkins wrote:
>
>> 1) remove rfkill_set_global_sw_state()
>> 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
>> registration, setting a flag.
>> 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
>> events, tho it may as well be set in all events)
>> 4) rfkill-input preserves existing behaviour - *if enabled* - by
>> initializing the global state from individual devices which have NVS.
>> (As before, each _type_ of rfkill device has its own global state).
>> 5) rfkill devices with NVS will have their current state preserved,
>> so long as the global state has not yet been set (by userspace or by
>> rfkill-input). Of course userspace can change the state in response
>> to the device being added.
>>
>
> I can agree to that, it will avoid the regression I have been
> complaining about, and seems to address the major complaints against
> what we have now...
>
Yeah, I hope it avoids the objections about NVS in /dev/rfkill. We avoid
the separate NVS operation. And it exports the NVS state through the
specific device it comes from, rather than exporting it as a global state.
So userspace gets more specific information; at the same time the
interface is simpler because the information comes in a single message,
and it's less prescriptive because it doesn't push the idea of global
state(s) quite so much.
> I think it should probably be enhanced (it doesn't have to be enhanced
> _now_, however) to let the core call back into NVS-providing drivers to
> checkpoint NVS state. But that's not something I feel strongly about.
>
Ok. I think that feature would be subject to further debate /
clarification. So I will ignore it and code up the basics outlined above
:-).
> Otherwise, the driver will checkpoint to NVS whatever is the state of
> its rfkill device, which could have been changed outside of the global
> scope. While that's exacly what we do right now, it is arguably not the
> best way to go about it (i.e. it is broken).
>
>
>> Comments?
>>
>
> Let's do it :-)
>
Cool :-). I'll see what I can do.
Alan
Hi Johannes!
On Mon, 01 Jun 2009, Johannes Berg wrote:
> On Mon, 2009-06-01 at 09:28 -0300, Henrique de Moraes Holschuh wrote:
> > On Mon, 01 Jun 2009, Alan Jenkins wrote:
> > >> See, Henrique says that the use case is Thinkpads which store the
> > >> previous state in the BIOS. But that matters to you only if you use a
> > >> mixture of operation systems, which we don't have to support.
> >
> > Well, if you do all in userspace, how do you propose to avoid the usual race
> > conditions of the sort "radio starts on, but it should have started off",
> > etc? You'd have to kick the radios off on rfkill module load for safety,
> > and that will also cause nastyness (it kicks my built-in wlan
> > (eeepcargh)/bluetooth(most)/wwan(most) off bus, then hotplugs it again!).
>
> Have you bothered reading the code? If you turn things off with
> CHANGE_ALL that applies to hot-plugged devices too.
I mean platform devices which remember state. Those will be turned on/off
by the platform firmware at boot and often at resume time, and overriding
them in rfkill (especially if it is done later, by a rfkilld) will cause
some flicker (on/off/on, off/on/off) in certain situations.
This is a regression, because the current interface doesn't suffer this
limitation.
Also, remember that this on/off stuff CAN mean kicking devices off-bus,
that's very usual for everything other than WLAN, so it can be a lot more
annoying than just turning a transmitter off for a moment.
I certainly expect whatever the current global state to be applied to all
new devices getting registered, that is not something I would complain
about.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Hi Johannes,
> Would everybody be happy with this rolled in?
>
> johannes
>
> Subject: rfkill: userspace API improvements
>
> This adds the two following things to /dev/rfkill:
> 1) notification to userspace with a new operation
> RFKILL_OP_NVS_REPORT about default states restored
> from platform non-volatile storage
I really don't understand why this is needed. What benefit does it give
us compared to just sent OP_CHANGE and OP_CHANGE as an update. My X200
for example does this anyway on suspend/resume.
So what is rfkilld suppose to be doing when receiving this report? What
is the expected behavior? Why do we bother with multi-OS crap here? I am
really unclear what are we trying to solve here.
> 2) the ability to ignore input events in the kernel
> while a handler daemon is connected, if the input
> part is compiled in.
For the rfkill-input ioctl:
Acked-by: Marcel Holtmann <[email protected]>
Regards
Marcel
On Mon, 2009-06-08 at 12:10 +0100, Alan Jenkins wrote:
> >> - rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state());
> >> err = rfkill_register(wwan_rfkill);
> >> if (err)
> >> goto register_wwan_err;
> >>
> >
> > Hmm. Anyone know anything about HP? That kinda looks persistent too.
> >
>
> Quite possibly. I just don't know, and it's never been treated that way
> before. The old core, when I first read it, you were supposed to report
> the initial state so that it knew whether it differed from the default
> state. So the core could "optimise away" the initialization if the
> current state was the same. Then rfkill_set_default() was added, but it
> was only used in tp-acpi and then eeepc-laptop.
Oh, good point. Then maybe that was just to avoid the core
initialisation that I just always did for ease of use.
> The counter is the sony-laptop case. That driver also hits ACPI to
> query it's current state. But apparently it doesn't always power up in
> a useful state, because there's a specific git commit which forces the
> radios to unblock at load time.
>
>
> I think this patch should preserve the existing behaviour. But the
> rfkill rewrite as a whole is a good opportunity to re-check this issue.
> There's only a few maintainers to contact so I don't mind doing it -
> unless you were going to check with them about the rewrite anyway?
I wasn't planning on doing anything more than before -- where I had
copied hopefully all maintainers on the rewrite patch.
> > Ah, this is the quirky backward compat code you're talking about. I
> > guess we need it, although I don't particularly like it.
> >
>
> I don't like it either. The patch as a whole only makes sense because
> rfkill-input is going away, so the global states will become less
> important. When you use rfkill-input, you really want the individual
> states to match the global ones, otherwise your user experience
> suffers. When you don't use rfkill-input, the "global" states will just
> be load-time defaults (once suspend is fixed).
Yeah. Want to fix suspend too? I would now, but I really need to get
lunch first :)
johannes
Hi Alan,
> > I really don't understand why this is needed. What benefit does it give
> > us compared to just sent OP_CHANGE and OP_CHANGE as an update. My X200
> > for example does this anyway on suspend/resume.
> >
>
> This is required for boot only. I have no reason for this event to be
> generated on resume.
>
> The same effect could be had by generating an OP_CHANGE on f_open,
> _only_ when a platform driver has provided a value from NVS. But it
> does seem clearer to make it a different type of event.
that is my whole point. If the kernel driver wants to preserve these
then it can just issue OP_ADD to notify use about the current state of
the values. The OP_ADD gets send once you open /dev/rfkill and if they
not match with our policy we have to change them anyway. I really don't
see the need for an extra operation here. Let me ask this again, how is
it different from just sending the OP_ADD and then let rfkilld decide to
either use that value or enforce its own policy. If the wish is to
enforce policy you can't do anything about it anyway.
> > So what is rfkilld suppose to be doing when receiving this report? What
> > is the expected behavior? Why do we bother with multi-OS crap here? I am
> > really unclear what are we trying to solve here.
>
> In order to replicate the kernel behavior, it is expected that you set
> your internal state from this event. E.g. when the user next presses
> the wireless toggle key, you set the inverse of that internal state.
>
> Since this event is generated by a platform driver, you can expect it to
> be present following coldplug (the udev initscript). If the event is
> not present after coldplug, you may then issue OP_CHANGE yourself, to
> e.g. restore the state from a file. You would not be expected to handle
> OP_NVS_REPORT after startup. (Unless the daemon is restarted).
>
> Replicating the kernel behavior will allow us to avoid causing a couple
> of niggly little regressions on at least two platforms. It preserves
> the behavior when dual-booting (possibly between different linux
> distros), and when the BIOS setup screen exposes the NVS state as an
> option. The new behavior you suggest will annoy any users who have
> become used to these scenarios "just working".
>
> You may not use these platforms yourself. But I'm as annoyed as
> Henrique is, we don't want to impose regressions just because other
> platforms don't implement the feature.
>
> Why the fuss about implementing this, it seems easy enough? Start
> rfkilld after udev (like everything else). If you get NVS_REPORT, then
> use those states. Fill in any other states from defaults or state files
> and issue OP_CHANGE for them, just as you're already planning. Ignore
> any subsequent NVS_REPORTs. That should cover it.
>
> It's the cost for starting from a working implementation. You benefit
> from having existing drivers and users, you pay by not breaking them
> without good reason.
I really don't care about current behavior, because that has been just a
hack anyway. And it happens to work if there is proper BIOS support. We
are at the point now where we stop working around a complicated and in
some cases broken implementation. Overloading it with weird special
cases is just wrong and so far I am not buying into any of the arguments
here. The point behind the whole effort from Johannes is to finally fix
RFKILL support. If it breaks current behavior, I couldn't care less in
some cases.
So Johannes and I talked about it a lot last week. And we will be doing
rfkilld so finally deprecated the broken idea of rfkill-input and move
the policy into userspace where it belongs. To make this clear, the
concept of cross-OS state keeping is broken. Having the BIOS or a
different OS dictate policy makes no sense.
Regards
Marcel
On Mon, 2009-06-01 at 09:28 -0300, Henrique de Moraes Holschuh wrote:
> On Mon, 01 Jun 2009, Alan Jenkins wrote:
> >> See, Henrique says that the use case is Thinkpads which store the
> >> previous state in the BIOS. But that matters to you only if you use a
> >> mixture of operation systems, which we don't have to support.
>
> Well, if you do all in userspace, how do you propose to avoid the usual race
> conditions of the sort "radio starts on, but it should have started off",
> etc? You'd have to kick the radios off on rfkill module load for safety,
> and that will also cause nastyness (it kicks my built-in wlan
> (eeepcargh)/bluetooth(most)/wwan(most) off bus, then hotplugs it again!).
Have you bothered reading the code? If you turn things off with
CHANGE_ALL that applies to hot-plugged devices too.
johannes
On Tue, 02 Jun 2009, Marcel Holtmann wrote:
> Forget about this EPO crap. That is just a stupid concept anyway.
Is it? So, if I use the _hardware switch_ on my laptop to kill all
internal radios, it shouldn't be enforced by the OS on extra radios I
plugged? Or on shitty internal WLAN cards that doesn't tie properly to
the mini-pci and mini-pcie hardware kill lines?
And any userspace PoS program can decide to bring up such radios that
are not hardware-killed even if I am clearly trying to disable them all?
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Mon, 01 Jun 2009, Johannes Berg wrote:
> > Just don't expose a rfkill type until the first rfkill structure of that
> > type gets registered. THAT closes all holes in a sensible,
> > principle-of-least-suprise way. The current code (including the rewrite)
> > already deals with defaults and firmware-backed state storage just fine in
> > that case. All you need is a full interface that deals with global state
> > hotplug (which ain't difficult, that's one or two more notifications only).
>
> Global state hotplug is just not really possible to support, and I don't
I used 'expose' for a reason. You don't expose them to userspace until
there is an user for that type. You don't even have to hide it again after
the last user gets unregistered...
That's not hotplug as in 'create a new rfkill type for the kernel'.
> think even your original code supported that, since it cannot affect
> previously registered rfkill instances. You definitely don't want to
> hotplug a wireless device and turn off all others "due to that".
You do recall how the 'override system default' machinery worked? It would
return an error if a rfkill struct of that type had already been registered,
or if a call to set the default for that particular rfkill type had already
happened.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Johannes Berg wrote:
> On Sun, 2009-06-07 at 09:57 -0300, Henrique de Moraes Holschuh wrote:
>
>
>>> 1) remove rfkill_set_global_sw_state()
>>> 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
>>> registration, setting a flag.
>>> 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
>>> events, tho it may as well be set in all events)
>>> 4) rfkill-input preserves existing behaviour - *if enabled* - by
>>> initializing the global state from individual devices which have NVS.
>>> (As before, each _type_ of rfkill device has its own global state).
>>> 5) rfkill devices with NVS will have their current state preserved,
>>> so long as the global state has not yet been set (by userspace or by
>>> rfkill-input). Of course userspace can change the state in response
>>> to the device being added.
>>>
>
>
>> Let's do it :-)
>>
>
>
> Alan, you're right in that we currently overwrite devices with the
> global state upon registration. My suggestion would be to not do that
> when set_sw_state() has been called on the device before it was
> registered, by simply keeping track of that. Anyone want to propose a
> patch?
>
> johannes
>
I'll have a go. It seems within my abilities and I have hardware to
test it on. What could possibly go wrong :-).
I'll try to do it in one swoop, get rid of set_global_sw_state() and
convert tp-acpi as well as eeepc-laptop.
Thanks
Alan
Hi Dan,
> > > > How should userspace test CONFIG_RFKILL_INPUT to determine whether
> > > > it's safe to start the daemon? With the old core, debian-eeepc
> > > > scripts check if the module rfkill-input exists (which should work
> > > > even if it's built in). If it exists, the scripts don't perform any
> > > > rfkill actions. (Yeah, according to the doc this is not allowed
> > > > because the scripts don't use "claim", but you can see how it's
> > > > useful).
> > > >
> > > > The new rfkill-input isn't a module, so I'm not sure how your daemon
> > > > would test for it.
> > >
> > > Maybe we should add an ioctl that disables rfkill-input if present.
> >
> > I am against it. Can we just add a module parameter that allows us to
> > disable it. I am against cluttering a new interface with legacy stuff
> > since we are removing rfkill-input and replacing it by rfkilld anyway in
> > a near future (meaning when I am back from vacation).
>
> Module parameter to what? Module parameters are almost always the wrong
> thing to do. Or, just don't built rfkill_input it into your kernel
we will make rfkill-input go away. This is just for the interim to
detect if rfkill-input is supported by the kernel or not. And it allows
to compile it into the kernel and disable it via modprobe.conf.
Regards
Marcel
On Tue, 2009-06-02 at 09:38 +0200, Marcel Holtmann wrote:
> If you really don't wanna have rfkilld _not_ impose a policy on cold
> boot, then we can certainly add that. However that is part of rfkilld
> and not the kernel.
>
> For global states, I am questioning the platform that actually has
> storage for global states. All platforms that I have seen so far store
> individual per device based states and not the global one.
Yeah, unfortunately the platform drivers do store/restore global state.
And in a sense that makes (made) sense since the default rfkill-input
policy was to toggle everything based on the platform button. It's icky
though.
The problem with not telling rfkilld about this though is that the
kernel will suddenly impose a hotplug policy that rfkilld doesn't know
about. This might not even matter though since the _ADD will be sent
with that policy already applied, and if it wants to change the policy
rfkilld will do _CHANGE_ALL.
> If you wanna just accept what the BIOS (or other OS) tells you then that
> is an acceptable policy. So rfkilld will just map key input events in
> that case. Fine by me. Question is if we can't do that right now? I
> think we just can.
Yes, we probably can do that right now, by making rfkilld start up
without setting a policy (CHANGE_ALL). It just doesn't know what the
policy is, then.
> Question is if we really have a global state here. I doubt it since all
> of these are device specific states. And having the BIOS or ACPI dictate
> what state my external Bluetooth or WiFi device is in is pointless and a
> total broken concept.
Unfortunately that's how it worked before, it's part of the rfkill
legacy that it seems we'll have to accept. I guess we have only
ourselves to blame for not reviewing Henrique's rfkill implementation...
> > You propose to exclude a feature that currently works, on the grounds
> > that it is inherently broken. But you haven't said that this has ever
> > caused incorrect behavior. All you have said so far is that it is a hack.
>
> This never worked so far. The mac80211 or Bluetooth subsystems have no
> RFKILL state and thus global states are not enforced. An external dongle
> without RFKILL support is not taken into account. You are not talking
> about global states here. They are all device specific. The device just
> happens to be a platform device builtin the system.
Right, but rfkill does actually have a function to set the global policy
state (rfkill_set_global_sw_state) which some platform drivers insist on
calling.
The only reason we would need the NVS_REPORT then is to detect whether
or not anything in the kernel called rfkill_set_global_sw_state(). If we
can live with just configuring rfkilld for the (arguably broken) case
where somebody cares about this, I'm happy with removing it.
Hope that helps clear up your confusion.
johannes
On Mon, 2009-06-01 at 09:28 -0300, Henrique de Moraes Holschuh wrote:
> It can have default values just fine. And you can't wait for userspace or
> platform drivers to register a default, it just doesn't work, you cannot
> expect that all relevant drivers are loaded before "rfkilld".
>
> Just don't expose a rfkill type until the first rfkill structure of that
> type gets registered. THAT closes all holes in a sensible,
> principle-of-least-suprise way. The current code (including the rewrite)
> already deals with defaults and firmware-backed state storage just fine in
> that case. All you need is a full interface that deals with global state
> hotplug (which ain't difficult, that's one or two more notifications only).
Global state hotplug is just not really possible to support, and I don't
think even your original code supported that, since it cannot affect
previously registered rfkill instances. You definitely don't want to
hotplug a wireless device and turn off all others "due to that".
johannes
On Sun, 2009-05-31 at 21:03 +0200, Marcel Holtmann wrote:
> > Maybe we should add an ioctl that disables rfkill-input if present.
>
> I am against it. Can we just add a module parameter that allows us to
> disable it. I am against cluttering a new interface with legacy stuff
> since we are removing rfkill-input and replacing it by rfkilld anyway in
> a near future (meaning when I am back from vacation).
Right. But you'll get a bunch of people get it mixed up. I think I would
prefer adding the ioctl, but having it only when rfkill-input is
compiled in, so userspace would always just get -ENOSYS if there's no
rfkill-input present, which means the "cluttering" of the interface
occurs only for as long as we have rfkill-input and at some point you
would just remove that one line of code from rfkilld.
johannes
Johannes Berg wrote:
> On Sun, 2009-05-31 at 21:01 +0200, Marcel Holtmann wrote:
>
>
>>> You have a point there, but I'm not sure it even cares? When restarted
>>> it will probably want to impose its current policy anyway? It would be
>>> easy to add that we send the global default value for newly added ones
>>> too but I'm not sure it's necessary -- Marcel?
>>>
>> we can be smart and send an additional CHANGE_ALL when opening the
>> control device if it is set. We can also just send these anyway. Doesn't
>> really matter? Does it?
>>
>
> Well, I don't think we want/need this.
>
> See, Henrique says that the use case is Thinkpads which store the
> previous state in the BIOS. But that matters to you only if you use a
> mixture of operation systems, which we don't have to support.
>
"Other OS's" also includes the BIOS. My BIOS setup screen has an option
to toggle the wireless state. It's great to have this just work, and
annoying to have regressions.
And it's not a "mixture" of operating systems if you dual-boot linux,
but that will have the same problem. We'll get rather weird behaviour
if you dual boot A and B, where linux version A restores state to a
file, and linux version B uses the hardware persistent state.
> On the other hand, people on machines that don't store the rfkill state
> in the BIOS might care about having their machine boot up with the same
> rfkill state(s) as they shut down with, so the sane thing to do would be
> to have rfkilld store the state persistently, and then recover it at
> boot. At which point the BIOS state becomes irrelevant and a detail we
> actually end up not even _wanting_ to support because it means we need
> to be aware of machine differences in userspace.
>
Complexity sucks, but I don't think this is irrelevant. I can see you
don't want to rely on the kernel for the default value (even though it's
controllable by module option).
I like one of the solutions Marcels suggested, that /dev/rfkill should
report the "global default values" only when they have been set - either
by userspace or by a platform driver.
Thanks
Alan
Would everybody be happy with this rolled in?
johannes
Subject: rfkill: userspace API improvements
This adds the two following things to /dev/rfkill:
1) notification to userspace with a new operation
RFKILL_OP_NVS_REPORT about default states restored
from platform non-volatile storage
2) the ability to ignore input events in the kernel
while a handler daemon is connected, if the input
part is compiled in.
Signed-off-by: Johannes Berg <[email protected]>
---
include/linux/rfkill.h | 8 +++++
net/rfkill/core.c | 67 ++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 74 insertions(+), 1 deletion(-)
--- wireless-testing.orig/net/rfkill/core.c 2009-06-01 09:43:26.000000000 +0200
+++ wireless-testing/net/rfkill/core.c 2009-06-01 14:10:48.000000000 +0200
@@ -85,6 +85,7 @@ struct rfkill_data {
struct list_head events;
struct mutex mtx;
wait_queue_head_t read_wait;
+ bool input_handler;
};
@@ -115,7 +116,7 @@ MODULE_PARM_DESC(default_state,
"Default initial state for all radio types, 0 = radio off");
static struct {
- bool cur, def;
+ bool cur, def, restored;
} rfkill_global_states[NUM_RFKILL_TYPES];
static unsigned long rfkill_states_default_locked;
@@ -318,6 +319,8 @@ static void rfkill_set_block(struct rfki
}
#ifdef CONFIG_RFKILL_INPUT
+static atomic_t rfkill_input_disabled = ATOMIC_INIT(0);
+
/**
* __rfkill_switch_all - Toggle state of all switches of given type
* @type: type of interfaces to be affected
@@ -354,6 +357,9 @@ static void __rfkill_switch_all(const en
*/
void rfkill_switch_all(enum rfkill_type type, bool blocked)
{
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
if (!rfkill_epo_lock_active)
@@ -376,6 +382,9 @@ void rfkill_epo(void)
struct rfkill *rfkill;
int i;
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = true;
@@ -401,6 +410,9 @@ void rfkill_restore_states(void)
{
int i;
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = false;
@@ -417,6 +429,9 @@ void rfkill_restore_states(void)
*/
void rfkill_remove_epo_lock(void)
{
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = false;
mutex_unlock(&rfkill_global_mutex);
@@ -451,6 +466,8 @@ bool rfkill_get_global_sw_state(const en
void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
{
+ BUG_ON(type == RFKILL_TYPE_ALL);
+
mutex_lock(&rfkill_global_mutex);
/* don't allow unblock when epo */
@@ -465,6 +482,7 @@ void rfkill_set_global_sw_state(const en
rfkill_global_states[type].cur = blocked;
rfkill_global_states[type].def = blocked;
+ rfkill_global_states[type].restored = true;
out:
mutex_unlock(&rfkill_global_mutex);
}
@@ -939,6 +957,7 @@ static int rfkill_fop_open(struct inode
struct rfkill_data *data;
struct rfkill *rfkill;
struct rfkill_int_event *ev, *tmp;
+ enum rfkill_type i;
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
@@ -963,6 +982,19 @@ static int rfkill_fop_open(struct inode
rfkill_fill_event(&ev->ev, rfkill, RFKILL_OP_ADD);
list_add_tail(&ev->list, &data->events);
}
+
+ for (i = 0; i < NUM_RFKILL_TYPES; i++) {
+ if (!rfkill_global_states[i].restored)
+ continue;
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ goto free;
+ ev->ev.idx = 0;
+ ev->ev.type = RFKILL_TYPE_ALL;
+ ev->ev.op = RFKILL_OP_NVS_REPORT;
+ list_add_tail(&ev->list, &data->events);
+ }
+
mutex_unlock(&data->mtx);
mutex_unlock(&rfkill_global_mutex);
@@ -1103,17 +1135,50 @@ static int rfkill_fop_release(struct ino
list_for_each_entry_safe(ev, tmp, &data->events, list)
kfree(ev);
+#ifdef CONFIG_RFKILL_INPUT
+ if (data->input_handler)
+ atomic_dec(&rfkill_input_disabled);
+#endif
+
kfree(data);
return 0;
}
+#ifdef CONFIG_RFKILL_INPUT
+static long rfkill_fop_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rfkill_data *data = file->private_data;
+
+ if (_IOC_TYPE(cmd) != RFKILL_IOC_MAGIC)
+ return -ENOSYS;
+
+ if (_IOC_NR(cmd) != RFKILL_IOC_NOINPUT)
+ return -ENOSYS;
+
+ mutex_lock(&data->mtx);
+
+ if (!data->input_handler) {
+ atomic_inc(&rfkill_input_disabled);
+ data->input_handler = true;
+ }
+
+ mutex_unlock(&data->mtx);
+
+ return 0;
+}
+#endif
+
static const struct file_operations rfkill_fops = {
.open = rfkill_fop_open,
.read = rfkill_fop_read,
.write = rfkill_fop_write,
.poll = rfkill_fop_poll,
.release = rfkill_fop_release,
+#ifdef CONFIG_RFKILL_INPUT
+ .unlocked_ioctl = rfkill_fop_ioctl,
+#endif
};
static struct miscdevice rfkill_miscdev = {
--- wireless-testing.orig/include/linux/rfkill.h 2009-06-01 13:49:36.000000000 +0200
+++ wireless-testing/include/linux/rfkill.h 2009-06-01 14:08:35.000000000 +0200
@@ -56,12 +56,15 @@ enum rfkill_type {
* @RFKILL_OP_DEL: a device was removed
* @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device
* @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all)
+ * @RFKILL_OP_NVS_REPORT: kernel report about default state that was
+ * restored from platform non-volatile storage
*/
enum rfkill_operation {
RFKILL_OP_ADD = 0,
RFKILL_OP_DEL,
RFKILL_OP_CHANGE,
RFKILL_OP_CHANGE_ALL,
+ RFKILL_OP_NVS_REPORT,
};
/**
@@ -82,6 +85,11 @@ struct rfkill_event {
__u8 soft, hard;
} __packed;
+/* ioctl for turning off rfkill-input (if present) */
+#define RFKILL_IOC_MAGIC 'R'
+#define RFKILL_IOC_NOINPUT 1
+#define RFKILL_IOCTL_NOINPUT _IO(RFKILL_IOC_MAGIC, RFKILL_IOC_NOINPUT)
+
/* and that's all userspace gets */
#ifdef __KERNEL__
/* don't allow anyone to use these in the kernel */
On Wed, 03 Jun 2009, Dan Williams wrote:
> On Wed, 2009-06-03 at 01:10 -0300, Henrique de Moraes Holschuh wrote:
> > On Tue, 02 Jun 2009, Marcel Holtmann wrote:
> > > Forget about this EPO crap. That is just a stupid concept anyway.
> >
> > Is it? So, if I use the _hardware switch_ on my laptop to kill all
> > internal radios, it shouldn't be enforced by the OS on extra radios I
> > plugged? Or on shitty internal WLAN cards that doesn't tie properly to
> > the mini-pci and mini-pcie hardware kill lines?
> >
> > And any userspace PoS program can decide to bring up such radios that
> > are not hardware-killed even if I am clearly trying to disable them all?
>
> You hardkilled. Of course it should bring down everything. If you
> don't want to hardkill, then disable specific radios via /sys or some
> UI. The hardswitch is the "eject" button.
That's exactly my point. I want EPO to mean "no, you cannot turn this crap
on, GO AWAY" for anyone but root (or whomever SELinux allowed to do it,
etc).
> Other types like pushbutton switches (Fn+F5, HP laptop touchbuttons,
> etc) that *aren't* hardswitches connected to a GPIO on the wifi device
> should probably just bring up some window letting you pick what gets
> rfkilled.
Which reminds me, we STILL don't have a proper keycode for that. Care to
propose one to Dmitry?
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Mon, 2009-06-01 at 14:56 -0300, Henrique de Moraes Holschuh wrote:
> I have a stable userspace ABI that exposes a sysfs control that the user can
> use to enable/disable bluetooth and wwan. I need that thing to remain
> working well for at least one more year before I can try to remove it.
Ick, ok. I guess I may need something like that later too, so patches
welcome.
johannes
On Mon, 01 Jun 2009, Johannes Berg wrote:
> On Mon, 2009-06-01 at 12:50 -0300, Henrique de Moraes Holschuh wrote:
> > Yes, it "calls itself" right now... so I can certainly do that :)
> >
> > However, the in-driver shortcut means I give rfkill a kick and say "the soft
> > state has changed, deal with it", instead of "please change the state" which
> > it might deny due to EPO, etc.
>
> But ... now I'm confused ... why would the driver ever ask to change the
> state? Sounds like something that should be a button instead.
I have a stable userspace ABI that exposes a sysfs control that the user can
use to enable/disable bluetooth and wwan. I need that thing to remain
working well for at least one more year before I can try to remove it.
And right now, it ignores EPO since such state change do not go through the
rfkill core (this is not a regression, but might as well get it fixed now
that I expect rfkill to see more and more usage).
Before you ask, thinkpad-acpi enable/disable radio support predates the
existence of the rfkill subsystem (it was called ibm-acpi back then), this
is the reason for the driver-specific way of enabling/disabling radios.
Anyway, this sort of stuff it is not an input device (and it is not supposed
to be subject to the whims of userspace hotkey processing to begin with), so
it can't go over the input layer. That, and I have no desire to attempt to
get EV_SW SW_BLUETOOTH and EV_SW SW_WWAN past Dmitri without a good reason
(such as a device that actually has those switches...).
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Sun, 2009-06-07 at 18:16 +0100, Alan Jenkins wrote:
> Hmm, this looks more like a core feature than an rfkill-input bug:
>
> "
> v4: set default global state from userspace for rfkill hotplug
> (pointed out by Marcel)
>
> ...
>
> + if (ev.op == RFKILL_OP_CHANGE_ALL) {
> + if (ev.type == RFKILL_TYPE_ALL) {
> + enum rfkill_type i;
> + for (i = 0; i < NUM_RFKILL_TYPES; i++)
> + rfkill_global_states[i].cur = ev.soft;
> + } else {
> + rfkill_global_states[ev.type].cur = ev.soft;
> + }
> + }
>
>
> It still looks like this global state will apply even if rfkill-input is
> disabled and userspace has not requested OP_CHANGE_ALL; the default
> state will be enforced on hotplug. If you want to keep this, I think we
> still need my full scheme.
Your text and code quoting are in disconnect -- you're quoting code from
the /dev/rfkill handler. That confuses me. But anyway, yes, we should
probably simply not enforce the global state _iff_ the device set its
own state before registration.
> Eek! On a related note, what are we doing on resume? Johannes added it
> in a response to one of my annoying eeepc-laptop scenarios, but I didn't
> look at the code, only the results. It seems the individual devices are
> forced into the _global_ states (indexed by device type) on resume. I
> thought you were trying to neuter these global states so userspace had
> full discretion? Shouldn't we just restore the individual device state?
Yes, that seems like a bug.
johannes
Johannes Berg wrote:
> On Mon, 2009-06-08 at 11:14 +0100, Alan Jenkins wrote:
>
>> rfkill_set_global_sw_state() (previously rfkill_set_default()) will no
>> longer be exported by the rewritten rfkill core.
>>
>> Instead, platform drivers which can provide persistent soft-rfkill state
>> across power-down/reboot should indicate their initial state by calling
>> rfkill_set_sw_state() before registration. Otherwise, they will be
>> initialized to a default value during registration by a set_block call.
>>
>> We remove existing calls to rfkill_set_sw_state() which happen before
>> registration, since these had no effect in the old model. If these
>> drivers do have persistent state, the calls can be put back (subject
>> to testing :-). This affects hp-wmi and acer-wmi.
>>
>
> Cool.
>
>
>> Drivers with persistent state will affect the global state only if
>> rfkill-input is enabled. This is required, otherwise booting with
>> wireless soft-blocked and pressing the wireless-toggle key once would
>> have no apparent effect. This special case will be removed in future
>> along with rfkill-input, in favour of a more flexible userspace daemon
>> (see Documentation/feature-removal-schedule.txt).
>>
>
> How does that work?
>
>
>> --- a/drivers/platform/x86/acer-wmi.c
>> +++ b/drivers/platform/x86/acer-wmi.c
>>
>
>
>> @@ -996,8 +995,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
>> (void *)(unsigned long)cap);
>> if (!rfkill_dev)
>> return ERR_PTR(-ENOMEM);
>> - get_u32(&state, cap);
>> - rfkill_set_sw_state(rfkill_dev, !state);
>>
>
> That does seem persistent, I'd think? get_u32 here hits ACPI iirc.
>
>
>> diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
>> index 8d93114..16fffe4 100644
>> --- a/drivers/platform/x86/hp-wmi.c
>> +++ b/drivers/platform/x86/hp-wmi.c
>> @@ -422,7 +422,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
>> RFKILL_TYPE_WLAN,
>> &hp_wmi_rfkill_ops,
>> (void *) 0);
>> - rfkill_set_sw_state(wifi_rfkill, hp_wmi_wifi_state());
>> err = rfkill_register(wifi_rfkill);
>> if (err)
>> goto register_wifi_error;
>> @@ -433,8 +432,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
>> RFKILL_TYPE_BLUETOOTH,
>> &hp_wmi_rfkill_ops,
>> (void *) 1);
>> - rfkill_set_sw_state(bluetooth_rfkill,
>> - hp_wmi_bluetooth_state());
>> err = rfkill_register(bluetooth_rfkill);
>> if (err)
>> goto register_bluetooth_error;
>> @@ -445,7 +442,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
>> RFKILL_TYPE_WWAN,
>> &hp_wmi_rfkill_ops,
>> (void *) 2);
>> - rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state());
>> err = rfkill_register(wwan_rfkill);
>> if (err)
>> goto register_wwan_err;
>>
>
> Hmm. Anyone know anything about HP? That kinda looks persistent too.
>
Quite possibly. I just don't know, and it's never been treated that way
before. The old core, when I first read it, you were supposed to report
the initial state so that it knew whether it differed from the default
state. So the core could "optimise away" the initialization if the
current state was the same. Then rfkill_set_default() was added, but it
was only used in tp-acpi and then eeepc-laptop.
The counter is the sony-laptop case. That driver also hits ACPI to
query it's current state. But apparently it doesn't always power up in
a useful state, because there's a specific git commit which forces the
radios to unblock at load time.
I think this patch should preserve the existing behaviour. But the
rfkill rewrite as a whole is a good opportunity to re-check this issue.
There's only a few maintainers to contact so I don't mind doing it -
unless you were going to check with them about the rewrite anyway?
>> @@ -1200,8 +1185,20 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
>> atp_rfk->id = id;
>> atp_rfk->ops = tp_rfkops;
>>
>> - rfkill_set_states(atp_rfk->rfkill, initial_sw_state,
>> - tpacpi_rfk_check_hwblock_state());
>> + initial_sw_status = (tp_rfkops->get_status)();
>> + if (initial_sw_status < 0) {
>> + printk(TPACPI_ERR
>> + "failed to read initial state for %s, error %d; "
>> + "will turn radio off\n", name, initial_sw_status);
>>
>
> That message seems wrong now, it would not turn off but impose the
> current default, I think?
>
Yes, good point.
>> /**
>> - * rfkill_set_global_sw_state - set global sw block default
>>
>
> There's a static inline in the !RFKILL case, please remove that too.
>
>
>> @@ -916,7 +885,17 @@ int __must_check rfkill_register(struct rfkill *rfkill)
>> if (rfkill->ops->poll)
>> schedule_delayed_work(&rfkill->poll_work,
>> round_jiffies_relative(POLL_INTERVAL));
>> - schedule_work(&rfkill->sync_work);
>> +
>> + if (!rfkill->persistent || rfkill_epo_lock_active) {
>> + schedule_work(&rfkill->sync_work);
>> + } else {
>> +#ifdef CONFIG_RFKILL_INPUT
>> + bool soft_blocked = !!(rfkill->state & RFKILL_BLOCK_SW);
>> +
>> + if (!atomic_read(&rfkill_input_disabled))
>> + __rfkill_switch_all(rfkill->type, soft_blocked);
>> +#endif
>> + }
>>
>
> Ah, this is the quirky backward compat code you're talking about. I
> guess we need it, although I don't particularly like it.
>
I don't like it either. The patch as a whole only makes sense because
rfkill-input is going away, so the global states will become less
important. When you use rfkill-input, you really want the individual
states to match the global ones, otherwise your user experience
suffers. When you don't use rfkill-input, the "global" states will just
be load-time defaults (once suspend is fixed).
> Looks good except for these few comments!
>
> johannes
>
Great, thanks for the feedback
Marcel Holtmann wrote:
> Hi Alan,
>
>
>>>>>> I don't think we should expect userspace to know whether or not a device
>>>>>> has a persistent state. Yes, it _could_ maintain whitelists, but why
>>>>>> should it have to if the driver already knows?
>>>>>>
>>>>>>
>>>>> If you want that, then the best approach seems an extra sysfs attribute
>>>>> for this. It is not intrusive on the event API and lets udev etc. have
>>>>> these information, too.
>>>>>
>>>>>
>>>> I have no problems with either approach. As long as the information of
>>>> which devices have restored their initial state from NVS is available to
>>>> userspace, it is enough.
>>>>
>>>>
>>> just to get the semantic right here. We are not telling userspace if a
>>> state has been restored or not. We are telling userspace that this
>>> specific RFKILL switch is capable of storing something in a persistent
>>> state over boot. There is a difference here.
>>>
>>> If a RFKILL driver claims it is capable of persistent storage then it
>>> better work or it should not claims it. Either it does it all the time
>>> or doesn't do it at all. Otherwise we end up in policy again and that is
>>> not the job of the kernel.
>>>
>>>
>>>
>>>> Do note that this information also needs to be available for resume (state
>>>> should be checkpointed to NVS on sleep, and restored from NVS on resume. I
>>>> believe tpacpi does this, but if it doesn't, I will fix it eventually).
>>>>
>>>>
>>> Correct. That is the job of the driver. If it is broken, that needs
>>> fixing.
>>>
>>>
>> The core needs fixing too, currently it restores state for all devices
>> on resume.
>>
>> This is my fault again, for coming up with scenarios you probably don't
>> care about :-). The problem is that suspend to disk provides a
>> possibility that the state will change for _any_ driver. It's more
>> obvious with rfkill-input and NVS, if the wireless toggle key is pressed
>> when the disk image is being written out. But you can also contrive it
>> with no NVS and rfkilld, if rfkilld gets started in the initramfs of the
>> resume kernel. We took the easy way out, rather than adding resume
>> handlers to all drivers, or trying to work out what the real design
>> problem was :-).
>>
>> I hope that explains the issue. I agree with your logic, I just want to
>> be clear that it needs more work on the rfkill core. Drivers with NVS
>> should have a resume handler to call rfkill_set_sw_state(), but for this
>> to work the core will need to stop restoring their state (for NVS
>> drivers only). As a detail, I think this behaviour difference with NVS
>> means it should be flagged with a more explicit API, e.g.
>> rfkill_init_persistent_sw_state().
>>
>
> I don't see any problem here. If the driver needs extra help from the
> RFKILL subsystem to express its states, that is just fine with me.
>
>
>> rfkill-input would like another (even more intrusive) hack here to set
>> the global state on resume. But I for one can live without it for the
>> transition. I think NVS state change over suspend is much more of a
>> corner case. At least on eeepc-laptop it only seems to happen if the
>> user does something relatively odd. And the worst that will happen is
>> they have to press the wireless-toggle key a second time before it
>> starts working.
>>
>
> So one think is NVS states and the other are the HW switches. For the
> NVS ones we need extra support for suspend/resume details, but that is
> as mentioned above just fine. Also please keep in mind that NVS states
> are per device and not global. If a system wants to treat them as global
> that is userspace policy and not our concern from the kernel side.
>
> For the HW states, I think they gonna store its state pretty obvious and
> if we need a resume callback to poll its current state, then that seems
> logical to me too.
>
Ok. I will work on this and post a few more patches. I guess it's
still easiest if I post this as running code, and try to justify it to
you guys.
1/3: The original patch removing set_global_sw_state() - this hasn't
been applied yet.
2/3: Remove the core restore-on-resume code. Devices with NVS will
maintain their state, and _must_ check NVS on resume. Other devices are
expected to restore their own state.
I think this should work, because it's how the rfkill rewrite was
designed to start with. It may take me a while to figure out _why_ it
works though. I'm not sure about the ordering - whether my
acpi/platform driver resume callback will run before the rfkill class
resume handler, and if so then how does it work? I don't want to submit
code unless I understand it :-).
3/3: Export the "persistent" flag in sysfs - now we have hopefully
settled what it means.
Thanks
Alan
Marcel Holtmann wrote:
> Hi Alan,
>
>
>>>>>>>>> We just need to fix the platform drivers then. They should not set
>>>>>>>>> global states since that is not what they are controlling. They
>>>>>>>>> control
>>>>>>>>>
>>>>>>>>>
>>>>>>>> We should change things, yes. So that the platform stores the global
>>>>>>>> state. That was half-broken on the old core (the platform stored the
>>>>>>>> state of its own device, which could be out of sync with the global
>>>>>>>> state), but the part of it setting the global state is correct.
>>>>>>>>
>>>>>>>> That needs a new in-kernel API to tie the core to platform drivers
>>>>>>>> capable of storing global states without causing problems when drivers
>>>>>>>> are unloaded, but it is not hard.
>>>>>>>>
>>>>>>>> As for NVS events, they have a clear use case: to let rfkilld know
>>>>>>>> which
>>>>>>>> global states it could leave alone the first time it loads, and which
>>>>>>>> ones have to be restored...
>>>>>>>>
>>>>>>>>
>>>>>>> show me an example of a platform device that stores the global state. I
>>>>>>> think you are confusing the word platform as in system with a platform
>>>>>>> device. The ThinkPad Bluetooth and WWAN switches are platform devices
>>>>>>> and control each one specific device. Same goes for the EeePC. They are
>>>>>>> not controlling a global state.
>>>>>>>
>>>>>>>
>>>>>> I don't know what big difference you see between the two uses of
>>>>>> "platform",
>>>>>> but I will just work around it to get something useful out of this mail.
>>>>>>
>>>>>> The laptop stores in NVS the state of its 'switches'. This is as close as
>>>>>> one gets from 'storing the global state'. When the laptop boots,
>>>>>> these devices get set by the firware to the state in NVS. It is the best
>>>>>> place to store global state, because these devices will be in their proper
>>>>>> state (i.e. matching what will be the global state when the rfkill core
>>>>>> loads) all the time. It also gives you for free multi-OS/multi-kernel
>>>>>> state
>>>>>> storage for these devices, and compatibility with BIOSes that let you
>>>>>> define
>>>>>> the initial state for the devices in the firmware configuration, etc.
>>>>>>
>>>>>>
>>>>> it stores the state of its switches and why should these be enforced as
>>>>> a global state? Who says that this is a global state? For me that sounds
>>>>> like policy.
>>>>>
>>>>>
>>>> We don't seem to be getting very far :-(. I agree that these do not
>>>> appear to be global states, just the states of individual rfkill
>>>> devices.
>>>>
>>>> So I would propose the following changes. (I'm happy to write the
>>>> code as well, but I think it's easier to read English).
>>>>
>>>> 1) remove rfkill_set_global_sw_state()
>>>> 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
>>>> registration, setting a flag.
>>>> 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
>>>> events, tho it may as well be set in all events)
>>>>
>>>>
>>> you can do things like this already if you just set the states correctly
>>> between rfkill_alloc and rfkill_register. So you should make sure you
>>> register your RFKILL switch with the correct state and not toggle it
>>> later. As far as I can tell the tpacpi driver does that already.
>>>
>>>
>> Ah. I need to read the (rewritten) code again.
>>
>
> it could be still broken to some degree, but some stuff is just because
> rfkill-input interferes. So disable rfkill-input via ioctl or not
> compile it at all.
>
Hmm, this looks more like a core feature than an rfkill-input bug:
"
v4: set default global state from userspace for rfkill hotplug
(pointed out by Marcel)
...
+ if (ev.op == RFKILL_OP_CHANGE_ALL) {
+ if (ev.type == RFKILL_TYPE_ALL) {
+ enum rfkill_type i;
+ for (i = 0; i < NUM_RFKILL_TYPES; i++)
+ rfkill_global_states[i].cur = ev.soft;
+ } else {
+ rfkill_global_states[ev.type].cur = ev.soft;
+ }
+ }
It still looks like this global state will apply even if rfkill-input is
disabled and userspace has not requested OP_CHANGE_ALL; the default
state will be enforced on hotplug. If you want to keep this, I think we
still need my full scheme.
Eek! On a related note, what are we doing on resume? Johannes added it
in a response to one of my annoying eeepc-laptop scenarios, but I didn't
look at the code, only the results. It seems the individual devices are
forced into the _global_ states (indexed by device type) on resume. I
thought you were trying to neuter these global states so userspace had
full discretion? Shouldn't we just restore the individual device state?
static int rfkill_resume(struct device *dev)
{
struct rfkill *rfkill = to_rfkill(dev);
bool cur;
mutex_lock(&rfkill_global_mutex);
cur = rfkill_global_states[rfkill->type].cur;
rfkill_set_block(rfkill, cur);
mutex_unlock(&rfkill_global_mutex);
rfkill->suspended = false;
schedule_work(&rfkill->uevent_work);
rfkill_resume_polling(rfkill);
return 0;
}
>> I'm still more familiar with the old rfkill core. My understanding was
>> that the old core required drivers to say what their current state was,
>> but if that differed from the global state then it would be changed to
>> match.
>>
>>
>>>> 4) rfkill-input preserves existing behaviour - *if enabled* - by
>>>> initializing the global state from individual devices which have NVS.
>>>> (As before, each _type_ of rfkill device has its own global state).
>>>>
>>>>
>>> That still sounds horribly wrong and has been for a long time, but
>>> again, I don't care about rfkill-input since it will go away.
>>>
>>>
>>>
>>>> 5) rfkill devices with NVS will have their current state preserved,
>>>> so long as the global state has not yet been set (by userspace or by
>>>> rfkill-input). Of course userspace can change the state in response
>>>> to the device being added.
>>>>
>>>>
>>> If you register your RFKILL switch properly, they do that already. See
>>> my comment above. You start up with the proper state to begin with.
>>>
>>>
>>>
>>>> => rfkilld then has the information required to implement the same
>>>> policy as rfkill-input. Furthermore, it will have enough information
>>>> that it could implement file-based storage as a fallback, and still
>>>> support NVS where available.
>>>>
>>>> It will also allow implementation (or configuration) of completely
>>>> different policy to rfkill-input. E.g. if your internal wireless
>>>> w/NVS is broken and should be disabled, that can be done independently
>>>> of your replacement USB wireless adaptor.
>>>>
>>>>
>>> I did actually looked into this and userspace has all information
>>> available to create a proper policy if you wanna treat your NVS states
>>> (for example the tpacpi ones) as global states, you can easily do that
>>> right now. It became really simple with /dev/rfkill.
>>>
>>>
>> I still think userspace is missing an important piece of information:
>> whether the state of a certain rfkill device is persistent or not.
>>
>> The driver knows exactly whether this is the case; from what you say it
>> will call rfkill_set_state() before rfkill_register(). If it _doesn't_
>> do this, there won't be any persistent state for userspace to retrieve
>> anyway :-).
>>
>> I don't think we should expect userspace to know whether or not a device
>> has a persistent state. Yes, it _could_ maintain whitelists, but why
>> should it have to if the driver already knows?
>>
>
> If you want that, then the best approach seems an extra sysfs attribute
> for this. It is not intrusive on the event API and lets udev etc. have
> these information, too.
>
Urg. Yes, it would be nice to expose it in sysfs. I guess I can live
with readfile("/sys/class/rfkill%d/persistent"), if there is strong
objection to cluttering up struct rfkill_event with extra flags.
Thanks
Alan
On Wed, 03 Jun 2009, Marcel Holtmann wrote:
> > > We just need to fix the platform drivers then. They should not set
> > > global states since that is not what they are controlling. They control
> >
> > We should change things, yes. So that the platform stores the global
> > state. That was half-broken on the old core (the platform stored the
> > state of its own device, which could be out of sync with the global
> > state), but the part of it setting the global state is correct.
> >
> > That needs a new in-kernel API to tie the core to platform drivers
> > capable of storing global states without causing problems when drivers
> > are unloaded, but it is not hard.
> >
> > As for NVS events, they have a clear use case: to let rfkilld know which
> > global states it could leave alone the first time it loads, and which
> > ones have to be restored...
>
> show me an example of a platform device that stores the global state. I
> think you are confusing the word platform as in system with a platform
> device. The ThinkPad Bluetooth and WWAN switches are platform devices
> and control each one specific device. Same goes for the EeePC. They are
> not controlling a global state.
I don't know what big difference you see between the two uses of "platform",
but I will just work around it to get something useful out of this mail.
The laptop stores in NVS the state of its 'switches'. This is as close as
one gets from 'storing the global state'. When the laptop boots,
these devices get set by the firware to the state in NVS. It is the best
place to store global state, because these devices will be in their proper
state (i.e. matching what will be the global state when the rfkill core
loads) all the time. It also gives you for free multi-OS/multi-kernel state
storage for these devices, and compatibility with BIOSes that let you define
the initial state for the devices in the firmware configuration, etc.
There. I didn't use the 'p' word once. Now, please tell me why show we
just dump the storage done by the firmware in NVS, and instead store the
global state for those switches in userspace?
If it was a extremely complicated and fragile thing to do, it would be a
different matter, but it isn't. It isn't even ugly code, even after we fix
it to be all about global states.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
Hi Henrique,
> > > > > > Forget about this EPO crap. That is just a stupid concept anyway.
> > > > >
> > > > > Is it? So, if I use the _hardware switch_ on my laptop to kill all
> > > > > internal radios, it shouldn't be enforced by the OS on extra radios I
> > > > > plugged? Or on shitty internal WLAN cards that doesn't tie properly to
> > > > > the mini-pci and mini-pcie hardware kill lines?
> > > > >
> > > > > And any userspace PoS program can decide to bring up such radios that
> > > > > are not hardware-killed even if I am clearly trying to disable them all?
> > > >
> > > > You hardkilled. Of course it should bring down everything. If you
> > > > don't want to hardkill, then disable specific radios via /sys or some
> > > > UI. The hardswitch is the "eject" button.
> > >
> > > That's exactly my point. I want EPO to mean "no, you cannot turn this crap
> > > on, GO AWAY" for anyone but root (or whomever SELinux allowed to do it,
> > > etc).
> >
> > let me repeat, this is what you want and that is policy. Feel free to
> > implement that in userspace. Leave such policy out of the kernel.
>
> EPO is not policy, it is functionality. Entering EPO and going out of
> EPO state would be policy, though.
>
> What happens while in EPO state is NOT policy. The very definition of
> EPO is that, once enabled, it cannot be overriden until it is disabled
> (and it is usual to have strong rules for how it can be disabled, but
> this is not important right now). EPO is a term from real world
> engineering, its meaning is set in stone.
>
> You can remove the EPO functionality and add a "switch all radios off"
> and "switch all radios on" accell commands. But that's all you can do
> if you depend on userspace. It will -not- be EPO.
>
> You can have EPO in the kernel, and have userspace command the kernel to
> enter and exit EPO state.
I fully disagree with you here and your concept of EPO and how it is
suppose to work is fully flawed. EPO is a policy and if you define it as
all radios go off, then that is fine, but again it is your policy. It is
not mine and with us two disagreeing on this, it makes it clear that it
should not be done inside the kernel.
In addition to that, the EPO only works if your RFKILL lines are
actually hardwired in system. If it involves any kind of software
besides firmware (that includes ACPI to some sort) it makes no
difference whatsoever.
Also there is no problem in doing this in userspace and have proper
enforcement for this. I did mention Unix access rights and SELinux
before and the same applies here. If you wanna enforce EPO on all soft
kill states, then you have to build your system the right way. And you
certainly can do this now with /dev/rfkill.
Regards
Marcel
Hi Henrique,
> > any user program with proper rights (remember that /dev/rfkill can now
> > be controlled by Unix permissions and SELinux) can bring up a specific
> > device. That is policy and it belongs in userspace.
>
> If I hardkill (EPO) the devices, I want them to stay hardkilled, and only a
> system daemon (if that) should be able to mess with that.
>
> I very much doubt I am the only one who see things that way :-)
>
> I'd like to keep working towards that goal (no, we're not there yet), and
> not away from it.
please re-read my reply. The permission of the /dev/rfkill device are up
to the distributions now. They can use UID/GID permissions and also
SELinux to enforce them. However this again is policy that is up to
userspace and we leave it there.
If you don't wanna have anybody else mess with RFKILL states, then
make /dev/rfkill read/write only by root. If you don't wanna have
anybody mess with it, make it read-only for all I care.
And let me repeat this, the concept of EPO is a policy and not something
the kernel should enforce by itself.
> > This of course only works on soft blocked devices. The hard blocked
> > devices stay off. And in case of ThinkPads where the button does the
> > hard block, you can't bring it back from software.
>
> Yes. But the rfkill core is also meant to bring some band-aid help to the
> devices that the hardware can't kill by itself. That's good usability.
And that is again policy that is up to the userspace. If there is no
hard kill-line available, software or band-aid fixes don't help. It is
like putting a sheet of paper over a hole ;)
Regards
Marcel
On Mon, 2009-06-01 at 09:45 -0300, Henrique de Moraes Holschuh wrote:
> I mean platform devices which remember state. Those will be turned on/off
> by the platform firmware at boot and often at resume time, and overriding
> them in rfkill (especially if it is done later, by a rfkilld) will cause
> some flicker (on/off/on, off/on/off) in certain situations.
>
> This is a regression, because the current interface doesn't suffer this
> limitation.
Right, but that was already covered by the discussion and patch I just
sent, no need to complain, afaict? What's your gripe with the current
add-on patch that I just sent out?
johannes
Johannes Berg wrote:
> On Mon, 2009-06-08 at 12:10 +0100, Alan Jenkins wrote:
>
>
>>>> - rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state());
>>>> err = rfkill_register(wwan_rfkill);
>>>> if (err)
>>>> goto register_wwan_err;
>>>>
>>>>
>>> Hmm. Anyone know anything about HP? That kinda looks persistent too.
>>>
>>>
>> Quite possibly. I just don't know, and it's never been treated that way
>> before. The old core, when I first read it, you were supposed to report
>> the initial state so that it knew whether it differed from the default
>> state. So the core could "optimise away" the initialization if the
>> current state was the same. Then rfkill_set_default() was added, but it
>> was only used in tp-acpi and then eeepc-laptop.
>>
>
> Oh, good point. Then maybe that was just to avoid the core
> initialisation that I just always did for ease of use.
>
>
>> The counter is the sony-laptop case. That driver also hits ACPI to
>> query it's current state. But apparently it doesn't always power up in
>> a useful state, because there's a specific git commit which forces the
>> radios to unblock at load time.
>>
>>
>> I think this patch should preserve the existing behaviour. But the
>> rfkill rewrite as a whole is a good opportunity to re-check this issue.
>> There's only a few maintainers to contact so I don't mind doing it -
>> unless you were going to check with them about the rewrite anyway?
>>
>
> I wasn't planning on doing anything more than before -- where I had
> copied hopefully all maintainers on the rewrite patch.
>
>
>
>>> Ah, this is the quirky backward compat code you're talking about. I
>>> guess we need it, although I don't particularly like it.
>>>
>>>
>> I don't like it either. The patch as a whole only makes sense because
>> rfkill-input is going away, so the global states will become less
>> important. When you use rfkill-input, you really want the individual
>> states to match the global ones, otherwise your user experience
>> suffers. When you don't use rfkill-input, the "global" states will just
>> be load-time defaults (once suspend is fixed).
>>
>
> Yeah. Want to fix suspend too? I would now, but I really need to get
> lunch first :)
>
Sure, will do!
Hi Henrique,
> > > I don't think we should expect userspace to know whether or not a device
> > > has a persistent state. Yes, it _could_ maintain whitelists, but why
> > > should it have to if the driver already knows?
> >
> > If you want that, then the best approach seems an extra sysfs attribute
> > for this. It is not intrusive on the event API and lets udev etc. have
> > these information, too.
>
> I have no problems with either approach. As long as the information of
> which devices have restored their initial state from NVS is available to
> userspace, it is enough.
just to get the semantic right here. We are not telling userspace if a
state has been restored or not. We are telling userspace that this
specific RFKILL switch is capable of storing something in a persistent
state over boot. There is a difference here.
If a RFKILL driver claims it is capable of persistent storage then it
better work or it should not claims it. Either it does it all the time
or doesn't do it at all. Otherwise we end up in policy again and that is
not the job of the kernel.
> Do note that this information also needs to be available for resume (state
> should be checkpointed to NVS on sleep, and restored from NVS on resume. I
> believe tpacpi does this, but if it doesn't, I will fix it eventually).
Correct. That is the job of the driver. If it is broken, that needs
fixing.
Regards
Marcel
Hi Henrique,
> > 3G cards have two ways of being killed:
> >
> > 1) Put them to sleep; Sierra for example has code that does this
> > already, it will take the modem out of D3 sleep on startup. No reason
> > why rfkill couldn't just stick it back there. Drivers that implement
> > this method would be easy to integrate with rfkill if the BIOS/platform
> > module doesn't do it for them (hp-wmi will drop the 3G card off the bus
> > on the 2530p when you rfkill it, just like bluetooth, so nothing else is
> > needed there).
>
> thinkpads will power down the card (and obviously kick it off-bus), as
> well. This is how they disable bluetooth and 3G.
>
> I have code in thinkpad-acpi that tells the fw to power these radios
> down during sleep, and resume with them powered off. Needs to be
> unbroken to not do that on wake devices one of these days (_if_ the
> thinkpad supports bluetooth or wwan as wake devices, something I also
> have to find out).
>
> BTW: please do send me a note of how you guys will want to do sleep/
> resume with the new core, I probably will have to adjust thinkpad-acpi
> to play well with it.
>
> > 2) CFUN=0; that is supposed to shut down all radios, but of course that
> > requires the serial port of the card and the firmware to be loaded and
> > whatever. And that should only be done by the daemon (if any, like
> > ModemManager or Wader or whatever) that is arbitrating the serial port.
> > That's not something that rfkilld should do.
>
> And, to make matters worse, you are likely to find that lots of devices
> screw that up (probably by just ignoring it silently) :-( The people
> writing the firmware for these modems do an even worse job than the
> people writing ACPI BIOSes...
I was talking about the external 3G cards. So besides the Option HSO,
that has a RFKILL switch, we have no control right now.
For all the built-in ones, I just expect that we have a platform RFKILL
switch that does the right thing. Naive, but good enough for now :(
Regards
Marcel
Marcel Holtmann wrote:
> Hi Alan,
>
>
>>> I really don't understand why this is needed. What benefit does it give
>>> us compared to just sent OP_CHANGE and OP_CHANGE as an update. My X200
>>> for example does this anyway on suspend/resume.
>>>
>>>
>> This is required for boot only. I have no reason for this event to be
>> generated on resume.
>>
>> The same effect could be had by generating an OP_CHANGE on f_open,
>> _only_ when a platform driver has provided a value from NVS. But it
>> does seem clearer to make it a different type of event.
>>
>
> that is my whole point. If the kernel driver wants to preserve these
> then it can just issue OP_ADD to notify use about the current state of
> the values. The OP_ADD gets send once you open /dev/rfkill and if they
> not match with our policy we have to change them anyway. I really don't
> see the need for an extra operation here. Let me ask this again, how is
> it different from just sending the OP_ADD and then let rfkilld decide to
> either use that value or enforce its own policy.
I don't understand.
Isn't OP_ADD generated for all rfkill devices, by the core itself? How
does the driver ask to generate this event or not?
As I understand it, the difference between NVS_REPORT and ADD is that
ADD is generated for all drivers. NVS_REPORT is only generated if they
call rfkill_set_global_sw_state().
The difference is that this exposes an extra bit of information: whether
the state was restored from NVS.
> If the wish is to
> enforce policy you can't do anything about it anyway.
>
Sure.
>>> So what is rfkilld suppose to be doing when receiving this report? What
>>> is the expected behavior? Why do we bother with multi-OS crap here? I am
>>> really unclear what are we trying to solve here.
>>>
>> In order to replicate the kernel behavior, it is expected that you set
>> your internal state from this event. E.g. when the user next presses
>> the wireless toggle key, you set the inverse of that internal state.
>>
>> Since this event is generated by a platform driver, you can expect it to
>> be present following coldplug (the udev initscript). If the event is
>> not present after coldplug, you may then issue OP_CHANGE yourself, to
>> e.g. restore the state from a file. You would not be expected to handle
>> OP_NVS_REPORT after startup. (Unless the daemon is restarted).
>>
>> Replicating the kernel behavior will allow us to avoid causing a couple
>> of niggly little regressions on at least two platforms. It preserves
>> the behavior when dual-booting (possibly between different linux
>> distros), and when the BIOS setup screen exposes the NVS state as an
>> option. The new behavior you suggest will annoy any users who have
>> become used to these scenarios "just working".
>>
>> You may not use these platforms yourself. But I'm as annoyed as
>> Henrique is, we don't want to impose regressions just because other
>> platforms don't implement the feature.
>>
>> Why the fuss about implementing this, it seems easy enough? Start
>> rfkilld after udev (like everything else). If you get NVS_REPORT, then
>> use those states. Fill in any other states from defaults or state files
>> and issue OP_CHANGE for them, just as you're already planning. Ignore
>> any subsequent NVS_REPORTs. That should cover it.
>>
>> It's the cost for starting from a working implementation. You benefit
>> from having existing drivers and users, you pay by not breaking them
>> without good reason.
>>
>
> I really don't care about current behavior, because that has been just a
> hack anyway.
Linux exports a stable ABI. A reasonable justification would be "this
is trivial and has practically no users who care". Hackishness is
second to that.
> And it happens to work if there is proper BIOS support. We
> are at the point now where we stop working around a complicated and in
> some cases broken implementation. Overloading it with weird special
> cases is just wrong and so far I am not buying into any of the arguments
> here. The point behind the whole effort from Johannes is to finally fix
> RFKILL support. If it breaks current behavior, I couldn't care less in
> some cases.
>
I really don't understand. You say above that we don't need this
change, because drivers can already achieve the same event by
selectively generating OP_ADD. In that case it's already overloaded
with "weird special cases", and NVS_REPORT is a cosmetic difference only.
> So Johannes and I talked about it a lot last week. And we will be doing
> rfkilld so finally deprecated the broken idea of rfkill-input and move
> the policy into userspace where it belongs.
Great. I think we can agree on one thing, that implementing
rfkill-input in the kernel without the possibility of a userspace
override is warped and twisted :-).
> To make this clear, the
> concept of cross-OS state keeping is broken.
> Having the BIOS or a
> different OS dictate policy makes no sense.
>
Seems to me these are two different points, and you haven't really said
why cross-OS state is broken.
It doesn't make sense to be _dictated_ to by your other OS/BIOS. That's
exactly what rfkilld would avoid, by allowing a configurable policy.
"support cross-OS state keeping" is a policy which can be applied - or
not. NVS_REPORT provides the necessary information.
So, this policy doesn't work if the other OS is un-cooperative and
soft-blocks the wireless on shutdown. That's the full extent to which
it is broken, yes? [And it's a relatively obscure problem, so the risk
is that it just rots, and the effort spent on this code is wasted].
But on eeepc-laptop, it seems safe to assume that the drivers on the
Other OS do the co-operative thing, because otherwise it would cause
confusion with the option in the BIOS setup.
Henrique (or anyone), has this ever been a problem on thinkpad-acpi? Do
you ever get a state in NVS at boot time, which the user had not requested?
Note that the linux ACPI project has the explicit goal of compatibility
with Windows. If a new platform comes along with a Windows ACPI driver
which screws up the platform state, it would be reasonable for the Linux
ACPI driver to not call set_global_sw_state().
You propose to exclude a feature that currently works, on the grounds
that it is inherently broken. But you haven't said that this has ever
caused incorrect behavior. All you have said so far is that it is a hack.
Since this was the original intent, it's a pity
rfkill_set_global_sw_state() wasn't listed in the feature removal
schedule (or a user-level description of NVS). I suggested something
like NVS_REPORT because, in the absence of a description of removal, it
looked like an oversight. In the past I've raised a couple of apparent
oversights about the rewrite, and they were treated as constructive.
But now its clear this was a policy decision, and I ended up draging out
an explanation of the policy from nitpicking details of the
implementation. I guess this is my fault for opening the discussion
based on the one loose thread of a detail, instead of asking if the
change was an oversight or intentional :-(.
Thanks
Alan
Hi Alan,
> >> > > > We just need to fix the platform drivers then. They should not set
> >> > > > global states since that is not what they are controlling. They
> >> > > > control
> >> > >
> >> > > We should change things, yes. So that the platform stores the global
> >> > > state. That was half-broken on the old core (the platform stored the
> >> > > state of its own device, which could be out of sync with the global
> >> > > state), but the part of it setting the global state is correct.
> >> > >
> >> > > That needs a new in-kernel API to tie the core to platform drivers
> >> > > capable of storing global states without causing problems when drivers
> >> > > are unloaded, but it is not hard.
> >> > >
> >> > > As for NVS events, they have a clear use case: to let rfkilld know
> >> > > which
> >> > > global states it could leave alone the first time it loads, and which
> >> > > ones have to be restored...
> >> >
> >> > show me an example of a platform device that stores the global state. I
> >> > think you are confusing the word platform as in system with a platform
> >> > device. The ThinkPad Bluetooth and WWAN switches are platform devices
> >> > and control each one specific device. Same goes for the EeePC. They are
> >> > not controlling a global state.
> >>
> >> I don't know what big difference you see between the two uses of
> >> "platform",
> >> but I will just work around it to get something useful out of this mail.
> >>
> >> The laptop stores in NVS the state of its 'switches'. This is as close as
> >> one gets from 'storing the global state'. When the laptop boots,
> >> these devices get set by the firware to the state in NVS. It is the best
> >> place to store global state, because these devices will be in their proper
> >> state (i.e. matching what will be the global state when the rfkill core
> >> loads) all the time. It also gives you for free multi-OS/multi-kernel
> >> state
> >> storage for these devices, and compatibility with BIOSes that let you
> >> define
> >> the initial state for the devices in the firmware configuration, etc.
> >
> > it stores the state of its switches and why should these be enforced as
> > a global state? Who says that this is a global state? For me that sounds
> > like policy.
>
> We don't seem to be getting very far :-(. I agree that these do not
> appear to be global states, just the states of individual rfkill
> devices.
>
> So I would propose the following changes. (I'm happy to write the
> code as well, but I think it's easier to read English).
>
> 1) remove rfkill_set_global_sw_state()
> 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
> registration, setting a flag.
> 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
> events, tho it may as well be set in all events)
you can do things like this already if you just set the states correctly
between rfkill_alloc and rfkill_register. So you should make sure you
register your RFKILL switch with the correct state and not toggle it
later. As far as I can tell the tpacpi driver does that already.
> 4) rfkill-input preserves existing behaviour - *if enabled* - by
> initializing the global state from individual devices which have NVS.
> (As before, each _type_ of rfkill device has its own global state).
That still sounds horribly wrong and has been for a long time, but
again, I don't care about rfkill-input since it will go away.
> 5) rfkill devices with NVS will have their current state preserved,
> so long as the global state has not yet been set (by userspace or by
> rfkill-input). Of course userspace can change the state in response
> to the device being added.
If you register your RFKILL switch properly, they do that already. See
my comment above. You start up with the proper state to begin with.
> => rfkilld then has the information required to implement the same
> policy as rfkill-input. Furthermore, it will have enough information
> that it could implement file-based storage as a fallback, and still
> support NVS where available.
>
> It will also allow implementation (or configuration) of completely
> different policy to rfkill-input. E.g. if your internal wireless
> w/NVS is broken and should be disabled, that can be done independently
> of your replacement USB wireless adaptor.
I did actually looked into this and userspace has all information
available to create a proper policy if you wanna treat your NVS states
(for example the tpacpi ones) as global states, you can easily do that
right now. It became really simple with /dev/rfkill.
So I have code for adding full native and integrated RFKILL support to
the Bluetooth subsystem and that works nicely in conjunction with the
hardware killswitch my system has. I still have to test WiMAX and do
some modifications for the Ericsson MBM 3G stuff. So at some point we
should have softkill support for all major radios. Only exception are
the 3G cards that present themselves as modems with PPP. They are to
tricky and I have no idea right now how we could integrate them.
Regards
Marcel
On Mon, 2009-06-01 at 10:33 -0300, Henrique de Moraes Holschuh wrote:
> I don't know if I have any gripes with your latest patch ;) Can't test it
> right now. I was reacting to the comments in the thread.
Well could you read it and see if the API does what you require? A
discussion about that would be so much more useful than handwaving about
LED blinking and security issues...
> On a related note, Johannes, would you be opposed to exporting something I
> could call from thinkpad-acpi to request a radio state change on a rfkill
> struct? This would allow me to remove some of the mess in thinkpad-acpi.
I don't think I understand what you want? What do you mean by "request a
radio state change"?
johannes
Johannes Berg wrote:
> On Tue, 2009-06-02 at 09:38 +0200, Marcel Holtmann wrote:
>
>
>> If you really don't wanna have rfkilld _not_ impose a policy on cold
>> boot, then we can certainly add that. However that is part of rfkilld
>> and not the kernel.
>>
>> For global states, I am questioning the platform that actually has
>> storage for global states. All platforms that I have seen so far store
>> individual per device based states and not the global one.
>>
>
> Yeah, unfortunately the platform drivers do store/restore global state.
> And in a sense that makes (made) sense since the default rfkill-input
> policy was to toggle everything based on the platform button. It's icky
> though.
>
> The problem with not telling rfkilld about this though is that the
> kernel will suddenly impose a hotplug policy that rfkilld doesn't know
> about. This might not even matter though since the _ADD will be sent
> with that policy already applied, and if it wants to change the policy
> rfkilld will do _CHANGE_ALL.
>
>
>> If you wanna just accept what the BIOS (or other OS) tells you then that
>> is an acceptable policy. So rfkilld will just map key input events in
>> that case. Fine by me. Question is if we can't do that right now? I
>> think we just can.
>>
>
> Yes, we probably can do that right now, by making rfkilld start up
> without setting a policy (CHANGE_ALL). It just doesn't know what the
> policy is, then.
>
>
>> Question is if we really have a global state here. I doubt it since all
>> of these are device specific states. And having the BIOS or ACPI dictate
>> what state my external Bluetooth or WiFi device is in is pointless and a
>> total broken concept.
>>
>
> Unfortunately that's how it worked before, it's part of the rfkill
> legacy that it seems we'll have to accept. I guess we have only
> ourselves to blame for not reviewing Henrique's rfkill implementation...
>
>
>>> You propose to exclude a feature that currently works, on the grounds
>>> that it is inherently broken. But you haven't said that this has ever
>>> caused incorrect behavior. All you have said so far is that it is a hack.
>>>
>> This never worked so far. The mac80211 or Bluetooth subsystems have no
>> RFKILL state and thus global states are not enforced. An external dongle
>> without RFKILL support is not taken into account. You are not talking
>> about global states here. They are all device specific. The device just
>> happens to be a platform device builtin the system.
>>
>
> Right, but rfkill does actually have a function to set the global policy
> state (rfkill_set_global_sw_state) which some platform drivers insist on
> calling.
>
> The only reason we would need the NVS_REPORT then is to detect whether
> or not anything in the kernel called rfkill_set_global_sw_state(). If we
> can live with just configuring rfkilld for the (arguably broken) case
> where somebody cares about this, I'm happy with removing it.
>
> Hope that helps clear up your confusion.
>
The reason for drivers doing this is that otherwise, the kernel forces
the new rfkill device to the current global default. So this API is
used for the device to preserve it's current state - without it, we just
throw the NVS information away and no-one can get at it.
We could switch to using the non-global set_sw_state() after
registration. But that's pretty nasty because it means most drivers
call set_sw_state() before registration, a few drivers call it
afterwards, and it's not going to be obvious why. Plus I think it
bypasses EPO. So I think some sort of separate API is needed.
If we can also export this NVS status to userspace somehow, then
userspace can distinguish between
a) rfkill is currently unblocked, because this was the kernel default,
so there's no reason not to override the state
b) rfkill is currently unblocked, because this was restored from NVS,
which hopefully reflects the most recent request from the user. rfkilld
is still able to override this state, but it would also be reasonable
not to.
I take Marcels point, if /dev/rfkill exposes a sub-optimal interface, it
can be very difficult to fix it. It's probably better to fix the mess
of global states before trying to add NVS information to /dev/rfkill.
Btw, I think there's a third scenario to add to the other OS / BIOS
setup. Some hardware has a handover mechanism, right? Where the BIOS
handles rfkill keypresses by default, and toggles the soft rfkill state,
but allows the driver to take over when loaded. So if the user presses
the key _before_ the driver gets loaded - they will see the wireless LED
change, and they will expect that state change to persist when the
driver is loaded.
Thanks
Alan
On Sun, 2009-06-07 at 17:46 +0200, Marcel Holtmann wrote:
> Hi Alan,
>
> > >> > > > We just need to fix the platform drivers then. They should not set
> > >> > > > global states since that is not what they are controlling. They
> > >> > > > control
> > >> > >
> > >> > > We should change things, yes. So that the platform stores the global
> > >> > > state. That was half-broken on the old core (the platform stored the
> > >> > > state of its own device, which could be out of sync with the global
> > >> > > state), but the part of it setting the global state is correct.
> > >> > >
> > >> > > That needs a new in-kernel API to tie the core to platform drivers
> > >> > > capable of storing global states without causing problems when drivers
> > >> > > are unloaded, but it is not hard.
> > >> > >
> > >> > > As for NVS events, they have a clear use case: to let rfkilld know
> > >> > > which
> > >> > > global states it could leave alone the first time it loads, and which
> > >> > > ones have to be restored...
> > >> >
> > >> > show me an example of a platform device that stores the global state. I
> > >> > think you are confusing the word platform as in system with a platform
> > >> > device. The ThinkPad Bluetooth and WWAN switches are platform devices
> > >> > and control each one specific device. Same goes for the EeePC. They are
> > >> > not controlling a global state.
> > >>
> > >> I don't know what big difference you see between the two uses of
> > >> "platform",
> > >> but I will just work around it to get something useful out of this mail.
> > >>
> > >> The laptop stores in NVS the state of its 'switches'. This is as close as
> > >> one gets from 'storing the global state'. When the laptop boots,
> > >> these devices get set by the firware to the state in NVS. It is the best
> > >> place to store global state, because these devices will be in their proper
> > >> state (i.e. matching what will be the global state when the rfkill core
> > >> loads) all the time. It also gives you for free multi-OS/multi-kernel
> > >> state
> > >> storage for these devices, and compatibility with BIOSes that let you
> > >> define
> > >> the initial state for the devices in the firmware configuration, etc.
> > >
> > > it stores the state of its switches and why should these be enforced as
> > > a global state? Who says that this is a global state? For me that sounds
> > > like policy.
> >
> > We don't seem to be getting very far :-(. I agree that these do not
> > appear to be global states, just the states of individual rfkill
> > devices.
> >
> > So I would propose the following changes. (I'm happy to write the
> > code as well, but I think it's easier to read English).
> >
> > 1) remove rfkill_set_global_sw_state()
> > 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
> > registration, setting a flag.
> > 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
> > events, tho it may as well be set in all events)
>
> you can do things like this already if you just set the states correctly
> between rfkill_alloc and rfkill_register. So you should make sure you
> register your RFKILL switch with the correct state and not toggle it
> later. As far as I can tell the tpacpi driver does that already.
>
> > 4) rfkill-input preserves existing behaviour - *if enabled* - by
> > initializing the global state from individual devices which have NVS.
> > (As before, each _type_ of rfkill device has its own global state).
>
> That still sounds horribly wrong and has been for a long time, but
> again, I don't care about rfkill-input since it will go away.
>
> > 5) rfkill devices with NVS will have their current state preserved,
> > so long as the global state has not yet been set (by userspace or by
> > rfkill-input). Of course userspace can change the state in response
> > to the device being added.
>
> If you register your RFKILL switch properly, they do that already. See
> my comment above. You start up with the proper state to begin with.
>
> > => rfkilld then has the information required to implement the same
> > policy as rfkill-input. Furthermore, it will have enough information
> > that it could implement file-based storage as a fallback, and still
> > support NVS where available.
> >
> > It will also allow implementation (or configuration) of completely
> > different policy to rfkill-input. E.g. if your internal wireless
> > w/NVS is broken and should be disabled, that can be done independently
> > of your replacement USB wireless adaptor.
>
> I did actually looked into this and userspace has all information
> available to create a proper policy if you wanna treat your NVS states
> (for example the tpacpi ones) as global states, you can easily do that
> right now. It became really simple with /dev/rfkill.
>
> So I have code for adding full native and integrated RFKILL support to
> the Bluetooth subsystem and that works nicely in conjunction with the
> hardware killswitch my system has. I still have to test WiMAX and do
> some modifications for the Ericsson MBM 3G stuff. So at some point we
> should have softkill support for all major radios. Only exception are
> the 3G cards that present themselves as modems with PPP. They are to
> tricky and I have no idea right now how we could integrate them.
3G cards have two ways of being killed:
1) Put them to sleep; Sierra for example has code that does this
already, it will take the modem out of D3 sleep on startup. No reason
why rfkill couldn't just stick it back there. Drivers that implement
this method would be easy to integrate with rfkill if the BIOS/platform
module doesn't do it for them (hp-wmi will drop the 3G card off the bus
on the 2530p when you rfkill it, just like bluetooth, so nothing else is
needed there).
2) CFUN=0; that is supposed to shut down all radios, but of course that
requires the serial port of the card and the firmware to be loaded and
whatever. And that should only be done by the daemon (if any, like
ModemManager or Wader or whatever) that is arbitrating the serial port.
That's not something that rfkilld should do.
Dan
On Mon, 2009-06-01 at 09:49 -0300, Henrique de Moraes Holschuh wrote:
> On Mon, 01 Jun 2009, Johannes Berg wrote:
> > > Just don't expose a rfkill type until the first rfkill structure of that
> > > type gets registered. THAT closes all holes in a sensible,
> > > principle-of-least-suprise way. The current code (including the rewrite)
> > > already deals with defaults and firmware-backed state storage just fine in
> > > that case. All you need is a full interface that deals with global state
> > > hotplug (which ain't difficult, that's one or two more notifications only).
> >
> > Global state hotplug is just not really possible to support, and I don't
>
> I used 'expose' for a reason. You don't expose them to userspace until
> there is an user for that type. You don't even have to hide it again after
> the last user gets unregistered...
>
> That's not hotplug as in 'create a new rfkill type for the kernel'.
I ... just don't understand what you're getting at. Are you talking
about /dev/rfkill with or without the add-on I just sent a while ago?
> > think even your original code supported that, since it cannot affect
> > previously registered rfkill instances. You definitely don't want to
> > hotplug a wireless device and turn off all others "due to that".
>
> You do recall how the 'override system default' machinery worked? It would
> return an error if a rfkill struct of that type had already been registered,
> or if a call to set the default for that particular rfkill type had already
> happened.
Exactly. It never touched older devices, and ignored (well, it returned
an error, but that was quite pointless) future attempts to set the
default.
johannes
Hi Alan,
> >>>>>>> We just need to fix the platform drivers then. They should not set
> >>>>>>> global states since that is not what they are controlling. They
> >>>>>>> control
> >>>>>>>
> >>>>>> We should change things, yes. So that the platform stores the global
> >>>>>> state. That was half-broken on the old core (the platform stored the
> >>>>>> state of its own device, which could be out of sync with the global
> >>>>>> state), but the part of it setting the global state is correct.
> >>>>>>
> >>>>>> That needs a new in-kernel API to tie the core to platform drivers
> >>>>>> capable of storing global states without causing problems when drivers
> >>>>>> are unloaded, but it is not hard.
> >>>>>>
> >>>>>> As for NVS events, they have a clear use case: to let rfkilld know
> >>>>>> which
> >>>>>> global states it could leave alone the first time it loads, and which
> >>>>>> ones have to be restored...
> >>>>>>
> >>>>> show me an example of a platform device that stores the global state. I
> >>>>> think you are confusing the word platform as in system with a platform
> >>>>> device. The ThinkPad Bluetooth and WWAN switches are platform devices
> >>>>> and control each one specific device. Same goes for the EeePC. They are
> >>>>> not controlling a global state.
> >>>>>
> >>>> I don't know what big difference you see between the two uses of
> >>>> "platform",
> >>>> but I will just work around it to get something useful out of this mail.
> >>>>
> >>>> The laptop stores in NVS the state of its 'switches'. This is as close as
> >>>> one gets from 'storing the global state'. When the laptop boots,
> >>>> these devices get set by the firware to the state in NVS. It is the best
> >>>> place to store global state, because these devices will be in their proper
> >>>> state (i.e. matching what will be the global state when the rfkill core
> >>>> loads) all the time. It also gives you for free multi-OS/multi-kernel
> >>>> state
> >>>> storage for these devices, and compatibility with BIOSes that let you
> >>>> define
> >>>> the initial state for the devices in the firmware configuration, etc.
> >>>>
> >>> it stores the state of its switches and why should these be enforced as
> >>> a global state? Who says that this is a global state? For me that sounds
> >>> like policy.
> >>>
> >> We don't seem to be getting very far :-(. I agree that these do not
> >> appear to be global states, just the states of individual rfkill
> >> devices.
> >>
> >> So I would propose the following changes. (I'm happy to write the
> >> code as well, but I think it's easier to read English).
> >>
> >> 1) remove rfkill_set_global_sw_state()
> >> 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
> >> registration, setting a flag.
> >> 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
> >> events, tho it may as well be set in all events)
> >>
> >
> > you can do things like this already if you just set the states correctly
> > between rfkill_alloc and rfkill_register. So you should make sure you
> > register your RFKILL switch with the correct state and not toggle it
> > later. As far as I can tell the tpacpi driver does that already.
> >
>
> Ah. I need to read the (rewritten) code again.
it could be still broken to some degree, but some stuff is just because
rfkill-input interferes. So disable rfkill-input via ioctl or not
compile it at all.
> I'm still more familiar with the old rfkill core. My understanding was
> that the old core required drivers to say what their current state was,
> but if that differed from the global state then it would be changed to
> match.
>
> >> 4) rfkill-input preserves existing behaviour - *if enabled* - by
> >> initializing the global state from individual devices which have NVS.
> >> (As before, each _type_ of rfkill device has its own global state).
> >>
> >
> > That still sounds horribly wrong and has been for a long time, but
> > again, I don't care about rfkill-input since it will go away.
> >
> >
> >> 5) rfkill devices with NVS will have their current state preserved,
> >> so long as the global state has not yet been set (by userspace or by
> >> rfkill-input). Of course userspace can change the state in response
> >> to the device being added.
> >>
> >
> > If you register your RFKILL switch properly, they do that already. See
> > my comment above. You start up with the proper state to begin with.
> >
> >
> >> => rfkilld then has the information required to implement the same
> >> policy as rfkill-input. Furthermore, it will have enough information
> >> that it could implement file-based storage as a fallback, and still
> >> support NVS where available.
> >>
> >> It will also allow implementation (or configuration) of completely
> >> different policy to rfkill-input. E.g. if your internal wireless
> >> w/NVS is broken and should be disabled, that can be done independently
> >> of your replacement USB wireless adaptor.
> >>
> >
> > I did actually looked into this and userspace has all information
> > available to create a proper policy if you wanna treat your NVS states
> > (for example the tpacpi ones) as global states, you can easily do that
> > right now. It became really simple with /dev/rfkill.
> >
>
> I still think userspace is missing an important piece of information:
> whether the state of a certain rfkill device is persistent or not.
>
> The driver knows exactly whether this is the case; from what you say it
> will call rfkill_set_state() before rfkill_register(). If it _doesn't_
> do this, there won't be any persistent state for userspace to retrieve
> anyway :-).
>
> I don't think we should expect userspace to know whether or not a device
> has a persistent state. Yes, it _could_ maintain whitelists, but why
> should it have to if the driver already knows?
If you want that, then the best approach seems an extra sysfs attribute
for this. It is not intrusive on the event API and lets udev etc. have
these information, too.
> > So I have code for adding full native and integrated RFKILL support to
> > the Bluetooth subsystem and that works nicely in conjunction with the
> > hardware killswitch my system has. I still have to test WiMAX and do
> > some modifications for the Ericsson MBM 3G stuff. So at some point we
> > should have softkill support for all major radios.
>
> Nice.
>
> > Only exception are
> > the 3G cards that present themselves as modems with PPP. They are to
> > tricky and I have no idea right now how we could integrate them.
> >
>
> Would a urfkill work, analagous to uinput?. I.e. allow the userspace
> driver (whatever talks to the configuration interface) to export its own
> rfkill device. I don't know what the uinput interface is, but you could
> create one by opening /dev/urfkill and writing the initial state, and
> destroy it on close().
Maybe. I am not sold on this right now, but I also haven't spend enough
time on this. We might just accept that these special external devices
can't be fixed easily anyway.
Regards
Marcel
On Mon, 2009-06-08 at 11:14 +0100, Alan Jenkins wrote:
> rfkill_set_global_sw_state() (previously rfkill_set_default()) will no
> longer be exported by the rewritten rfkill core.
>
> Instead, platform drivers which can provide persistent soft-rfkill state
> across power-down/reboot should indicate their initial state by calling
> rfkill_set_sw_state() before registration. Otherwise, they will be
> initialized to a default value during registration by a set_block call.
>
> We remove existing calls to rfkill_set_sw_state() which happen before
> registration, since these had no effect in the old model. If these
> drivers do have persistent state, the calls can be put back (subject
> to testing :-). This affects hp-wmi and acer-wmi.
Cool.
> Drivers with persistent state will affect the global state only if
> rfkill-input is enabled. This is required, otherwise booting with
> wireless soft-blocked and pressing the wireless-toggle key once would
> have no apparent effect. This special case will be removed in future
> along with rfkill-input, in favour of a more flexible userspace daemon
> (see Documentation/feature-removal-schedule.txt).
How does that work?
> --- a/drivers/platform/x86/acer-wmi.c
> +++ b/drivers/platform/x86/acer-wmi.c
> @@ -996,8 +995,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
> (void *)(unsigned long)cap);
> if (!rfkill_dev)
> return ERR_PTR(-ENOMEM);
> - get_u32(&state, cap);
> - rfkill_set_sw_state(rfkill_dev, !state);
That does seem persistent, I'd think? get_u32 here hits ACPI iirc.
> diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
> index 8d93114..16fffe4 100644
> --- a/drivers/platform/x86/hp-wmi.c
> +++ b/drivers/platform/x86/hp-wmi.c
> @@ -422,7 +422,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
> RFKILL_TYPE_WLAN,
> &hp_wmi_rfkill_ops,
> (void *) 0);
> - rfkill_set_sw_state(wifi_rfkill, hp_wmi_wifi_state());
> err = rfkill_register(wifi_rfkill);
> if (err)
> goto register_wifi_error;
> @@ -433,8 +432,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
> RFKILL_TYPE_BLUETOOTH,
> &hp_wmi_rfkill_ops,
> (void *) 1);
> - rfkill_set_sw_state(bluetooth_rfkill,
> - hp_wmi_bluetooth_state());
> err = rfkill_register(bluetooth_rfkill);
> if (err)
> goto register_bluetooth_error;
> @@ -445,7 +442,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
> RFKILL_TYPE_WWAN,
> &hp_wmi_rfkill_ops,
> (void *) 2);
> - rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state());
> err = rfkill_register(wwan_rfkill);
> if (err)
> goto register_wwan_err;
Hmm. Anyone know anything about HP? That kinda looks persistent too.
> @@ -1200,8 +1185,20 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
> atp_rfk->id = id;
> atp_rfk->ops = tp_rfkops;
>
> - rfkill_set_states(atp_rfk->rfkill, initial_sw_state,
> - tpacpi_rfk_check_hwblock_state());
> + initial_sw_status = (tp_rfkops->get_status)();
> + if (initial_sw_status < 0) {
> + printk(TPACPI_ERR
> + "failed to read initial state for %s, error %d; "
> + "will turn radio off\n", name, initial_sw_status);
That message seems wrong now, it would not turn off but impose the
current default, I think?
> /**
> - * rfkill_set_global_sw_state - set global sw block default
There's a static inline in the !RFKILL case, please remove that too.
> @@ -916,7 +885,17 @@ int __must_check rfkill_register(struct rfkill *rfkill)
> if (rfkill->ops->poll)
> schedule_delayed_work(&rfkill->poll_work,
> round_jiffies_relative(POLL_INTERVAL));
> - schedule_work(&rfkill->sync_work);
> +
> + if (!rfkill->persistent || rfkill_epo_lock_active) {
> + schedule_work(&rfkill->sync_work);
> + } else {
> +#ifdef CONFIG_RFKILL_INPUT
> + bool soft_blocked = !!(rfkill->state & RFKILL_BLOCK_SW);
> +
> + if (!atomic_read(&rfkill_input_disabled))
> + __rfkill_switch_all(rfkill->type, soft_blocked);
> +#endif
> + }
Ah, this is the quirky backward compat code you're talking about. I
guess we need it, although I don't particularly like it.
Looks good except for these few comments!
johannes
Hi Johannes,
> > If you really don't wanna have rfkilld _not_ impose a policy on cold
> > boot, then we can certainly add that. However that is part of rfkilld
> > and not the kernel.
> >
> > For global states, I am questioning the platform that actually has
> > storage for global states. All platforms that I have seen so far store
> > individual per device based states and not the global one.
>
> Yeah, unfortunately the platform drivers do store/restore global state.
> And in a sense that makes (made) sense since the default rfkill-input
> policy was to toggle everything based on the platform button. It's icky
> though.
>
> The problem with not telling rfkilld about this though is that the
> kernel will suddenly impose a hotplug policy that rfkilld doesn't know
> about. This might not even matter though since the _ADD will be sent
> with that policy already applied, and if it wants to change the policy
> rfkilld will do _CHANGE_ALL.
that is my understanding. We will see the current states and if just
wanna notify about a global soft/hard-block we can send a CHANGE_ALL
after the ADD events (if we think that is useful for telling global
block state).
> > If you wanna just accept what the BIOS (or other OS) tells you then that
> > is an acceptable policy. So rfkilld will just map key input events in
> > that case. Fine by me. Question is if we can't do that right now? I
> > think we just can.
>
> Yes, we probably can do that right now, by making rfkilld start up
> without setting a policy (CHANGE_ALL). It just doesn't know what the
> policy is, then.
That is actually fine since it is also not enforcing it. I don't see any
problem here at all.
> > Question is if we really have a global state here. I doubt it since all
> > of these are device specific states. And having the BIOS or ACPI dictate
> > what state my external Bluetooth or WiFi device is in is pointless and a
> > total broken concept.
>
> Unfortunately that's how it worked before, it's part of the rfkill
> legacy that it seems we'll have to accept. I guess we have only
> ourselves to blame for not reviewing Henrique's rfkill implementation...
That is just utterly broken and I fully object to messing up a proper
new interface with some stupid legacy crap. It never worked before for
non-builtin devices anyway. It just happens to work on specific system
where everything was tight together. And these system are still working
fine without NVS_REPORT btw. My X200 and the EeePC showed no problems so
far.
> > > You propose to exclude a feature that currently works, on the grounds
> > > that it is inherently broken. But you haven't said that this has ever
> > > caused incorrect behavior. All you have said so far is that it is a hack.
> >
> > This never worked so far. The mac80211 or Bluetooth subsystems have no
> > RFKILL state and thus global states are not enforced. An external dongle
> > without RFKILL support is not taken into account. You are not talking
> > about global states here. They are all device specific. The device just
> > happens to be a platform device builtin the system.
>
> Right, but rfkill does actually have a function to set the global policy
> state (rfkill_set_global_sw_state) which some platform drivers insist on
> calling.
We just need to fix the platform drivers then. They should not set
global states since that is not what they are controlling. They control
the devices built-in to their platform/system. All this USB hotplug does
enough damage already and messing with global states is just wrong.
> The only reason we would need the NVS_REPORT then is to detect whether
> or not anything in the kernel called rfkill_set_global_sw_state(). If we
> can live with just configuring rfkilld for the (arguably broken) case
> where somebody cares about this, I'm happy with removing it.
Yes, please. Don't add this. Lets use our current base and start working
on rfkilld and see how it goes. I am assuming it is not as broken as
people think it is. I am against just adding new commands for the sake
of some wrong believes in legacy crap. Extending interfaces is simple,
but deprecating pieces is complicated.
> Hope that helps clear up your confusion.
I did have the same understanding here. Problem is just that we keep
mixing device specific and global states and that they are used wrongly.
And also platform devices are not necessary global. Linux just names
them platform if they have no proper root.
Regards
Marcel
On Mon, 2009-06-01 at 12:50 -0300, Henrique de Moraes Holschuh wrote:
> Yes, it "calls itself" right now... so I can certainly do that :)
>
> However, the in-driver shortcut means I give rfkill a kick and say "the soft
> state has changed, deal with it", instead of "please change the state" which
> it might deny due to EPO, etc.
But ... now I'm confused ... why would the driver ever ask to change the
state? Sounds like something that should be a button instead.
johannes
Hmm, that came out a lot more acid than normal. Sorry about that, it is
just that "let's level it for the lowest common factor" _really_ gets under
my skin.
My point stands: platforms that are capable of better user experience should
not get punished because a lot of other stuff out there doesn't. Especially
when it is a regression.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
The new code added by this patch will make rfkill create
a misc character device /dev/rfkill that userspace can use
to control rfkill soft blocks and get status of devices as
well as events when the status changes.
Using it is very simple -- when you open it you can read
a number of times to get the initial state, and every
further read blocks (you can poll) on getting the next
event from the kernel. The same structure you read is
also used when writing to it to change the soft block of
a given device, all devices of a given type, or all
devices.
This also makes CONFIG_RFKILL_INPUT selectable again in
order to be able to test without it present since its
functionality can now be replaced by userspace entirely
and distros and users may not want the input part of
rfkill interfering with their userspace code. We will
also write a userspace daemon to handle all that and
consequently add the input code to the feature removal
schedule.
In order to have rfkilld support both kernels with and
without CONFIG_RFKILL_INPUT (or new kernels after its
eventual removal) we also add an ioctl (that only exists
if rfkill-input is present) to disable rfkill-input.
It is not very efficient, but at least gives the correct
behaviour in all cases.
Signed-off-by: Johannes Berg <[email protected]>
---
v4: set default global state from userspace for rfkill hotplug
(pointed out by Marcel)
v5: add ioctl
Documentation/feature-removal-schedule.txt | 7
include/linux/rfkill.h | 84 +++++--
net/rfkill/Kconfig | 4
net/rfkill/core.c | 329 ++++++++++++++++++++++++++++-
4 files changed, 393 insertions(+), 31 deletions(-)
--- wireless-testing.orig/include/linux/rfkill.h 2009-06-01 22:34:04.000000000 +0200
+++ wireless-testing/include/linux/rfkill.h 2009-06-02 10:06:18.000000000 +0200
@@ -22,34 +22,17 @@
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
+#include <linux/types.h>
/* define userspace visible states */
#define RFKILL_STATE_SOFT_BLOCKED 0
#define RFKILL_STATE_UNBLOCKED 1
#define RFKILL_STATE_HARD_BLOCKED 2
-/* and that's all userspace gets */
-#ifdef __KERNEL__
-/* don't allow anyone to use these in the kernel */
-enum rfkill_user_states {
- RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
- RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
- RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
-};
-#undef RFKILL_STATE_SOFT_BLOCKED
-#undef RFKILL_STATE_UNBLOCKED
-#undef RFKILL_STATE_HARD_BLOCKED
-
-#include <linux/types.h>
-#include <linux/kernel.h>
-#include <linux/list.h>
-#include <linux/mutex.h>
-#include <linux/device.h>
-#include <linux/leds.h>
-
/**
* enum rfkill_type - type of rfkill switch.
*
+ * @RFKILL_TYPE_ALL: toggles all switches (userspace only)
* @RFKILL_TYPE_WLAN: switch is on a 802.11 wireless network device.
* @RFKILL_TYPE_BLUETOOTH: switch is on a bluetooth device.
* @RFKILL_TYPE_UWB: switch is on a ultra wideband device.
@@ -58,6 +41,7 @@ enum rfkill_user_states {
* @NUM_RFKILL_TYPES: number of defined rfkill types
*/
enum rfkill_type {
+ RFKILL_TYPE_ALL = 0,
RFKILL_TYPE_WLAN,
RFKILL_TYPE_BLUETOOTH,
RFKILL_TYPE_UWB,
@@ -66,6 +50,62 @@ enum rfkill_type {
NUM_RFKILL_TYPES,
};
+/**
+ * enum rfkill_operation - operation types
+ * @RFKILL_OP_ADD: a device was added
+ * @RFKILL_OP_DEL: a device was removed
+ * @RFKILL_OP_CHANGE: a device's state changed -- userspace changes one device
+ * @RFKILL_OP_CHANGE_ALL: userspace changes all devices (of a type, or all)
+ */
+enum rfkill_operation {
+ RFKILL_OP_ADD = 0,
+ RFKILL_OP_DEL,
+ RFKILL_OP_CHANGE,
+ RFKILL_OP_CHANGE_ALL,
+};
+
+/**
+ * struct rfkill_event - events for userspace on /dev/rfkill
+ * @idx: index of dev rfkill
+ * @type: type of the rfkill struct
+ * @op: operation code
+ * @hard: hard state (0/1)
+ * @soft: soft state (0/1)
+ *
+ * Structure used for userspace communication on /dev/rfkill,
+ * used for events from the kernel and control to the kernel.
+ */
+struct rfkill_event {
+ __u32 idx;
+ __u8 type;
+ __u8 op;
+ __u8 soft, hard;
+} __packed;
+
+/* ioctl for turning off rfkill-input (if present) */
+#define RFKILL_IOC_MAGIC 'R'
+#define RFKILL_IOC_NOINPUT 1
+#define RFKILL_IOCTL_NOINPUT _IO(RFKILL_IOC_MAGIC, RFKILL_IOC_NOINPUT)
+
+/* and that's all userspace gets */
+#ifdef __KERNEL__
+/* don't allow anyone to use these in the kernel */
+enum rfkill_user_states {
+ RFKILL_USER_STATE_SOFT_BLOCKED = RFKILL_STATE_SOFT_BLOCKED,
+ RFKILL_USER_STATE_UNBLOCKED = RFKILL_STATE_UNBLOCKED,
+ RFKILL_USER_STATE_HARD_BLOCKED = RFKILL_STATE_HARD_BLOCKED,
+};
+#undef RFKILL_STATE_SOFT_BLOCKED
+#undef RFKILL_STATE_UNBLOCKED
+#undef RFKILL_STATE_HARD_BLOCKED
+
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/list.h>
+#include <linux/mutex.h>
+#include <linux/device.h>
+#include <linux/leds.h>
+
/* this is opaque */
struct rfkill;
@@ -84,11 +124,7 @@ struct rfkill;
* the rfkill core query your driver before setting a requested
* block.
* @set_block: turn the transmitter on (blocked == false) or off
- * (blocked == true) -- this is called only while the transmitter
- * is not hard-blocked, but note that the core's view of whether
- * the transmitter is hard-blocked might differ from your driver's
- * view due to race conditions, so it is possible that it is still
- * called at the same time as you are calling rfkill_set_hw_state().
+ * (blocked == true) -- ignore and return 0 when hard blocked.
* This callback must be assigned.
*/
struct rfkill_ops {
--- wireless-testing.orig/net/rfkill/core.c 2009-06-01 22:34:04.000000000 +0200
+++ wireless-testing/net/rfkill/core.c 2009-06-02 10:06:18.000000000 +0200
@@ -28,6 +28,10 @@
#include <linux/mutex.h>
#include <linux/rfkill.h>
#include <linux/spinlock.h>
+#include <linux/miscdevice.h>
+#include <linux/wait.h>
+#include <linux/poll.h>
+#include <linux/fs.h>
#include "rfkill.h"
@@ -49,6 +53,8 @@ struct rfkill {
unsigned long state;
+ u32 idx;
+
bool registered;
bool suspended;
@@ -69,6 +75,18 @@ struct rfkill {
};
#define to_rfkill(d) container_of(d, struct rfkill, dev)
+struct rfkill_int_event {
+ struct list_head list;
+ struct rfkill_event ev;
+};
+
+struct rfkill_data {
+ struct list_head list;
+ struct list_head events;
+ struct mutex mtx;
+ wait_queue_head_t read_wait;
+ bool input_handler;
+};
MODULE_AUTHOR("Ivo van Doorn <[email protected]>");
@@ -90,6 +108,7 @@ MODULE_LICENSE("GPL");
*/
static LIST_HEAD(rfkill_list); /* list of registered rf switches */
static DEFINE_MUTEX(rfkill_global_mutex);
+static LIST_HEAD(rfkill_fds); /* list of open fds of /dev/rfkill */
static unsigned int rfkill_default_state = 1;
module_param_named(default_state, rfkill_default_state, uint, 0444);
@@ -171,12 +190,48 @@ static inline void rfkill_led_trigger_un
}
#endif /* CONFIG_RFKILL_LEDS */
-static void rfkill_uevent(struct rfkill *rfkill)
+static void rfkill_fill_event(struct rfkill_event *ev, struct rfkill *rfkill,
+ enum rfkill_operation op)
+{
+ unsigned long flags;
+
+ ev->idx = rfkill->idx;
+ ev->type = rfkill->type;
+ ev->op = op;
+
+ spin_lock_irqsave(&rfkill->lock, flags);
+ ev->hard = !!(rfkill->state & RFKILL_BLOCK_HW);
+ ev->soft = !!(rfkill->state & (RFKILL_BLOCK_SW |
+ RFKILL_BLOCK_SW_PREV));
+ spin_unlock_irqrestore(&rfkill->lock, flags);
+}
+
+static void rfkill_send_events(struct rfkill *rfkill, enum rfkill_operation op)
+{
+ struct rfkill_data *data;
+ struct rfkill_int_event *ev;
+
+ list_for_each_entry(data, &rfkill_fds, list) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ continue;
+ rfkill_fill_event(&ev->ev, rfkill, op);
+ mutex_lock(&data->mtx);
+ list_add_tail(&ev->list, &data->events);
+ mutex_unlock(&data->mtx);
+ wake_up_interruptible(&data->read_wait);
+ }
+}
+
+static void rfkill_event(struct rfkill *rfkill)
{
if (!rfkill->registered || rfkill->suspended)
return;
kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);
+
+ /* also send event to /dev/rfkill */
+ rfkill_send_events(rfkill, RFKILL_OP_CHANGE);
}
static bool __rfkill_set_hw_state(struct rfkill *rfkill,
@@ -260,9 +315,12 @@ static void rfkill_set_block(struct rfki
spin_unlock_irqrestore(&rfkill->lock, flags);
rfkill_led_trigger_event(rfkill);
- rfkill_uevent(rfkill);
+ rfkill_event(rfkill);
}
+#ifdef CONFIG_RFKILL_INPUT
+static atomic_t rfkill_input_disabled = ATOMIC_INIT(0);
+
/**
* __rfkill_switch_all - Toggle state of all switches of given type
* @type: type of interfaces to be affected
@@ -299,6 +357,9 @@ static void __rfkill_switch_all(const en
*/
void rfkill_switch_all(enum rfkill_type type, bool blocked)
{
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
if (!rfkill_epo_lock_active)
@@ -321,6 +382,9 @@ void rfkill_epo(void)
struct rfkill *rfkill;
int i;
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = true;
@@ -331,6 +395,7 @@ void rfkill_epo(void)
rfkill_global_states[i].def = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
+
mutex_unlock(&rfkill_global_mutex);
}
@@ -345,6 +410,9 @@ void rfkill_restore_states(void)
{
int i;
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = false;
@@ -361,6 +429,9 @@ void rfkill_restore_states(void)
*/
void rfkill_remove_epo_lock(void)
{
+ if (atomic_read(&rfkill_input_disabled))
+ return;
+
mutex_lock(&rfkill_global_mutex);
rfkill_epo_lock_active = false;
mutex_unlock(&rfkill_global_mutex);
@@ -391,9 +462,12 @@ bool rfkill_get_global_sw_state(const en
{
return rfkill_global_states[type].cur;
}
+#endif
void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
{
+ BUG_ON(type == RFKILL_TYPE_ALL);
+
mutex_lock(&rfkill_global_mutex);
/* don't allow unblock when epo */
@@ -537,6 +611,15 @@ static ssize_t rfkill_type_show(struct d
return sprintf(buf, "%s\n", rfkill_get_type_str(rfkill->type));
}
+static ssize_t rfkill_idx_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rfkill *rfkill = to_rfkill(dev);
+
+ return sprintf(buf, "%d\n", rfkill->idx);
+}
+
static u8 user_state_from_blocked(unsigned long state)
{
if (state & RFKILL_BLOCK_HW)
@@ -594,6 +677,7 @@ static ssize_t rfkill_claim_store(struct
static struct device_attribute rfkill_dev_attrs[] = {
__ATTR(name, S_IRUGO, rfkill_name_show, NULL),
__ATTR(type, S_IRUGO, rfkill_type_show, NULL),
+ __ATTR(index, S_IRUGO, rfkill_idx_show, NULL),
__ATTR(state, S_IRUGO|S_IWUSR, rfkill_state_show, rfkill_state_store),
__ATTR(claim, S_IRUGO|S_IWUSR, rfkill_claim_show, rfkill_claim_store),
__ATTR_NULL
@@ -708,7 +792,7 @@ struct rfkill * __must_check rfkill_allo
if (WARN_ON(!name))
return NULL;
- if (WARN_ON(type >= NUM_RFKILL_TYPES))
+ if (WARN_ON(type == RFKILL_TYPE_ALL || type >= NUM_RFKILL_TYPES))
return NULL;
rfkill = kzalloc(sizeof(*rfkill), GFP_KERNEL);
@@ -754,7 +838,9 @@ static void rfkill_uevent_work(struct wo
rfkill = container_of(work, struct rfkill, uevent_work);
- rfkill_uevent(rfkill);
+ mutex_lock(&rfkill_global_mutex);
+ rfkill_event(rfkill);
+ mutex_unlock(&rfkill_global_mutex);
}
static void rfkill_sync_work(struct work_struct *work)
@@ -785,6 +871,7 @@ int __must_check rfkill_register(struct
goto unlock;
}
+ rfkill->idx = rfkill_no;
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
@@ -819,6 +906,7 @@ int __must_check rfkill_register(struct
INIT_WORK(&rfkill->sync_work, rfkill_sync_work);
schedule_work(&rfkill->sync_work);
+ rfkill_send_events(rfkill, RFKILL_OP_ADD);
mutex_unlock(&rfkill_global_mutex);
return 0;
@@ -848,6 +936,7 @@ void rfkill_unregister(struct rfkill *rf
device_del(&rfkill->dev);
mutex_lock(&rfkill_global_mutex);
+ rfkill_send_events(rfkill, RFKILL_OP_DEL);
list_del_init(&rfkill->node);
mutex_unlock(&rfkill_global_mutex);
@@ -862,6 +951,226 @@ void rfkill_destroy(struct rfkill *rfkil
}
EXPORT_SYMBOL(rfkill_destroy);
+static int rfkill_fop_open(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data;
+ struct rfkill *rfkill;
+ struct rfkill_int_event *ev, *tmp;
+
+ data = kzalloc(sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&data->events);
+ mutex_init(&data->mtx);
+ init_waitqueue_head(&data->read_wait);
+
+ mutex_lock(&rfkill_global_mutex);
+ mutex_lock(&data->mtx);
+ /*
+ * start getting events from elsewhere but hold mtx to get
+ * startup events added first
+ */
+ list_add(&data->list, &rfkill_fds);
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ ev = kzalloc(sizeof(*ev), GFP_KERNEL);
+ if (!ev)
+ goto free;
+ rfkill_fill_event(&ev->ev, rfkill, RFKILL_OP_ADD);
+ list_add_tail(&ev->list, &data->events);
+ }
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+
+ file->private_data = data;
+
+ return nonseekable_open(inode, file);
+
+ free:
+ mutex_unlock(&data->mtx);
+ mutex_unlock(&rfkill_global_mutex);
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+ kfree(data);
+ return -ENOMEM;
+}
+
+static unsigned int rfkill_fop_poll(struct file *file, poll_table *wait)
+{
+ struct rfkill_data *data = file->private_data;
+ unsigned int res = POLLOUT | POLLWRNORM;
+
+ poll_wait(file, &data->read_wait, wait);
+
+ mutex_lock(&data->mtx);
+ if (!list_empty(&data->events))
+ res = POLLIN | POLLRDNORM;
+ mutex_unlock(&data->mtx);
+
+ return res;
+}
+
+static bool rfkill_readable(struct rfkill_data *data)
+{
+ bool r;
+
+ mutex_lock(&data->mtx);
+ r = !list_empty(&data->events);
+ mutex_unlock(&data->mtx);
+
+ return r;
+}
+
+static ssize_t rfkill_fop_read(struct file *file, char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev;
+ unsigned long sz;
+ int ret;
+
+ mutex_lock(&data->mtx);
+
+ while (list_empty(&data->events)) {
+ if (file->f_flags & O_NONBLOCK) {
+ ret = -EAGAIN;
+ goto out;
+ }
+ mutex_unlock(&data->mtx);
+ ret = wait_event_interruptible(data->read_wait,
+ rfkill_readable(data));
+ mutex_lock(&data->mtx);
+
+ if (ret)
+ goto out;
+ }
+
+ ev = list_first_entry(&data->events, struct rfkill_int_event,
+ list);
+
+ sz = min_t(unsigned long, sizeof(ev->ev), count);
+ ret = sz;
+ if (copy_to_user(buf, &ev->ev, sz))
+ ret = -EFAULT;
+
+ list_del(&ev->list);
+ kfree(ev);
+ out:
+ mutex_unlock(&data->mtx);
+ return ret;
+}
+
+static ssize_t rfkill_fop_write(struct file *file, const char __user *buf,
+ size_t count, loff_t *pos)
+{
+ struct rfkill *rfkill;
+ struct rfkill_event ev;
+
+ /* we don't need the 'hard' variable but accept it */
+ if (count < sizeof(ev) - 1)
+ return -EINVAL;
+
+ if (copy_from_user(&ev, buf, sizeof(ev) - 1))
+ return -EFAULT;
+
+ if (ev.op != RFKILL_OP_CHANGE && ev.op != RFKILL_OP_CHANGE_ALL)
+ return -EINVAL;
+
+ if (ev.type >= NUM_RFKILL_TYPES)
+ return -EINVAL;
+
+ mutex_lock(&rfkill_global_mutex);
+
+ if (ev.op == RFKILL_OP_CHANGE_ALL) {
+ if (ev.type == RFKILL_TYPE_ALL) {
+ enum rfkill_type i;
+ for (i = 0; i < NUM_RFKILL_TYPES; i++)
+ rfkill_global_states[i].cur = ev.soft;
+ } else {
+ rfkill_global_states[ev.type].cur = ev.soft;
+ }
+ }
+
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ if (rfkill->idx != ev.idx && ev.op != RFKILL_OP_CHANGE_ALL)
+ continue;
+
+ if (rfkill->type != ev.type && ev.type != RFKILL_TYPE_ALL)
+ continue;
+
+ rfkill_set_block(rfkill, ev.soft);
+ }
+ mutex_unlock(&rfkill_global_mutex);
+
+ return count;
+}
+
+static int rfkill_fop_release(struct inode *inode, struct file *file)
+{
+ struct rfkill_data *data = file->private_data;
+ struct rfkill_int_event *ev, *tmp;
+
+ mutex_lock(&rfkill_global_mutex);
+ list_del(&data->list);
+ mutex_unlock(&rfkill_global_mutex);
+
+ mutex_destroy(&data->mtx);
+ list_for_each_entry_safe(ev, tmp, &data->events, list)
+ kfree(ev);
+
+#ifdef CONFIG_RFKILL_INPUT
+ if (data->input_handler)
+ atomic_dec(&rfkill_input_disabled);
+#endif
+
+ kfree(data);
+
+ return 0;
+}
+
+#ifdef CONFIG_RFKILL_INPUT
+static long rfkill_fop_ioctl(struct file *file, unsigned int cmd,
+ unsigned long arg)
+{
+ struct rfkill_data *data = file->private_data;
+
+ if (_IOC_TYPE(cmd) != RFKILL_IOC_MAGIC)
+ return -ENOSYS;
+
+ if (_IOC_NR(cmd) != RFKILL_IOC_NOINPUT)
+ return -ENOSYS;
+
+ mutex_lock(&data->mtx);
+
+ if (!data->input_handler) {
+ atomic_inc(&rfkill_input_disabled);
+ data->input_handler = true;
+ }
+
+ mutex_unlock(&data->mtx);
+
+ return 0;
+}
+#endif
+
+static const struct file_operations rfkill_fops = {
+ .open = rfkill_fop_open,
+ .read = rfkill_fop_read,
+ .write = rfkill_fop_write,
+ .poll = rfkill_fop_poll,
+ .release = rfkill_fop_release,
+#ifdef CONFIG_RFKILL_INPUT
+ .unlocked_ioctl = rfkill_fop_ioctl,
+#endif
+};
+
+static struct miscdevice rfkill_miscdev = {
+ .name = "rfkill",
+ .fops = &rfkill_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
static int __init rfkill_init(void)
{
@@ -875,10 +1184,19 @@ static int __init rfkill_init(void)
if (error)
goto out;
+ error = misc_register(&rfkill_miscdev);
+ if (error) {
+ class_unregister(&rfkill_class);
+ goto out;
+ }
+
#ifdef CONFIG_RFKILL_INPUT
error = rfkill_handler_init();
- if (error)
+ if (error) {
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
+ goto out;
+ }
#endif
out:
@@ -891,6 +1209,7 @@ static void __exit rfkill_exit(void)
#ifdef CONFIG_RFKILL_INPUT
rfkill_handler_exit();
#endif
+ misc_deregister(&rfkill_miscdev);
class_unregister(&rfkill_class);
}
module_exit(rfkill_exit);
--- wireless-testing.orig/net/rfkill/Kconfig 2009-06-01 22:34:04.000000000 +0200
+++ wireless-testing/net/rfkill/Kconfig 2009-06-01 22:36:50.000000000 +0200
@@ -18,7 +18,7 @@ config RFKILL_LEDS
default y
config RFKILL_INPUT
- bool
+ bool "RF switch input support"
depends on RFKILL
depends on INPUT = y || RFKILL = INPUT
- default y
+ default y if !EMBEDDED
--- wireless-testing.orig/Documentation/feature-removal-schedule.txt 2009-06-01 22:32:03.000000000 +0200
+++ wireless-testing/Documentation/feature-removal-schedule.txt 2009-06-01 22:36:50.000000000 +0200
@@ -437,3 +437,10 @@ Why: Superseded by tdfxfb. I2C/DDC suppo
driver but this caused driver conflicts.
Who: Jean Delvare <[email protected]>
Krzysztof Helt <[email protected]>
+
+---------------------------
+
+What: CONFIG_RFKILL_INPUT
+When: 2.6.33
+Why: Should be implemented in userspace, policy daemon.
+Who: Johannes Berg <[email protected]>
Hi Henrique,
> > > > We just need to fix the platform drivers then. They should not set
> > > > global states since that is not what they are controlling. They control
> > >
> > > We should change things, yes. So that the platform stores the global
> > > state. That was half-broken on the old core (the platform stored the
> > > state of its own device, which could be out of sync with the global
> > > state), but the part of it setting the global state is correct.
> > >
> > > That needs a new in-kernel API to tie the core to platform drivers
> > > capable of storing global states without causing problems when drivers
> > > are unloaded, but it is not hard.
> > >
> > > As for NVS events, they have a clear use case: to let rfkilld know which
> > > global states it could leave alone the first time it loads, and which
> > > ones have to be restored...
> >
> > show me an example of a platform device that stores the global state. I
> > think you are confusing the word platform as in system with a platform
> > device. The ThinkPad Bluetooth and WWAN switches are platform devices
> > and control each one specific device. Same goes for the EeePC. They are
> > not controlling a global state.
>
> I don't know what big difference you see between the two uses of "platform",
> but I will just work around it to get something useful out of this mail.
>
> The laptop stores in NVS the state of its 'switches'. This is as close as
> one gets from 'storing the global state'. When the laptop boots,
> these devices get set by the firware to the state in NVS. It is the best
> place to store global state, because these devices will be in their proper
> state (i.e. matching what will be the global state when the rfkill core
> loads) all the time. It also gives you for free multi-OS/multi-kernel state
> storage for these devices, and compatibility with BIOSes that let you define
> the initial state for the devices in the firmware configuration, etc.
it stores the state of its switches and why should these be enforced as
a global state? Who says that this is a global state? For me that sounds
like policy.
> There. I didn't use the 'p' word once. Now, please tell me why show we
> just dump the storage done by the firmware in NVS, and instead store the
> global state for those switches in userspace?
We are getting OP_ADD for these switches when opening /dev/rfkill and it
is up to a userspace policy to either enforce this on all attached
devices or not.
> If it was a extremely complicated and fragile thing to do, it would be a
> different matter, but it isn't. It isn't even ugly code, even after we fix
> it to be all about global states.
This is not about ugly code or anything alike. We wanna get the RFKILL
subsystem cleaned up now. And just overloading it with things that might
be a good idea is bad. Enhancing it later when it is actually needed is
way easy. And I am not convinced even a little bit that we should treat
the firmware stored states as global. They are not global states. You
are just abusing them as this.
Regards
Marcel
Marcel Holtmann wrote:
> Hi Johannes,
>
>
>> Would everybody be happy with this rolled in?
>>
>> johannes
>>
>> Subject: rfkill: userspace API improvements
>>
>> This adds the two following things to /dev/rfkill:
>> 1) notification to userspace with a new operation
>> RFKILL_OP_NVS_REPORT about default states restored
>> from platform non-volatile storage
>>
>
> I really don't understand why this is needed. What benefit does it give
> us compared to just sent OP_CHANGE and OP_CHANGE as an update. My X200
> for example does this anyway on suspend/resume.
>
This is required for boot only. I have no reason for this event to be
generated on resume.
The same effect could be had by generating an OP_CHANGE on f_open,
_only_ when a platform driver has provided a value from NVS. But it
does seem clearer to make it a different type of event.
> So what is rfkilld suppose to be doing when receiving this report? What
> is the expected behavior? Why do we bother with multi-OS crap here? I am
> really unclear what are we trying to solve here.
>
In order to replicate the kernel behavior, it is expected that you set
your internal state from this event. E.g. when the user next presses
the wireless toggle key, you set the inverse of that internal state.
Since this event is generated by a platform driver, you can expect it to
be present following coldplug (the udev initscript). If the event is
not present after coldplug, you may then issue OP_CHANGE yourself, to
e.g. restore the state from a file. You would not be expected to handle
OP_NVS_REPORT after startup. (Unless the daemon is restarted).
Replicating the kernel behavior will allow us to avoid causing a couple
of niggly little regressions on at least two platforms. It preserves
the behavior when dual-booting (possibly between different linux
distros), and when the BIOS setup screen exposes the NVS state as an
option. The new behavior you suggest will annoy any users who have
become used to these scenarios "just working".
You may not use these platforms yourself. But I'm as annoyed as
Henrique is, we don't want to impose regressions just because other
platforms don't implement the feature.
Why the fuss about implementing this, it seems easy enough? Start
rfkilld after udev (like everything else). If you get NVS_REPORT, then
use those states. Fill in any other states from defaults or state files
and issue OP_CHANGE for them, just as you're already planning. Ignore
any subsequent NVS_REPORTs. That should cover it.
It's the cost for starting from a working implementation. You benefit
from having existing drivers and users, you pay by not breaking them
without good reason.
Thanks
Alan
Marcel Holtmann wrote:
> Hi Alan,
>
>
>>>>>>> We just need to fix the platform drivers then. They should not set
>>>>>>> global states since that is not what they are controlling. They
>>>>>>> control
>>>>>>>
>>>>>> We should change things, yes. So that the platform stores the global
>>>>>> state. That was half-broken on the old core (the platform stored the
>>>>>> state of its own device, which could be out of sync with the global
>>>>>> state), but the part of it setting the global state is correct.
>>>>>>
>>>>>> That needs a new in-kernel API to tie the core to platform drivers
>>>>>> capable of storing global states without causing problems when drivers
>>>>>> are unloaded, but it is not hard.
>>>>>>
>>>>>> As for NVS events, they have a clear use case: to let rfkilld know
>>>>>> which
>>>>>> global states it could leave alone the first time it loads, and which
>>>>>> ones have to be restored...
>>>>>>
>>>>> show me an example of a platform device that stores the global state. I
>>>>> think you are confusing the word platform as in system with a platform
>>>>> device. The ThinkPad Bluetooth and WWAN switches are platform devices
>>>>> and control each one specific device. Same goes for the EeePC. They are
>>>>> not controlling a global state.
>>>>>
>>>> I don't know what big difference you see between the two uses of
>>>> "platform",
>>>> but I will just work around it to get something useful out of this mail.
>>>>
>>>> The laptop stores in NVS the state of its 'switches'. This is as close as
>>>> one gets from 'storing the global state'. When the laptop boots,
>>>> these devices get set by the firware to the state in NVS. It is the best
>>>> place to store global state, because these devices will be in their proper
>>>> state (i.e. matching what will be the global state when the rfkill core
>>>> loads) all the time. It also gives you for free multi-OS/multi-kernel
>>>> state
>>>> storage for these devices, and compatibility with BIOSes that let you
>>>> define
>>>> the initial state for the devices in the firmware configuration, etc.
>>>>
>>> it stores the state of its switches and why should these be enforced as
>>> a global state? Who says that this is a global state? For me that sounds
>>> like policy.
>>>
>> We don't seem to be getting very far :-(. I agree that these do not
>> appear to be global states, just the states of individual rfkill
>> devices.
>>
>> So I would propose the following changes. (I'm happy to write the
>> code as well, but I think it's easier to read English).
>>
>> 1) remove rfkill_set_global_sw_state()
>> 2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
>> registration, setting a flag.
>> 3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
>> events, tho it may as well be set in all events)
>>
>
> you can do things like this already if you just set the states correctly
> between rfkill_alloc and rfkill_register. So you should make sure you
> register your RFKILL switch with the correct state and not toggle it
> later. As far as I can tell the tpacpi driver does that already.
>
Ah. I need to read the (rewritten) code again.
I'm still more familiar with the old rfkill core. My understanding was
that the old core required drivers to say what their current state was,
but if that differed from the global state then it would be changed to
match.
>> 4) rfkill-input preserves existing behaviour - *if enabled* - by
>> initializing the global state from individual devices which have NVS.
>> (As before, each _type_ of rfkill device has its own global state).
>>
>
> That still sounds horribly wrong and has been for a long time, but
> again, I don't care about rfkill-input since it will go away.
>
>
>> 5) rfkill devices with NVS will have their current state preserved,
>> so long as the global state has not yet been set (by userspace or by
>> rfkill-input). Of course userspace can change the state in response
>> to the device being added.
>>
>
> If you register your RFKILL switch properly, they do that already. See
> my comment above. You start up with the proper state to begin with.
>
>
>> => rfkilld then has the information required to implement the same
>> policy as rfkill-input. Furthermore, it will have enough information
>> that it could implement file-based storage as a fallback, and still
>> support NVS where available.
>>
>> It will also allow implementation (or configuration) of completely
>> different policy to rfkill-input. E.g. if your internal wireless
>> w/NVS is broken and should be disabled, that can be done independently
>> of your replacement USB wireless adaptor.
>>
>
> I did actually looked into this and userspace has all information
> available to create a proper policy if you wanna treat your NVS states
> (for example the tpacpi ones) as global states, you can easily do that
> right now. It became really simple with /dev/rfkill.
>
I still think userspace is missing an important piece of information:
whether the state of a certain rfkill device is persistent or not.
The driver knows exactly whether this is the case; from what you say it
will call rfkill_set_state() before rfkill_register(). If it _doesn't_
do this, there won't be any persistent state for userspace to retrieve
anyway :-).
I don't think we should expect userspace to know whether or not a device
has a persistent state. Yes, it _could_ maintain whitelists, but why
should it have to if the driver already knows?
> So I have code for adding full native and integrated RFKILL support to
> the Bluetooth subsystem and that works nicely in conjunction with the
> hardware killswitch my system has. I still have to test WiMAX and do
> some modifications for the Ericsson MBM 3G stuff. So at some point we
> should have softkill support for all major radios.
Nice.
> Only exception are
> the 3G cards that present themselves as modems with PPP. They are to
> tricky and I have no idea right now how we could integrate them.
>
Would a urfkill work, analagous to uinput?. I.e. allow the userspace
driver (whatever talks to the configuration interface) to export its own
rfkill device. I don't know what the uinput interface is, but you could
create one by opening /dev/urfkill and writing the initial state, and
destroy it on close().
Regards
Alan
rfkill_set_global_sw_state() (previously rfkill_set_default()) will no
longer be exported by the rewritten rfkill core.
Instead, platform drivers which can provide persistent soft-rfkill state
across power-down/reboot should indicate their initial state by calling
rfkill_set_sw_state() before registration. Otherwise, they will be
initialized to a default value during registration by a set_block call.
We remove existing calls to rfkill_set_sw_state() which happen before
registration, since these had no effect in the old model. If these
drivers do have persistent state, the calls can be put back (subject
to testing :-). This affects hp-wmi and acer-wmi.
Drivers with persistent state will affect the global state only if
rfkill-input is enabled. This is required, otherwise booting with
wireless soft-blocked and pressing the wireless-toggle key once would
have no apparent effect. This special case will be removed in future
along with rfkill-input, in favour of a more flexible userspace daemon
(see Documentation/feature-removal-schedule.txt).
Now rfkill_global_states[n].def is only used to preserve global states
over EPO, it is renamed to ".sav".
Signed-off-by: Alan Jenkins <[email protected]>
---
v2: nothing (wrong patch)
v3:
- fix thinkpad-acpi message to reflect altered error-case behaviour
- remove !CONFIG_RFKILL stub for rfkill_set_global_sw_state()
drivers/platform/x86/acer-wmi.c | 3 -
drivers/platform/x86/eeepc-laptop.c | 8 ++--
drivers/platform/x86/hp-wmi.c | 4 --
drivers/platform/x86/thinkpad_acpi.c | 31 ++++++-------
include/linux/rfkill.h | 28 +++--------
net/rfkill/core.c | 81 ++++++++++++---------------------
6 files changed, 56 insertions(+), 99 deletions(-)
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index b618fa5..09a503e 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -988,7 +988,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
char *name, u32 cap)
{
int err;
- u32 state;
struct rfkill *rfkill_dev;
rfkill_dev = rfkill_alloc(name, dev, type,
@@ -996,8 +995,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
(void *)(unsigned long)cap);
if (!rfkill_dev)
return ERR_PTR(-ENOMEM);
- get_u32(&state, cap);
- rfkill_set_sw_state(rfkill_dev, !state);
err = rfkill_register(rfkill_dev);
if (err) {
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
index 1208d0c..03bf522 100644
--- a/drivers/platform/x86/eeepc-laptop.c
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -675,8 +675,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_wlan_rfkill)
goto wlan_fail;
- rfkill_set_global_sw_state(RFKILL_TYPE_WLAN,
- get_acpi(CM_ASL_WLAN) != 1);
+ rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill,
+ get_acpi(CM_ASL_WLAN) != 1);
result = rfkill_register(ehotk->eeepc_wlan_rfkill);
if (result)
goto wlan_fail;
@@ -693,8 +693,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_bluetooth_rfkill)
goto bluetooth_fail;
- rfkill_set_global_sw_state(RFKILL_TYPE_BLUETOOTH,
- get_acpi(CM_ASL_BLUETOOTH) != 1);
+ rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill,
+ get_acpi(CM_ASL_BLUETOOTH) != 1);
result = rfkill_register(ehotk->eeepc_bluetooth_rfkill);
if (result)
goto bluetooth_fail;
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
index 8d93114..16fffe4 100644
--- a/drivers/platform/x86/hp-wmi.c
+++ b/drivers/platform/x86/hp-wmi.c
@@ -422,7 +422,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_WLAN,
&hp_wmi_rfkill_ops,
(void *) 0);
- rfkill_set_sw_state(wifi_rfkill, hp_wmi_wifi_state());
err = rfkill_register(wifi_rfkill);
if (err)
goto register_wifi_error;
@@ -433,8 +432,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_BLUETOOTH,
&hp_wmi_rfkill_ops,
(void *) 1);
- rfkill_set_sw_state(bluetooth_rfkill,
- hp_wmi_bluetooth_state());
err = rfkill_register(bluetooth_rfkill);
if (err)
goto register_bluetooth_error;
@@ -445,7 +442,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_WWAN,
&hp_wmi_rfkill_ops,
(void *) 2);
- rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state());
err = rfkill_register(wwan_rfkill);
if (err)
goto register_wwan_err;
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index cfcafa4..d6ab9c8 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -1168,21 +1168,6 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
- initial_sw_status = (tp_rfkops->get_status)();
- if (initial_sw_status < 0) {
- printk(TPACPI_ERR
- "failed to read initial state for %s, error %d; "
- "will turn radio off\n", name, initial_sw_status);
- } else {
- initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
- if (set_default) {
- /* try to set the initial state as the default for the
- * rfkill type, since we ask the firmware to preserve
- * it across S5 in NVRAM */
- rfkill_set_global_sw_state(rfktype, initial_sw_state);
- }
- }
-
atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
if (atp_rfk)
atp_rfk->rfkill = rfkill_alloc(name,
@@ -1200,8 +1185,20 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
atp_rfk->id = id;
atp_rfk->ops = tp_rfkops;
- rfkill_set_states(atp_rfk->rfkill, initial_sw_state,
- tpacpi_rfk_check_hwblock_state());
+ initial_sw_status = (tp_rfkops->get_status)();
+ if (initial_sw_status < 0) {
+ printk(TPACPI_ERR
+ "failed to read initial state for %s, error %d\n"
+ name, initial_sw_status);
+ } else {
+ initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
+ if (set_default) {
+ /* try to keep the initial state, since we ask the
+ * firmware to preserve it across S5 in NVRAM */
+ rfkill_set_sw_state(atp_rfk->rfkill, initial_sw_state);
+ }
+ }
+ rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state());
res = rfkill_register(atp_rfk->rfkill);
if (res < 0) {
diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h
index ee3edde..e98a4b3 100644
--- a/include/linux/rfkill.h
+++ b/include/linux/rfkill.h
@@ -156,8 +156,14 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
* @rfkill: rfkill structure to be registered
*
* This function should be called by the transmitter driver to register
- * the rfkill structure needs to be registered. Before calling this function
- * the driver needs to be ready to service method calls from rfkill.
+ * the rfkill structure. Before calling this function the driver needs
+ * to be ready to service method calls from rfkill.
+ *
+ * If the software blocked state is not set before registration,
+ * set_block will be called to initialize it to a default value.
+ *
+ * If the hardware blocked state is not set before registration,
+ * it is assumed to be unblocked.
*/
int __must_check rfkill_register(struct rfkill *rfkill);
@@ -250,19 +256,6 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked);
void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw);
/**
- * rfkill_set_global_sw_state - set global sw block default
- * @type: rfkill type to set default for
- * @blocked: default to set
- *
- * This function sets the global default -- use at boot if your platform has
- * an rfkill switch. If not early enough this call may be ignored.
- *
- * XXX: instead of ignoring -- how about just updating all currently
- * registered drivers?
- */
-void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked);
-
-/**
* rfkill_blocked - query rfkill block
*
* @rfkill: rfkill struct to query
@@ -316,11 +309,6 @@ static inline void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
{
}
-static inline void rfkill_set_global_sw_state(const enum rfkill_type type,
- bool blocked)
-{
-}
-
static inline bool rfkill_blocked(struct rfkill *rfkill)
{
return false;
diff --git a/net/rfkill/core.c b/net/rfkill/core.c
index 11b7314..d14493f 100644
--- a/net/rfkill/core.c
+++ b/net/rfkill/core.c
@@ -57,6 +57,7 @@ struct rfkill {
bool registered;
bool suspended;
+ bool persistent;
const struct rfkill_ops *ops;
void *data;
@@ -116,11 +117,9 @@ MODULE_PARM_DESC(default_state,
"Default initial state for all radio types, 0 = radio off");
static struct {
- bool cur, def;
+ bool cur, sav;
} rfkill_global_states[NUM_RFKILL_TYPES];
-static unsigned long rfkill_states_default_locked;
-
static bool rfkill_epo_lock_active;
@@ -392,7 +391,7 @@ void rfkill_epo(void)
rfkill_set_block(rfkill, true);
for (i = 0; i < NUM_RFKILL_TYPES; i++) {
- rfkill_global_states[i].def = rfkill_global_states[i].cur;
+ rfkill_global_states[i].sav = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
@@ -417,7 +416,7 @@ void rfkill_restore_states(void)
rfkill_epo_lock_active = false;
for (i = 0; i < NUM_RFKILL_TYPES; i++)
- __rfkill_switch_all(i, rfkill_global_states[i].def);
+ __rfkill_switch_all(i, rfkill_global_states[i].sav);
mutex_unlock(&rfkill_global_mutex);
}
@@ -464,29 +463,6 @@ bool rfkill_get_global_sw_state(const enum rfkill_type type)
}
#endif
-void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
-{
- BUG_ON(type == RFKILL_TYPE_ALL);
-
- mutex_lock(&rfkill_global_mutex);
-
- /* don't allow unblock when epo */
- if (rfkill_epo_lock_active && !blocked)
- goto out;
-
- /* too late */
- if (rfkill_states_default_locked & BIT(type))
- goto out;
-
- rfkill_states_default_locked |= BIT(type);
-
- rfkill_global_states[type].cur = blocked;
- rfkill_global_states[type].def = blocked;
- out:
- mutex_unlock(&rfkill_global_mutex);
-}
-EXPORT_SYMBOL(rfkill_set_global_sw_state);
-
bool rfkill_set_hw_state(struct rfkill *rfkill, bool blocked)
{
@@ -532,13 +508,14 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
blocked = blocked || hwblock;
spin_unlock_irqrestore(&rfkill->lock, flags);
- if (!rfkill->registered)
- return blocked;
+ if (!rfkill->registered) {
+ rfkill->persistent = true;
+ } else {
+ if (prev != blocked && !hwblock)
+ schedule_work(&rfkill->uevent_work);
- if (prev != blocked && !hwblock)
- schedule_work(&rfkill->uevent_work);
-
- rfkill_led_trigger_event(rfkill);
+ rfkill_led_trigger_event(rfkill);
+ }
return blocked;
}
@@ -563,13 +540,14 @@ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
spin_unlock_irqrestore(&rfkill->lock, flags);
- if (!rfkill->registered)
- return;
-
- if (swprev != sw || hwprev != hw)
- schedule_work(&rfkill->uevent_work);
+ if (!rfkill->registered) {
+ rfkill->persistent = true;
+ } else {
+ if (swprev != sw || hwprev != hw)
+ schedule_work(&rfkill->uevent_work);
- rfkill_led_trigger_event(rfkill);
+ rfkill_led_trigger_event(rfkill);
+ }
}
EXPORT_SYMBOL(rfkill_set_states);
@@ -888,15 +866,6 @@ int __must_check rfkill_register(struct rfkill *rfkill)
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
- if (!(rfkill_states_default_locked & BIT(rfkill->type))) {
- /* first of its kind */
- BUILD_BUG_ON(NUM_RFKILL_TYPES >
- sizeof(rfkill_states_default_locked) * 8);
- rfkill_states_default_locked |= BIT(rfkill->type);
- rfkill_global_states[rfkill->type].cur =
- rfkill_global_states[rfkill->type].def;
- }
-
list_add_tail(&rfkill->node, &rfkill_list);
error = device_add(dev);
@@ -916,7 +885,17 @@ int __must_check rfkill_register(struct rfkill *rfkill)
if (rfkill->ops->poll)
schedule_delayed_work(&rfkill->poll_work,
round_jiffies_relative(POLL_INTERVAL));
- schedule_work(&rfkill->sync_work);
+
+ if (!rfkill->persistent || rfkill_epo_lock_active) {
+ schedule_work(&rfkill->sync_work);
+ } else {
+#ifdef CONFIG_RFKILL_INPUT
+ bool soft_blocked = !!(rfkill->state & RFKILL_BLOCK_SW);
+
+ if (!atomic_read(&rfkill_input_disabled))
+ __rfkill_switch_all(rfkill->type, soft_blocked);
+#endif
+ }
rfkill_send_events(rfkill, RFKILL_OP_ADD);
@@ -1191,7 +1170,7 @@ static int __init rfkill_init(void)
int i;
for (i = 0; i < NUM_RFKILL_TYPES; i++)
- rfkill_global_states[i].def = !rfkill_default_state;
+ rfkill_global_states[i].cur = !rfkill_default_state;
error = class_register(&rfkill_class);
if (error)
--
1.5.4.3
rfkill_set_global_sw_state() (previously rfkill_set_default()) will no
longer be exported by the rewritten rfkill core.
Instead, platform drivers which can provide persistent soft-rfkill state
across power-down/reboot should indicate their initial state by calling
rfkill_set_sw_state() before registration. Otherwise, they will be
initialized to a default value during registration by a set_block call.
We remove existing calls to rfkill_set_sw_state() which happen before
registration, since these had no effect in the old model. If these
drivers do have persistent state, the calls can be put back (subject
to testing :-). This affects hp-wmi and acer-wmi.
Drivers with persistent state will affect the global state only if
rfkill-input is enabled. This is required, otherwise booting with
wireless soft-blocked and pressing the wireless-toggle key once would
have no apparent effect. This special case will be removed in future
along with rfkill-input, in favour of a more flexible userspace daemon
(see Documentation/feature-removal-schedule.txt).
Now rfkill_global_states[n].def is only used to preserve global states
over EPO, it is renamed to ".sav".
Signed-off-by: Alan Jenkins <[email protected]>
---
drivers/platform/x86/acer-wmi.c | 3 -
drivers/platform/x86/eeepc-laptop.c | 8 ++--
drivers/platform/x86/hp-wmi.c | 4 --
drivers/platform/x86/thinkpad_acpi.c | 31 ++++++-------
include/linux/rfkill.h | 23 +++------
net/rfkill/core.c | 81 ++++++++++++---------------------
6 files changed, 56 insertions(+), 94 deletions(-)
diff --git a/drivers/platform/x86/acer-wmi.c b/drivers/platform/x86/acer-wmi.c
index b618fa5..09a503e 100644
--- a/drivers/platform/x86/acer-wmi.c
+++ b/drivers/platform/x86/acer-wmi.c
@@ -988,7 +988,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
char *name, u32 cap)
{
int err;
- u32 state;
struct rfkill *rfkill_dev;
rfkill_dev = rfkill_alloc(name, dev, type,
@@ -996,8 +995,6 @@ static struct rfkill *acer_rfkill_register(struct device *dev,
(void *)(unsigned long)cap);
if (!rfkill_dev)
return ERR_PTR(-ENOMEM);
- get_u32(&state, cap);
- rfkill_set_sw_state(rfkill_dev, !state);
err = rfkill_register(rfkill_dev);
if (err) {
diff --git a/drivers/platform/x86/eeepc-laptop.c b/drivers/platform/x86/eeepc-laptop.c
index 1208d0c..03bf522 100644
--- a/drivers/platform/x86/eeepc-laptop.c
+++ b/drivers/platform/x86/eeepc-laptop.c
@@ -675,8 +675,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_wlan_rfkill)
goto wlan_fail;
- rfkill_set_global_sw_state(RFKILL_TYPE_WLAN,
- get_acpi(CM_ASL_WLAN) != 1);
+ rfkill_set_sw_state(ehotk->eeepc_wlan_rfkill,
+ get_acpi(CM_ASL_WLAN) != 1);
result = rfkill_register(ehotk->eeepc_wlan_rfkill);
if (result)
goto wlan_fail;
@@ -693,8 +693,8 @@ static int eeepc_hotk_add(struct acpi_device *device)
if (!ehotk->eeepc_bluetooth_rfkill)
goto bluetooth_fail;
- rfkill_set_global_sw_state(RFKILL_TYPE_BLUETOOTH,
- get_acpi(CM_ASL_BLUETOOTH) != 1);
+ rfkill_set_sw_state(ehotk->eeepc_bluetooth_rfkill,
+ get_acpi(CM_ASL_BLUETOOTH) != 1);
result = rfkill_register(ehotk->eeepc_bluetooth_rfkill);
if (result)
goto bluetooth_fail;
diff --git a/drivers/platform/x86/hp-wmi.c b/drivers/platform/x86/hp-wmi.c
index 8d93114..16fffe4 100644
--- a/drivers/platform/x86/hp-wmi.c
+++ b/drivers/platform/x86/hp-wmi.c
@@ -422,7 +422,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_WLAN,
&hp_wmi_rfkill_ops,
(void *) 0);
- rfkill_set_sw_state(wifi_rfkill, hp_wmi_wifi_state());
err = rfkill_register(wifi_rfkill);
if (err)
goto register_wifi_error;
@@ -433,8 +432,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_BLUETOOTH,
&hp_wmi_rfkill_ops,
(void *) 1);
- rfkill_set_sw_state(bluetooth_rfkill,
- hp_wmi_bluetooth_state());
err = rfkill_register(bluetooth_rfkill);
if (err)
goto register_bluetooth_error;
@@ -445,7 +442,6 @@ static int __init hp_wmi_bios_setup(struct platform_device *device)
RFKILL_TYPE_WWAN,
&hp_wmi_rfkill_ops,
(void *) 2);
- rfkill_set_sw_state(wwan_rfkill, hp_wmi_wwan_state());
err = rfkill_register(wwan_rfkill);
if (err)
goto register_wwan_err;
diff --git a/drivers/platform/x86/thinkpad_acpi.c b/drivers/platform/x86/thinkpad_acpi.c
index cfcafa4..ab6968a 100644
--- a/drivers/platform/x86/thinkpad_acpi.c
+++ b/drivers/platform/x86/thinkpad_acpi.c
@@ -1168,21 +1168,6 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
BUG_ON(id >= TPACPI_RFK_SW_MAX || tpacpi_rfkill_switches[id]);
- initial_sw_status = (tp_rfkops->get_status)();
- if (initial_sw_status < 0) {
- printk(TPACPI_ERR
- "failed to read initial state for %s, error %d; "
- "will turn radio off\n", name, initial_sw_status);
- } else {
- initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
- if (set_default) {
- /* try to set the initial state as the default for the
- * rfkill type, since we ask the firmware to preserve
- * it across S5 in NVRAM */
- rfkill_set_global_sw_state(rfktype, initial_sw_state);
- }
- }
-
atp_rfk = kzalloc(sizeof(struct tpacpi_rfk), GFP_KERNEL);
if (atp_rfk)
atp_rfk->rfkill = rfkill_alloc(name,
@@ -1200,8 +1185,20 @@ static int __init tpacpi_new_rfkill(const enum tpacpi_rfk_id id,
atp_rfk->id = id;
atp_rfk->ops = tp_rfkops;
- rfkill_set_states(atp_rfk->rfkill, initial_sw_state,
- tpacpi_rfk_check_hwblock_state());
+ initial_sw_status = (tp_rfkops->get_status)();
+ if (initial_sw_status < 0) {
+ printk(TPACPI_ERR
+ "failed to read initial state for %s, error %d; "
+ "will turn radio off\n", name, initial_sw_status);
+ } else {
+ initial_sw_state = (initial_sw_status == TPACPI_RFK_RADIO_OFF);
+ if (set_default) {
+ /* try to keep the initial state, since we ask the
+ * firmware to preserve it across S5 in NVRAM */
+ rfkill_set_sw_state(atp_rfk->rfkill, initial_sw_state);
+ }
+ }
+ rfkill_set_hw_state(atp_rfk->rfkill, tpacpi_rfk_check_hwblock_state());
res = rfkill_register(atp_rfk->rfkill);
if (res < 0) {
diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h
index ee3edde..f8852bb 100644
--- a/include/linux/rfkill.h
+++ b/include/linux/rfkill.h
@@ -156,8 +156,14 @@ struct rfkill * __must_check rfkill_alloc(const char *name,
* @rfkill: rfkill structure to be registered
*
* This function should be called by the transmitter driver to register
- * the rfkill structure needs to be registered. Before calling this function
- * the driver needs to be ready to service method calls from rfkill.
+ * the rfkill structure. Before calling this function the driver needs
+ * to be ready to service method calls from rfkill.
+ *
+ * If the software blocked state is not set before registration,
+ * set_block will be called to initialize it to a default value.
+ *
+ * If the hardware blocked state is not set before registration,
+ * it is assumed to be unblocked.
*/
int __must_check rfkill_register(struct rfkill *rfkill);
@@ -250,19 +256,6 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked);
void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw);
/**
- * rfkill_set_global_sw_state - set global sw block default
- * @type: rfkill type to set default for
- * @blocked: default to set
- *
- * This function sets the global default -- use at boot if your platform has
- * an rfkill switch. If not early enough this call may be ignored.
- *
- * XXX: instead of ignoring -- how about just updating all currently
- * registered drivers?
- */
-void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked);
-
-/**
* rfkill_blocked - query rfkill block
*
* @rfkill: rfkill struct to query
diff --git a/net/rfkill/core.c b/net/rfkill/core.c
index 11b7314..d14493f 100644
--- a/net/rfkill/core.c
+++ b/net/rfkill/core.c
@@ -57,6 +57,7 @@ struct rfkill {
bool registered;
bool suspended;
+ bool persistent;
const struct rfkill_ops *ops;
void *data;
@@ -116,11 +117,9 @@ MODULE_PARM_DESC(default_state,
"Default initial state for all radio types, 0 = radio off");
static struct {
- bool cur, def;
+ bool cur, sav;
} rfkill_global_states[NUM_RFKILL_TYPES];
-static unsigned long rfkill_states_default_locked;
-
static bool rfkill_epo_lock_active;
@@ -392,7 +391,7 @@ void rfkill_epo(void)
rfkill_set_block(rfkill, true);
for (i = 0; i < NUM_RFKILL_TYPES; i++) {
- rfkill_global_states[i].def = rfkill_global_states[i].cur;
+ rfkill_global_states[i].sav = rfkill_global_states[i].cur;
rfkill_global_states[i].cur = true;
}
@@ -417,7 +416,7 @@ void rfkill_restore_states(void)
rfkill_epo_lock_active = false;
for (i = 0; i < NUM_RFKILL_TYPES; i++)
- __rfkill_switch_all(i, rfkill_global_states[i].def);
+ __rfkill_switch_all(i, rfkill_global_states[i].sav);
mutex_unlock(&rfkill_global_mutex);
}
@@ -464,29 +463,6 @@ bool rfkill_get_global_sw_state(const enum rfkill_type type)
}
#endif
-void rfkill_set_global_sw_state(const enum rfkill_type type, bool blocked)
-{
- BUG_ON(type == RFKILL_TYPE_ALL);
-
- mutex_lock(&rfkill_global_mutex);
-
- /* don't allow unblock when epo */
- if (rfkill_epo_lock_active && !blocked)
- goto out;
-
- /* too late */
- if (rfkill_states_default_locked & BIT(type))
- goto out;
-
- rfkill_states_default_locked |= BIT(type);
-
- rfkill_global_states[type].cur = blocked;
- rfkill_global_states[type].def = blocked;
- out:
- mutex_unlock(&rfkill_global_mutex);
-}
-EXPORT_SYMBOL(rfkill_set_global_sw_state);
-
bool rfkill_set_hw_state(struct rfkill *rfkill, bool blocked)
{
@@ -532,13 +508,14 @@ bool rfkill_set_sw_state(struct rfkill *rfkill, bool blocked)
blocked = blocked || hwblock;
spin_unlock_irqrestore(&rfkill->lock, flags);
- if (!rfkill->registered)
- return blocked;
+ if (!rfkill->registered) {
+ rfkill->persistent = true;
+ } else {
+ if (prev != blocked && !hwblock)
+ schedule_work(&rfkill->uevent_work);
- if (prev != blocked && !hwblock)
- schedule_work(&rfkill->uevent_work);
-
- rfkill_led_trigger_event(rfkill);
+ rfkill_led_trigger_event(rfkill);
+ }
return blocked;
}
@@ -563,13 +540,14 @@ void rfkill_set_states(struct rfkill *rfkill, bool sw, bool hw)
spin_unlock_irqrestore(&rfkill->lock, flags);
- if (!rfkill->registered)
- return;
-
- if (swprev != sw || hwprev != hw)
- schedule_work(&rfkill->uevent_work);
+ if (!rfkill->registered) {
+ rfkill->persistent = true;
+ } else {
+ if (swprev != sw || hwprev != hw)
+ schedule_work(&rfkill->uevent_work);
- rfkill_led_trigger_event(rfkill);
+ rfkill_led_trigger_event(rfkill);
+ }
}
EXPORT_SYMBOL(rfkill_set_states);
@@ -888,15 +866,6 @@ int __must_check rfkill_register(struct rfkill *rfkill)
dev_set_name(dev, "rfkill%lu", rfkill_no);
rfkill_no++;
- if (!(rfkill_states_default_locked & BIT(rfkill->type))) {
- /* first of its kind */
- BUILD_BUG_ON(NUM_RFKILL_TYPES >
- sizeof(rfkill_states_default_locked) * 8);
- rfkill_states_default_locked |= BIT(rfkill->type);
- rfkill_global_states[rfkill->type].cur =
- rfkill_global_states[rfkill->type].def;
- }
-
list_add_tail(&rfkill->node, &rfkill_list);
error = device_add(dev);
@@ -916,7 +885,17 @@ int __must_check rfkill_register(struct rfkill *rfkill)
if (rfkill->ops->poll)
schedule_delayed_work(&rfkill->poll_work,
round_jiffies_relative(POLL_INTERVAL));
- schedule_work(&rfkill->sync_work);
+
+ if (!rfkill->persistent || rfkill_epo_lock_active) {
+ schedule_work(&rfkill->sync_work);
+ } else {
+#ifdef CONFIG_RFKILL_INPUT
+ bool soft_blocked = !!(rfkill->state & RFKILL_BLOCK_SW);
+
+ if (!atomic_read(&rfkill_input_disabled))
+ __rfkill_switch_all(rfkill->type, soft_blocked);
+#endif
+ }
rfkill_send_events(rfkill, RFKILL_OP_ADD);
@@ -1191,7 +1170,7 @@ static int __init rfkill_init(void)
int i;
for (i = 0; i < NUM_RFKILL_TYPES; i++)
- rfkill_global_states[i].def = !rfkill_default_state;
+ rfkill_global_states[i].cur = !rfkill_default_state;
error = class_register(&rfkill_class);
if (error)
On Mon, 01 Jun 2009, Johannes Berg wrote:
> > I mean platform devices which remember state. Those will be turned on/off
> > by the platform firmware at boot and often at resume time, and overriding
> > them in rfkill (especially if it is done later, by a rfkilld) will cause
> > some flicker (on/off/on, off/on/off) in certain situations.
> >
> > This is a regression, because the current interface doesn't suffer this
> > limitation.
>
> Right, but that was already covered by the discussion and patch I just
> sent, no need to complain, afaict? What's your gripe with the current
> add-on patch that I just sent out?
I don't know if I have any gripes with your latest patch ;) Can't test it
right now. I was reacting to the comments in the thread.
On a related note, Johannes, would you be opposed to exporting something I
could call from thinkpad-acpi to request a radio state change on a rfkill
struct? This would allow me to remove some of the mess in thinkpad-acpi.
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On Sun, 07 Jun 2009, Dan Williams wrote:
> 3G cards have two ways of being killed:
>
> 1) Put them to sleep; Sierra for example has code that does this
> already, it will take the modem out of D3 sleep on startup. No reason
> why rfkill couldn't just stick it back there. Drivers that implement
> this method would be easy to integrate with rfkill if the BIOS/platform
> module doesn't do it for them (hp-wmi will drop the 3G card off the bus
> on the 2530p when you rfkill it, just like bluetooth, so nothing else is
> needed there).
thinkpads will power down the card (and obviously kick it off-bus), as
well. This is how they disable bluetooth and 3G.
I have code in thinkpad-acpi that tells the fw to power these radios
down during sleep, and resume with them powered off. Needs to be
unbroken to not do that on wake devices one of these days (_if_ the
thinkpad supports bluetooth or wwan as wake devices, something I also
have to find out).
BTW: please do send me a note of how you guys will want to do sleep/
resume with the new core, I probably will have to adjust thinkpad-acpi
to play well with it.
> 2) CFUN=0; that is supposed to shut down all radios, but of course that
> requires the serial port of the card and the firmware to be loaded and
> whatever. And that should only be done by the daemon (if any, like
> ModemManager or Wader or whatever) that is arbitrating the serial port.
> That's not something that rfkilld should do.
And, to make matters worse, you are likely to find that lots of devices
screw that up (probably by just ignoring it silently) :-( The people
writing the firmware for these modems do an even worse job than the
people writing ACPI BIOSes...
--
"One disk to rule them all, One disk to find them. One disk to bring
them all and in the darkness grind them. In the Land of Redmond
where the shadows lie." -- The Silicon Valley Tarot
Henrique Holschuh
On 6/4/09, Marcel Holtmann <[email protected]> wrote:
> Hi Henrique,
>
>> > > > We just need to fix the platform drivers then. They should not set
>> > > > global states since that is not what they are controlling. They
>> > > > control
>> > >
>> > > We should change things, yes. So that the platform stores the global
>> > > state. That was half-broken on the old core (the platform stored the
>> > > state of its own device, which could be out of sync with the global
>> > > state), but the part of it setting the global state is correct.
>> > >
>> > > That needs a new in-kernel API to tie the core to platform drivers
>> > > capable of storing global states without causing problems when drivers
>> > > are unloaded, but it is not hard.
>> > >
>> > > As for NVS events, they have a clear use case: to let rfkilld know
>> > > which
>> > > global states it could leave alone the first time it loads, and which
>> > > ones have to be restored...
>> >
>> > show me an example of a platform device that stores the global state. I
>> > think you are confusing the word platform as in system with a platform
>> > device. The ThinkPad Bluetooth and WWAN switches are platform devices
>> > and control each one specific device. Same goes for the EeePC. They are
>> > not controlling a global state.
>>
>> I don't know what big difference you see between the two uses of
>> "platform",
>> but I will just work around it to get something useful out of this mail.
>>
>> The laptop stores in NVS the state of its 'switches'. This is as close as
>> one gets from 'storing the global state'. When the laptop boots,
>> these devices get set by the firware to the state in NVS. It is the best
>> place to store global state, because these devices will be in their proper
>> state (i.e. matching what will be the global state when the rfkill core
>> loads) all the time. It also gives you for free multi-OS/multi-kernel
>> state
>> storage for these devices, and compatibility with BIOSes that let you
>> define
>> the initial state for the devices in the firmware configuration, etc.
>
> it stores the state of its switches and why should these be enforced as
> a global state? Who says that this is a global state? For me that sounds
> like policy.
We don't seem to be getting very far :-(. I agree that these do not
appear to be global states, just the states of individual rfkill
devices.
So I would propose the following changes. (I'm happy to write the
code as well, but I think it's easier to read English).
1) remove rfkill_set_global_sw_state()
2) rfkill devices with NVS can e.g. call rfkill_has_nvs() before
registration, setting a flag.
3) the "has NVS" flag is reported by /dev/rfkill, (at least in ADD
events, tho it may as well be set in all events)
4) rfkill-input preserves existing behaviour - *if enabled* - by
initializing the global state from individual devices which have NVS.
(As before, each _type_ of rfkill device has its own global state).
5) rfkill devices with NVS will have their current state preserved,
so long as the global state has not yet been set (by userspace or by
rfkill-input). Of course userspace can change the state in response
to the device being added.
=> rfkilld then has the information required to implement the same
policy as rfkill-input. Furthermore, it will have enough information
that it could implement file-based storage as a fallback, and still
support NVS where available.
It will also allow implementation (or configuration) of completely
different policy to rfkill-input. E.g. if your internal wireless
w/NVS is broken and should be disabled, that can be done independently
of your replacement USB wireless adaptor.
Comments?
Alan
Hi Henrique,
> > Forget about this EPO crap. That is just a stupid concept anyway.
>
> Is it? So, if I use the _hardware switch_ on my laptop to kill all
> internal radios, it shouldn't be enforced by the OS on extra radios I
> plugged? Or on shitty internal WLAN cards that doesn't tie properly to
> the mini-pci and mini-pcie hardware kill lines?
>
> And any userspace PoS program can decide to bring up such radios that
> are not hardware-killed even if I am clearly trying to disable them all?
any user program with proper rights (remember that /dev/rfkill can now
be controlled by Unix permissions and SELinux) can bring up a specific
device. That is policy and it belongs in userspace.
This of course only works on soft blocked devices. The hard blocked
devices stay off. And in case of ThinkPads where the button does the
hard block, you can't bring it back from software.
Regards
Marcel
Hi Alan,
> >>> I really don't understand why this is needed. What benefit does it give
> >>> us compared to just sent OP_CHANGE and OP_CHANGE as an update. My X200
> >>> for example does this anyway on suspend/resume.
> >>>
> >>>
> >> This is required for boot only. I have no reason for this event to be
> >> generated on resume.
> >>
> >> The same effect could be had by generating an OP_CHANGE on f_open,
> >> _only_ when a platform driver has provided a value from NVS. But it
> >> does seem clearer to make it a different type of event.
> >>
> >
> > that is my whole point. If the kernel driver wants to preserve these
> > then it can just issue OP_ADD to notify use about the current state of
> > the values. The OP_ADD gets send once you open /dev/rfkill and if they
> > not match with our policy we have to change them anyway. I really don't
> > see the need for an extra operation here. Let me ask this again, how is
> > it different from just sending the OP_ADD and then let rfkilld decide to
> > either use that value or enforce its own policy.
>
> I don't understand.
>
> Isn't OP_ADD generated for all rfkill devices, by the core itself? How
> does the driver ask to generate this event or not?
>
> As I understand it, the difference between NVS_REPORT and ADD is that
> ADD is generated for all drivers. NVS_REPORT is only generated if they
> call rfkill_set_global_sw_state().
>
> The difference is that this exposes an extra bit of information: whether
> the state was restored from NVS.
currently every time you open an instance of /dev/rfkill you get the
OP_ADD for the current RFKILL states. It gives you the current state.
Some times you see it followed by a OP_CHANGE, but that are drivers that
don't register their RFKILL switches with the current state and then end
up changing it later. That needs fixing btw., but is a minor detail that
can be taken care of later.
So we get the current state when opening /dev/rfkill and once we fixed
the drivers to register with the current state, I don't see any need for
an extra NVS operation.
If you really don't wanna have rfkilld _not_ impose a policy on cold
boot, then we can certainly add that. However that is part of rfkilld
and not the kernel.
For global states, I am questioning the platform that actually has
storage for global states. All platforms that I have seen so far store
individual per device based states and not the global one.
> >>> So what is rfkilld suppose to be doing when receiving this report? What
> >>> is the expected behavior? Why do we bother with multi-OS crap here? I am
> >>> really unclear what are we trying to solve here.
> >>>
> >> In order to replicate the kernel behavior, it is expected that you set
> >> your internal state from this event. E.g. when the user next presses
> >> the wireless toggle key, you set the inverse of that internal state.
> >>
> >> Since this event is generated by a platform driver, you can expect it to
> >> be present following coldplug (the udev initscript). If the event is
> >> not present after coldplug, you may then issue OP_CHANGE yourself, to
> >> e.g. restore the state from a file. You would not be expected to handle
> >> OP_NVS_REPORT after startup. (Unless the daemon is restarted).
> >>
> >> Replicating the kernel behavior will allow us to avoid causing a couple
> >> of niggly little regressions on at least two platforms. It preserves
> >> the behavior when dual-booting (possibly between different linux
> >> distros), and when the BIOS setup screen exposes the NVS state as an
> >> option. The new behavior you suggest will annoy any users who have
> >> become used to these scenarios "just working".
> >>
> >> You may not use these platforms yourself. But I'm as annoyed as
> >> Henrique is, we don't want to impose regressions just because other
> >> platforms don't implement the feature.
> >>
> >> Why the fuss about implementing this, it seems easy enough? Start
> >> rfkilld after udev (like everything else). If you get NVS_REPORT, then
> >> use those states. Fill in any other states from defaults or state files
> >> and issue OP_CHANGE for them, just as you're already planning. Ignore
> >> any subsequent NVS_REPORTs. That should cover it.
> >>
> >> It's the cost for starting from a working implementation. You benefit
> >> from having existing drivers and users, you pay by not breaking them
> >> without good reason.
> >>
> >
> > I really don't care about current behavior, because that has been just a
> > hack anyway.
>
> Linux exports a stable ABI. A reasonable justification would be "this
> is trivial and has practically no users who care". Hackishness is
> second to that.
If we have to break it, we will break it. Don't give me the stable
non-sense API/ABI thing. We have done this in the past and we will keep
doing it. However that said, we try to make the issues arising from it
as small as possible.
> > And it happens to work if there is proper BIOS support. We
> > are at the point now where we stop working around a complicated and in
> > some cases broken implementation. Overloading it with weird special
> > cases is just wrong and so far I am not buying into any of the arguments
> > here. The point behind the whole effort from Johannes is to finally fix
> > RFKILL support. If it breaks current behavior, I couldn't care less in
> > some cases.
>
> I really don't understand. You say above that we don't need this
> change, because drivers can already achieve the same event by
> selectively generating OP_ADD. In that case it's already overloaded
> with "weird special cases", and NVS_REPORT is a cosmetic difference only.
Drivers can not generate OP_ADD, except the re-register the RFKILL
switch, but OP_ADD is send for every instance of /dev/rfkill to inform
the userspace application of the current available switches.
> > So Johannes and I talked about it a lot last week. And we will be doing
> > rfkilld so finally deprecated the broken idea of rfkill-input and move
> > the policy into userspace where it belongs.
>
> Great. I think we can agree on one thing, that implementing
> rfkill-input in the kernel without the possibility of a userspace
> override is warped and twisted :-).
I understand why rfkill-input exists and that it is needed. The only
reason why it is inside the kernel is that nobody bothered to create a
proper user interface for RFKILL. Since that is fixed now, we can
finally do it right, yeah :)
> > To make this clear, the
> > concept of cross-OS state keeping is broken.
>
> > Having the BIOS or a
> > different OS dictate policy makes no sense.
> >
>
> Seems to me these are two different points, and you haven't really said
> why cross-OS state is broken.
>
> It doesn't make sense to be _dictated_ to by your other OS/BIOS. That's
> exactly what rfkilld would avoid, by allowing a configurable policy.
>
> "support cross-OS state keeping" is a policy which can be applied - or
> not. NVS_REPORT provides the necessary information.
If you wanna just accept what the BIOS (or other OS) tells you then that
is an acceptable policy. So rfkilld will just map key input events in
that case. Fine by me. Question is if we can't do that right now? I
think we just can.
> So, this policy doesn't work if the other OS is un-cooperative and
> soft-blocks the wireless on shutdown. That's the full extent to which
> it is broken, yes? [And it's a relatively obscure problem, so the risk
> is that it just rots, and the effort spent on this code is wasted].
If the other OS soft-blocks it and our policy is to just accept that,
then it is fine. We can always soft-unblock it later. If our policy is
to restore unblock state, then we soft-unblock it when rfkilld starts.
> But on eeepc-laptop, it seems safe to assume that the drivers on the
> Other OS do the co-operative thing, because otherwise it would cause
> confusion with the option in the BIOS setup.
The BIOS block is not enforced by ACPI, then it is pointless anyway and
we should just ignore it. Really. We are not trying to fix totally
broken BIOS details here.
> Henrique (or anyone), has this ever been a problem on thinkpad-acpi? Do
> you ever get a state in NVS at boot time, which the user had not requested?
>
> Note that the linux ACPI project has the explicit goal of compatibility
> with Windows. If a new platform comes along with a Windows ACPI driver
> which screws up the platform state, it would be reasonable for the Linux
> ACPI driver to not call set_global_sw_state().
Question is if we really have a global state here. I doubt it since all
of these are device specific states. And having the BIOS or ACPI dictate
what state my external Bluetooth or WiFi device is in is pointless and a
total broken concept.
> You propose to exclude a feature that currently works, on the grounds
> that it is inherently broken. But you haven't said that this has ever
> caused incorrect behavior. All you have said so far is that it is a hack.
This never worked so far. The mac80211 or Bluetooth subsystems have no
RFKILL state and thus global states are not enforced. An external dongle
without RFKILL support is not taken into account. You are not talking
about global states here. They are all device specific. The device just
happens to be a platform device builtin the system.
> Since this was the original intent, it's a pity
> rfkill_set_global_sw_state() wasn't listed in the feature removal
> schedule (or a user-level description of NVS). I suggested something
> like NVS_REPORT because, in the absence of a description of removal, it
> looked like an oversight. In the past I've raised a couple of apparent
> oversights about the rewrite, and they were treated as constructive.
>
> But now its clear this was a policy decision, and I ended up draging out
> an explanation of the policy from nitpicking details of the
> implementation. I guess this is my fault for opening the discussion
> based on the one loose thread of a detail, instead of asking if the
> change was an oversight or intentional :-(.
I am confused now. Do we have the concept of a driver that will set the
system wide RFKILL state? The real hardware switches should do that, but
that is enforced differently anyway. As long as it is a software switch,
the enforcing should be done via /dev/rfkill and that is possible right
now (or when rfkill-input is finally replaced).
Regards
Marcel
Hi Alan,
> >> If you really don't wanna have rfkilld _not_ impose a policy on cold
> >> boot, then we can certainly add that. However that is part of rfkilld
> >> and not the kernel.
> >>
> >> For global states, I am questioning the platform that actually has
> >> storage for global states. All platforms that I have seen so far store
> >> individual per device based states and not the global one.
> >>
> >
> > Yeah, unfortunately the platform drivers do store/restore global state.
> > And in a sense that makes (made) sense since the default rfkill-input
> > policy was to toggle everything based on the platform button. It's icky
> > though.
> >
> > The problem with not telling rfkilld about this though is that the
> > kernel will suddenly impose a hotplug policy that rfkilld doesn't know
> > about. This might not even matter though since the _ADD will be sent
> > with that policy already applied, and if it wants to change the policy
> > rfkilld will do _CHANGE_ALL.
> >
> >
> >> If you wanna just accept what the BIOS (or other OS) tells you then that
> >> is an acceptable policy. So rfkilld will just map key input events in
> >> that case. Fine by me. Question is if we can't do that right now? I
> >> think we just can.
> >>
> >
> > Yes, we probably can do that right now, by making rfkilld start up
> > without setting a policy (CHANGE_ALL). It just doesn't know what the
> > policy is, then.
> >
> >
> >> Question is if we really have a global state here. I doubt it since all
> >> of these are device specific states. And having the BIOS or ACPI dictate
> >> what state my external Bluetooth or WiFi device is in is pointless and a
> >> total broken concept.
> >>
> >
> > Unfortunately that's how it worked before, it's part of the rfkill
> > legacy that it seems we'll have to accept. I guess we have only
> > ourselves to blame for not reviewing Henrique's rfkill implementation...
> >
> >
> >>> You propose to exclude a feature that currently works, on the grounds
> >>> that it is inherently broken. But you haven't said that this has ever
> >>> caused incorrect behavior. All you have said so far is that it is a hack.
> >>>
> >> This never worked so far. The mac80211 or Bluetooth subsystems have no
> >> RFKILL state and thus global states are not enforced. An external dongle
> >> without RFKILL support is not taken into account. You are not talking
> >> about global states here. They are all device specific. The device just
> >> happens to be a platform device builtin the system.
> >>
> >
> > Right, but rfkill does actually have a function to set the global policy
> > state (rfkill_set_global_sw_state) which some platform drivers insist on
> > calling.
> >
> > The only reason we would need the NVS_REPORT then is to detect whether
> > or not anything in the kernel called rfkill_set_global_sw_state(). If we
> > can live with just configuring rfkilld for the (arguably broken) case
> > where somebody cares about this, I'm happy with removing it.
> >
> > Hope that helps clear up your confusion.
> >
>
> The reason for drivers doing this is that otherwise, the kernel forces
> the new rfkill device to the current global default. So this API is
> used for the device to preserve it's current state - without it, we just
> throw the NVS information away and no-one can get at it.
>
> We could switch to using the non-global set_sw_state() after
> registration. But that's pretty nasty because it means most drivers
> call set_sw_state() before registration, a few drivers call it
> afterwards, and it's not going to be obvious why. Plus I think it
> bypasses EPO. So I think some sort of separate API is needed.
as I said, we might need to fix drivers that register their RFKILL
switch with the wrong state to begin with. This is a driver issue and
that can be easily fixed inside the kernel.
Forget about this EPO crap. That is just a stupid concept anyway.
> If we can also export this NVS status to userspace somehow, then
> userspace can distinguish between
>
> a) rfkill is currently unblocked, because this was the kernel default,
> so there's no reason not to override the state
> b) rfkill is currently unblocked, because this was restored from NVS,
> which hopefully reflects the most recent request from the user. rfkilld
> is still able to override this state, but it would also be reasonable
> not to.
>
> I take Marcels point, if /dev/rfkill exposes a sub-optimal interface, it
> can be very difficult to fix it. It's probably better to fix the mess
> of global states before trying to add NVS information to /dev/rfkill.
>
> Btw, I think there's a third scenario to add to the other OS / BIOS
> setup. Some hardware has a handover mechanism, right? Where the BIOS
> handles rfkill keypresses by default, and toggles the soft rfkill state,
> but allows the driver to take over when loaded. So if the user presses
> the key _before_ the driver gets loaded - they will see the wireless LED
> change, and they will expect that state change to persist when the
> driver is loaded.
We should be able to handle this properly without an extra operation in
the user interface.
Regards
Marcel