rfkill: add master_switch_mode and EPO lock to rfkill and rfkill-input
Add of software-based sanity to rfkill and rfkill-input so that it can reproduce what hardware-based EPO switches do, blocking all transmitters and locking down any further attempts to unblock them until the switch is deactivated. rfkill-input is responsible for issuing the EPO control requests, like before. While an rfkill EPO is active, all transmitters are locked to one of the BLOCKED states and all attempts to change that through the rfkill API (userspace and kernel) will be either ignored or return -EPERM errors. The lock will be released upon receipt of EV_SW SW_RFKILL_ALL ON by rfkill-input, or should modular rfkill-input be unloaded. This makes rfkill and rfkill-input extend the operation of an existing wireless master kill switch to all wireless devices in the system, even those that are not under hardware or firmware control. Since the above is the expected operational behavior for the master rfkill switch, the EPO lock functionality is not optional. Also, extend rfkill-input to allow for three different behaviors when it receives an EV_SW SW_RFKILL_ALL ON input event. The user can set which behavior he wants through the master_switch_mode parameter: master_switch_mode = 0: EV_SW SW_RFKILL_ALL ON just unlocks rfkill controller state changes (so that the rfkill userspace and kernel APIs can now be used to change rfkill controller states again), but doesn't change any of their states (so they will all remain blocked). This is the safest mode of operation, as it requires explicit operator action to re-enable a transmitter. master_switch_mode = 1: EV_SW SW_RFKILL_ALL ON causes rfkill-input to attempt to restore the system to the state before the last EV_SW SW_RFKILL_ALL OFF event, or to the default global states if no EV_SW SW_RFKILL_ALL OFF ever happened. This is the recommended mode of operation for laptops. master_switch_mode = 2: tries to unblock all rfkill controllers (i.e. enable all transmitters) when an EV_SW SW_RFKILL_ALL ON event is received. This is the default mode of operation, as it mimics the previous behavior of rfkill-input. In order to implement these features in a clean way, the entire event handling of rfkill-input was refactored into a single worker function. Protection against input event DoS (repeatedly firing rfkill events for rfkill-input to process) was removed during the code refactoring. It will be added back in a future patch. Note that with these changes, rfkill-input doesn't need to explicitly handle any radio types for which KEY_<radio type> or SW_<radio type> events do not exist yet. Code to handle EV_SW SW_{WLAN,WWAN,BLUETOOTH,WIMAX,...} was added as it might be needed in the future (and its implementation is not that obvious), but is currently #ifdef'd out to avoid wasting resources. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br> Cc: Ivo van Doorn <IvDoorn@gmail.com> Cc: Dmitry Torokhov <dtor@mail.ru> Signed-off-by: John W. Linville <linville@tuxdriver.com>
This commit is contained in:
Родитель
68d2413bec
Коммит
d003922dab
|
@ -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
|
to tell the devices registered with the rfkill class to change
|
||||||
their state (i.e. translates the input layer event into real
|
their state (i.e. translates the input layer event into real
|
||||||
action).
|
action).
|
||||||
|
|
||||||
* rfkill-input implements EPO by handling EV_SW SW_RFKILL_ALL 0
|
* rfkill-input implements EPO by handling EV_SW SW_RFKILL_ALL 0
|
||||||
(power off all transmitters) in a special way: it ignores any
|
(power off all transmitters) in a special way: it ignores any
|
||||||
overrides and local state cache and forces all transmitters to the
|
overrides and local state cache and forces all transmitters to the
|
||||||
RFKILL_STATE_SOFT_BLOCKED state (including those which are already
|
RFKILL_STATE_SOFT_BLOCKED state (including those which are already
|
||||||
supposed to be BLOCKED). Note that the opposite event (power on all
|
supposed to be BLOCKED).
|
||||||
transmitters) is handled normally.
|
* 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
|
Userspace uevent handler or kernel platform-specific drivers hooked to the
|
||||||
rfkill notifier chain:
|
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
|
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
|
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
|
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
|
switch in a laptop which is NOT a hotkey, but a real sliding/rocker switch.
|
||||||
in hardware, even if the O.S. has gone to lunch. An example of an input device
|
An example of an input device which SHOULD NOT generate *_RFKILL_ALL events by
|
||||||
which SHOULD NOT generate *_RFKILL_ALL events by default, is any sort of hot
|
default, is any sort of hot key that is type-specific (e.g. the one for WLAN).
|
||||||
key that does nothing by itself, as well as any hot key that is type-specific
|
|
||||||
(e.g. the one for WLAN).
|
|
||||||
|
|
||||||
|
|
||||||
3.1 Guidelines for wireless device drivers
|
3.1 Guidelines for wireless device drivers
|
||||||
|
|
|
@ -24,138 +24,291 @@ MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>");
|
||||||
MODULE_DESCRIPTION("Input layer to RF switch connector");
|
MODULE_DESCRIPTION("Input layer to RF switch connector");
|
||||||
MODULE_LICENSE("GPL");
|
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 rfkill_task {
|
||||||
struct work_struct work;
|
struct work_struct work;
|
||||||
enum rfkill_type type;
|
|
||||||
struct mutex mutex; /* ensures that task is serialized */
|
/* ensures that task is serialized */
|
||||||
spinlock_t lock; /* for accessing last and desired state */
|
struct mutex mutex;
|
||||||
unsigned long last; /* last schedule */
|
|
||||||
enum rfkill_state desired_state; /* on/off */
|
/* 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_handle_global_op(enum rfkill_global_sched_op op)
|
||||||
|
{
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#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;
|
||||||
|
|
||||||
|
if (sp)
|
||||||
|
state = (s) ? RFKILL_STATE_UNBLOCKED :
|
||||||
|
RFKILL_STATE_SOFT_BLOCKED;
|
||||||
|
else
|
||||||
|
state = rfkill_get_global_state(type);
|
||||||
|
|
||||||
|
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_handler(struct work_struct *work)
|
static void rfkill_task_handler(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct rfkill_task *task = container_of(work, struct rfkill_task, work);
|
struct rfkill_task *task =
|
||||||
|
container_of(work, struct rfkill_task, work);
|
||||||
|
bool doit = true;
|
||||||
|
|
||||||
mutex_lock(&task->mutex);
|
mutex_lock(&task->mutex);
|
||||||
|
|
||||||
rfkill_switch_all(task->type, task->desired_state);
|
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);
|
mutex_unlock(&task->mutex);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rfkill_task_epo_handler(struct work_struct *work)
|
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_global_op(enum rfkill_global_sched_op op)
|
||||||
{
|
{
|
||||||
rfkill_epo();
|
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 DECLARE_WORK(epo_work, rfkill_task_epo_handler);
|
#ifdef RFKILL_NEED_SWSET
|
||||||
|
/* Use this if you need to add EV_SW SW_WLAN/WWAN/BLUETOOTH/etc handling */
|
||||||
|
|
||||||
static void rfkill_schedule_epo(void)
|
static void rfkill_schedule_set(enum rfkill_type type,
|
||||||
{
|
|
||||||
schedule_work(&epo_work);
|
|
||||||
}
|
|
||||||
|
|
||||||
static void rfkill_schedule_set(struct rfkill_task *task,
|
|
||||||
enum rfkill_state desired_state)
|
enum rfkill_state desired_state)
|
||||||
{
|
{
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
if (unlikely(work_pending(&epo_work)))
|
if (rfkill_is_epo_lock_active())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
spin_lock_irqsave(&task->lock, flags);
|
spin_lock_irqsave(&rfkill_task.lock, flags);
|
||||||
|
if (!rfkill_task.global_op_pending) {
|
||||||
if (time_after(jiffies, task->last + msecs_to_jiffies(200))) {
|
set_bit(type, rfkill_task.sw_pending);
|
||||||
task->desired_state = desired_state;
|
set_bit(type, rfkill_task.sw_setpending);
|
||||||
task->last = jiffies;
|
clear_bit(type, rfkill_task.sw_togglestate);
|
||||||
schedule_work(&task->work);
|
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(&rfkill_task.lock, flags);
|
||||||
spin_unlock_irqrestore(&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;
|
unsigned long flags;
|
||||||
|
|
||||||
if (unlikely(work_pending(&epo_work)))
|
if (rfkill_is_epo_lock_active())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
spin_lock_irqsave(&task->lock, flags);
|
spin_lock_irqsave(&rfkill_task.lock, flags);
|
||||||
|
if (!rfkill_task.global_op_pending) {
|
||||||
if (time_after(jiffies, task->last + msecs_to_jiffies(200))) {
|
set_bit(type, rfkill_task.sw_pending);
|
||||||
task->desired_state =
|
change_bit(type, rfkill_task.sw_togglestate);
|
||||||
rfkill_state_complement(task->desired_state);
|
schedule_work(&rfkill_task.work);
|
||||||
task->last = jiffies;
|
|
||||||
schedule_work(&task->work);
|
|
||||||
}
|
}
|
||||||
|
spin_unlock_irqrestore(&rfkill_task.lock, flags);
|
||||||
spin_unlock_irqrestore(&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)
|
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) {
|
if (state) {
|
||||||
rfkill_schedule_set(&rfkill_wwan,
|
switch (rfkill_master_switch_mode) {
|
||||||
RFKILL_STATE_UNBLOCKED);
|
case RFKILL_INPUT_MASTER_UNBLOCKALL:
|
||||||
rfkill_schedule_set(&rfkill_wimax,
|
rfkill_schedule_global_op(RFKILL_GLOBAL_OP_UNBLOCK);
|
||||||
RFKILL_STATE_UNBLOCKED);
|
break;
|
||||||
rfkill_schedule_set(&rfkill_uwb,
|
case RFKILL_INPUT_MASTER_RESTORE:
|
||||||
RFKILL_STATE_UNBLOCKED);
|
rfkill_schedule_global_op(RFKILL_GLOBAL_OP_RESTORE);
|
||||||
rfkill_schedule_set(&rfkill_bt,
|
break;
|
||||||
RFKILL_STATE_UNBLOCKED);
|
case RFKILL_INPUT_MASTER_DONOTHING:
|
||||||
rfkill_schedule_set(&rfkill_wlan,
|
rfkill_schedule_global_op(RFKILL_GLOBAL_OP_UNLOCK);
|
||||||
RFKILL_STATE_UNBLOCKED);
|
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
|
} else
|
||||||
rfkill_schedule_epo();
|
rfkill_schedule_global_op(RFKILL_GLOBAL_OP_EPO);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void rfkill_event(struct input_handle *handle, unsigned int type,
|
static void rfkill_event(struct input_handle *handle, unsigned int type,
|
||||||
unsigned int code, int data)
|
unsigned int code, int data)
|
||||||
{
|
{
|
||||||
if (type == EV_KEY && data == 1) {
|
if (type == EV_KEY && data == 1) {
|
||||||
|
enum rfkill_type t;
|
||||||
|
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case KEY_WLAN:
|
case KEY_WLAN:
|
||||||
rfkill_schedule_toggle(&rfkill_wlan);
|
t = RFKILL_TYPE_WLAN;
|
||||||
break;
|
break;
|
||||||
case KEY_BLUETOOTH:
|
case KEY_BLUETOOTH:
|
||||||
rfkill_schedule_toggle(&rfkill_bt);
|
t = RFKILL_TYPE_BLUETOOTH;
|
||||||
break;
|
break;
|
||||||
case KEY_UWB:
|
case KEY_UWB:
|
||||||
rfkill_schedule_toggle(&rfkill_uwb);
|
t = RFKILL_TYPE_UWB;
|
||||||
break;
|
break;
|
||||||
case KEY_WIMAX:
|
case KEY_WIMAX:
|
||||||
rfkill_schedule_toggle(&rfkill_wimax);
|
t = RFKILL_TYPE_WIMAX;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
rfkill_schedule_toggle(t);
|
||||||
|
return;
|
||||||
} else if (type == EV_SW) {
|
} else if (type == EV_SW) {
|
||||||
switch (code) {
|
switch (code) {
|
||||||
case SW_RFKILL_ALL:
|
case SW_RFKILL_ALL:
|
||||||
rfkill_schedule_evsw_rfkillall(data);
|
rfkill_schedule_evsw_rfkillall(data);
|
||||||
break;
|
return;
|
||||||
default:
|
default:
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -256,11 +409,9 @@ static struct input_handler rfkill_handler = {
|
||||||
|
|
||||||
static int __init rfkill_handler_init(void)
|
static int __init rfkill_handler_init(void)
|
||||||
{
|
{
|
||||||
unsigned long last_run = jiffies - msecs_to_jiffies(500);
|
if (rfkill_master_switch_mode >= RFKILL_INPUT_MASTER_MAX)
|
||||||
rfkill_wlan.last = last_run;
|
return -EINVAL;
|
||||||
rfkill_bt.last = last_run;
|
|
||||||
rfkill_uwb.last = last_run;
|
|
||||||
rfkill_wimax.last = last_run;
|
|
||||||
return input_register_handler(&rfkill_handler);
|
return input_register_handler(&rfkill_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -268,6 +419,7 @@ static void __exit rfkill_handler_exit(void)
|
||||||
{
|
{
|
||||||
input_unregister_handler(&rfkill_handler);
|
input_unregister_handler(&rfkill_handler);
|
||||||
flush_scheduled_work();
|
flush_scheduled_work();
|
||||||
|
rfkill_remove_epo_lock();
|
||||||
}
|
}
|
||||||
|
|
||||||
module_init(rfkill_handler_init);
|
module_init(rfkill_handler_init);
|
||||||
|
|
|
@ -14,6 +14,8 @@
|
||||||
void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state);
|
void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state);
|
||||||
void rfkill_epo(void);
|
void rfkill_epo(void);
|
||||||
void rfkill_restore_states(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);
|
enum rfkill_state rfkill_get_global_state(const enum rfkill_type type);
|
||||||
|
|
||||||
#endif /* __RFKILL_INPUT_H */
|
#endif /* __RFKILL_INPUT_H */
|
||||||
|
|
|
@ -51,6 +51,7 @@ struct rfkill_gsw_state {
|
||||||
|
|
||||||
static struct rfkill_gsw_state rfkill_global_states[RFKILL_TYPE_MAX];
|
static struct rfkill_gsw_state rfkill_global_states[RFKILL_TYPE_MAX];
|
||||||
static unsigned long rfkill_states_lockdflt[BITS_TO_LONGS(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);
|
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).
|
* Acquires rfkill_global_mutex and calls __rfkill_switch_all(@type, @state).
|
||||||
* Please refer to __rfkill_switch_all() for details.
|
* 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)
|
void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state)
|
||||||
{
|
{
|
||||||
mutex_lock(&rfkill_global_mutex);
|
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);
|
mutex_unlock(&rfkill_global_mutex);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL(rfkill_switch_all);
|
EXPORT_SYMBOL(rfkill_switch_all);
|
||||||
|
@ -289,6 +293,7 @@ void rfkill_epo(void)
|
||||||
|
|
||||||
mutex_lock(&rfkill_global_mutex);
|
mutex_lock(&rfkill_global_mutex);
|
||||||
|
|
||||||
|
rfkill_epo_lock_active = true;
|
||||||
list_for_each_entry(rfkill, &rfkill_list, node) {
|
list_for_each_entry(rfkill, &rfkill_list, node) {
|
||||||
mutex_lock(&rfkill->mutex);
|
mutex_lock(&rfkill->mutex);
|
||||||
rfkill_toggle_radio(rfkill, RFKILL_STATE_SOFT_BLOCKED, 1);
|
rfkill_toggle_radio(rfkill, RFKILL_STATE_SOFT_BLOCKED, 1);
|
||||||
|
@ -317,12 +322,42 @@ void rfkill_restore_states(void)
|
||||||
|
|
||||||
mutex_lock(&rfkill_global_mutex);
|
mutex_lock(&rfkill_global_mutex);
|
||||||
|
|
||||||
|
rfkill_epo_lock_active = false;
|
||||||
for (i = 0; i < RFKILL_TYPE_MAX; i++)
|
for (i = 0; i < RFKILL_TYPE_MAX; i++)
|
||||||
__rfkill_switch_all(i, rfkill_global_states[i].default_state);
|
__rfkill_switch_all(i, rfkill_global_states[i].default_state);
|
||||||
mutex_unlock(&rfkill_global_mutex);
|
mutex_unlock(&rfkill_global_mutex);
|
||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(rfkill_restore_states);
|
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
|
* rfkill_get_global_state - returns global state for a type
|
||||||
* @type: the type to get the global state of
|
* @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);
|
error = mutex_lock_killable(&rfkill->mutex);
|
||||||
if (error)
|
if (error)
|
||||||
return 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);
|
mutex_unlock(&rfkill->mutex);
|
||||||
|
|
||||||
return error ? error : count;
|
return error ? error : count;
|
||||||
|
@ -491,7 +531,7 @@ static ssize_t rfkill_claim_store(struct device *dev,
|
||||||
return error;
|
return error;
|
||||||
|
|
||||||
if (rfkill->user_claim != claim) {
|
if (rfkill->user_claim != claim) {
|
||||||
if (!claim) {
|
if (!claim && !rfkill_epo_lock_active) {
|
||||||
mutex_lock(&rfkill->mutex);
|
mutex_lock(&rfkill->mutex);
|
||||||
rfkill_toggle_radio(rfkill,
|
rfkill_toggle_radio(rfkill,
|
||||||
rfkill_global_states[rfkill->type].current_state,
|
rfkill_global_states[rfkill->type].current_state,
|
||||||
|
|
Загрузка…
Ссылка в новой задаче