diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h index ca89ae1b0219672e282813ba8d81ef262f05f1dd..844e961148610ec6bf8ba112ba9fc2152ecfa75a 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 3edc585dcfa6ddbb75b9cc580194f13ad3c10c5f..4ae4486c77eaef79cb532b222d72d4b2686a6aac 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); }