mirror of
				https://git.kernel.org/pub/scm/linux/kernel/git/chenhuacai/linux-loongson
				synced 2025-10-31 07:02:06 +00:00 
			
		
		
		
	 d8b105f900
			
		
	
	
		d8b105f900
		
	
	
	
	
		
			
			Initialise correctly last fields, so tasks can be actually executed. On some architectures the initial jiffies value is not zero, so later all rfkill incorrectly decides that rfkill_*.last is in future. Signed-off-by: Dmitry Baryshkov <dbaryshkov@gmail.com> Signed-off-by: John W. Linville <linville@tuxdriver.com>
		
			
				
	
	
		
			275 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			275 lines
		
	
	
		
			6.8 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 <linux/sched.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_schedule_evsw_rfkillall(int state)
 | |
| {
 | |
| 	/* EVERY radio type. state != 0 means radios ON */
 | |
| 	/* handle EPO (emergency power off) through shortcut */
 | |
| 	if (state) {
 | |
| 		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();
 | |
| }
 | |
| 
 | |
| 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:
 | |
| 			rfkill_schedule_evsw_rfkillall(data);
 | |
| 			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";
 | |
| 
 | |
| 	/* causes rfkill_start() to be called */
 | |
| 	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_start(struct input_handle *handle)
 | |
| {
 | |
| 	/* Take event_lock to guard against configuration changes, we
 | |
| 	 * should be able to deal with concurrency with rfkill_event()
 | |
| 	 * just fine (which event_lock will also avoid). */
 | |
| 	spin_lock_irq(&handle->dev->event_lock);
 | |
| 
 | |
| 	if (test_bit(EV_SW, handle->dev->evbit)) {
 | |
| 		if (test_bit(SW_RFKILL_ALL, handle->dev->swbit))
 | |
| 			rfkill_schedule_evsw_rfkillall(test_bit(SW_RFKILL_ALL,
 | |
| 							handle->dev->sw));
 | |
| 		/* add resync for further EV_SW events here */
 | |
| 	}
 | |
| 
 | |
| 	spin_unlock_irq(&handle->dev->event_lock);
 | |
| }
 | |
| 
 | |
| 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,
 | |
| 	.start =	rfkill_start,
 | |
| 	.name =		"rfkill",
 | |
| 	.id_table =	rfkill_ids,
 | |
| };
 | |
| 
 | |
| static int __init rfkill_handler_init(void)
 | |
| {
 | |
| 	unsigned long last_run = jiffies - msecs_to_jiffies(500);
 | |
| 	rfkill_wlan.last = last_run;
 | |
| 	rfkill_bt.last = last_run;
 | |
| 	rfkill_uwb.last = last_run;
 | |
| 	rfkill_wimax.last = last_run;
 | |
| 	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);
 |