aboutsummaryrefslogtreecommitdiff
path: root/net/rfkill
diff options
context:
space:
mode:
Diffstat (limited to 'net/rfkill')
-rw-r--r--net/rfkill/rfkill-input.c98
-rw-r--r--net/rfkill/rfkill-input.h1
-rw-r--r--net/rfkill/rfkill.c314
3 files changed, 346 insertions, 67 deletions
diff --git a/net/rfkill/rfkill-input.c b/net/rfkill/rfkill-input.c
index e4b051dbed6..8aa82273014 100644
--- a/net/rfkill/rfkill-input.c
+++ b/net/rfkill/rfkill-input.c
@@ -30,39 +30,62 @@ struct rfkill_task {
spinlock_t lock; /* for accessing last and desired state */
unsigned long last; /* last schedule */
enum rfkill_state desired_state; /* on/off */
- enum rfkill_state current_state; /* on/off */
};
static void rfkill_task_handler(struct work_struct *work)
{
struct rfkill_task *task = container_of(work, struct rfkill_task, work);
- enum rfkill_state state;
mutex_lock(&task->mutex);
- /*
- * Use temp variable to fetch desired state to keep it
- * consistent even if rfkill_schedule_toggle() runs in
- * another thread or interrupts us.
- */
- state = task->desired_state;
+ rfkill_switch_all(task->type, task->desired_state);
- if (state != task->current_state) {
- rfkill_switch_all(task->type, state);
- task->current_state = 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);
}
- mutex_unlock(&task->mutex);
+ 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 = !task->desired_state;
+ task->desired_state =
+ rfkill_state_complement(task->desired_state);
task->last = jiffies;
schedule_work(&task->work);
}
@@ -70,26 +93,26 @@ static void rfkill_schedule_toggle(struct rfkill_task *task)
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_ON, \
- .current_state = RFKILL_STATE_ON, \
+#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 down)
+ unsigned int code, int data)
{
- if (type == EV_KEY && down == 1) {
+ if (type == EV_KEY && data == 1) {
switch (code) {
case KEY_WLAN:
rfkill_schedule_toggle(&rfkill_wlan);
@@ -106,6 +129,28 @@ static void rfkill_event(struct input_handle *handle, unsigned int type,
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;
+ }
}
}
@@ -168,6 +213,11 @@ static const struct input_device_id rfkill_ids[] = {
.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) },
+ },
{ }
};
diff --git a/net/rfkill/rfkill-input.h b/net/rfkill/rfkill-input.h
index 4dae5006fc7..f63d0504568 100644
--- a/net/rfkill/rfkill-input.h
+++ b/net/rfkill/rfkill-input.h
@@ -12,5 +12,6 @@
#define __RFKILL_INPUT_H
void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state);
+void rfkill_epo(void);
#endif /* __RFKILL_INPUT_H */
diff --git a/net/rfkill/rfkill.c b/net/rfkill/rfkill.c
index 4e10a95de83..7a560b78509 100644
--- a/net/rfkill/rfkill.c
+++ b/net/rfkill/rfkill.c
@@ -39,8 +39,56 @@ MODULE_LICENSE("GPL");
static LIST_HEAD(rfkill_list); /* list of registered rf switches */
static DEFINE_MUTEX(rfkill_mutex);
+static unsigned int rfkill_default_state = RFKILL_STATE_UNBLOCKED;
+module_param_named(default_state, rfkill_default_state, uint, 0444);
+MODULE_PARM_DESC(default_state,
+ "Default initial state for all radio types, 0 = radio off");
+
static enum rfkill_state rfkill_states[RFKILL_TYPE_MAX];
+static BLOCKING_NOTIFIER_HEAD(rfkill_notifier_list);
+
+
+/**
+ * register_rfkill_notifier - Add notifier to rfkill notifier chain
+ * @nb: pointer to the new entry to add to the chain
+ *
+ * See blocking_notifier_chain_register() for return value and further
+ * observations.
+ *
+ * Adds a notifier to the rfkill notifier chain. The chain will be
+ * called with a pointer to the relevant rfkill structure as a parameter,
+ * refer to include/linux/rfkill.h for the possible events.
+ *
+ * Notifiers added to this chain are to always return NOTIFY_DONE. This
+ * chain is a blocking notifier chain: notifiers can sleep.
+ *
+ * Calls to this chain may have been done through a workqueue. One must
+ * assume unordered asynchronous behaviour, there is no way to know if
+ * actions related to the event that generated the notification have been
+ * carried out already.
+ */
+int register_rfkill_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_register(&rfkill_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(register_rfkill_notifier);
+
+/**
+ * unregister_rfkill_notifier - remove notifier from rfkill notifier chain
+ * @nb: pointer to the entry to remove from the chain
+ *
+ * See blocking_notifier_chain_unregister() for return value and further
+ * observations.
+ *
+ * Removes a notifier from the rfkill notifier chain.
+ */
+int unregister_rfkill_notifier(struct notifier_block *nb)
+{
+ return blocking_notifier_chain_unregister(&rfkill_notifier_list, nb);
+}
+EXPORT_SYMBOL_GPL(unregister_rfkill_notifier);
+
static void rfkill_led_trigger(struct rfkill *rfkill,
enum rfkill_state state)
@@ -50,24 +98,101 @@ static void rfkill_led_trigger(struct rfkill *rfkill,
if (!led->name)
return;
- if (state == RFKILL_STATE_OFF)
+ if (state != RFKILL_STATE_UNBLOCKED)
led_trigger_event(led, LED_OFF);
else
led_trigger_event(led, LED_FULL);
#endif /* CONFIG_RFKILL_LEDS */
}
+static void notify_rfkill_state_change(struct rfkill *rfkill)
+{
+ blocking_notifier_call_chain(&rfkill_notifier_list,
+ RFKILL_STATE_CHANGED,
+ rfkill);
+}
+
+static void update_rfkill_state(struct rfkill *rfkill)
+{
+ enum rfkill_state newstate, oldstate;
+
+ if (rfkill->get_state) {
+ mutex_lock(&rfkill->mutex);
+ if (!rfkill->get_state(rfkill->data, &newstate)) {
+ oldstate = rfkill->state;
+ rfkill->state = newstate;
+ if (oldstate != newstate)
+ notify_rfkill_state_change(rfkill);
+ }
+ mutex_unlock(&rfkill->mutex);
+ }
+}
+
+/**
+ * rfkill_toggle_radio - wrapper for toggle_radio hook
+ *
+ * @rfkill: the rfkill struct to use
+ * @force: calls toggle_radio even if cache says it is not needed,
+ * and also makes sure notifications of the state will be
+ * sent even if it didn't change
+ * @state: the new state to call toggle_radio() with
+ *
+ * Calls rfkill->toggle_radio, enforcing the API for toggle_radio
+ * calls and handling all the red tape such as issuing notifications
+ * if the call is successful.
+ *
+ * Note that @force cannot override a (possibly cached) state of
+ * RFKILL_STATE_HARD_BLOCKED. Any device making use of
+ * RFKILL_STATE_HARD_BLOCKED implements either get_state() or
+ * rfkill_force_state(), so the cache either is bypassed or valid.
+ *
+ * Note that we do call toggle_radio for RFKILL_STATE_SOFT_BLOCKED
+ * even if the radio is in RFKILL_STATE_HARD_BLOCKED state, so as to
+ * give the driver a hint that it should double-BLOCK the transmitter.
+ *
+ * Caller must have aquired rfkill_mutex.
+ */
static int rfkill_toggle_radio(struct rfkill *rfkill,
- enum rfkill_state state)
+ enum rfkill_state state,
+ int force)
{
int retval = 0;
+ enum rfkill_state oldstate, newstate;
+
+ oldstate = rfkill->state;
+
+ if (rfkill->get_state && !force &&
+ !rfkill->get_state(rfkill->data, &newstate))
+ rfkill->state = newstate;
+
+ switch (state) {
+ case RFKILL_STATE_HARD_BLOCKED:
+ /* typically happens when refreshing hardware state,
+ * such as on resume */
+ state = RFKILL_STATE_SOFT_BLOCKED;
+ break;
+ case RFKILL_STATE_UNBLOCKED:
+ /* force can't override this, only rfkill_force_state() can */
+ if (rfkill->state == RFKILL_STATE_HARD_BLOCKED)
+ return -EPERM;
+ break;
+ case RFKILL_STATE_SOFT_BLOCKED:
+ /* nothing to do, we want to give drivers the hint to double
+ * BLOCK even a transmitter that is already in state
+ * RFKILL_STATE_HARD_BLOCKED */
+ break;
+ }
- if (state != rfkill->state) {
+ if (force || state != rfkill->state) {
retval = rfkill->toggle_radio(rfkill->data, state);
- if (!retval) {
+ /* never allow a HARD->SOFT downgrade! */
+ if (!retval && rfkill->state != RFKILL_STATE_HARD_BLOCKED)
rfkill->state = state;
- rfkill_led_trigger(rfkill, state);
- }
+ }
+
+ if (force || rfkill->state != oldstate) {
+ rfkill_led_trigger(rfkill, rfkill->state);
+ notify_rfkill_state_change(rfkill);
}
return retval;
@@ -82,7 +207,6 @@ static int rfkill_toggle_radio(struct rfkill *rfkill,
* a specific switch is claimed by userspace in which case it is
* left alone.
*/
-
void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state)
{
struct rfkill *rfkill;
@@ -93,13 +217,66 @@ void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state)
list_for_each_entry(rfkill, &rfkill_list, node) {
if ((!rfkill->user_claim) && (rfkill->type == type))
- rfkill_toggle_radio(rfkill, state);
+ rfkill_toggle_radio(rfkill, state, 0);
}
mutex_unlock(&rfkill_mutex);
}
EXPORT_SYMBOL(rfkill_switch_all);
+/**
+ * rfkill_epo - emergency power off all transmitters
+ *
+ * This kicks all rfkill devices to RFKILL_STATE_SOFT_BLOCKED, ignoring
+ * everything in its path but rfkill_mutex.
+ */
+void rfkill_epo(void)
+{
+ struct rfkill *rfkill;
+
+ mutex_lock(&rfkill_mutex);
+ list_for_each_entry(rfkill, &rfkill_list, node) {
+ rfkill_toggle_radio(rfkill, RFKILL_STATE_SOFT_BLOCKED, 1);
+ }
+ mutex_unlock(&rfkill_mutex);
+}
+EXPORT_SYMBOL_GPL(rfkill_epo);
+
+/**
+ * rfkill_force_state - Force the internal rfkill radio state
+ * @rfkill: pointer to the rfkill class to modify.
+ * @state: the current radio state the class should be forced to.
+ *
+ * This function updates the internal state of the radio cached
+ * by the rfkill class. It should be used when the driver gets
+ * a notification by the firmware/hardware of the current *real*
+ * state of the radio rfkill switch.
+ *
+ * It may not be called from an atomic context.
+ */
+int rfkill_force_state(struct rfkill *rfkill, enum rfkill_state state)
+{
+ enum rfkill_state oldstate;
+
+ if (state != RFKILL_STATE_SOFT_BLOCKED &&
+ state != RFKILL_STATE_UNBLOCKED &&
+ state != RFKILL_STATE_HARD_BLOCKED)
+ return -EINVAL;
+
+ mutex_lock(&rfkill->mutex);
+
+ oldstate = rfkill->state;
+ rfkill->state = state;
+
+ if (state != oldstate)
+ notify_rfkill_state_change(rfkill);
+
+ mutex_unlock(&rfkill->mutex);
+
+ return 0;
+}
+EXPORT_SYMBOL(rfkill_force_state);
+
static ssize_t rfkill_name_show(struct device *dev,
struct device_attribute *attr,
char *buf)
@@ -109,31 +286,31 @@ static ssize_t rfkill_name_show(struct device *dev,
return sprintf(buf, "%s\n", rfkill->name);
}
-static ssize_t rfkill_type_show(struct device *dev,
- struct device_attribute *attr,
- char *buf)
+static const char *rfkill_get_type_str(enum rfkill_type type)
{
- struct rfkill *rfkill = to_rfkill(dev);
- const char *type;
-
- switch (rfkill->type) {
+ switch (type) {
case RFKILL_TYPE_WLAN:
- type = "wlan";
- break;
+ return "wlan";
case RFKILL_TYPE_BLUETOOTH:
- type = "bluetooth";
- break;
+ return "bluetooth";
case RFKILL_TYPE_UWB:
- type = "ultrawideband";
- break;
+ return "ultrawideband";
case RFKILL_TYPE_WIMAX:
- type = "wimax";
- break;
+ return "wimax";
+ case RFKILL_TYPE_WWAN:
+ return "wwan";
default:
BUG();
}
+}
+
+static ssize_t rfkill_type_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct rfkill *rfkill = to_rfkill(dev);
- return sprintf(buf, "%s\n", type);
+ return sprintf(buf, "%s\n", rfkill_get_type_str(rfkill->type));
}
static ssize_t rfkill_state_show(struct device *dev,
@@ -142,6 +319,7 @@ static ssize_t rfkill_state_show(struct device *dev,
{
struct rfkill *rfkill = to_rfkill(dev);
+ update_rfkill_state(rfkill);
return sprintf(buf, "%d\n", rfkill->state);
}
@@ -156,10 +334,14 @@ static ssize_t rfkill_state_store(struct device *dev,
if (!capable(CAP_NET_ADMIN))
return -EPERM;
+ /* RFKILL_STATE_HARD_BLOCKED is illegal here... */
+ if (state != RFKILL_STATE_UNBLOCKED &&
+ state != RFKILL_STATE_SOFT_BLOCKED)
+ return -EINVAL;
+
if (mutex_lock_interruptible(&rfkill->mutex))
return -ERESTARTSYS;
- error = rfkill_toggle_radio(rfkill,
- state ? RFKILL_STATE_ON : RFKILL_STATE_OFF);
+ error = rfkill_toggle_radio(rfkill, state, 0);
mutex_unlock(&rfkill->mutex);
return error ? error : count;
@@ -200,7 +382,8 @@ static ssize_t rfkill_claim_store(struct device *dev,
if (rfkill->user_claim != claim) {
if (!claim)
rfkill_toggle_radio(rfkill,
- rfkill_states[rfkill->type]);
+ rfkill_states[rfkill->type],
+ 0);
rfkill->user_claim = claim;
}
@@ -233,12 +416,12 @@ static int rfkill_suspend(struct device *dev, pm_message_t state)
if (dev->power.power_state.event != state.event) {
if (state.event & PM_EVENT_SLEEP) {
- mutex_lock(&rfkill->mutex);
-
- if (rfkill->state == RFKILL_STATE_ON)
- rfkill->toggle_radio(rfkill->data,
- RFKILL_STATE_OFF);
+ /* Stop transmitter, keep state, no notifies */
+ update_rfkill_state(rfkill);
+ mutex_lock(&rfkill->mutex);
+ rfkill->toggle_radio(rfkill->data,
+ RFKILL_STATE_SOFT_BLOCKED);
mutex_unlock(&rfkill->mutex);
}
@@ -255,8 +438,8 @@ static int rfkill_resume(struct device *dev)
if (dev->power.power_state.event != PM_EVENT_ON) {
mutex_lock(&rfkill->mutex);
- if (rfkill->state == RFKILL_STATE_ON)
- rfkill->toggle_radio(rfkill->data, RFKILL_STATE_ON);
+ /* restore radio state AND notify everybody */
+ rfkill_toggle_radio(rfkill, rfkill->state, 1);
mutex_unlock(&rfkill->mutex);
}
@@ -269,34 +452,71 @@ static int rfkill_resume(struct device *dev)
#define rfkill_resume NULL
#endif
+static int rfkill_blocking_uevent_notifier(struct notifier_block *nb,
+ unsigned long eventid,
+ void *data)
+{
+ struct rfkill *rfkill = (struct rfkill *)data;
+
+ switch (eventid) {
+ case RFKILL_STATE_CHANGED:
+ kobject_uevent(&rfkill->dev.kobj, KOBJ_CHANGE);
+ break;
+ default:
+ break;
+ }
+
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block rfkill_blocking_uevent_nb = {
+ .notifier_call = rfkill_blocking_uevent_notifier,
+ .priority = 0,
+};
+
+static int rfkill_dev_uevent(struct device *dev, struct kobj_uevent_env *env)
+{
+ struct rfkill *rfkill = to_rfkill(dev);
+ int error;
+
+ error = add_uevent_var(env, "RFKILL_NAME=%s", rfkill->name);
+ if (error)
+ return error;
+ error = add_uevent_var(env, "RFKILL_TYPE=%s",
+ rfkill_get_type_str(rfkill->type));
+ if (error)
+ return error;
+ error = add_uevent_var(env, "RFKILL_STATE=%d", rfkill->state);
+ return error;
+}
+
static struct class rfkill_class = {
.name = "rfkill",
.dev_release = rfkill_release,
.dev_attrs = rfkill_dev_attrs,
.suspend = rfkill_suspend,
.resume = rfkill_resume,
+ .dev_uevent = rfkill_dev_uevent,
};
static int rfkill_add_switch(struct rfkill *rfkill)
{
- int error;
-
mutex_lock(&rfkill_mutex);
- error = rfkill_toggle_radio(rfkill, rfkill_states[rfkill->type]);
- if (!error)
- list_add_tail(&rfkill->node, &rfkill_list);
+ rfkill_toggle_radio(rfkill, rfkill_states[rfkill->type], 0);
+
+ list_add_tail(&rfkill->node, &rfkill_list);
mutex_unlock(&rfkill_mutex);
- return error;
+ return 0;
}
static void rfkill_remove_switch(struct rfkill *rfkill)
{
mutex_lock(&rfkill_mutex);
list_del_init(&rfkill->node);
- rfkill_toggle_radio(rfkill, RFKILL_STATE_OFF);
+ rfkill_toggle_radio(rfkill, RFKILL_STATE_SOFT_BLOCKED, 1);
mutex_unlock(&rfkill_mutex);
}
@@ -412,7 +632,7 @@ int rfkill_register(struct rfkill *rfkill)
EXPORT_SYMBOL(rfkill_register);
/**
- * rfkill_unregister - Uegister a rfkill structure.
+ * rfkill_unregister - Unregister a rfkill structure.
* @rfkill: rfkill structure to be unregistered
*
* This function should be called by the network driver during device
@@ -436,8 +656,13 @@ static int __init rfkill_init(void)
int error;
int i;
+ /* RFKILL_STATE_HARD_BLOCKED is illegal here... */
+ if (rfkill_default_state != RFKILL_STATE_SOFT_BLOCKED &&
+ rfkill_default_state != RFKILL_STATE_UNBLOCKED)
+ return -EINVAL;
+
for (i = 0; i < ARRAY_SIZE(rfkill_states); i++)
- rfkill_states[i] = RFKILL_STATE_ON;
+ rfkill_states[i] = rfkill_default_state;
error = class_register(&rfkill_class);
if (error) {
@@ -445,11 +670,14 @@ static int __init rfkill_init(void)
return error;
}
+ register_rfkill_notifier(&rfkill_blocking_uevent_nb);
+
return 0;
}
static void __exit rfkill_exit(void)
{
+ unregister_rfkill_notifier(&rfkill_blocking_uevent_nb);
class_unregister(&rfkill_class);
}