summaryrefslogtreecommitdiff
path: root/net/rfkill/rfkill-input.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/rfkill/rfkill-input.c')
-rw-r--r--net/rfkill/rfkill-input.c337
1 files changed, 261 insertions, 76 deletions
diff --git a/net/rfkill/rfkill-input.c b/net/rfkill/rfkill-input.c
index bfdade72e066..84efde97c5a7 100644
--- a/net/rfkill/rfkill-input.c
+++ b/net/rfkill/rfkill-input.c
@@ -24,138 +24,318 @@ 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 */
+};
+
+/* Delay (in ms) between consecutive switch ops */
+#define RFKILL_OPS_DELAY 200
+
+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 */
+ struct delayed_work dwork;
+
+ /* 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;
+
+ /* last time it was scheduled */
+ unsigned long last_scheduled;
};
+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)
{
- struct rfkill_task *task = container_of(work, struct rfkill_task, work);
+ struct rfkill_task *task = container_of(work,
+ struct rfkill_task, dwork.work);
+ bool doit = true;
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);
}
-static void rfkill_task_epo_handler(struct work_struct *work)
+static struct rfkill_task rfkill_task = {
+ .dwork = __DELAYED_WORK_INITIALIZER(rfkill_task.dwork,
+ rfkill_task_handler),
+ .mutex = __MUTEX_INITIALIZER(rfkill_task.mutex),
+ .lock = __SPIN_LOCK_UNLOCKED(rfkill_task.lock),
+};
+
+static unsigned long rfkill_ratelimit(const unsigned long last)
{
- rfkill_epo();
+ const unsigned long delay = msecs_to_jiffies(RFKILL_OPS_DELAY);
+ return (time_after(jiffies, last + delay)) ? 0 : delay;
}
-static DECLARE_WORK(epo_work, rfkill_task_epo_handler);
+static void rfkill_schedule_ratelimited(void)
+{
+ if (!delayed_work_pending(&rfkill_task.dwork)) {
+ schedule_delayed_work(&rfkill_task.dwork,
+ rfkill_ratelimit(rfkill_task.last_scheduled));
+ rfkill_task.last_scheduled = jiffies;
+ }
+}
-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;
+ if (op == RFKILL_GLOBAL_OP_EPO && !rfkill_is_epo_lock_active()) {
+ /* bypass the limiter for EPO */
+ cancel_delayed_work(&rfkill_task.dwork);
+ schedule_delayed_work(&rfkill_task.dwork, 0);
+ rfkill_task.last_scheduled = jiffies;
+ } else
+ rfkill_schedule_ratelimited();
+ 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);
+ rfkill_schedule_ratelimited();
}
-
- 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);
+ rfkill_schedule_ratelimited();
}
-
- 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,18 +436,23 @@ 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;
+
+ /*
+ * The penalty to not doing this is a possible RFKILL_OPS_DELAY delay
+ * at the first use. Acceptable, but if we can avoid it, why not?
+ */
+ rfkill_task.last_scheduled =
+ jiffies - msecs_to_jiffies(RFKILL_OPS_DELAY) - 1;
return input_register_handler(&rfkill_handler);
}
static void __exit rfkill_handler_exit(void)
{
input_unregister_handler(&rfkill_handler);
- flush_scheduled_work();
+ cancel_delayed_work_sync(&rfkill_task.dwork);
+ rfkill_remove_epo_lock();
}
module_init(rfkill_handler_init);