diff options
-rw-r--r-- | Documentation/rfkill.txt | 20 | ||||
-rw-r--r-- | net/rfkill/rfkill-input.c | 306 | ||||
-rw-r--r-- | net/rfkill/rfkill-input.h | 2 | ||||
-rw-r--r-- | net/rfkill/rfkill.c | 46 |
4 files changed, 287 insertions, 87 deletions
diff --git a/Documentation/rfkill.txt b/Documentation/rfkill.txt index b65f079..4d3ee31 100644 --- a/Documentation/rfkill.txt +++ b/Documentation/rfkill.txt @@ -191,12 +191,20 @@ Userspace input handlers (uevents) or kernel input handlers (rfkill-input): to tell the devices registered with the rfkill class to change their state (i.e. translates the input layer event into real action). + * rfkill-input implements EPO by handling EV_SW SW_RFKILL_ALL 0 (power off all transmitters) in a special way: it ignores any overrides and local state cache and forces all transmitters to the RFKILL_STATE_SOFT_BLOCKED state (including those which are already - supposed to be BLOCKED). Note that the opposite event (power on all - transmitters) is handled normally. + supposed to be BLOCKED). + * rfkill EPO will remain active until rfkill-input receives an + EV_SW SW_RFKILL_ALL 1 event. While the EPO is active, transmitters + are locked in the blocked state (rfkill will refuse to unblock them). + * rfkill-input implements different policies that the user can + select for handling EV_SW SW_RFKILL_ALL 1. It will unlock rfkill, + and either do nothing (leave transmitters blocked, but now unlocked), + restore the transmitters to their state before the EPO, or unblock + them all. Userspace uevent handler or kernel platform-specific drivers hooked to the rfkill notifier chain: @@ -331,11 +339,9 @@ class to get a sysfs interface :-) correct event for your switch/button. These events are emergency power-off events when they are trying to turn the transmitters off. An example of an input device which SHOULD generate *_RFKILL_ALL events is the wireless-kill -switch in a laptop which is NOT a hotkey, but a real switch that kills radios -in hardware, even if the O.S. has gone to lunch. An example of an input device -which SHOULD NOT generate *_RFKILL_ALL events by default, is any sort of hot -key that does nothing by itself, as well as any hot key that is type-specific -(e.g. the one for WLAN). +switch in a laptop which is NOT a hotkey, but a real sliding/rocker switch. +An example of an input device which SHOULD NOT generate *_RFKILL_ALL events by +default, is any sort of hot key that is type-specific (e.g. the one for WLAN). 3.1 Guidelines for wireless device drivers diff --git a/net/rfkill/rfkill-input.c b/net/rfkill/rfkill-input.c index bfdade7..7706541 100644 --- a/net/rfkill/rfkill-input.c +++ b/net/rfkill/rfkill-input.c @@ -24,138 +24,291 @@ MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>"); MODULE_DESCRIPTION("Input layer to RF switch connector"); MODULE_LICENSE("GPL"); +enum rfkill_input_master_mode { + RFKILL_INPUT_MASTER_DONOTHING = 0, + RFKILL_INPUT_MASTER_RESTORE = 1, + RFKILL_INPUT_MASTER_UNBLOCKALL = 2, + RFKILL_INPUT_MASTER_MAX, /* marker */ +}; + +static enum rfkill_input_master_mode rfkill_master_switch_mode = + RFKILL_INPUT_MASTER_UNBLOCKALL; +module_param_named(master_switch_mode, rfkill_master_switch_mode, uint, 0); +MODULE_PARM_DESC(master_switch_mode, + "SW_RFKILL_ALL ON should: 0=do nothing; 1=restore; 2=unblock all"); + +enum rfkill_global_sched_op { + RFKILL_GLOBAL_OP_EPO = 0, + RFKILL_GLOBAL_OP_RESTORE, + RFKILL_GLOBAL_OP_UNLOCK, + RFKILL_GLOBAL_OP_UNBLOCK, +}; + +/* + * Currently, the code marked with RFKILL_NEED_SWSET is inactive. + * If handling of EV_SW SW_WLAN/WWAN/BLUETOOTH/etc is needed in the + * future, when such events are added, that code will be necessary. + */ + struct rfkill_task { struct work_struct work; - enum rfkill_type type; - struct mutex mutex; /* ensures that task is serialized */ - spinlock_t lock; /* for accessing last and desired state */ - unsigned long last; /* last schedule */ - enum rfkill_state desired_state; /* on/off */ + + /* ensures that task is serialized */ + struct mutex mutex; + + /* protects everything below */ + spinlock_t lock; + + /* pending regular switch operations (1=pending) */ + unsigned long sw_pending[BITS_TO_LONGS(RFKILL_TYPE_MAX)]; + +#ifdef RFKILL_NEED_SWSET + /* set operation pending (1=pending) */ + unsigned long sw_setpending[BITS_TO_LONGS(RFKILL_TYPE_MAX)]; + + /* desired state for pending set operation (1=unblock) */ + unsigned long sw_newstate[BITS_TO_LONGS(RFKILL_TYPE_MAX)]; +#endif + + /* should the state be complemented (1=yes) */ + unsigned long sw_togglestate[BITS_TO_LONGS(RFKILL_TYPE_MAX)]; + + bool global_op_pending; + enum rfkill_global_sched_op op; }; -static void rfkill_task_handler(struct work_struct *work) +static void __rfkill_handle_global_op(enum rfkill_global_sched_op op) { - struct rfkill_task *task = container_of(work, struct rfkill_task, work); + unsigned int i; + + switch (op) { + case RFKILL_GLOBAL_OP_EPO: + rfkill_epo(); + break; + case RFKILL_GLOBAL_OP_RESTORE: + rfkill_restore_states(); + break; + case RFKILL_GLOBAL_OP_UNLOCK: + rfkill_remove_epo_lock(); + break; + case RFKILL_GLOBAL_OP_UNBLOCK: + rfkill_remove_epo_lock(); + for (i = 0; i < RFKILL_TYPE_MAX; i++) + rfkill_switch_all(i, RFKILL_STATE_UNBLOCKED); + break; + default: + /* memory corruption or bug, fail safely */ + rfkill_epo(); + WARN(1, "Unknown requested operation %d! " + "rfkill Emergency Power Off activated\n", + op); + } +} - mutex_lock(&task->mutex); +#ifdef RFKILL_NEED_SWSET +static void __rfkill_handle_normal_op(const enum rfkill_type type, + const bool sp, const bool s, const bool c) +{ + enum rfkill_state state; - rfkill_switch_all(task->type, task->desired_state); + if (sp) + state = (s) ? RFKILL_STATE_UNBLOCKED : + RFKILL_STATE_SOFT_BLOCKED; + else + state = rfkill_get_global_state(type); - mutex_unlock(&task->mutex); + if (c) + state = rfkill_state_complement(state); + + rfkill_switch_all(type, state); } +#else +static void __rfkill_handle_normal_op(const enum rfkill_type type, + const bool c) +{ + enum rfkill_state state; + + state = rfkill_get_global_state(type); + if (c) + state = rfkill_state_complement(state); + + rfkill_switch_all(type, state); +} +#endif -static void rfkill_task_epo_handler(struct work_struct *work) +static void rfkill_task_handler(struct work_struct *work) { - rfkill_epo(); + struct rfkill_task *task = + container_of(work, struct rfkill_task, work); + bool doit = true; + + mutex_lock(&task->mutex); + + spin_lock_irq(&task->lock); + while (doit) { + if (task->global_op_pending) { + enum rfkill_global_sched_op op = task->op; + task->global_op_pending = false; + memset(task->sw_pending, 0, sizeof(task->sw_pending)); + spin_unlock_irq(&task->lock); + + __rfkill_handle_global_op(op); + + /* make sure we do at least one pass with + * !task->global_op_pending */ + spin_lock_irq(&task->lock); + continue; + } else if (!rfkill_is_epo_lock_active()) { + unsigned int i = 0; + + while (!task->global_op_pending && + i < RFKILL_TYPE_MAX) { + if (test_and_clear_bit(i, task->sw_pending)) { + bool c; +#ifdef RFKILL_NEED_SWSET + bool sp, s; + sp = test_and_clear_bit(i, + task->sw_setpending); + s = test_bit(i, task->sw_newstate); +#endif + c = test_and_clear_bit(i, + task->sw_togglestate); + spin_unlock_irq(&task->lock); + +#ifdef RFKILL_NEED_SWSET + __rfkill_handle_normal_op(i, sp, s, c); +#else + __rfkill_handle_normal_op(i, c); +#endif + + spin_lock_irq(&task->lock); + } + i++; + } + } + doit = task->global_op_pending; + } + spin_unlock_irq(&task->lock); + + mutex_unlock(&task->mutex); } -static DECLARE_WORK(epo_work, rfkill_task_epo_handler); +static struct rfkill_task rfkill_task = { + .work = __WORK_INITIALIZER(rfkill_task.work, + rfkill_task_handler), + .mutex = __MUTEX_INITIALIZER(rfkill_task.mutex), + .lock = __SPIN_LOCK_UNLOCKED(rfkill_task.lock), +}; -static void rfkill_schedule_epo(void) +static void rfkill_schedule_global_op(enum rfkill_global_sched_op op) { - schedule_work(&epo_work); + unsigned long flags; + + spin_lock_irqsave(&rfkill_task.lock, flags); + rfkill_task.op = op; + rfkill_task.global_op_pending = true; + schedule_work(&rfkill_task.work); + spin_unlock_irqrestore(&rfkill_task.lock, flags); } -static void rfkill_schedule_set(struct rfkill_task *task, +#ifdef RFKILL_NEED_SWSET +/* Use this if you need to add EV_SW SW_WLAN/WWAN/BLUETOOTH/etc handling */ + +static void rfkill_schedule_set(enum rfkill_type type, enum rfkill_state desired_state) { unsigned long flags; - if (unlikely(work_pending(&epo_work))) + if (rfkill_is_epo_lock_active()) return; - spin_lock_irqsave(&task->lock, flags); - - if (time_after(jiffies, task->last + msecs_to_jiffies(200))) { - task->desired_state = desired_state; - task->last = jiffies; - schedule_work(&task->work); + spin_lock_irqsave(&rfkill_task.lock, flags); + if (!rfkill_task.global_op_pending) { + set_bit(type, rfkill_task.sw_pending); + set_bit(type, rfkill_task.sw_setpending); + clear_bit(type, rfkill_task.sw_togglestate); + if (desired_state) + set_bit(type, rfkill_task.sw_newstate); + else + clear_bit(type, rfkill_task.sw_newstate); + schedule_work(&rfkill_task.work); } - - spin_unlock_irqrestore(&task->lock, flags); + spin_unlock_irqrestore(&rfkill_task.lock, flags); } +#endif -static void rfkill_schedule_toggle(struct rfkill_task *task) +static void rfkill_schedule_toggle(enum rfkill_type type) { unsigned long flags; - if (unlikely(work_pending(&epo_work))) + if (rfkill_is_epo_lock_active()) return; - spin_lock_irqsave(&task->lock, flags); - - if (time_after(jiffies, task->last + msecs_to_jiffies(200))) { - task->desired_state = - rfkill_state_complement(task->desired_state); - task->last = jiffies; - schedule_work(&task->work); + spin_lock_irqsave(&rfkill_task.lock, flags); + if (!rfkill_task.global_op_pending) { + set_bit(type, rfkill_task.sw_pending); + change_bit(type, rfkill_task.sw_togglestate); + schedule_work(&rfkill_task.work); } - - spin_unlock_irqrestore(&task->lock, flags); + spin_unlock_irqrestore(&rfkill_task.lock, flags); } -#define DEFINE_RFKILL_TASK(n, t) \ - struct rfkill_task n = { \ - .work = __WORK_INITIALIZER(n.work, \ - rfkill_task_handler), \ - .type = t, \ - .mutex = __MUTEX_INITIALIZER(n.mutex), \ - .lock = __SPIN_LOCK_UNLOCKED(n.lock), \ - .desired_state = RFKILL_STATE_UNBLOCKED, \ - } - -static DEFINE_RFKILL_TASK(rfkill_wlan, RFKILL_TYPE_WLAN); -static DEFINE_RFKILL_TASK(rfkill_bt, RFKILL_TYPE_BLUETOOTH); -static DEFINE_RFKILL_TASK(rfkill_uwb, RFKILL_TYPE_UWB); -static DEFINE_RFKILL_TASK(rfkill_wimax, RFKILL_TYPE_WIMAX); -static DEFINE_RFKILL_TASK(rfkill_wwan, RFKILL_TYPE_WWAN); - static void rfkill_schedule_evsw_rfkillall(int state) { - /* EVERY radio type. state != 0 means radios ON */ - /* handle EPO (emergency power off) through shortcut */ if (state) { - rfkill_schedule_set(&rfkill_wwan, - RFKILL_STATE_UNBLOCKED); - rfkill_schedule_set(&rfkill_wimax, - RFKILL_STATE_UNBLOCKED); - rfkill_schedule_set(&rfkill_uwb, - RFKILL_STATE_UNBLOCKED); - rfkill_schedule_set(&rfkill_bt, - RFKILL_STATE_UNBLOCKED); - rfkill_schedule_set(&rfkill_wlan, - RFKILL_STATE_UNBLOCKED); + switch (rfkill_master_switch_mode) { + case RFKILL_INPUT_MASTER_UNBLOCKALL: + rfkill_schedule_global_op(RFKILL_GLOBAL_OP_UNBLOCK); + break; + case RFKILL_INPUT_MASTER_RESTORE: + rfkill_schedule_global_op(RFKILL_GLOBAL_OP_RESTORE); + break; + case RFKILL_INPUT_MASTER_DONOTHING: + rfkill_schedule_global_op(RFKILL_GLOBAL_OP_UNLOCK); + break; + default: + /* memory corruption or driver bug! fail safely */ + rfkill_schedule_global_op(RFKILL_GLOBAL_OP_EPO); + WARN(1, "Unknown rfkill_master_switch_mode (%d), " + "driver bug or memory corruption detected!\n", + rfkill_master_switch_mode); + break; + } } else - rfkill_schedule_epo(); + rfkill_schedule_global_op(RFKILL_GLOBAL_OP_EPO); } static void rfkill_event(struct input_handle *handle, unsigned int type, unsigned int code, int data) { if (type == EV_KEY && data == 1) { + enum rfkill_type t; + switch (code) { case KEY_WLAN: - rfkill_schedule_toggle(&rfkill_wlan); + t = RFKILL_TYPE_WLAN; break; case KEY_BLUETOOTH: - rfkill_schedule_toggle(&rfkill_bt); + t = RFKILL_TYPE_BLUETOOTH; break; case KEY_UWB: - rfkill_schedule_toggle(&rfkill_uwb); + t = RFKILL_TYPE_UWB; break; case KEY_WIMAX: - rfkill_schedule_toggle(&rfkill_wimax); + t = RFKILL_TYPE_WIMAX; break; default: - break; + return; } + rfkill_schedule_toggle(t); + return; } else if (type == EV_SW) { switch (code) { case SW_RFKILL_ALL: rfkill_schedule_evsw_rfkillall(data); - break; + return; default: - break; + return; } } } @@ -256,11 +409,9 @@ static struct input_handler rfkill_handler = { static int __init rfkill_handler_init(void) { - unsigned long last_run = jiffies - msecs_to_jiffies(500); - rfkill_wlan.last = last_run; - rfkill_bt.last = last_run; - rfkill_uwb.last = last_run; - rfkill_wimax.last = last_run; + if (rfkill_master_switch_mode >= RFKILL_INPUT_MASTER_MAX) + return -EINVAL; + return input_register_handler(&rfkill_handler); } @@ -268,6 +419,7 @@ static void __exit rfkill_handler_exit(void) { input_unregister_handler(&rfkill_handler); flush_scheduled_work(); + rfkill_remove_epo_lock(); } module_init(rfkill_handler_init); diff --git a/net/rfkill/rfkill-input.h b/net/rfkill/rfkill-input.h index d1e03e8..fe8df6b 100644 --- a/net/rfkill/rfkill-input.h +++ b/net/rfkill/rfkill-input.h @@ -14,6 +14,8 @@ void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state); void rfkill_epo(void); void rfkill_restore_states(void); +void rfkill_remove_epo_lock(void); +bool rfkill_is_epo_lock_active(void); enum rfkill_state rfkill_get_global_state(const enum rfkill_type type); #endif /* __RFKILL_INPUT_H */ diff --git a/net/rfkill/rfkill.c b/net/rfkill/rfkill.c index fdf87d2..e348eab 100644 --- a/net/rfkill/rfkill.c +++ b/net/rfkill/rfkill.c @@ -51,6 +51,7 @@ struct rfkill_gsw_state { static struct rfkill_gsw_state rfkill_global_states[RFKILL_TYPE_MAX]; static unsigned long rfkill_states_lockdflt[BITS_TO_LONGS(RFKILL_TYPE_MAX)]; +static bool rfkill_epo_lock_active; static BLOCKING_NOTIFIER_HEAD(rfkill_notifier_list); @@ -264,11 +265,14 @@ static void __rfkill_switch_all(const enum rfkill_type type, * * Acquires rfkill_global_mutex and calls __rfkill_switch_all(@type, @state). * Please refer to __rfkill_switch_all() for details. + * + * Does nothing if the EPO lock is active. */ void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state) { mutex_lock(&rfkill_global_mutex); - __rfkill_switch_all(type, state); + if (!rfkill_epo_lock_active) + __rfkill_switch_all(type, state); mutex_unlock(&rfkill_global_mutex); } EXPORT_SYMBOL(rfkill_switch_all); @@ -289,6 +293,7 @@ void rfkill_epo(void) mutex_lock(&rfkill_global_mutex); + rfkill_epo_lock_active = true; list_for_each_entry(rfkill, &rfkill_list, node) { mutex_lock(&rfkill->mutex); rfkill_toggle_radio(rfkill, RFKILL_STATE_SOFT_BLOCKED, 1); @@ -317,6 +322,7 @@ void rfkill_restore_states(void) mutex_lock(&rfkill_global_mutex); + rfkill_epo_lock_active = false; for (i = 0; i < RFKILL_TYPE_MAX; i++) __rfkill_switch_all(i, rfkill_global_states[i].default_state); mutex_unlock(&rfkill_global_mutex); @@ -324,6 +330,35 @@ void rfkill_restore_states(void) EXPORT_SYMBOL_GPL(rfkill_restore_states); /** + * rfkill_remove_epo_lock - unlock state changes + * + * Used by rfkill-input manually unlock state changes, when + * the EPO switch is deactivated. + */ +void rfkill_remove_epo_lock(void) +{ + mutex_lock(&rfkill_global_mutex); + rfkill_epo_lock_active = false; + mutex_unlock(&rfkill_global_mutex); +} +EXPORT_SYMBOL_GPL(rfkill_remove_epo_lock); + +/** + * rfkill_is_epo_lock_active - returns true EPO is active + * + * Returns 0 (false) if there is NOT an active EPO contidion, + * and 1 (true) if there is an active EPO contition, which + * locks all radios in one of the BLOCKED states. + * + * Can be called in atomic context. + */ +bool rfkill_is_epo_lock_active(void) +{ + return rfkill_epo_lock_active; +} +EXPORT_SYMBOL_GPL(rfkill_is_epo_lock_active); + +/** * rfkill_get_global_state - returns global state for a type * @type: the type to get the global state of * @@ -447,7 +482,12 @@ static ssize_t rfkill_state_store(struct device *dev, error = mutex_lock_killable(&rfkill->mutex); if (error) return error; - error = rfkill_toggle_radio(rfkill, state, 0); + + if (!rfkill_epo_lock_active) + error = rfkill_toggle_radio(rfkill, state, 0); + else + error = -EPERM; + mutex_unlock(&rfkill->mutex); return error ? error : count; @@ -491,7 +531,7 @@ static ssize_t rfkill_claim_store(struct device *dev, return error; if (rfkill->user_claim != claim) { - if (!claim) { + if (!claim && !rfkill_epo_lock_active) { mutex_lock(&rfkill->mutex); rfkill_toggle_radio(rfkill, rfkill_global_states[rfkill->type].current_state, |