Skip to contentSkip to conversation

toshiba_acpi: Add support for bluetooth toggling through rfkill

PATCH In review Philip Langdale 4 participants 8 messages Jul 27, 2008 – Jul 28, 2008 ACPI

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 toshiba_acpi: Add support for bluetooth toggling through rfkill

There's been a patch floating around for toshiba_acpi that exports an ad-hoc /proc interface to toggle the bluetooth adapter in a large number of Toshiba laptops. I'm not sure if it's still relevant for the latest models, but it is still required for older models such as my Tecra M3.

This change pulls in the low level Toshiba-specific code from the old patch and sets up an rfkill device and a polled input device to track the state of the hardware kill-switch.

---
 Kconfig        |    1
 toshiba_acpi.c |  332 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
 2 files changed, 325 insertions(+), 8 deletions(-)
drivers/acpi/Kconfig +1 -0
index c52fca8..bf1ae18 100644
@@ -262,6 +262,7 @@ config ACPI_ASUS
 config ACPI_TOSHIBA
 	tristate "Toshiba Laptop Extras"
 	depends on X86
+	select INPUT_POLLDEV
 	select BACKLIGHT_CLASS_DEVICE
 	---help---
 	  This driver adds support for access to certain system settings
drivers/acpi/toshiba_acpi.c +324 -8

427 lines not on this page · raw message

#1 lore raw
Quoted 8 lines · Philip Langdale

There's been a patch floating around for toshiba_acpi that exports an ad-hoc /proc interface to toggle the bluetooth adapter in a large number of Toshiba laptops. I'm not sure if it's still relevant for the latest models, but it is still required for older models such as my Tecra M3.

This change pulls in the low level Toshiba-specific code from the old patch and sets up an rfkill device and a polled input device to track the state of the hardware kill-switch.

You don't seem to be using rfkill_force_state() which is required to inform the rfkill layer about the state changes.

Ivo

Quoted 464 lines
---
  Kconfig        |    1
  toshiba_acpi.c |  332 +++++++++++++++++++++++++++++++++++++++++++++++++++++++--
  2 files changed, 325 insertions(+), 8 deletions(-)
drivers/acpi/Kconfig +1 -0
index c52fca8..bf1ae18 100644
@@ -262,6 +262,7 @@ config ACPI_ASUS
  config ACPI_TOSHIBA
  	tristate "Toshiba Laptop Extras"
  	depends on X86
+	select INPUT_POLLDEV
  	select BACKLIGHT_CLASS_DEVICE
  	---help---
  	  This driver adds support for access to certain system settings
drivers/acpi/toshiba_acpi.c +324 -8
index 0a43c8e..9f747d7 100644
@@ -3,6 +3,7 @@
   *
   *
   *  Copyright (C) 2002-2004 John Belmonte
+ *  Copyright (C) 2008 Philip Langdale
   *
   *  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
@@ -33,7 +34,7 @@
   *
   */
 
-#define TOSHIBA_ACPI_VERSION	"0.18"
+#define TOSHIBA_ACPI_VERSION	"0.19"
  #define PROC_INTERFACE_VERSION	1
 
  #include <linux/kernel.h>
@@ -42,6 +43,9 @@
  #include <linux/types.h>
  #include <linux/proc_fs.h>
  #include <linux/backlight.h>
+#include <linux/platform_device.h>
+#include <linux/rfkill.h>
+#include <linux/input-polldev.h>
 
  #include <asm/uaccess.h>
 
@@ -62,6 +66,10 @@ MODULE_LICENSE("GPL");
  #define METHOD_HCI_2		"\\_SB_.VALZ.GHCI"
  #define METHOD_VIDEO_OUT	"\\_SB_.VALX.DSSX"
 
+/* Toshiba ACPI Bluetooth object */
+#define BT_ACPI_OBJECT			"\\_SB_.BT"
+#define BT_ACPI_SOFT_UNBLOCKED_EVENT	0x90
+
  /* Toshiba HCI interface definitions
   *
   * HCI is Toshiba's "Hardware Control Interface" which is supposed to
@@ -90,6 +98,7 @@ MODULE_LICENSE("GPL");
  #define HCI_VIDEO_OUT			0x001c
  #define HCI_HOTKEY_EVENT		0x001e
  #define HCI_LCD_BRIGHTNESS		0x002a
+#define HCI_WIRELESS			0x0056
 
  /* field definitions */
  #define HCI_LCD_BRIGHTNESS_BITS		3
