forked from Minki/linux
5005657cbd
The current naming of rfkill_state causes a lot of confusion: not only the "kill" in rfkill suggests negative logic, but also the fact that rfkill cannot turn anything on (it can just force something off or stop forcing something off) is often forgotten. Rename RFKILL_STATE_OFF to RFKILL_STATE_SOFT_BLOCKED (transmitter is blocked and will not operate; state can be changed by a toggle_radio request), and RFKILL_STATE_ON to RFKILL_STATE_UNBLOCKED (transmitter is not blocked, and may operate). Also, add a new third state, RFKILL_STATE_HARD_BLOCKED (transmitter is blocked and will not operate; state cannot be changed through a toggle_radio request), which is used by drivers to indicate a wireless transmiter was blocked by a hardware rfkill line that accepts no overrides. Keep the old names as #defines, but document them as deprecated. This way, drivers can be converted to the new names *and* verified to actually use rfkill correctly one by one. Signed-off-by: Henrique de Moraes Holschuh <hmh@hmh.eng.br> Acked-by: Ivo van Doorn <IvDoorn@gmail.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
245 lines
5.9 KiB
C
245 lines
5.9 KiB
C
/*
|
|
* Input layer to RF Kill interface connector
|
|
*
|
|
* Copyright (c) 2007 Dmitry Torokhov
|
|
*/
|
|
|
|
/*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms of the GNU General Public License version 2 as published
|
|
* by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/input.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/workqueue.h>
|
|
#include <linux/init.h>
|
|
#include <linux/rfkill.h>
|
|
|
|
#include "rfkill-input.h"
|
|
|
|
MODULE_AUTHOR("Dmitry Torokhov <dtor@mail.ru>");
|
|
MODULE_DESCRIPTION("Input layer to RF switch connector");
|
|
MODULE_LICENSE("GPL");
|
|
|
|
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 */
|
|
};
|
|
|
|
static void rfkill_task_handler(struct work_struct *work)
|
|
{
|
|
struct rfkill_task *task = container_of(work, struct rfkill_task, work);
|
|
|
|
mutex_lock(&task->mutex);
|
|
|
|
rfkill_switch_all(task->type, task->desired_state);
|
|
|
|
mutex_unlock(&task->mutex);
|
|
}
|
|
|
|
static void rfkill_task_epo_handler(struct work_struct *work)
|
|
{
|
|
rfkill_epo();
|
|
}
|
|
|
|
static DECLARE_WORK(epo_work, rfkill_task_epo_handler);
|
|
|
|
static void rfkill_schedule_epo(void)
|
|
{
|
|
schedule_work(&epo_work);
|
|
}
|
|
|
|
static void rfkill_schedule_set(struct rfkill_task *task,
|
|
enum rfkill_state desired_state)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (unlikely(work_pending(&epo_work)))
|
|
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_unlock_irqrestore(&task->lock, flags);
|
|
}
|
|
|
|
static void rfkill_schedule_toggle(struct rfkill_task *task)
|
|
{
|
|
unsigned long flags;
|
|
|
|
if (unlikely(work_pending(&epo_work)))
|
|
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_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_event(struct input_handle *handle, unsigned int type,
|
|
unsigned int code, int data)
|
|
{
|
|
if (type == EV_KEY && data == 1) {
|
|
switch (code) {
|
|
case KEY_WLAN:
|
|
rfkill_schedule_toggle(&rfkill_wlan);
|
|
break;
|
|
case KEY_BLUETOOTH:
|
|
rfkill_schedule_toggle(&rfkill_bt);
|
|
break;
|
|
case KEY_UWB:
|
|
rfkill_schedule_toggle(&rfkill_uwb);
|
|
break;
|
|
case KEY_WIMAX:
|
|
rfkill_schedule_toggle(&rfkill_wimax);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
} else if (type == EV_SW) {
|
|
switch (code) {
|
|
case SW_RFKILL_ALL:
|
|
/* EVERY radio type. data != 0 means radios ON */
|
|
/* handle EPO (emergency power off) through shortcut */
|
|
if (data) {
|
|
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);
|
|
} else
|
|
rfkill_schedule_epo();
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int rfkill_connect(struct input_handler *handler, struct input_dev *dev,
|
|
const struct input_device_id *id)
|
|
{
|
|
struct input_handle *handle;
|
|
int error;
|
|
|
|
handle = kzalloc(sizeof(struct input_handle), GFP_KERNEL);
|
|
if (!handle)
|
|
return -ENOMEM;
|
|
|
|
handle->dev = dev;
|
|
handle->handler = handler;
|
|
handle->name = "rfkill";
|
|
|
|
error = input_register_handle(handle);
|
|
if (error)
|
|
goto err_free_handle;
|
|
|
|
error = input_open_device(handle);
|
|
if (error)
|
|
goto err_unregister_handle;
|
|
|
|
return 0;
|
|
|
|
err_unregister_handle:
|
|
input_unregister_handle(handle);
|
|
err_free_handle:
|
|
kfree(handle);
|
|
return error;
|
|
}
|
|
|
|
static void rfkill_disconnect(struct input_handle *handle)
|
|
{
|
|
input_close_device(handle);
|
|
input_unregister_handle(handle);
|
|
kfree(handle);
|
|
}
|
|
|
|
static const struct input_device_id rfkill_ids[] = {
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT,
|
|
.evbit = { BIT_MASK(EV_KEY) },
|
|
.keybit = { [BIT_WORD(KEY_WLAN)] = BIT_MASK(KEY_WLAN) },
|
|
},
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT,
|
|
.evbit = { BIT_MASK(EV_KEY) },
|
|
.keybit = { [BIT_WORD(KEY_BLUETOOTH)] = BIT_MASK(KEY_BLUETOOTH) },
|
|
},
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT,
|
|
.evbit = { BIT_MASK(EV_KEY) },
|
|
.keybit = { [BIT_WORD(KEY_UWB)] = BIT_MASK(KEY_UWB) },
|
|
},
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_KEYBIT,
|
|
.evbit = { BIT_MASK(EV_KEY) },
|
|
.keybit = { [BIT_WORD(KEY_WIMAX)] = BIT_MASK(KEY_WIMAX) },
|
|
},
|
|
{
|
|
.flags = INPUT_DEVICE_ID_MATCH_EVBIT | INPUT_DEVICE_ID_MATCH_SWBIT,
|
|
.evbit = { BIT(EV_SW) },
|
|
.swbit = { [BIT_WORD(SW_RFKILL_ALL)] = BIT_MASK(SW_RFKILL_ALL) },
|
|
},
|
|
{ }
|
|
};
|
|
|
|
static struct input_handler rfkill_handler = {
|
|
.event = rfkill_event,
|
|
.connect = rfkill_connect,
|
|
.disconnect = rfkill_disconnect,
|
|
.name = "rfkill",
|
|
.id_table = rfkill_ids,
|
|
};
|
|
|
|
static int __init rfkill_handler_init(void)
|
|
{
|
|
return input_register_handler(&rfkill_handler);
|
|
}
|
|
|
|
static void __exit rfkill_handler_exit(void)
|
|
{
|
|
input_unregister_handler(&rfkill_handler);
|
|
flush_scheduled_work();
|
|
}
|
|
|
|
module_init(rfkill_handler_init);
|
|
module_exit(rfkill_handler_exit);
|