Subject: [PATCH 1/2] rfkill - Add rfkill driver to misc input devices

From: Ivo van Doorn <[email-masked]>
Date: Tue, 8 Aug 2006 16:30:12 +0200

This will add the rfkill driver to the input/misc section of the kernel.
rfkill is usefull for newtwork devices that contain a hardware button
to enable or disable the radio.
With rfkill a generic interface is created for the network drivers,
as well as providing a  uniform way for userspace to listen
to the hardware button events.

Signed-off-by Ivo van Doorn <[email-masked]>

---

diff --git a/drivers/input/misc/Kconfig b/drivers/input/misc/Kconfig
index a6dfc74..ac10c02 100644
--- a/drivers/input/misc/Kconfig
+++ b/drivers/input/misc/Kconfig
@@ -79,4 +79,19 @@ config HP_SDC_RTC
 	  Say Y here if you want to support the built-in real time clock
 	  of the HP SDC controller.
 
+config RFKILL
+	tristate "RF button support"
+	help
+	  If you say yes here, the rfkill driver will be build
+	  which allowed network devices to register their hardware
+	  RF button which controls the radio state. This driver
+	  will then create an input device for it.
+
+	  When the input device is not used, the rfkill driver
+	  will make sure that when the RF button is pressed the radio
+	  is enabled or disabled accordingly. When the input device
+	  has been opened by the user this radio control will be left
+	  to the user, and rfkill will only send the RF button status
+	  change to userspace.
+
 endif
diff --git a/drivers/input/misc/Makefile b/drivers/input/misc/Makefile
index 415c491..e788a1b 100644
--- a/drivers/input/misc/Makefile
+++ b/drivers/input/misc/Makefile
@@ -11,3 +11,4 @@ obj-$(CONFIG_INPUT_UINPUT)		+= uinput.o
 obj-$(CONFIG_INPUT_WISTRON_BTNS)	+= wistron_btns.o
 obj-$(CONFIG_HP_SDC_RTC)		+= hp_sdc_rtc.o
 obj-$(CONFIG_INPUT_IXP4XX_BEEPER)	+= ixp4xx-beeper.o
