OpenWrt – Blame information for rev 2
?pathlinks?
Rev | Author | Line No. | Line |
---|---|---|---|
1 | office | 1 | /* |
2 | * Button Hotplug driver |
||
3 | * |
||
4 | * Copyright (C) 2008-2010 Gabor Juhos <juhosg@openwrt.org> |
||
5 | * |
||
6 | * Based on the diag.c - GPIO interface driver for Broadcom boards |
||
7 | * Copyright (C) 2006 Mike Baker <mbm@openwrt.org>, |
||
8 | * Copyright (C) 2006-2007 Felix Fietkau <nbd@nbd.name> |
||
9 | * Copyright (C) 2008 Andy Boyett <agb@openwrt.org> |
||
10 | * |
||
11 | * This program is free software; you can redistribute it and/or modify it |
||
12 | * under the terms of the GNU General Public License version 2 as published |
||
13 | * by the Free Software Foundation. |
||
14 | */ |
||
15 | |||
16 | #include <linux/module.h> |
||
17 | #include <linux/version.h> |
||
18 | #include <linux/kmod.h> |
||
19 | #include <linux/input.h> |
||
20 | |||
21 | #include <linux/workqueue.h> |
||
22 | #include <linux/skbuff.h> |
||
23 | #include <linux/netlink.h> |
||
24 | #include <linux/kobject.h> |
||
25 | |||
26 | #define DRV_NAME "button-hotplug" |
||
27 | #define DRV_VERSION "0.4.1" |
||
28 | #define DRV_DESC "Button Hotplug driver" |
||
29 | |||
30 | #define BH_SKB_SIZE 2048 |
||
31 | |||
32 | #define PFX DRV_NAME ": " |
||
33 | |||
34 | #undef BH_DEBUG |
||
35 | |||
36 | #ifdef BH_DEBUG |
||
37 | #define BH_DBG(fmt, args...) printk(KERN_DEBUG "%s: " fmt, DRV_NAME, ##args ) |
||
38 | #else |
||
39 | #define BH_DBG(fmt, args...) do {} while (0) |
||
40 | #endif |
||
41 | |||
42 | #define BH_ERR(fmt, args...) printk(KERN_ERR "%s: " fmt, DRV_NAME, ##args ) |
||
43 | |||
44 | #ifndef BIT_MASK |
||
45 | #define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) |
||
46 | #endif |
||
47 | |||
48 | struct bh_priv { |
||
49 | unsigned long *seen; |
||
50 | struct input_handle handle; |
||
51 | }; |
||
52 | |||
53 | struct bh_event { |
||
54 | const char *name; |
||
55 | char *action; |
||
56 | unsigned long seen; |
||
57 | |||
58 | struct sk_buff *skb; |
||
59 | struct work_struct work; |
||
60 | }; |
||
61 | |||
62 | struct bh_map { |
||
63 | unsigned int code; |
||
64 | const char *name; |
||
65 | }; |
||
66 | |||
67 | extern u64 uevent_next_seqnum(void); |
||
68 | |||
69 | #define BH_MAP(_code, _name) \ |
||
70 | { \ |
||
71 | .code = (_code), \ |
||
72 | .name = (_name), \ |
||
73 | } |
||
74 | |||
75 | static struct bh_map button_map[] = { |
||
76 | BH_MAP(BTN_0, "BTN_0"), |
||
77 | BH_MAP(BTN_1, "BTN_1"), |
||
78 | BH_MAP(BTN_2, "BTN_2"), |
||
79 | BH_MAP(BTN_3, "BTN_3"), |
||
80 | BH_MAP(BTN_4, "BTN_4"), |
||
81 | BH_MAP(BTN_5, "BTN_5"), |
||
82 | BH_MAP(BTN_6, "BTN_6"), |
||
83 | BH_MAP(BTN_7, "BTN_7"), |
||
84 | BH_MAP(BTN_8, "BTN_8"), |
||
85 | BH_MAP(BTN_9, "BTN_9"), |
||
86 | BH_MAP(KEY_RESTART, "reset"), |
||
87 | BH_MAP(KEY_POWER, "power"), |
||
88 | BH_MAP(KEY_RFKILL, "rfkill"), |
||
89 | BH_MAP(KEY_WPS_BUTTON, "wps"), |
||
90 | BH_MAP(KEY_WIMAX, "wwan"), |
||
91 | }; |
||
92 | |||
93 | /* -------------------------------------------------------------------------*/ |
||
94 | |||
95 | static int bh_event_add_var(struct bh_event *event, int argv, |
||
96 | const char *format, ...) |
||
97 | { |
||
98 | static char buf[128]; |
||
99 | char *s; |
||
100 | va_list args; |
||
101 | int len; |
||
102 | |||
103 | if (argv) |
||
104 | return 0; |
||
105 | |||
106 | va_start(args, format); |
||
107 | len = vsnprintf(buf, sizeof(buf), format, args); |
||
108 | va_end(args); |
||
109 | |||
110 | if (len >= sizeof(buf)) { |
||
111 | BH_ERR("buffer size too small\n"); |
||
112 | WARN_ON(1); |
||
113 | return -ENOMEM; |
||
114 | } |
||
115 | |||
116 | s = skb_put(event->skb, len + 1); |
||
117 | strcpy(s, buf); |
||
118 | |||
119 | BH_DBG("added variable '%s'\n", s); |
||
120 | |||
121 | return 0; |
||
122 | } |
||
123 | |||
124 | static int button_hotplug_fill_event(struct bh_event *event) |
||
125 | { |
||
126 | int ret; |
||
127 | |||
128 | ret = bh_event_add_var(event, 0, "HOME=%s", "/"); |
||
129 | if (ret) |
||
130 | return ret; |
||
131 | |||
132 | ret = bh_event_add_var(event, 0, "PATH=%s", |
||
133 | "/sbin:/bin:/usr/sbin:/usr/bin"); |
||
134 | if (ret) |
||
135 | return ret; |
||
136 | |||
137 | ret = bh_event_add_var(event, 0, "SUBSYSTEM=%s", "button"); |
||
138 | if (ret) |
||
139 | return ret; |
||
140 | |||
141 | ret = bh_event_add_var(event, 0, "ACTION=%s", event->action); |
||
142 | if (ret) |
||
143 | return ret; |
||
144 | |||
145 | ret = bh_event_add_var(event, 0, "BUTTON=%s", event->name); |
||
146 | if (ret) |
||
147 | return ret; |
||
148 | |||
149 | ret = bh_event_add_var(event, 0, "SEEN=%ld", event->seen); |
||
150 | if (ret) |
||
151 | return ret; |
||
152 | |||
153 | ret = bh_event_add_var(event, 0, "SEQNUM=%llu", uevent_next_seqnum()); |
||
154 | |||
155 | return ret; |
||
156 | } |
||
157 | |||
158 | static void button_hotplug_work(struct work_struct *work) |
||
159 | { |
||
160 | struct bh_event *event = container_of(work, struct bh_event, work); |
||
161 | int ret = 0; |
||
162 | |||
163 | event->skb = alloc_skb(BH_SKB_SIZE, GFP_KERNEL); |
||
164 | if (!event->skb) |
||
165 | goto out_free_event; |
||
166 | |||
167 | ret = bh_event_add_var(event, 0, "%s@", event->action); |
||
168 | if (ret) |
||
169 | goto out_free_skb; |
||
170 | |||
171 | ret = button_hotplug_fill_event(event); |
||
172 | if (ret) |
||
173 | goto out_free_skb; |
||
174 | |||
175 | NETLINK_CB(event->skb).dst_group = 1; |
||
176 | broadcast_uevent(event->skb, 0, 1, GFP_KERNEL); |
||
177 | |||
178 | out_free_skb: |
||
179 | if (ret) { |
||
180 | BH_ERR("work error %d\n", ret); |
||
181 | kfree_skb(event->skb); |
||
182 | } |
||
183 | out_free_event: |
||
184 | kfree(event); |
||
185 | } |
||
186 | |||
187 | static int button_hotplug_create_event(const char *name, unsigned long seen, |
||
188 | int pressed) |
||
189 | { |
||
190 | struct bh_event *event; |
||
191 | |||
192 | BH_DBG("create event, name=%s, seen=%lu, pressed=%d\n", |
||
193 | name, seen, pressed); |
||
194 | |||
195 | event = kzalloc(sizeof(*event), GFP_KERNEL); |
||
196 | if (!event) |
||
197 | return -ENOMEM; |
||
198 | |||
199 | event->name = name; |
||
200 | event->seen = seen; |
||
201 | event->action = pressed ? "pressed" : "released"; |
||
202 | |||
203 | INIT_WORK(&event->work, (void *)(void *)button_hotplug_work); |
||
204 | schedule_work(&event->work); |
||
205 | |||
206 | return 0; |
||
207 | } |
||
208 | |||
209 | /* -------------------------------------------------------------------------*/ |
||
210 | |||
211 | static int button_get_index(unsigned int code) |
||
212 | { |
||
213 | int i; |
||
214 | |||
215 | for (i = 0; i < ARRAY_SIZE(button_map); i++) |
||
216 | if (button_map[i].code == code) |
||
217 | return i; |
||
218 | |||
219 | return -1; |
||
220 | } |
||
221 | static void button_hotplug_event(struct input_handle *handle, |
||
222 | unsigned int type, unsigned int code, int value) |
||
223 | { |
||
224 | struct bh_priv *priv = handle->private; |
||
225 | unsigned long seen = jiffies; |
||
226 | int btn; |
||
227 | |||
228 | BH_DBG("event type=%u, code=%u, value=%d\n", type, code, value); |
||
229 | |||
230 | if (type != EV_KEY) |
||
231 | return; |
||
232 | |||
233 | btn = button_get_index(code); |
||
234 | if (btn < 0) |
||
235 | return; |
||
236 | |||
237 | button_hotplug_create_event(button_map[btn].name, |
||
238 | (seen - priv->seen[btn]) / HZ, value); |
||
239 | priv->seen[btn] = seen; |
||
240 | } |
||
241 | |||
242 | static int button_hotplug_connect(struct input_handler *handler, |
||
243 | struct input_dev *dev, const struct input_device_id *id) |
||
244 | { |
||
245 | struct bh_priv *priv; |
||
246 | int ret; |
||
247 | int i; |
||
248 | |||
249 | for (i = 0; i < ARRAY_SIZE(button_map); i++) |
||
250 | if (test_bit(button_map[i].code, dev->keybit)) |
||
251 | break; |
||
252 | |||
253 | if (i == ARRAY_SIZE(button_map)) |
||
254 | return -ENODEV; |
||
255 | |||
256 | priv = kzalloc(sizeof(*priv) + |
||
257 | (sizeof(unsigned long) * ARRAY_SIZE(button_map)), |
||
258 | GFP_KERNEL); |
||
259 | if (!priv) |
||
260 | return -ENOMEM; |
||
261 | |||
262 | priv->seen = (unsigned long *) &priv[1]; |
||
263 | priv->handle.private = priv; |
||
264 | priv->handle.dev = dev; |
||
265 | priv->handle.handler = handler; |
||
266 | priv->handle.name = DRV_NAME; |
||
267 | |||
268 | ret = input_register_handle(&priv->handle); |
||
269 | if (ret) |
||
270 | goto err_free_priv; |
||
271 | |||
272 | ret = input_open_device(&priv->handle); |
||
273 | if (ret) |
||
274 | goto err_unregister_handle; |
||
275 | |||
276 | BH_DBG("connected to %s\n", dev->name); |
||
277 | |||
278 | return 0; |
||
279 | |||
280 | err_unregister_handle: |
||
281 | input_unregister_handle(&priv->handle); |
||
282 | |||
283 | err_free_priv: |
||
284 | kfree(priv); |
||
285 | return ret; |
||
286 | } |
||
287 | |||
288 | static void button_hotplug_disconnect(struct input_handle *handle) |
||
289 | { |
||
290 | struct bh_priv *priv = handle->private; |
||
291 | |||
292 | input_close_device(handle); |
||
293 | input_unregister_handle(handle); |
||
294 | |||
295 | kfree(priv); |
||
296 | } |
||
297 | |||
298 | static const struct input_device_id button_hotplug_ids[] = { |
||
299 | { |
||
300 | .flags = INPUT_DEVICE_ID_MATCH_EVBIT, |
||
301 | .evbit = { BIT_MASK(EV_KEY) }, |
||
302 | }, |
||
303 | { |
||
304 | /* Terminating entry */ |
||
305 | }, |
||
306 | }; |
||
307 | |||
308 | MODULE_DEVICE_TABLE(input, button_hotplug_ids); |
||
309 | |||
310 | static struct input_handler button_hotplug_handler = { |
||
311 | .event = button_hotplug_event, |
||
312 | .connect = button_hotplug_connect, |
||
313 | .disconnect = button_hotplug_disconnect, |
||
314 | .name = DRV_NAME, |
||
315 | .id_table = button_hotplug_ids, |
||
316 | }; |
||
317 | |||
318 | /* -------------------------------------------------------------------------*/ |
||
319 | |||
320 | static int __init button_hotplug_init(void) |
||
321 | { |
||
322 | int ret; |
||
323 | |||
324 | printk(KERN_INFO DRV_DESC " version " DRV_VERSION "\n"); |
||
325 | ret = input_register_handler(&button_hotplug_handler); |
||
326 | if (ret) |
||
327 | BH_ERR("unable to register input handler\n"); |
||
328 | |||
329 | return ret; |
||
330 | } |
||
331 | module_init(button_hotplug_init); |
||
332 | |||
333 | static void __exit button_hotplug_exit(void) |
||
334 | { |
||
335 | input_unregister_handler(&button_hotplug_handler); |
||
336 | } |
||
337 | module_exit(button_hotplug_exit); |
||
338 | |||
339 | MODULE_DESCRIPTION(DRV_DESC); |
||
340 | MODULE_VERSION(DRV_VERSION); |
||
341 | MODULE_AUTHOR("Gabor Juhos <juhosg@openwrt.org>"); |
||
342 | MODULE_LICENSE("GPL v2"); |
||
343 |