@@ -98,9 +107,14 @@ MODULE_LICENSE("GPL");
  #define HCI_VIDEO_OUT_LCD		0x1
  #define HCI_VIDEO_OUT_CRT		0x2
  #define HCI_VIDEO_OUT_TV		0x4
+#define HCI_WIRELESS_KILL_SWITCH	0x01
+#define HCI_WIRELESS_BT_PRESENT		0x0f
+#define HCI_WIRELESS_BT_ATTACH		0x40
+#define HCI_WIRELESS_BT_POWER		0x80
 
  static const struct acpi_device_id toshiba_device_ids[] = {
  	{"TOS6200", 0},
+	{"TOS6208", 0},
  	{"TOS1900", 0},
  	{"", 0},
  };
@@ -193,7 +207,7 @@ static acpi_status hci_raw(const u32 in[HCI_WORDS], u32 out[HCI_WORDS])
  	return status;
  }
 
-/* common hci tasks (get or set one value)
+/* common hci tasks (get or set one or two value)
   *
   * In addition to the ACPI status, the HCI system returns a result which
   * may be useful (such as "not supported").
@@ -218,6 +232,191 @@ static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result)
  	return status;
  }
 
+static acpi_status hci_write2(u32 reg, u32 in1, u32 in2, u32* result)
+{
+        u32 in[HCI_WORDS] = { HCI_SET, reg, in1, in2, 0, 0 };
+        u32 out[HCI_WORDS];
+        acpi_status status = hci_raw(in, out);
+        *result = (status == AE_OK) ? out[0] : HCI_FAILURE;
+        return status;
+}
+
+static acpi_status hci_read2(u32 reg, u32* out1, u32* out2, u32* result)
+{
+        u32 in[HCI_WORDS] = { HCI_GET, reg, *out1, *out2, 0, 0 };
+        u32 out[HCI_WORDS];
+        acpi_status status = hci_raw(in, out);
+        *out1 = out[2];
+        *out2 = out[3];
+        *result = (status == AE_OK) ? out[0] : HCI_FAILURE;
+        return status;
+}
+
+struct toshiba_acpi_dev {
+	struct platform_device *p_dev;
+	struct rfkill *rfk_dev;
+	struct input_polled_dev *poll_dev;
+
+	const char *bt_name;
+	acpi_handle bt_handle;
+	bool ignore_next_bt_event;
+
+	bool last_rfk_state;
+
+	struct mutex mutex;
+};
+
+static struct toshiba_acpi_dev toshiba_acpi = {
+	.bt_name = "Toshiba Bluetooth",
+	.ignore_next_bt_event = FALSE,
+	.last_rfk_state = FALSE,
+};
+
+/* Bluetooth rfkill handlers */
+
+static u32 hci_get_bt_present(bool *present)
+{
+	u32 hci_result;
+	u32 value, value2;
+	value = 0;
+	value2 = 0;
+	hci_read2(HCI_WIRELESS, &value, &value2, &hci_result);
+	if (hci_result == HCI_SUCCESS) {
+		*present = (value & HCI_WIRELESS_BT_PRESENT) ? TRUE : FALSE;
+	}
+	return hci_result;
+}
+
+static u32 hci_get_bt_on(bool *on)
+{
+	u32 hci_result;
+	u32 value, value2;
+	value = 0;
+	value2 = 0x0001;
+	hci_read2(HCI_WIRELESS, &value, &value2, &hci_result);
+	if (hci_result == HCI_SUCCESS) {
+		*on = (value & HCI_WIRELESS_BT_POWER) &&
+		      (value & HCI_WIRELESS_BT_ATTACH);
+	}
+	return hci_result;
+}
+
+static u32 hci_get_radio_state(bool *radio_state)
+{
+	u32 hci_result;
+	u32 value, value2;
+
+	value = 0;
+	value2 = 0x0001;
+	hci_read2(HCI_WIRELESS, &value, &value2, &hci_result);
+
+	*radio_state = value & HCI_WIRELESS_KILL_SWITCH;
+	return hci_result;
+}
+
+static int bt_rfkill_toggle_radio(void *data, enum rfkill_state state)
+{
+	u32 result1, result2;
+	u32 value;
+
+	struct toshiba_acpi_dev *dev = data;
+
+	value = state == RFKILL_STATE_UNBLOCKED;
+
+	if (state == RFKILL_STATE_SOFT_BLOCKED) {
+		/*
+		 * The ACPI event is emitted on every transition to
+		 * the SOFT_BLOCKED state. We want to respond to
+		 * it by turning the bluetooth device on when going
+		 * from HARD_BLOCKED, but we certainly don't want
+		 * to when going from UNBLOCKED! So we have to explictly
+		 * ignore the next event in that case.
+		 */
+		dev->ignore_next_bt_event = TRUE;
+	}
+
+	mutex_lock(&dev->mutex);
+	hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_POWER, &result1);
+	hci_write2(HCI_WIRELESS, value, HCI_WIRELESS_BT_ATTACH, &result2);
+	mutex_unlock(&dev->mutex);
+
+	if (result1 != HCI_SUCCESS || result2 != HCI_SUCCESS) {
+		return -EFAULT;
+	}
+
+	return 0;
+}
+
+static int bt_rfkill_get_state(void *data, enum rfkill_state *state)
+{
+	u32 hci_result;
+	bool radio_on;
+	bool bt_on;
+
+	hci_result = hci_get_bt_on(&bt_on);
+	if (hci_result != HCI_SUCCESS) {
+		return -EFAULT;
+	}
+
+	hci_result = hci_get_radio_state(&radio_on);
+	if (hci_result != HCI_SUCCESS) {
+		return -EFAULT;
+	}
+
+	if (bt_on) {
+		*state = RFKILL_STATE_UNBLOCKED;
+	} else if (radio_on) {
+		*state = RFKILL_STATE_SOFT_BLOCKED;
+	} else {
+		*state = RFKILL_STATE_HARD_BLOCKED;
+	}
+
+	return 0;
+}
+
+static void bt_acpi_notify(acpi_handle handle, u32 event, void *data)
+{
+	struct toshiba_acpi_dev *dev = data;
+
+	switch (event) {
+	case BT_ACPI_SOFT_UNBLOCKED_EVENT:
+		if (!dev->ignore_next_bt_event) {
+			bt_rfkill_toggle_radio(data, RFKILL_STATE_UNBLOCKED);
+		} else {
+			dev->ignore_next_bt_event = FALSE;
+		}
+		break;
+	default:
+		printk(MY_NOTICE "Unknown event notified on BT object\n");
+		break;
+	}
+}
+
+static void bt_poll_rfkill(struct input_polled_dev *poll_dev)
+{
+	bool state_changed;
+	bool new_rfk_state;
+	bool value;
+	u32 hci_result;
+
+	struct toshiba_acpi_dev *dev = poll_dev->private;
+
+	hci_result = hci_get_radio_state(&value);
+	if (hci_result != HCI_SUCCESS) {
+		return; /* Can't do anything useful */
+	}
+	new_rfk_state = !value;
+
+	mutex_lock(&dev->mutex);
+	state_changed = new_rfk_state != dev->last_rfk_state;
+	dev->last_rfk_state = new_rfk_state;
+	mutex_unlock(&dev->mutex);
+
+	if (unlikely(state_changed)) {
+		input_report_switch(poll_dev->input, SW_RFKILL_ALL, new_rfk_state);
+	}
+}
+
  static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ;
  static struct backlight_device *toshiba_backlight_device;
  static int force_fan;
