On Thu, 2007-05-31 at 13:53 +0100, Bastien Nocera wrote: > On Thu, 2007-05-31 at 13:36 +0100, Richard Hughes wrote: > > Attached patch adds a kernel thread to do polling on Toshiba hardware. > > > > Toshiba hardware is a little oddball, and does not provide ACPI events > > on some key presses, typically Fn hotkey buttons. The key interface is > > now polled, and events now matched to a list of toshiba specific > > scancodes, and are squirted to userspace using the INPUT subsystem. > > > > This means that toshiba laptops buttons "just work" without any > > userspace daemon (using uinput) such as fnfx or bodges such as using a > > userspace hal addon. Doing the polling in kernel is more efficient, and > > makes stuff just work out of the box. You can assign the keys using > > standard X keymaps, or using tools such as gnome-keybinding-properties. > > > > This is similar to other patches sent for the thinkpad_acpi driver, and > > is part of the "Unf*ck my keyboard" initiative to make multimedia keys > > just work. > > > > Changes from the first patch involve switching to a workqueue for the > > polling, not breaking the spaces in "hotkeys_via_input" and also masking > > out the fn key button up. > > A couple of things: > - use a switch statement in toshiba_deliver_button_event(), would look a > bit cleaner. Agree, done. > - make the magic number #defines Agree, done. > - not sure if it's possible, but you should disable the workqueue > altogether if nothing has the input device opened. Do we get an event when the [input]->users value changes? If not, we could do 1Hz (users > 0) checking when the input device is unclaimed, and then switch to 4Hz polling when the input device is open. > Hopefully we'll find a way to receive an interrupt, or some kind of > event when the keys are pressed in the future, and completely avoid the > polling. In the meantime, it should be minimised. Nope, impossible AFAICS. The hardware is just broken. Windows XP has an toshiba supplied daemon that polls, so I think we have to just bite the bullet. New patch attached. Richard.
diff --git a/drivers/acpi/toshiba_acpi.c b/drivers/acpi/toshiba_acpi.c index 3906d47..cf4ab76 100644 --- a/drivers/acpi/toshiba_acpi.c +++ b/drivers/acpi/toshiba_acpi.c @@ -27,13 +27,13 @@ * engineering the Windows drivers * Yasushi Nagato - changes for linux kernel 2.4 -> 2.5 * Rob Miller - TV out and hotkeys help - * + * Richard Hughes - emit INPUT events, based on a patch from Daniel Silverstone * * TODO * */ -#define TOSHIBA_ACPI_VERSION "0.18" +#define TOSHIBA_ACPI_VERSION "0.19" #define PROC_INTERFACE_VERSION 1 #include <linux/kernel.h> @@ -42,6 +42,8 @@ #include <linux/types.h> #include <linux/proc_fs.h> #include <linux/backlight.h> +#include <linux/input.h> +#include <linux/workqueue.h> #include <asm/uaccess.h> @@ -99,6 +101,16 @@ MODULE_LICENSE("GPL"); #define HCI_VIDEO_OUT_CRT 0x2 #define HCI_VIDEO_OUT_TV 0x4 +/* key definitions */ +#define HCI_HKEY_MUTE 0x0101 +#define HCI_HKEY_BREAK 0x013b +#define HCI_HKEY_SEARCH 0x013c +#define HCI_HKEY_SUSPEND 0x013d +#define HCI_HKEY_HIBERNATE 0x013e +#define HCI_HKEY_BRIGHTNESSDOWN 0x0140 +#define HCI_HKEY_BRIGHTNESSUP 0x0141 +#define HCI_HKEY_WLAN 0x0142 + /* utility */ @@ -213,9 +225,15 @@ static acpi_status hci_read1(u32 reg, u32 * out1, u32 * result) static struct proc_dir_entry *toshiba_proc_dir /*= 0*/ ; static struct backlight_device *toshiba_backlight_device; +static struct input_dev *toshiba_input; static int force_fan; static int last_key_event; static int key_event_valid; +static int hotkeys_over_input = 1; +static int hotkeys_check_per_sec = 2; + +module_param(hotkeys_over_input, uint, 0400); +module_param(hotkeys_check_per_sec, uint, 0400); typedef struct _ProcItem { const char *name; @@ -443,27 +461,33 @@ static char *read_keys(char *p) u32 hci_result; u32 value; - if (!key_event_valid) { - hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); - if (hci_result == HCI_SUCCESS) { - key_event_valid = 1; - last_key_event = value; - } else if (hci_result == HCI_EMPTY) { - /* better luck next time */ - } else if (hci_result == HCI_NOT_SUPPORTED) { - /* This is a workaround for an unresolved issue on - * some machines where system events sporadically - * become disabled. */ - hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); - printk(MY_NOTICE "Re-enabled hotkeys\n"); - } else { - printk(MY_ERR "Error reading hotkey status\n"); - goto end; + if (!hotkeys_over_input) { + if (!key_event_valid) { + hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); + if (hci_result == HCI_SUCCESS) { + key_event_valid = 1; + last_key_event = value; + } else if (hci_result == HCI_EMPTY) { + /* better luck next time */ + } else if (hci_result == HCI_NOT_SUPPORTED) { + /* This is a workaround for an unresolved issue on + * some machines where system events sporadically + * become disabled. */ + hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); + printk(MY_NOTICE "Re-enabled hotkeys\n"); + } else { + printk(MY_ERR "Error reading hotkey status\n"); + goto end; + } } + } else { + key_event_valid = 0; + last_key_event = 0; } p += sprintf(p, "hotkey_ready: %d\n", key_event_valid); p += sprintf(p, "hotkey: 0x%04x\n", last_key_event); + p += sprintf(p, "hotkeys_via_input: %d\n", hotkeys_over_input); end: return p; @@ -534,15 +558,133 @@ static acpi_status __exit remove_device(void) } static struct backlight_ops toshiba_backlight_data = { - .get_brightness = get_lcd, - .update_status = set_lcd_status, + .get_brightness = get_lcd, + .update_status = set_lcd_status, }; +static void toshiba_keys_fire(struct work_struct *work); +static struct workqueue_struct *toshiba_keys_wq; +static DECLARE_DELAYED_WORK(toshiba_keys_work, toshiba_keys_fire); + +static void toshiba_deliver_button_event(u32 value) +{ + int keycode = KEY_UNKNOWN; + int key_down = 1; + + if (!toshiba_input) + return; + + /* translate MSB to key up */ + if (value & 0x80) { + value &= ~0x80; + key_down = 0; + } + + /* ignore FN on its own */ + if (value == 0x0100) + return; + + /* translate keys to keycodes */ + switch (value) { + case HCI_HKEY_MUTE: + keycode = KEY_MUTE; + break; + case HCI_HKEY_BREAK: + keycode = KEY_BREAK; + break; + case HCI_HKEY_SEARCH: + keycode = KEY_SEARCH; + break; + case HCI_HKEY_SUSPEND: + keycode = KEY_SLEEP; + break; + case HCI_HKEY_HIBERNATE: + keycode = KEY_SUSPEND; + break; + case HCI_HKEY_BRIGHTNESSDOWN: + keycode = KEY_BRIGHTNESSDOWN; + break; + case HCI_HKEY_BRIGHTNESSUP: + keycode = KEY_BRIGHTNESSUP; + break; + case HCI_HKEY_WLAN: + keycode = KEY_WLAN; + break; + default: + keycode = KEY_UNKNOWN; + } + + if (keycode != KEY_UNKNOWN) { + printk(MY_INFO "mapped hkey %i to keycode %i [%i]\n", value, keycode, key_down); + input_report_key(toshiba_input, keycode, key_down); + input_sync(toshiba_input); + } +} + +static void toshiba_keys_clear(void) +{ + int dropped = 0; + int clear_queue = 0; + u32 hci_result, value; + + do { + /* We don't want to get stuck here; older toshibas such as the + * A60 may load and then return junk during the hci_read */ + if (clear_queue++ > 16) + break; + + hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); + if (hci_result == HCI_SUCCESS) { + dropped++; + } else if (hci_result == HCI_EMPTY) { + /* better luck next time */ + } else if (hci_result == HCI_NOT_SUPPORTED) { + /* This is a workaround for an unresolved issue on + * some machines where system events sporadically + * become disabled. */ + hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); + printk(MY_NOTICE "Re-enabled hotkeys\n"); + } + } while (hci_result != HCI_EMPTY); + + printk(MY_INFO "Dropped %d keys from the queue on startup\n", dropped); +} + +static void toshiba_keys_fire(struct work_struct *work) +{ + u32 hci_result, value; + + do { + hci_read1(HCI_SYSTEM_EVENT, &value, &hci_result); + if (hci_result == HCI_SUCCESS) { + /* we got a button event */ + toshiba_deliver_button_event(value); + } else if (hci_result == HCI_EMPTY) { + /* better luck next time */ + } else if (hci_result == HCI_NOT_SUPPORTED) { + /* This is a workaround for an + * unresolved issue on some machines + * where system events sporadically + * become disabled. */ + hci_write1(HCI_SYSTEM_EVENT, 1, &hci_result); + printk(MY_NOTICE "Re-enabled hotkeys\n"); + } + } while (hci_result == HCI_SUCCESS); + + /* reschedule another check */ + queue_delayed_work(toshiba_keys_wq, &toshiba_keys_work, HZ / hotkeys_check_per_sec); +} + static void __exit toshiba_acpi_exit(void) { if (toshiba_backlight_device) backlight_device_unregister(toshiba_backlight_device); + if (toshiba_keys_wq) { + cancel_delayed_work(&toshiba_keys_work); + destroy_workqueue(toshiba_keys_wq); + } + remove_device(); if (toshiba_proc_dir) @@ -551,6 +693,45 @@ static void __exit toshiba_acpi_exit(void) return; } +static int __init toshiba_input_init(void) +{ + int error; + + /* use INPUT for key events */ + toshiba_input = input_allocate_device(); + if (!toshiba_input) { + error = -ENOMEM; + goto err_no_mem; + } + + toshiba_keys_clear(); + + /* create one 'keyboard' virtual input device for all the acpi events */ + toshiba_input->name = "Toshiba Extra Buttons"; + toshiba_input->phys = "toshiba/input0"; + toshiba_input->id.bustype = BUS_HOST; + toshiba_input->id.product = 0x0001; + toshiba_input->evbit[0] = BIT(EV_KEY); + set_bit(KEY_MUTE, toshiba_input->keybit); + set_bit(KEY_BREAK, toshiba_input->keybit); /* probably should be KEY_LOCK */ + set_bit(KEY_SEARCH, toshiba_input->keybit); + set_bit(KEY_SUSPEND, toshiba_input->keybit); + set_bit(KEY_SLEEP, toshiba_input->keybit); + set_bit(KEY_BRIGHTNESSDOWN, toshiba_input->keybit); + set_bit(KEY_BRIGHTNESSUP, toshiba_input->keybit); + set_bit(KEY_WLAN, toshiba_input->keybit); + + error = input_register_device(toshiba_input); + if (error) + goto err_free_input; + return 0; + +err_free_input: + input_free_device(toshiba_input); +err_no_mem: + return error; +} + static int __init toshiba_acpi_init(void) { acpi_status status = AE_OK; @@ -590,12 +771,38 @@ static int __init toshiba_acpi_init(void) toshiba_backlight_device = backlight_device_register("toshiba",NULL, NULL, &toshiba_backlight_data); - if (IS_ERR(toshiba_backlight_device)) { + if (IS_ERR(toshiba_backlight_device)) { printk(KERN_ERR "Could not register toshiba backlight device\n"); toshiba_backlight_device = NULL; toshiba_acpi_exit(); } - toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; + toshiba_backlight_device->props.max_brightness = HCI_LCD_BRIGHTNESS_LEVELS - 1; + + /* we have to poll the device as we do not get events */ + if (hotkeys_over_input && hotkeys_check_per_sec > 0) { + + toshiba_input_init (); + /* just abort */ + if (!toshiba_input) { + printk(KERN_ERR "could not allocate input device\n"); + toshiba_acpi_exit(); + } + + /* setup workqueue */ + toshiba_keys_wq = create_singlethread_workqueue("ktoshkeyd"); + if (!toshiba_keys_wq) { + printk(KERN_ERR "failed to create workqueue\n"); + toshiba_acpi_exit(); + } + + /* sanitise to something sane */ + if (hotkeys_check_per_sec > 10) + hotkeys_check_per_sec = 10; + printk(KERN_INFO "ktoshkeyd checks per second : %d\n", hotkeys_check_per_sec); + + /* start polling after delay */ + queue_delayed_work(toshiba_keys_wq, &toshiba_keys_work, HZ / hotkeys_check_per_sec); + } return (ACPI_SUCCESS(status)) ? 0 : -ENODEV; }