summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--include/linux/rfkill.h5
-rw-r--r--net/rfkill/rfkill.c53
2 files changed, 55 insertions, 3 deletions
diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h
index ca89ae1b0219..844e96114861 100644
--- a/include/linux/rfkill.h
+++ b/include/linux/rfkill.h
@@ -61,6 +61,8 @@ enum rfkill_state {
* @data: Pointer to the RF button drivers private data which will be
* passed along when toggling radio state.
* @toggle_radio(): Mandatory handler to control state of the radio.
+ * @get_state(): handler to read current radio state from hardware,
+ * may be called from atomic context, should return 0 on success.
* @led_trigger: A LED trigger for this button's LED.
* @dev: Device structure integrating the switch into device tree.
* @node: Used to place switch into list of all switches known to the
@@ -80,6 +82,7 @@ struct rfkill {
void *data;
int (*toggle_radio)(void *data, enum rfkill_state state);
+ int (*get_state)(void *data, enum rfkill_state *state);
#ifdef CONFIG_RFKILL_LEDS
struct led_trigger led_trigger;
@@ -95,6 +98,8 @@ void rfkill_free(struct rfkill *rfkill);
int rfkill_register(struct rfkill *rfkill);
void rfkill_unregister(struct rfkill *rfkill);
+int rfkill_force_state(struct rfkill *rfkill, enum rfkill_state state);
+
/**
* rfkill_get_led_name - Get the LED trigger name for the button's LED.
* This function might return a NULL pointer if registering of the
diff --git a/net/rfkill/rfkill.c b/net/rfkill/rfkill.c
index 3edc585dcfa6..4ae4486c77ea 100644
--- a/net/rfkill/rfkill.c
+++ b/net/rfkill/rfkill.c
@@ -62,19 +62,39 @@ static void rfkill_led_trigger(struct rfkill *rfkill,
#endif /* CONFIG_RFKILL_LEDS */
}
+static void update_rfkill_state(struct rfkill *rfkill)
+{
+ enum rfkill_state newstate;
+
+ if (rfkill->get_state) {
+ mutex_lock(&rfkill->mutex);
+ if (!rfkill->get_state(rfkill->data, &newstate))
+ rfkill->state = newstate;
+ mutex_unlock(&rfkill->mutex);
+ }
+}
+
static int rfkill_toggle_radio(struct rfkill *rfkill,
enum rfkill_state state)
{
int retval = 0;
+ enum rfkill_state oldstate, newstate;
+
+ oldstate = rfkill->state;
+
+ if (rfkill->get_state &&
+ !rfkill->get_state(rfkill->data, &newstate))
+ rfkill->state = newstate;
if (state != rfkill->state) {
retval = rfkill->toggle_radio(rfkill->data, state);
- if (!retval) {
+ if (!retval)
rfkill->state = state;
- rfkill_led_trigger(rfkill, state);
- }
}
+ if (rfkill->state != oldstate)
+ rfkill_led_trigger(rfkill, rfkill->state);
+
return retval;
}
@@ -105,6 +125,32 @@ void rfkill_switch_all(enum rfkill_type type, enum rfkill_state state)
}
EXPORT_SYMBOL(rfkill_switch_all);
+/**
+ * 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)
+{
+ if (state != RFKILL_STATE_OFF &&
+ state != RFKILL_STATE_ON)
+ return -EINVAL;
+
+ mutex_lock(&rfkill->mutex);
+ rfkill->state = state;
+ 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)
@@ -147,6 +193,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);
}