@@ -547,6 +746,22 @@ static struct backlight_ops toshiba_backlight_data = {
 
  static void toshiba_acpi_exit(void)
  {
+	if (toshiba_acpi.poll_dev) {
+		input_unregister_polled_device(toshiba_acpi.poll_dev);
+		input_free_polled_device(toshiba_acpi.poll_dev);
+	}
+
+	if (toshiba_acpi.bt_handle) {
+		acpi_remove_notify_handler(toshiba_acpi.bt_handle,
+					   ACPI_ALL_NOTIFY,
+					   bt_acpi_notify);
+	}
+
+	if (toshiba_acpi.rfk_dev) {
+		rfkill_unregister(toshiba_acpi.rfk_dev);
+		rfkill_free(toshiba_acpi.rfk_dev);
+	}
+
  	if (toshiba_backlight_device)
  		backlight_device_unregister(toshiba_backlight_device);
 
@@ -555,6 +770,8 @@ static void toshiba_acpi_exit(void)
  	if (toshiba_proc_dir)
  		remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
 
+	platform_device_unregister(toshiba_acpi.p_dev);
+
  	return;
  }
 
@@ -562,6 +779,10 @@ static int __init toshiba_acpi_init(void)
  {
  	acpi_status status = AE_OK;
  	u32 hci_result;
+	bool bt_present;
+	bool bt_on;
+	bool radio_on;
+	int ret = 0;
 
  	if (acpi_disabled)
  		return -ENODEV;
@@ -578,6 +799,18 @@ static int __init toshiba_acpi_init(void)
  	       TOSHIBA_ACPI_VERSION);
  	printk(MY_INFO "    HCI method: %s\n", method_hci);
 
+	mutex_init(&toshiba_acpi.mutex);
+
+	toshiba_acpi.p_dev = platform_device_register_simple("toshiba_acpi",
+							      -1, NULL, 0);
+	if (IS_ERR(toshiba_acpi.p_dev)) {
+		ret = PTR_ERR(toshiba_acpi.p_dev);
+		printk(MY_ERR "unable to register platform device\n");
+		toshiba_acpi.p_dev= NULL;
+		toshiba_acpi_exit();
+		return ret;
+	}
+
  	force_fan = 0;
  	key_event_valid = 0;
 
@@ -586,19 +819,23 @@ static int __init toshiba_acpi_init(void)
 
  	toshiba_proc_dir = proc_mkdir(PROC_TOSHIBA, acpi_root_dir);
  	if (!toshiba_proc_dir) {
-		status = AE_ERROR;
+		toshiba_acpi_exit();
+		return -ENODEV;
  	} else {
  		toshiba_proc_dir->owner = THIS_MODULE;
  		status = add_device();
-		if (ACPI_FAILURE(status))
-			remove_proc_entry(PROC_TOSHIBA, acpi_root_dir);
+		if (ACPI_FAILURE(status)) {
+			toshiba_acpi_exit();
+			return -ENODEV;
+		}
  	}
 
-	toshiba_backlight_device = backlight_device_register("toshiba",NULL,
+	toshiba_backlight_device = backlight_device_register("toshiba",
+						&toshiba_acpi.p_dev->dev,
  						NULL,
  						&toshiba_backlight_data);
          if (IS_ERR(toshiba_backlight_device)) {
-		int ret = PTR_ERR(toshiba_backlight_device);
+		ret = PTR_ERR(toshiba_backlight_device);
 
  		printk(KERN_ERR "Could not register toshiba backlight device\n");
  		toshiba_backlight_device = NULL;
@@ -607,7 +844,86 @@ static int __init toshiba_acpi_init(void)
  	}
          toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1;
 
-	return (ACPI_SUCCESS(status)) ? 0 : -ENODEV;
+	/* Register rfkill switch for Bluetooth */
+	if (hci_get_bt_present(&bt_present) == HCI_SUCCESS && bt_present) {
+		toshiba_acpi.rfk_dev = rfkill_allocate(&toshiba_acpi.p_dev->dev,
+							RFKILL_TYPE_BLUETOOTH);
+		if (!toshiba_acpi.rfk_dev) {
+			printk(MY_ERR "unable to allocate rfkill device\n");
+			toshiba_acpi_exit();
+			return -ENOMEM;
+		}
+
+		toshiba_acpi.rfk_dev->name = toshiba_acpi.bt_name;
+		toshiba_acpi.rfk_dev->state = RFKILL_STATE_OFF;
+		toshiba_acpi.rfk_dev->toggle_radio = bt_rfkill_toggle_radio;
+		toshiba_acpi.rfk_dev->get_state = bt_rfkill_get_state;
+		toshiba_acpi.rfk_dev->user_claim_unsupported = 1;
+		toshiba_acpi.rfk_dev->data = &toshiba_acpi;
+		toshiba_acpi.rfk_dev->dev.class->suspend = NULL;
+		toshiba_acpi.rfk_dev->dev.class->resume = NULL;
+
+		ret = rfkill_register(toshiba_acpi.rfk_dev);
+		if (ret) {
+			printk(MY_ERR "unable to register rfkill device\n");
+			toshiba_acpi_exit();
+			return -ENOMEM;
+		}
+
+		if (hci_get_bt_on(&bt_on) == HCI_SUCCESS && bt_on) {
+			toshiba_acpi.rfk_dev->state = RFKILL_STATE_UNBLOCKED;
+		} else if (hci_get_radio_state(&radio_on) == HCI_SUCCESS && radio_on) {
+			toshiba_acpi.rfk_dev->state = RFKILL_STATE_HARD_BLOCKED;
+		} else {
+			toshiba_acpi.rfk_dev->state = RFKILL_STATE_SOFT_BLOCKED;
+		}
+
+		/* Register for acpi events on BT ACPI object. */
+		status = acpi_get_handle(NULL, BT_ACPI_OBJECT, &toshiba_acpi.bt_handle);
+		if (ACPI_FAILURE(status)) {
+			printk(MY_ERR "unable to find ACPI Bluetooth object\n");
+			toshiba_acpi.bt_handle = NULL;
+			toshiba_acpi_exit();
+			return -ENODEV;
+		}
+		status = acpi_install_notify_handler(toshiba_acpi.bt_handle,
+						     ACPI_ALL_NOTIFY,
+						     bt_acpi_notify,
+						     &toshiba_acpi);
+		if (ACPI_FAILURE(status)) {
+			printk(MY_ERR "unable to register for events "
+				      "on ACPI Bluetooth object\n");
+			toshiba_acpi.bt_handle = NULL;
+			toshiba_acpi_exit();
+			return -ENODEV;
+		}
+	}
+
+	/* Register input device for kill switch */
+	toshiba_acpi.poll_dev = input_allocate_polled_device();
+	if (!toshiba_acpi.poll_dev) {
+		printk(MY_ERR "unable to allocate kill-switch input device\n");
+		toshiba_acpi_exit();
+		return -ENOMEM;
+	}
+	toshiba_acpi.poll_dev->private = &toshiba_acpi;
+	toshiba_acpi.poll_dev->poll = bt_poll_rfkill;
+	toshiba_acpi.poll_dev->poll_interval = 1000; /* msecs */
+
+	toshiba_acpi.poll_dev->input->name = toshiba_acpi.bt_name;
+	toshiba_acpi.poll_dev->input->id.bustype = BUS_HOST;
+	toshiba_acpi.poll_dev->input->id.vendor = 0x0930; /* Toshiba USB Vendor ID */
+	toshiba_acpi.poll_dev->input->evbit[0] = BIT(EV_SW);
+	set_bit(SW_RFKILL_ALL, toshiba_acpi.poll_dev->input->keybit);
+
+	ret = input_register_polled_device(toshiba_acpi.poll_dev);
+	if (ret) {
+		printk(MY_ERR "unable to register kill-switch input device\n");
+		toshiba_acpi_exit();
+		return ret;
+	}
+
+	return 0;
  }
 
  module_init(toshiba_acpi_init);
#2 lore raw
Quoted 3 lines · Ivo van Doorn

You don't seem to be using rfkill_force_state() which is required to inform the rfkill layer about the state changes.

Hmm? According to rfkill.txt, one can either use force_state() or implement the get_state() hook, and I have done the later. If this is not the correct method, can you please explain when I should be using force_state?

--phil

#3 lore raw
Quoted 8 lines · Philip Langdale
Quoted 3 lines · Ivo van Doorn

You don't seem to be using rfkill_force_state() which is required to inform the rfkill layer about the state changes.

Hmm? According to rfkill.txt, one can either use force_state() or implement the get_state() hook, and I have done the later. If this is not the correct method, can you please explain when I should be using force_state?

There is a bunch of rfkill bug fix patches that was not merged in wireless-testing yet (which is a pity, it would be really good if they could go into 2.6.27). One of those patches fixes the docs to make it clear that rfkill_force_state() is the way to go if you have events of any sort.

The big difference from get_state() is that rfkill_force_state() propagates state changes as soon as your driver notices them and sends them to rfkill through rfkill_force_state()... while get_state() can only do that when someone asks rfkill about the current state.

Signature · 4 lines
  "One disk to rule them all, One disk to find them. One disk to bring
  them all and in the darkness grind them. In the Land of Redmond
  where the shadows lie." -- The Silicon Valley Tarot
  Henrique Holschuh
#4 lore raw
Quoted 6 lines · Henrique de Moraes Holschuh
Quoted 5 lines · Philip Langdale
Quoted 3 lines · Ivo van Doorn

You don't seem to be using rfkill_force_state() which is required to inform the rfkill layer about the state changes.

Quoted 7 lines
Quoted 3 lines

Hmm? According to rfkill.txt, one can either use force_state() or implement the get_state() hook, and I have done the later. If this is not the correct method, can you please explain when I should be using force_state?

There is a bunch of rfkill bug fix patches that was not merged in wireless-testing yet (which is a pity, it would be really good if they could go into 2.6.27). One of those patches fixes the docs to make it clear that

Lots of wireless people (including John) were at OLS this past week, so it's not entirely surprising that patch merging might have been slow.

Dan

Quoted 7 lines

rfkill_force_state() is the way to go if you have events of any sort.

The big difference from get_state() is that rfkill_force_state() propagates state changes as soon as your driver notices them and sends them to rfkill through rfkill_force_state()... while get_state() can only do that when someone asks rfkill about the current state.

#5 lore raw
Quoted 10 lines · Dan Williams
Quoted 9 lines · Henrique de Moraes Holschuh
Quoted 7 lines · Philip Langdale
Quoted 2 lines · Ivo van Doorn

You don't seem to be using rfkill_force_state() which is required to inform the rfkill layer about the state changes.

Hmm? According to rfkill.txt, one can either use force_state() or implement the get_state() hook, and I have done the later. If this is not the correct method, can you please explain when I should be using force_state?

Quoted 6 lines
Quoted 3 lines

There is a bunch of rfkill bug fix patches that was not merged in wireless-testing yet (which is a pity, it would be really good if they could go into 2.6.27). One of those patches fixes the docs to make it clear that

Lots of wireless people (including John) were at OLS this past week, so it's not entirely surprising that patch merging might have been slow.

That would explain it, yes... Well, I sure hope this means the patches still have a non-zero chance of being sent to mainline for 2.6.27 :-) I am not up to date on how merges are usually handled in the wireless and net subsystems.

