| /* | 
 |  *  Button Hotplug driver | 
 |  * | 
 |  *  Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> | 
 |  * | 
 |  *  Based on the diag.c - GPIO interface driver for Broadcom boards | 
 |  *    Copyright (C) 2006 Mike Baker <mbm@openwrt.org>, | 
 |  *    Copyright (C) 2006-2007 Felix Fietkau <nbd@nbd.name> | 
 |  *    Copyright (C) 2008 Andy Boyett <agb@openwrt.org> | 
 |  * | 
 |  *  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/version.h> | 
 | #include <linux/kmod.h> | 
 | #include <linux/input.h> | 
 |  | 
 | #include <linux/workqueue.h> | 
 | #include <linux/skbuff.h> | 
 | #include <linux/netlink.h> | 
 | #include <linux/kobject.h> | 
 |  | 
 | #define DRV_NAME	"button-hotplug" | 
 | #define DRV_VERSION	"0.4.1" | 
 | #define DRV_DESC	"Button Hotplug driver" | 
 |  | 
 | #define BH_SKB_SIZE	2048 | 
 |  | 
 | #define PFX	DRV_NAME ": " | 
 |  | 
 | #undef BH_DEBUG | 
 |  | 
 | #ifdef BH_DEBUG | 
 | #define BH_DBG(fmt, args...) printk(KERN_DEBUG "%s: " fmt, DRV_NAME, ##args ) | 
 | #else | 
 | #define BH_DBG(fmt, args...) do {} while (0) | 
 | #endif | 
 |  | 
 | #define BH_ERR(fmt, args...) printk(KERN_ERR "%s: " fmt, DRV_NAME, ##args ) | 
 |  | 
 | #ifndef BIT_MASK | 
 | #define BIT_MASK(nr)            (1UL << ((nr) % BITS_PER_LONG)) | 
 | #endif | 
 |  | 
 | struct bh_priv { | 
 | 	unsigned long		*seen; | 
 | 	struct input_handle	handle; | 
 | }; | 
 |  | 
 | struct bh_event { | 
 | 	const char		*name; | 
 | 	char			*action; | 
 | 	unsigned long		seen; | 
 |  | 
 | 	struct sk_buff		*skb; | 
 | 	struct work_struct	work; | 
 | }; | 
 |  | 
 | struct bh_map { | 
 | 	unsigned int	code; | 
 | 	const char	*name; | 
 | }; | 
 |  | 
 | extern u64 uevent_next_seqnum(void); | 
 |  | 
 | #define BH_MAP(_code, _name)		\ | 
 | 	{				\ | 
 | 		.code = (_code),	\ | 
 | 		.name = (_name),	\ | 
 | 	} | 
 |  | 
 | static struct bh_map button_map[] = { | 
 | 	BH_MAP(BTN_0,		"BTN_0"), | 
 | 	BH_MAP(BTN_1,		"BTN_1"), | 
 | 	BH_MAP(BTN_2,		"BTN_2"), | 
 | 	BH_MAP(BTN_3,		"BTN_3"), | 
 | 	BH_MAP(BTN_4,		"BTN_4"), | 
 | 	BH_MAP(BTN_5,		"BTN_5"), | 
 | 	BH_MAP(BTN_6,		"BTN_6"), | 
 | 	BH_MAP(BTN_7,		"BTN_7"), | 
 | 	BH_MAP(BTN_8,		"BTN_8"), | 
 | 	BH_MAP(BTN_9,		"BTN_9"), | 
 | 	BH_MAP(KEY_RESTART,	"reset"), | 
 | 	BH_MAP(KEY_POWER,	"power"), | 
 | 	BH_MAP(KEY_POWER2,	"reboot"), | 
 | 	BH_MAP(KEY_RFKILL,	"rfkill"), | 
 | 	BH_MAP(KEY_WPS_BUTTON,	"wps"), | 
 | 	BH_MAP(KEY_WIMAX,	"wwan"), | 
 | }; | 
 |  | 
 | /* -------------------------------------------------------------------------*/ | 
 |  | 
 | static int bh_event_add_var(struct bh_event *event, int argv, | 
 | 		const char *format, ...) | 
 | { | 
 | 	static char buf[128]; | 
 | 	char *s; | 
 | 	va_list args; | 
 | 	int len; | 
 |  | 
 | 	if (argv) | 
 | 		return 0; | 
 |  | 
 | 	va_start(args, format); | 
 | 	len = vsnprintf(buf, sizeof(buf), format, args); | 
 | 	va_end(args); | 
 |  | 
 | 	if (len >= sizeof(buf)) { | 
 | 		BH_ERR("buffer size too small\n"); | 
 | 		WARN_ON(1); | 
 | 		return -ENOMEM; | 
 | 	} | 
 |  | 
 | 	s = skb_put(event->skb, len + 1); | 
 | 	strcpy(s, buf); | 
 |  | 
 | 	BH_DBG("added variable '%s'\n", s); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | static int button_hotplug_fill_event(struct bh_event *event) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	ret = bh_event_add_var(event, 0, "HOME=%s", "/"); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = bh_event_add_var(event, 0, "PATH=%s", | 
 | 					"/sbin:/bin:/usr/sbin:/usr/bin"); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = bh_event_add_var(event, 0, "SUBSYSTEM=%s", "button"); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = bh_event_add_var(event, 0, "ACTION=%s", event->action); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = bh_event_add_var(event, 0, "BUTTON=%s", event->name); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = bh_event_add_var(event, 0, "SEEN=%ld", event->seen); | 
 | 	if (ret) | 
 | 		return ret; | 
 |  | 
 | 	ret = bh_event_add_var(event, 0, "SEQNUM=%llu", uevent_next_seqnum()); | 
 |  | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void button_hotplug_work(struct work_struct *work) | 
 | { | 
 | 	struct bh_event *event = container_of(work, struct bh_event, work); | 
 | 	int ret = 0; | 
 |  | 
 | 	event->skb = alloc_skb(BH_SKB_SIZE, GFP_KERNEL); | 
 | 	if (!event->skb) | 
 | 		goto out_free_event; | 
 |  | 
 | 	ret = bh_event_add_var(event, 0, "%s@", event->action); | 
 | 	if (ret) | 
 | 		goto out_free_skb; | 
 |  | 
 | 	ret = button_hotplug_fill_event(event); | 
 | 	if (ret) | 
 | 		goto out_free_skb; | 
 |  | 
 | 	NETLINK_CB(event->skb).dst_group = 1; | 
 | 	broadcast_uevent(event->skb, 0, 1, GFP_KERNEL); | 
 |  | 
 |  out_free_skb: | 
 | 	if (ret) { | 
 | 		BH_ERR("work error %d\n", ret); | 
 | 		kfree_skb(event->skb); | 
 | 	} | 
 |  out_free_event: | 
 | 	kfree(event); | 
 | } | 
 |  | 
 | static int button_hotplug_create_event(const char *name, unsigned long seen, | 
 | 		int pressed) | 
 | { | 
 | 	struct bh_event *event; | 
 |  | 
 | 	BH_DBG("create event, name=%s, seen=%lu, pressed=%d\n", | 
 | 		name, seen, pressed); | 
 |  | 
 | 	event = kzalloc(sizeof(*event), GFP_KERNEL); | 
 | 	if (!event) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	event->name = name; | 
 | 	event->seen = seen; | 
 | 	event->action = pressed ? "pressed" : "released"; | 
 |  | 
 | 	INIT_WORK(&event->work, (void *)(void *)button_hotplug_work); | 
 | 	schedule_work(&event->work); | 
 |  | 
 | 	return 0; | 
 | } | 
 |  | 
 | /* -------------------------------------------------------------------------*/ | 
 |  | 
 | static int button_get_index(unsigned int code) | 
 | { | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < ARRAY_SIZE(button_map); i++) | 
 | 		if (button_map[i].code == code) | 
 | 			return i; | 
 |  | 
 | 	return -1; | 
 | } | 
 | static void button_hotplug_event(struct input_handle *handle, | 
 | 			   unsigned int type, unsigned int code, int value) | 
 | { | 
 | 	struct bh_priv *priv = handle->private; | 
 | 	unsigned long seen = jiffies; | 
 | 	int btn; | 
 |  | 
 | 	BH_DBG("event type=%u, code=%u, value=%d\n", type, code, value); | 
 |  | 
 | 	if (type != EV_KEY) | 
 | 		return; | 
 |  | 
 | 	btn = button_get_index(code); | 
 | 	if (btn < 0) | 
 | 		return; | 
 |  | 
 | 	button_hotplug_create_event(button_map[btn].name, | 
 | 			(seen - priv->seen[btn]) / HZ, value); | 
 | 	priv->seen[btn] = seen; | 
 | } | 
 |  | 
 | static int button_hotplug_connect(struct input_handler *handler, | 
 | 		struct input_dev *dev, const struct input_device_id *id) | 
 | { | 
 | 	struct bh_priv *priv; | 
 | 	int ret; | 
 | 	int i; | 
 |  | 
 | 	for (i = 0; i < ARRAY_SIZE(button_map); i++) | 
 | 		if (test_bit(button_map[i].code, dev->keybit)) | 
 | 			break; | 
 |  | 
 | 	if (i == ARRAY_SIZE(button_map)) | 
 | 		return -ENODEV; | 
 |  | 
 | 	priv = kzalloc(sizeof(*priv) + | 
 | 		       (sizeof(unsigned long) * ARRAY_SIZE(button_map)), | 
 | 		       GFP_KERNEL); | 
 | 	if (!priv) | 
 | 		return -ENOMEM; | 
 |  | 
 | 	priv->seen = (unsigned long *) &priv[1]; | 
 | 	priv->handle.private = priv; | 
 | 	priv->handle.dev = dev; | 
 | 	priv->handle.handler = handler; | 
 | 	priv->handle.name = DRV_NAME; | 
 |  | 
 | 	ret = input_register_handle(&priv->handle); | 
 | 	if (ret) | 
 | 		goto err_free_priv; | 
 |  | 
 | 	ret = input_open_device(&priv->handle); | 
 | 	if (ret) | 
 | 		goto err_unregister_handle; | 
 |  | 
 | 	BH_DBG("connected to %s\n", dev->name); | 
 |  | 
 | 	return 0; | 
 |  | 
 |  err_unregister_handle: | 
 | 	input_unregister_handle(&priv->handle); | 
 |  | 
 |  err_free_priv: | 
 | 	kfree(priv); | 
 | 	return ret; | 
 | } | 
 |  | 
 | static void button_hotplug_disconnect(struct input_handle *handle) | 
 | { | 
 | 	struct bh_priv *priv = handle->private; | 
 |  | 
 | 	input_close_device(handle); | 
 | 	input_unregister_handle(handle); | 
 |  | 
 | 	kfree(priv); | 
 | } | 
 |  | 
 | static const struct input_device_id button_hotplug_ids[] = { | 
 | 	{ | 
 |                 .flags = INPUT_DEVICE_ID_MATCH_EVBIT, | 
 |                 .evbit = { BIT_MASK(EV_KEY) }, | 
 |         }, | 
 | 	{ | 
 | 		/* Terminating entry */ | 
 | 	}, | 
 | }; | 
 |  | 
 | MODULE_DEVICE_TABLE(input, button_hotplug_ids); | 
 |  | 
 | static struct input_handler button_hotplug_handler = { | 
 | 	.event =	button_hotplug_event, | 
 | 	.connect =	button_hotplug_connect, | 
 | 	.disconnect =	button_hotplug_disconnect, | 
 | 	.name =		DRV_NAME, | 
 | 	.id_table =	button_hotplug_ids, | 
 | }; | 
 |  | 
 | /* -------------------------------------------------------------------------*/ | 
 |  | 
 | static int __init button_hotplug_init(void) | 
 | { | 
 | 	int ret; | 
 |  | 
 | 	printk(KERN_INFO DRV_DESC " version " DRV_VERSION "\n"); | 
 | 	ret = input_register_handler(&button_hotplug_handler); | 
 | 	if (ret) | 
 | 		BH_ERR("unable to register input handler\n"); | 
 |  | 
 | 	return ret; | 
 | } | 
 | module_init(button_hotplug_init); | 
 |  | 
 | static void __exit button_hotplug_exit(void) | 
 | { | 
 | 	input_unregister_handler(&button_hotplug_handler); | 
 | } | 
 | module_exit(button_hotplug_exit); | 
 |  | 
 | MODULE_DESCRIPTION(DRV_DESC); | 
 | MODULE_VERSION(DRV_VERSION); | 
 | MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); | 
 | MODULE_LICENSE("GPL v2"); | 
 |  |