+obj-$(CONFIG_RFKILL)			+= rfkill.o
diff --git a/drivers/input/misc/rfkill.c b/drivers/input/misc/rfkill.c
new file mode 100644
index 0000000..5fbe320
--- /dev/null
+++ b/drivers/input/misc/rfkill.c
@@ -0,0 +1,210 @@
+/*
+	Copyright (C) 2006 Ivo van Doorn
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the
+	Free Software Foundation, Inc.,
+	59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/timer.h>
+#include <linux/rfkill.h>
+
+#include <asm/atomic.h>
+
+MODULE_AUTHOR("Ivo van Doorn <[email-masked]>");
+MODULE_VERSION("1.0");
+MODULE_DESCRIPTION("RF button support");
+MODULE_LICENSE("GPL");
+
+/*
+ * List of all registered buttons.
+ */
+static struct list_head rfkill_list;
+static spinlock_t rfkill_list_lock;
+
+/*
+ * Polling timer, poll_delay and use count.
+ */
+static struct timer_list poll_timer;
+static atomic_t poll_required;
+
+static void rfkill_toggle_radio(int new_status)
+{
+	struct list_head *entry;
+	struct rfkill *rfkill;
+
+	/*
+	 * Go through the list of all radio's to toggle the radio state.
+	 */
+	list_for_each(entry, &rfkill_list) {
+		rfkill =  list_entry(entry, struct rfkill, entry);
+
+		rfkill->current_status = new_status;
+
+		/*
+		 * If the input_device has been opened
+		 * all radio events should be send to user space.
+		 */
+		if (rfkill->input_dev->users) {
+			input_report_key(rfkill->input_dev,
+				KEY_RFKILL, new_status);
+			input_sync(rfkill->input_dev);
+			continue;
+		}
+
+		/*
+		 * If the hardware does not toggle the radio status automaticly,
+		 * we should take care of it.
+		 */
+		if (new_status && rfkill->enable_radio)
+			rfkill->enable_radio(rfkill->data);
+		else if (!new_status && rfkill->disable_radio)
+			rfkill->disable_radio(rfkill->data);
+	}
+}
+
+static void rfkill_poll_button(unsigned long data)
+{
+	struct list_head *entry;
+	struct rfkill *rfkill;
+	int status;
+
+	spin_lock(&rfkill_list_lock);
+
+	list_for_each(entry, &rfkill_list) {
+		rfkill =  list_entry(entry, struct rfkill, entry);
+
+		if (!rfkill->poll)
+			continue;
+
+		status = !!rfkill->poll(rfkill->data);
+
+		if (status != rfkill->current_status) {
+			rfkill_toggle_radio(status);
+			break;
+		}
+	}
+
+	spin_unlock(&rfkill_list_lock);
+
+	/*
+	 * Check if polling is still required.
+	 */
+	if (atomic_read(&poll_required)) {
+		poll_timer.expires = jiffies + RFKILL_POLL_DELAY;
+		add_timer(&poll_timer);
+	}
+}
+
+void rfkill_button_event(struct rfkill *rfkill, int status)
+{
+	if (status != rfkill->current_status) {
+		spin_lock(&rfkill_list_lock);
+		rfkill_toggle_radio(status);
+		spin_unlock(&rfkill_list_lock);
+	}
+}
+EXPORT_SYMBOL(rfkill_button_event);
+
+int rfkill_add_device(struct rfkill *rfkill)
+{
+	int status;
+
+	/*
+	 * Allocate, initialize and register input device.
+	 */
+	rfkill->input_dev = input_allocate_device();
+	if (!rfkill->input_dev) {
+		printk(KERN_ERR "Failed to allocate input device %s.\n",
+			rfkill->dev_name);
+		return -ENOMEM;
+	}
+
+	rfkill->input_dev->name = "Radio button";
+	rfkill->input_dev->phys = rfkill->dev_name;
+	rfkill->input_dev->id.bustype = BUS_HOST;
+	set_bit(KEY_RFKILL, rfkill->input_dev->keybit);
+
+	status = input_register_device(rfkill->input_dev);
+	if (status) {
+		printk(KERN_ERR "Failed to register input device %s.\n",
+			rfkill->dev_name);
+		input_free_device(rfkill->input_dev);
+		return status;
+	}
+
+	INIT_LIST_HEAD(&rfkill->entry);
+
+	spin_lock(&rfkill_list_lock);
+	list_add(&rfkill->entry, &rfkill_list);
+	spin_unlock(&rfkill_list_lock);
+
+	/*
+	 * If polling is required the poll_required counter should be
+	 * increased. If it was previously 0 we should start the polling timer.
+	 */
+	if (rfkill->poll) {
+		if (!atomic_read(&poll_required)) {
+			poll_timer.expires = jiffies + RFKILL_POLL_DELAY;
+			add_timer(&poll_timer);
+		}
+		atomic_inc(&poll_required);
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL(rfkill_add_device);
+
+void rfkill_del_device(struct rfkill *rfkill)
+{
+	spin_lock(&rfkill_list_lock);
+
+	/*
+	 * Check if this button required polling and if this
+	 * was the last button that required polling.
+	 */
+	if (rfkill->poll && atomic_dec_and_test(&poll_required))
+		del_timer(&poll_timer);
+
+	list_del(&rfkill->entry);
+
+	spin_unlock(&rfkill_list_lock);
+}
+EXPORT_SYMBOL(rfkill_del_device);
+
+static int __init radiobtn_init(void)
+{
+	printk(KERN_INFO "Loading rfkill driver.\n");
+
+	INIT_LIST_HEAD(&rfkill_list);
+	spin_lock_init(&rfkill_list_lock);
+
+	init_timer(&poll_timer);
+	poll_timer.function = rfkill_poll_button;
+	poll_timer.data = 0;
+	atomic_set(&poll_required, 0);
+
+	return 0;
+}
+
+static void __exit radiobtn_exit(void)
+{
+	printk(KERN_INFO "Unloading rfkill driver.\n");
+}
+
+module_init(radiobtn_init);
+module_exit(radiobtn_exit);
diff --git a/include/linux/input.h b/include/linux/input.h
index 56f1e0e..3c21480 100644
--- a/include/linux/input.h
+++ b/include/linux/input.h
@@ -488,6 +488,7 @@ #define KEY_PREVIOUS		0x19c
 #define KEY_DIGITS		0x19d
 #define KEY_TEEN		0x19e
 #define KEY_TWEN		0x19f
+#define KEY_RFKILL		0x1a0
 
 #define KEY_DEL_EOL		0x1c0
 #define KEY_DEL_EOS		0x1c1
diff --git a/include/linux/rfkill.h b/include/linux/rfkill.h
new file mode 100644
index 0000000..2e1c740
--- /dev/null
+++ b/include/linux/rfkill.h
@@ -0,0 +1,98 @@
+/*
+	Copyright (C) 2006 Ivo van Doorn
+
+	This program is free software; you can redistribute it and/or modify
+	it under the terms of the GNU General Public License as published by
+	the Free Software Foundation; either version 2 of the License, or
+	(at your option) any later version.
+
+	This program is distributed in the hope that it will be useful,
+	but WITHOUT ANY WARRANTY; without even the implied warranty of
+	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+	GNU General Public License for more details.
+
+	You should have received a copy of the GNU General Public License
+	along with this program; if not, write to the
+	Free Software Foundation, Inc.,
+	59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/*
+	RF button support
+	Laptops are quite often equiped with a RF button to enable or
+	disable the radio of the network device attached to that button.
+	This network device usually is an integrated wireless network device,
+	or bluetooth device.
+	Some of these devices will disable the radio automaticly when the
+	RF button has been pressed, while other devices need to be polled
+	for the RF button status.
+	But in all cases the only interface that will have its radio disabled
+	will be the device that has the RF button attached to it. But it could
+	be desired that userspace performs this disabling of all radios in case
+	there are also interfaces without a RF button that need to be disabled.
+
+	The rfkill driver will contain a list of all devices with a RF button,
+	hardware drivers need to register their hardware to teh rfkill
+	interface it can take care of everything. If the RF button requires
+	polling to obtain the status this will be handled by rfkill as well.
+	Once the status of the button has changed and the hardware does not
+	automaticly enable or disable the radio rfkill provides with the
+	interface to do this correctly.
+
+	For each registered hardware button an input device will be created.
+	If this input device has been opened by the user, rfkill will send a
+	signal to userspace instead of the hardware about the new button
+	status. This will allow userpace to perform the correct steps
+	in order to bring down all interfaces.
+ */
+
+#ifndef RFKILL_H
+#define RFKILL_H
+
+#include <linux/list.h>
+#include <linux/input.h>
+
+#define RFKILL_POLL_DELAY	( HZ / 10 ) /* 100 ms */
+
+/**
+ * struct rfkill - rfkill button control structure.
+ * @dev_name: Name of the interface. This will become the name
+ * 	of the input device which will be created for this button.
+ * @data: Private data which will be passed along with the radio and polling
+ * 	handlers.
+ * @poll(unsigned long data): Optional handler which will frequently be
+ * 	called to determine the current status of the RF button.
+ * @enable_radio(unsigned long data): Optional handler to enable the radio
+ * 	once the RF button has been pressed and the hardware does enable
+ * 	the radio automaticly.
+ * @disable_radio(unsigned long data): Optional handler to disable the radio
+ * 	once the RF button has been pressed and the hardware does disable
+ * 	the radio automaticly.
+ * @current_status: Contains the current status of the radio as it was
+ * 	previously indicated by the radio. This field may only be changed
+ * 	by the driver at initialization time.
+ */
+struct rfkill {
+	const char *dev_name;
+
+	unsigned long data;
+
+	int (*poll)(unsigned long data);
+	void (*enable_radio)(unsigned long data);
+	void (*disable_radio)(unsigned long data);
+
+	unsigned int current_status;
+
+	/*
+	 * These fields are private to rfkill, and
+	 * should not be used by the RF button driver.
+	 */
+	struct list_head entry;
+	struct input_dev *input_dev;
+};
+
+void rfkill_button_event(struct rfkill *rfkill, int status);
+int rfkill_add_device(struct rfkill *rfkill);
+void rfkill_del_device(struct rfkill *rfkill);
+
+#endif /* RFKILL_H */