Signature · 4 lines
  "One disk to rule them all, One disk to find them. One disk to bring
  them all and in the darkness grind them. In the Land of Redmond
  where the shadows lie." -- The Silicon Valley Tarot
  Henrique Holschuh
#6 lore raw
Quoted 15 lines · Henrique de Moraes Holschuh
Quoted 14 lines · Dan Williams
Quoted 12 lines · Henrique de Moraes Holschuh
Quoted 7 lines · Philip Langdale
Quoted 2 lines · Ivo van Doorn

You don't seem to be using rfkill_force_state() which is required to inform the rfkill layer about the state changes.

Hmm? According to rfkill.txt, one can either use force_state() or implement the get_state() hook, and I have done the later. If this is not the correct method, can you please explain when I should be using force_state?

There is a bunch of rfkill bug fix patches that was not merged in wireless-testing yet (which is a pity, it would be really good if they could go into 2.6.27). One of those patches fixes the docs to make it clear that

Quoted 7 lines
Quoted 2 lines

Lots of wireless people (including John) were at OLS this past week, so it's not entirely surprising that patch merging might have been slow.

That would explain it, yes... Well, I sure hope this means the patches still have a non-zero chance of being sent to mainline for 2.6.27 :-) I am not up to date on how merges are usually handled in the wireless and net subsystems.

The feature window for 2.6.27 closed with the release of 2.6.26, after that only bugfixes can go in.

Ivo

#7 lore raw
In reply to Henrique de Moraes Holschuh
Quoted 10 lines · Henrique de Moraes Holschuh

There is a bunch of rfkill bug fix patches that was not merged in wireless-testing yet (which is a pity, it would be really good if they could go into 2.6.27). One of those patches fixes the docs to make it clear that rfkill_force_state() is the way to go if you have events of any sort.

The big difference from get_state() is that rfkill_force_state() propagates state changes as soon as your driver notices them and sends them to rfkill through rfkill_force_state()... while get_state() can only do that when someone asks rfkill about the current state.

Fair enough. I'll update the patch when I get some time. And good point on how to react to the event; dunno what I was(n't) thinking. :-)

--phil