drivers: power: report battery voltage in AOSP compatible format
[GitHub/mt8127/android_kernel_alcatel_ttab.git] / kernel / power / tuxonice_netlink.c
1 /*
2 * kernel/power/tuxonice_netlink.c
3 *
4 * Copyright (C) 2004-2010 Nigel Cunningham (nigel at tuxonice net)
5 *
6 * This file is released under the GPLv2.
7 *
8 * Functions for communicating with a userspace helper via netlink.
9 */
10
11
12 #include <linux/suspend.h>
13 #include <linux/sched.h>
14 #include "tuxonice_netlink.h"
15 #include "tuxonice.h"
16 #include "tuxonice_modules.h"
17 #include "tuxonice_alloc.h"
18 #include "tuxonice_builtin.h"
19
20 static struct user_helper_data *uhd_list;
21
22 /*
23 * Refill our pool of SKBs for use in emergencies (eg, when eating memory and
24 * none can be allocated).
25 */
26 static void toi_fill_skb_pool(struct user_helper_data *uhd)
27 {
28 while (uhd->pool_level < uhd->pool_limit) {
29 struct sk_buff *new_skb = alloc_skb(NLMSG_SPACE(uhd->skb_size), TOI_ATOMIC_GFP);
30
31 if (!new_skb)
32 break;
33
34 new_skb->next = uhd->emerg_skbs;
35 uhd->emerg_skbs = new_skb;
36 uhd->pool_level++;
37 }
38 }
39
40 /*
41 * Try to allocate a single skb. If we can't get one, try to use one from
42 * our pool.
43 */
44 static struct sk_buff *toi_get_skb(struct user_helper_data *uhd)
45 {
46 struct sk_buff *skb = alloc_skb(NLMSG_SPACE(uhd->skb_size), TOI_ATOMIC_GFP);
47
48 if (skb)
49 return skb;
50
51 skb = uhd->emerg_skbs;
52 if (skb) {
53 uhd->pool_level--;
54 uhd->emerg_skbs = skb->next;
55 skb->next = NULL;
56 }
57
58 return skb;
59 }
60
61 void toi_send_netlink_message(struct user_helper_data *uhd, int type, void *params, size_t len)
62 {
63 struct sk_buff *skb;
64 struct nlmsghdr *nlh;
65 void *dest;
66 struct task_struct *t;
67
68 if (uhd->pid == -1)
69 return;
70
71 if (uhd->debug)
72 printk(KERN_ERR "toi_send_netlink_message: Send " "message type %d.\n", type);
73
74 skb = toi_get_skb(uhd);
75 if (!skb) {
76 printk(KERN_INFO "toi_netlink: Can't allocate skb!\n");
77 return;
78 }
79
80 nlh = nlmsg_put(skb, 0, uhd->sock_seq, type, len, 0);
81 uhd->sock_seq++;
82
83 dest = NLMSG_DATA(nlh);
84 if (params && len > 0)
85 memcpy(dest, params, len);
86
87 netlink_unicast(uhd->nl, skb, uhd->pid, 0);
88
89 toi_read_lock_tasklist();
90 t = find_task_by_pid_ns(uhd->pid, &init_pid_ns);
91 if (!t) {
92 toi_read_unlock_tasklist();
93 if (uhd->pid > -1)
94 printk(KERN_INFO "Hmm. Can't find the userspace task" " %d.\n", uhd->pid);
95 return;
96 }
97 wake_up_process(t);
98 toi_read_unlock_tasklist();
99
100 yield();
101 }
102 EXPORT_SYMBOL_GPL(toi_send_netlink_message);
103
104 static void send_whether_debugging(struct user_helper_data *uhd)
105 {
106 static u8 is_debugging = 1;
107
108 toi_send_netlink_message(uhd, NETLINK_MSG_IS_DEBUGGING, &is_debugging, sizeof(u8));
109 }
110
111 /*
112 * Set the PF_NOFREEZE flag on the given process to ensure it can run whilst we
113 * are hibernating.
114 */
115 static int nl_set_nofreeze(struct user_helper_data *uhd, __u32 pid)
116 {
117 struct task_struct *t;
118
119 if (uhd->debug)
120 printk(KERN_ERR "nl_set_nofreeze for pid %d.\n", pid);
121
122 toi_read_lock_tasklist();
123 t = find_task_by_pid_ns(pid, &init_pid_ns);
124 if (!t) {
125 toi_read_unlock_tasklist();
126 printk(KERN_INFO "Strange. Can't find the userspace task %d.\n", pid);
127 return -EINVAL;
128 }
129
130 t->flags |= PF_NOFREEZE;
131
132 toi_read_unlock_tasklist();
133 uhd->pid = pid;
134
135 toi_send_netlink_message(uhd, NETLINK_MSG_NOFREEZE_ACK, NULL, 0);
136
137 return 0;
138 }
139
140 /*
141 * Called when the userspace process has informed us that it's ready to roll.
142 */
143 static int nl_ready(struct user_helper_data *uhd, u32 version)
144 {
145 if (version != uhd->interface_version) {
146 printk(KERN_INFO "%s userspace process using invalid interface"
147 " version (%d - kernel wants %d). Trying to "
148 "continue without it.\n", uhd->name, version, uhd->interface_version);
149 if (uhd->not_ready)
150 uhd->not_ready();
151 return -EINVAL;
152 }
153
154 complete(&uhd->wait_for_process);
155
156 return 0;
157 }
158
159 void toi_netlink_close_complete(struct user_helper_data *uhd)
160 {
161 if (uhd->nl) {
162 netlink_kernel_release(uhd->nl);
163 uhd->nl = NULL;
164 }
165
166 while (uhd->emerg_skbs) {
167 struct sk_buff *next = uhd->emerg_skbs->next;
168 kfree_skb(uhd->emerg_skbs);
169 uhd->emerg_skbs = next;
170 }
171
172 uhd->pid = -1;
173 }
174 EXPORT_SYMBOL_GPL(toi_netlink_close_complete);
175
176 static int toi_nl_gen_rcv_msg(struct user_helper_data *uhd,
177 struct sk_buff *skb, struct nlmsghdr *nlh)
178 {
179 int type = nlh->nlmsg_type;
180 int *data;
181 int err;
182
183 if (uhd->debug)
184 printk(KERN_ERR "toi_user_rcv_skb: Received message %d.\n", type);
185
186 /* Let the more specific handler go first. It returns
187 * 1 for valid messages that it doesn't know. */
188 err = uhd->rcv_msg(skb, nlh);
189 if (err != 1)
190 return err;
191
192 /* Only allow one task to receive NOFREEZE privileges */
193 if (type == NETLINK_MSG_NOFREEZE_ME && uhd->pid != -1) {
194 printk(KERN_INFO "Received extra nofreeze me requests.\n");
195 return -EBUSY;
196 }
197
198 data = NLMSG_DATA(nlh);
199
200 switch (type) {
201 case NETLINK_MSG_NOFREEZE_ME:
202 return nl_set_nofreeze(uhd, nlh->nlmsg_pid);
203 case NETLINK_MSG_GET_DEBUGGING:
204 send_whether_debugging(uhd);
205 return 0;
206 case NETLINK_MSG_READY:
207 if (nlh->nlmsg_len != NLMSG_LENGTH(sizeof(u32))) {
208 printk(KERN_INFO "Invalid ready mesage.\n");
209 if (uhd->not_ready)
210 uhd->not_ready();
211 return -EINVAL;
212 }
213 return nl_ready(uhd, (u32) *data);
214 case NETLINK_MSG_CLEANUP:
215 toi_netlink_close_complete(uhd);
216 return 0;
217 }
218
219 return -EINVAL;
220 }
221
222 static void toi_user_rcv_skb(struct sk_buff *skb)
223 {
224 int err;
225 struct nlmsghdr *nlh;
226 struct user_helper_data *uhd = uhd_list;
227
228 while (uhd && uhd->netlink_id != skb->sk->sk_protocol)
229 uhd = uhd->next;
230
231 if (!uhd)
232 return;
233
234 while (skb->len >= NLMSG_SPACE(0)) {
235 u32 rlen;
236
237 nlh = (struct nlmsghdr *)skb->data;
238 if (nlh->nlmsg_len < sizeof(*nlh) || skb->len < nlh->nlmsg_len)
239 return;
240
241 rlen = NLMSG_ALIGN(nlh->nlmsg_len);
242 if (rlen > skb->len)
243 rlen = skb->len;
244
245 err = toi_nl_gen_rcv_msg(uhd, skb, nlh);
246 if (err)
247 netlink_ack(skb, nlh, err);
248 else if (nlh->nlmsg_flags & NLM_F_ACK)
249 netlink_ack(skb, nlh, 0);
250 skb_pull(skb, rlen);
251 }
252 }
253
254 static int netlink_prepare(struct user_helper_data *uhd)
255 {
256 struct netlink_kernel_cfg cfg = {
257 .groups = 0,
258 .input = toi_user_rcv_skb,
259 };
260
261 uhd->next = uhd_list;
262 uhd_list = uhd;
263
264 uhd->sock_seq = 0x42c0ffee;
265 uhd->nl = netlink_kernel_create(&init_net, uhd->netlink_id, &cfg);
266 if (!uhd->nl) {
267 printk(KERN_INFO "Failed to allocate netlink socket for %s.\n", uhd->name);
268 return -ENOMEM;
269 }
270
271 toi_fill_skb_pool(uhd);
272
273 return 0;
274 }
275
276 void toi_netlink_close(struct user_helper_data *uhd)
277 {
278 struct task_struct *t;
279
280 toi_read_lock_tasklist();
281 t = find_task_by_pid_ns(uhd->pid, &init_pid_ns);
282 if (t)
283 t->flags &= ~PF_NOFREEZE;
284 toi_read_unlock_tasklist();
285
286 toi_send_netlink_message(uhd, NETLINK_MSG_CLEANUP, NULL, 0);
287 }
288 EXPORT_SYMBOL_GPL(toi_netlink_close);
289
290 int toi_netlink_setup(struct user_helper_data *uhd)
291 {
292 /* In case userui didn't cleanup properly on us */
293 toi_netlink_close_complete(uhd);
294
295 if (netlink_prepare(uhd) < 0) {
296 printk(KERN_INFO "Netlink prepare failed.\n");
297 return 1;
298 }
299
300 if (toi_launch_userspace_program(uhd->program, uhd->netlink_id,
301 UMH_WAIT_EXEC, uhd->debug) < 0) {
302 printk(KERN_INFO "Launch userspace program failed.\n");
303 toi_netlink_close_complete(uhd);
304 return 1;
305 }
306
307 /* Wait 2 seconds for the userspace process to make contact */
308 wait_for_completion_timeout(&uhd->wait_for_process, 2 * HZ);
309
310 if (uhd->pid == -1) {
311 printk(KERN_INFO "%s: Failed to contact userspace process.\n", uhd->name);
312 toi_netlink_close_complete(uhd);
313 return 1;
314 }
315
316 return 0;
317 }
318 EXPORT_SYMBOL_GPL(toi_netlink_setup);