diff options
Diffstat (limited to 'net/rfkill/rfkill.c')
| -rw-r--r-- | net/rfkill/rfkill.c | 314 | 
1 files changed, 271 insertions, 43 deletions
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);  }  |