blob: 949037e92a7b27487b5034ac29dc1e580116166a [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org)
Jeff Dikeae2587e2007-10-16 01:26:57 -07003 * Copyright (C) 2001 - 2007 Jeff Dike (jdike@{addtoit,linux.intel}.com)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004 * Licensed under the GPL
5 */
6
Jeff Dikeae2587e2007-10-16 01:26:57 -07007#include "linux/console.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -07008#include "linux/ctype.h"
9#include "linux/interrupt.h"
Jeff Dike02dea082006-03-31 02:30:08 -080010#include "linux/list.h"
11#include "linux/mm.h"
Jeff Dikeae2587e2007-10-16 01:26:57 -070012#include "linux/module.h"
13#include "linux/notifier.h"
14#include "linux/reboot.h"
15#include "linux/proc_fs.h"
16#include "linux/slab.h"
17#include "linux/syscalls.h"
18#include "linux/utsname.h"
19#include "linux/workqueue.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070020#include "asm/uaccess.h"
Jeff Dikeae2587e2007-10-16 01:26:57 -070021#include "init.h"
22#include "irq_kern.h"
23#include "irq_user.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070024#include "kern_util.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070025#include "mconsole.h"
26#include "mconsole_kern.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070027#include "os.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070028
Jeff Diked50084a2006-01-06 00:18:50 -080029static int do_unlink_socket(struct notifier_block *notifier,
Linus Torvalds1da177e2005-04-16 15:20:36 -070030 unsigned long what, void *data)
31{
Jeff Dikeae2587e2007-10-16 01:26:57 -070032 return mconsole_unlink_socket();
Linus Torvalds1da177e2005-04-16 15:20:36 -070033}
34
35
36static struct notifier_block reboot_notifier = {
37 .notifier_call = do_unlink_socket,
38 .priority = 0,
39};
40
Jeff Diked50084a2006-01-06 00:18:50 -080041/* Safe without explicit locking for now. Tasklets provide their own
Linus Torvalds1da177e2005-04-16 15:20:36 -070042 * locking, and the interrupt handler is safe because it can't interrupt
43 * itself and it can only happen on CPU 0.
44 */
45
Jeff Dike90107722006-01-06 00:18:54 -080046static LIST_HEAD(mc_requests);
Linus Torvalds1da177e2005-04-16 15:20:36 -070047
David Howells6d5aefb2006-12-05 19:36:26 +000048static void mc_work_proc(struct work_struct *unused)
Linus Torvalds1da177e2005-04-16 15:20:36 -070049{
50 struct mconsole_entry *req;
51 unsigned long flags;
52
Jeff Dikeae2587e2007-10-16 01:26:57 -070053 while (!list_empty(&mc_requests)) {
Paolo 'Blaisorblade' Giarrussodbdb4c02006-04-10 22:53:39 -070054 local_irq_save(flags);
Jeff Dikeae2587e2007-10-16 01:26:57 -070055 req = list_entry(mc_requests.next, struct mconsole_entry, list);
Linus Torvalds1da177e2005-04-16 15:20:36 -070056 list_del(&req->list);
57 local_irq_restore(flags);
58 req->request.cmd->handler(&req->request);
59 kfree(req);
60 }
61}
62
David Howells6d5aefb2006-12-05 19:36:26 +000063static DECLARE_WORK(mconsole_work, mc_work_proc);
Linus Torvalds1da177e2005-04-16 15:20:36 -070064
Al Viro7bea96f2006-10-08 22:49:34 +010065static irqreturn_t mconsole_interrupt(int irq, void *dev_id)
Linus Torvalds1da177e2005-04-16 15:20:36 -070066{
67 /* long to avoid size mismatch warnings from gcc */
68 long fd;
69 struct mconsole_entry *new;
Al Viro3a512372006-10-24 11:15:29 +010070 static struct mc_request req; /* that's OK */
Linus Torvalds1da177e2005-04-16 15:20:36 -070071
72 fd = (long) dev_id;
Jeff Dikeae2587e2007-10-16 01:26:57 -070073 while (mconsole_get_request(fd, &req)) {
74 if (req.cmd->context == MCONSOLE_INTR)
Linus Torvalds1da177e2005-04-16 15:20:36 -070075 (*req.cmd->handler)(&req);
76 else {
Jeff Dike60baa152006-04-10 22:53:28 -070077 new = kmalloc(sizeof(*new), GFP_NOWAIT);
Jeff Dikeae2587e2007-10-16 01:26:57 -070078 if (new == NULL)
Linus Torvalds1da177e2005-04-16 15:20:36 -070079 mconsole_reply(&req, "Out of memory", 1, 0);
80 else {
81 new->request = req;
Al Viro3a512372006-10-24 11:15:29 +010082 new->request.regs = get_irq_regs()->regs;
Linus Torvalds1da177e2005-04-16 15:20:36 -070083 list_add(&new->list, &mc_requests);
84 }
85 }
86 }
Jeff Dikeae2587e2007-10-16 01:26:57 -070087 if (!list_empty(&mc_requests))
Linus Torvalds1da177e2005-04-16 15:20:36 -070088 schedule_work(&mconsole_work);
89 reactivate_fd(fd, MCONSOLE_IRQ);
Jeff Dikeae2587e2007-10-16 01:26:57 -070090 return IRQ_HANDLED;
Linus Torvalds1da177e2005-04-16 15:20:36 -070091}
92
93void mconsole_version(struct mc_request *req)
94{
95 char version[256];
96
Serge E. Hallyne9ff3992006-10-02 02:18:11 -070097 sprintf(version, "%s %s %s %s %s", utsname()->sysname,
Jeff Dikeae2587e2007-10-16 01:26:57 -070098 utsname()->nodename, utsname()->release, utsname()->version,
99 utsname()->machine);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700100 mconsole_reply(req, version, 0, 0);
101}
102
103void mconsole_log(struct mc_request *req)
104{
105 int len;
106 char *ptr = req->request.data;
107
108 ptr += strlen("log ");
109
110 len = req->len - (ptr - req->request.data);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700111 printk(KERN_WARNING "%.*s", len, ptr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700112 mconsole_reply(req, "", 0, 0);
113}
114
115/* This is a more convoluted version of mconsole_proc, which has some stability
116 * problems; however, we need it fixed, because it is expected that UML users
117 * mount HPPFS instead of procfs on /proc. And we want mconsole_proc to still
118 * show the real procfs content, not the ones from hppfs.*/
119#if 0
120void mconsole_proc(struct mc_request *req)
121{
122 struct nameidata nd;
123 struct file_system_type *proc;
124 struct super_block *super;
125 struct file *file;
126 int n, err;
127 char *ptr = req->request.data, *buf;
128
129 ptr += strlen("proc");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700130 while (isspace(*ptr)) ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700131
132 proc = get_fs_type("proc");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700133 if (proc == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700134 mconsole_reply(req, "procfs not registered", 1, 0);
135 goto out;
136 }
137
138 super = (*proc->get_sb)(proc, 0, NULL, NULL);
139 put_filesystem(proc);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700140 if (super == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700141 mconsole_reply(req, "Failed to get procfs superblock", 1, 0);
142 goto out;
143 }
144 up_write(&super->s_umount);
145
146 nd.dentry = super->s_root;
147 nd.mnt = NULL;
148 nd.flags = O_RDONLY + 1;
149 nd.last_type = LAST_ROOT;
150
151 /* START: it was experienced that the stability problems are closed
152 * if commenting out these two calls + the below read cycle. To
153 * make UML crash again, it was enough to readd either one.*/
154 err = link_path_walk(ptr, &nd);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700155 if (err) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700156 mconsole_reply(req, "Failed to look up file", 1, 0);
157 goto out_kill;
158 }
159
160 file = dentry_open(nd.dentry, nd.mnt, O_RDONLY);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700161 if (IS_ERR(file)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700162 mconsole_reply(req, "Failed to open file", 1, 0);
163 goto out_kill;
164 }
165 /*END*/
166
167 buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700168 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700169 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
170 goto out_fput;
171 }
172
Jeff Dikeae2587e2007-10-16 01:26:57 -0700173 if ((file->f_op != NULL) && (file->f_op->read != NULL)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700174 do {
175 n = (*file->f_op->read)(file, buf, PAGE_SIZE - 1,
176 &file->f_pos);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700177 if (n >= 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700178 buf[n] = '\0';
179 mconsole_reply(req, buf, 0, (n > 0));
180 }
181 else {
182 mconsole_reply(req, "Read of file failed",
183 1, 0);
184 goto out_free;
185 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700186 } while (n > 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700187 }
188 else mconsole_reply(req, "", 0, 0);
189
190 out_free:
191 kfree(buf);
192 out_fput:
193 fput(file);
194 out_kill:
195 deactivate_super(super);
196 out: ;
197}
198#endif
199
200void mconsole_proc(struct mc_request *req)
201{
202 char path[64];
203 char *buf;
204 int len;
205 int fd;
206 int first_chunk = 1;
207 char *ptr = req->request.data;
208
209 ptr += strlen("proc");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700210 while (isspace(*ptr))
211 ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700212 snprintf(path, sizeof(path), "/proc/%s", ptr);
213
214 fd = sys_open(path, 0, 0);
215 if (fd < 0) {
216 mconsole_reply(req, "Failed to open file", 1, 0);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700217 printk(KERN_ERR "open %s: %d\n",path,fd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700218 goto out;
219 }
220
221 buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700222 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700223 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
224 goto out_close;
225 }
226
227 for (;;) {
228 len = sys_read(fd, buf, PAGE_SIZE-1);
229 if (len < 0) {
230 mconsole_reply(req, "Read of file failed", 1, 0);
231 goto out_free;
232 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700233 /* Begin the file content on his own line. */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700234 if (first_chunk) {
235 mconsole_reply(req, "\n", 0, 1);
236 first_chunk = 0;
237 }
238 if (len == PAGE_SIZE-1) {
239 buf[len] = '\0';
240 mconsole_reply(req, buf, 0, 1);
241 } else {
242 buf[len] = '\0';
243 mconsole_reply(req, buf, 0, 0);
244 break;
245 }
246 }
247
248 out_free:
249 kfree(buf);
250 out_close:
251 sys_close(fd);
252 out:
253 /* nothing */;
254}
255
256#define UML_MCONSOLE_HELPTEXT \
257"Commands: \n\
258 version - Get kernel version \n\
259 help - Print this message \n\
260 halt - Halt UML \n\
261 reboot - Reboot UML \n\
262 config <dev>=<config> - Add a new device to UML; \n\
263 same syntax as command line \n\
264 config <dev> - Query the configuration of a device \n\
265 remove <dev> - Remove a device from UML \n\
266 sysrq <letter> - Performs the SysRq action controlled by the letter \n\
Jeff Dikedb805812006-02-01 03:06:23 -0800267 cad - invoke the Ctrl-Alt-Del handler \n\
Linus Torvalds1da177e2005-04-16 15:20:36 -0700268 stop - pause the UML; it will do nothing until it receives a 'go' \n\
269 go - continue the UML after a 'stop' \n\
270 log <string> - make UML enter <string> into the kernel log\n\
271 proc <file> - returns the contents of the UML's /proc/<file>\n\
Jeff Dike3eddddc2005-09-16 19:27:46 -0700272 stack <pid> - returns the stack of the specified pid\n\
Linus Torvalds1da177e2005-04-16 15:20:36 -0700273"
274
275void mconsole_help(struct mc_request *req)
276{
277 mconsole_reply(req, UML_MCONSOLE_HELPTEXT, 0, 0);
278}
279
280void mconsole_halt(struct mc_request *req)
281{
282 mconsole_reply(req, "", 0, 0);
283 machine_halt();
284}
285
286void mconsole_reboot(struct mc_request *req)
287{
288 mconsole_reply(req, "", 0, 0);
289 machine_restart(NULL);
290}
291
Linus Torvalds1da177e2005-04-16 15:20:36 -0700292void mconsole_cad(struct mc_request *req)
293{
294 mconsole_reply(req, "", 0, 0);
295 ctrl_alt_del();
296}
297
298void mconsole_go(struct mc_request *req)
299{
300 mconsole_reply(req, "Not stopped", 1, 0);
301}
302
303void mconsole_stop(struct mc_request *req)
304{
305 deactivate_fd(req->originating_fd, MCONSOLE_IRQ);
306 os_set_fd_block(req->originating_fd, 1);
Al Viro3a512372006-10-24 11:15:29 +0100307 mconsole_reply(req, "stopped", 0, 0);
Karol Swietlickicc0be0f2008-02-04 22:31:25 -0800308 for (;;) {
309 if (!mconsole_get_request(req->originating_fd, req))
310 continue;
Al Viro3a512372006-10-24 11:15:29 +0100311 if (req->cmd->handler == mconsole_go)
312 break;
313 if (req->cmd->handler == mconsole_stop) {
314 mconsole_reply(req, "Already stopped", 1, 0);
315 continue;
316 }
317 if (req->cmd->handler == mconsole_sysrq) {
318 struct pt_regs *old_regs;
319 old_regs = set_irq_regs((struct pt_regs *)&req->regs);
320 mconsole_sysrq(req);
321 set_irq_regs(old_regs);
322 continue;
323 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700324 (*req->cmd->handler)(req);
325 }
326 os_set_fd_block(req->originating_fd, 0);
327 reactivate_fd(req->originating_fd, MCONSOLE_IRQ);
328 mconsole_reply(req, "", 0, 0);
329}
330
Jeff Dike84f48d42007-02-10 01:44:01 -0800331static DEFINE_SPINLOCK(mc_devices_lock);
Paolo 'Blaisorblade' Giarrusso42947cb2006-02-01 03:06:29 -0800332static LIST_HEAD(mconsole_devices);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700333
334void mconsole_register_dev(struct mc_device *new)
335{
Jeff Dike84f48d42007-02-10 01:44:01 -0800336 spin_lock(&mc_devices_lock);
337 BUG_ON(!list_empty(&new->list));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700338 list_add(&new->list, &mconsole_devices);
Jeff Dike84f48d42007-02-10 01:44:01 -0800339 spin_unlock(&mc_devices_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340}
341
342static struct mc_device *mconsole_find_dev(char *name)
343{
344 struct list_head *ele;
345 struct mc_device *dev;
346
Jeff Dikeae2587e2007-10-16 01:26:57 -0700347 list_for_each(ele, &mconsole_devices) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700348 dev = list_entry(ele, struct mc_device, list);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700349 if (!strncmp(name, dev->name, strlen(dev->name)))
350 return dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700351 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700352 return NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700353}
354
Jeff Dike02dea082006-03-31 02:30:08 -0800355#define UNPLUGGED_PER_PAGE \
356 ((PAGE_SIZE - sizeof(struct list_head)) / sizeof(unsigned long))
357
358struct unplugged_pages {
359 struct list_head list;
360 void *pages[UNPLUGGED_PER_PAGE];
361};
362
Jeff Dike84f48d42007-02-10 01:44:01 -0800363static DECLARE_MUTEX(plug_mem_mutex);
Jeff Dike02dea082006-03-31 02:30:08 -0800364static unsigned long long unplugged_pages_count = 0;
Jeff Dikec59bce62007-02-10 01:44:04 -0800365static LIST_HEAD(unplugged_pages);
Jeff Dike02dea082006-03-31 02:30:08 -0800366static int unplug_index = UNPLUGGED_PER_PAGE;
367
Jeff Dikef28169d2007-02-10 01:43:53 -0800368static int mem_config(char *str, char **error_out)
Jeff Dike02dea082006-03-31 02:30:08 -0800369{
370 unsigned long long diff;
371 int err = -EINVAL, i, add;
372 char *ret;
373
Jeff Dikeae2587e2007-10-16 01:26:57 -0700374 if (str[0] != '=') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800375 *error_out = "Expected '=' after 'mem'";
Jeff Dike02dea082006-03-31 02:30:08 -0800376 goto out;
Jeff Dikef28169d2007-02-10 01:43:53 -0800377 }
Jeff Dike02dea082006-03-31 02:30:08 -0800378
379 str++;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700380 if (str[0] == '-')
Jeff Dike02dea082006-03-31 02:30:08 -0800381 add = 0;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700382 else if (str[0] == '+') {
Jeff Dike02dea082006-03-31 02:30:08 -0800383 add = 1;
384 }
Jeff Dikef28169d2007-02-10 01:43:53 -0800385 else {
386 *error_out = "Expected increment to start with '-' or '+'";
387 goto out;
388 }
Jeff Dike02dea082006-03-31 02:30:08 -0800389
390 str++;
391 diff = memparse(str, &ret);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700392 if (*ret != '\0') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800393 *error_out = "Failed to parse memory increment";
Jeff Dike02dea082006-03-31 02:30:08 -0800394 goto out;
Jeff Dikef28169d2007-02-10 01:43:53 -0800395 }
Jeff Dike02dea082006-03-31 02:30:08 -0800396
397 diff /= PAGE_SIZE;
398
Jeff Dike84f48d42007-02-10 01:44:01 -0800399 down(&plug_mem_mutex);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700400 for (i = 0; i < diff; i++) {
Jeff Dike02dea082006-03-31 02:30:08 -0800401 struct unplugged_pages *unplugged;
402 void *addr;
403
Jeff Dikeae2587e2007-10-16 01:26:57 -0700404 if (add) {
405 if (list_empty(&unplugged_pages))
Jeff Dike02dea082006-03-31 02:30:08 -0800406 break;
407
408 unplugged = list_entry(unplugged_pages.next,
409 struct unplugged_pages, list);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700410 if (unplug_index > 0)
Jeff Dike02dea082006-03-31 02:30:08 -0800411 addr = unplugged->pages[--unplug_index];
412 else {
413 list_del(&unplugged->list);
414 addr = unplugged;
415 unplug_index = UNPLUGGED_PER_PAGE;
416 }
417
418 free_page((unsigned long) addr);
419 unplugged_pages_count--;
420 }
421 else {
422 struct page *page;
423
424 page = alloc_page(GFP_ATOMIC);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700425 if (page == NULL)
Jeff Dike02dea082006-03-31 02:30:08 -0800426 break;
427
428 unplugged = page_address(page);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700429 if (unplug_index == UNPLUGGED_PER_PAGE) {
Jeff Dike02dea082006-03-31 02:30:08 -0800430 list_add(&unplugged->list, &unplugged_pages);
431 unplug_index = 0;
432 }
433 else {
434 struct list_head *entry = unplugged_pages.next;
435 addr = unplugged;
436
437 unplugged = list_entry(entry,
438 struct unplugged_pages,
439 list);
Jeff Dike02dea082006-03-31 02:30:08 -0800440 err = os_drop_memory(addr, PAGE_SIZE);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700441 if (err) {
442 printk(KERN_ERR "Failed to release "
443 "memory - errno = %d\n", err);
Jeff Dikef28169d2007-02-10 01:43:53 -0800444 *error_out = "Failed to release memory";
Jeff Dike84f48d42007-02-10 01:44:01 -0800445 goto out_unlock;
Jeff Dikef28169d2007-02-10 01:43:53 -0800446 }
447 unplugged->pages[unplug_index++] = addr;
Jeff Dike02dea082006-03-31 02:30:08 -0800448 }
449
450 unplugged_pages_count++;
451 }
452 }
453
454 err = 0;
Jeff Dike84f48d42007-02-10 01:44:01 -0800455out_unlock:
456 up(&plug_mem_mutex);
Jeff Dike02dea082006-03-31 02:30:08 -0800457out:
458 return err;
459}
460
461static int mem_get_config(char *name, char *str, int size, char **error_out)
462{
463 char buf[sizeof("18446744073709551615")];
464 int len = 0;
465
466 sprintf(buf, "%ld", uml_physmem);
467 CONFIG_CHUNK(str, size, len, buf, 1);
468
469 return len;
470}
471
472static int mem_id(char **str, int *start_out, int *end_out)
473{
474 *start_out = 0;
475 *end_out = 0;
476
477 return 0;
478}
479
Jeff Dikef28169d2007-02-10 01:43:53 -0800480static int mem_remove(int n, char **error_out)
Jeff Dike02dea082006-03-31 02:30:08 -0800481{
Jeff Dikef28169d2007-02-10 01:43:53 -0800482 *error_out = "Memory doesn't support the remove operation";
Jeff Dike02dea082006-03-31 02:30:08 -0800483 return -EBUSY;
484}
485
486static struct mc_device mem_mc = {
Jeff Dike84f48d42007-02-10 01:44:01 -0800487 .list = LIST_HEAD_INIT(mem_mc.list),
Jeff Dike02dea082006-03-31 02:30:08 -0800488 .name = "mem",
489 .config = mem_config,
490 .get_config = mem_get_config,
491 .id = mem_id,
492 .remove = mem_remove,
493};
494
Jeff Dike97a1fcb2007-07-23 18:43:48 -0700495static int __init mem_mc_init(void)
Jeff Dike02dea082006-03-31 02:30:08 -0800496{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700497 if (can_drop_memory())
Jeff Dike02dea082006-03-31 02:30:08 -0800498 mconsole_register_dev(&mem_mc);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700499 else printk(KERN_ERR "Can't release memory to the host - memory "
500 "hotplug won't be supported\n");
Jeff Dike02dea082006-03-31 02:30:08 -0800501 return 0;
502}
503
504__initcall(mem_mc_init);
505
Linus Torvalds1da177e2005-04-16 15:20:36 -0700506#define CONFIG_BUF_SIZE 64
507
Jeff Diked50084a2006-01-06 00:18:50 -0800508static void mconsole_get_config(int (*get_config)(char *, char *, int,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700509 char **),
510 struct mc_request *req, char *name)
511{
512 char default_buf[CONFIG_BUF_SIZE], *error, *buf;
513 int n, size;
514
Jeff Dikeae2587e2007-10-16 01:26:57 -0700515 if (get_config == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700516 mconsole_reply(req, "No get_config routine defined", 1, 0);
517 return;
518 }
519
520 error = NULL;
Jeff Dike91b165c2006-09-25 23:33:00 -0700521 size = ARRAY_SIZE(default_buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700522 buf = default_buf;
523
Jeff Dikeae2587e2007-10-16 01:26:57 -0700524 while (1) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700525 n = (*get_config)(name, buf, size, &error);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700526 if (error != NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700527 mconsole_reply(req, error, 1, 0);
528 goto out;
529 }
530
Jeff Dikeae2587e2007-10-16 01:26:57 -0700531 if (n <= size) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700532 mconsole_reply(req, buf, 0, 0);
533 goto out;
534 }
535
Jeff Dikeae2587e2007-10-16 01:26:57 -0700536 if (buf != default_buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700537 kfree(buf);
538
539 size = n;
540 buf = kmalloc(size, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700541 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700542 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
543 return;
544 }
545 }
546 out:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700547 if (buf != default_buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700548 kfree(buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700549}
550
551void mconsole_config(struct mc_request *req)
552{
553 struct mc_device *dev;
Jeff Dikef28169d2007-02-10 01:43:53 -0800554 char *ptr = req->request.data, *name, *error_string = "";
Linus Torvalds1da177e2005-04-16 15:20:36 -0700555 int err;
556
557 ptr += strlen("config");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700558 while (isspace(*ptr))
559 ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700560 dev = mconsole_find_dev(ptr);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700561 if (dev == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700562 mconsole_reply(req, "Bad configuration option", 1, 0);
563 return;
564 }
565
566 name = &ptr[strlen(dev->name)];
567 ptr = name;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700568 while ((*ptr != '=') && (*ptr != '\0'))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700569 ptr++;
570
Jeff Dikeae2587e2007-10-16 01:26:57 -0700571 if (*ptr == '=') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800572 err = (*dev->config)(name, &error_string);
573 mconsole_reply(req, error_string, err, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700574 }
575 else mconsole_get_config(dev->get_config, req, name);
576}
577
578void mconsole_remove(struct mc_request *req)
579{
Jeff Diked50084a2006-01-06 00:18:50 -0800580 struct mc_device *dev;
Jeff Dike29d56cf2005-06-25 14:55:25 -0700581 char *ptr = req->request.data, *err_msg = "";
Jeff Dike3a331a52006-01-06 00:19:05 -0800582 char error[256];
Jeff Dike29d56cf2005-06-25 14:55:25 -0700583 int err, start, end, n;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700584
585 ptr += strlen("remove");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700586 while (isspace(*ptr)) ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700587 dev = mconsole_find_dev(ptr);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700588 if (dev == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700589 mconsole_reply(req, "Bad remove option", 1, 0);
590 return;
591 }
Jeff Dike29d56cf2005-06-25 14:55:25 -0700592
Jeff Dike3a331a52006-01-06 00:19:05 -0800593 ptr = &ptr[strlen(dev->name)];
Jeff Dike29d56cf2005-06-25 14:55:25 -0700594
Jeff Dike3a331a52006-01-06 00:19:05 -0800595 err = 1;
596 n = (*dev->id)(&ptr, &start, &end);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700597 if (n < 0) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800598 err_msg = "Couldn't parse device number";
599 goto out;
600 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700601 else if ((n < start) || (n > end)) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800602 sprintf(error, "Invalid device number - must be between "
603 "%d and %d", start, end);
604 err_msg = error;
605 goto out;
606 }
Jeff Dike29d56cf2005-06-25 14:55:25 -0700607
Jeff Dikef28169d2007-02-10 01:43:53 -0800608 err_msg = NULL;
609 err = (*dev->remove)(n, &err_msg);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700610 switch(err) {
Jeff Diked40f6d72007-03-29 01:20:28 -0700611 case 0:
612 err_msg = "";
613 break;
Jeff Dike3a331a52006-01-06 00:19:05 -0800614 case -ENODEV:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700615 if (err_msg == NULL)
Jeff Dikef28169d2007-02-10 01:43:53 -0800616 err_msg = "Device doesn't exist";
Jeff Dike3a331a52006-01-06 00:19:05 -0800617 break;
618 case -EBUSY:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700619 if (err_msg == NULL)
Jeff Dikef28169d2007-02-10 01:43:53 -0800620 err_msg = "Device is currently open";
Jeff Dike3a331a52006-01-06 00:19:05 -0800621 break;
622 default:
623 break;
624 }
625out:
Jeff Dike29d56cf2005-06-25 14:55:25 -0700626 mconsole_reply(req, err_msg, err, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700627}
628
Jeff Dikef92afe52006-09-29 01:58:52 -0700629struct mconsole_output {
630 struct list_head list;
631 struct mc_request *req;
632};
633
Jeff Dike84f48d42007-02-10 01:44:01 -0800634static DEFINE_SPINLOCK(client_lock);
Jeff Dike6f517d32006-01-06 00:19:04 -0800635static LIST_HEAD(clients);
636static char console_buf[MCONSOLE_MAX_DATA];
Jeff Dike6f517d32006-01-06 00:19:04 -0800637
638static void console_write(struct console *console, const char *string,
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700639 unsigned int len)
Jeff Dike6f517d32006-01-06 00:19:04 -0800640{
641 struct list_head *ele;
642 int n;
643
Jeff Dikeae2587e2007-10-16 01:26:57 -0700644 if (list_empty(&clients))
Jeff Dike6f517d32006-01-06 00:19:04 -0800645 return;
646
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700647 while (len > 0) {
648 n = min((size_t) len, ARRAY_SIZE(console_buf));
649 strncpy(console_buf, string, n);
Jeff Dike6f517d32006-01-06 00:19:04 -0800650 string += n;
651 len -= n;
Jeff Dike6f517d32006-01-06 00:19:04 -0800652
Jeff Dikeae2587e2007-10-16 01:26:57 -0700653 list_for_each(ele, &clients) {
Jeff Dikef92afe52006-09-29 01:58:52 -0700654 struct mconsole_output *entry;
Jeff Dike6f517d32006-01-06 00:19:04 -0800655
Jeff Dikef92afe52006-09-29 01:58:52 -0700656 entry = list_entry(ele, struct mconsole_output, list);
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700657 mconsole_reply_len(entry->req, console_buf, n, 0, 1);
Jeff Dike6f517d32006-01-06 00:19:04 -0800658 }
Jeff Dike6f517d32006-01-06 00:19:04 -0800659 }
660}
661
662static struct console mc_console = { .name = "mc",
663 .write = console_write,
Jeff Dikea174b302006-01-11 12:17:28 -0800664 .flags = CON_ENABLED,
Jeff Dike6f517d32006-01-06 00:19:04 -0800665 .index = -1 };
666
667static int mc_add_console(void)
668{
669 register_console(&mc_console);
670 return 0;
671}
672
673late_initcall(mc_add_console);
674
675static void with_console(struct mc_request *req, void (*proc)(void *),
676 void *arg)
677{
Jeff Dikef92afe52006-09-29 01:58:52 -0700678 struct mconsole_output entry;
Jeff Dike6f517d32006-01-06 00:19:04 -0800679 unsigned long flags;
680
Jeff Dikef92afe52006-09-29 01:58:52 -0700681 entry.req = req;
Jeff Dike84f48d42007-02-10 01:44:01 -0800682 spin_lock_irqsave(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800683 list_add(&entry.list, &clients);
Jeff Dike84f48d42007-02-10 01:44:01 -0800684 spin_unlock_irqrestore(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800685
686 (*proc)(arg);
687
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700688 mconsole_reply_len(req, "", 0, 0, 0);
Jeff Dike6f517d32006-01-06 00:19:04 -0800689
Jeff Dike84f48d42007-02-10 01:44:01 -0800690 spin_lock_irqsave(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800691 list_del(&entry.list);
Jeff Dike84f48d42007-02-10 01:44:01 -0800692 spin_unlock_irqrestore(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800693}
694
Jeff Dike4111b022006-01-06 00:19:05 -0800695#ifdef CONFIG_MAGIC_SYSRQ
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700696
697#include <linux/sysrq.h>
698
Jeff Dike4111b022006-01-06 00:19:05 -0800699static void sysrq_proc(void *arg)
700{
701 char *op = arg;
Al Viro7bea96f2006-10-08 22:49:34 +0100702 handle_sysrq(*op, NULL);
Jeff Dike4111b022006-01-06 00:19:05 -0800703}
704
705void mconsole_sysrq(struct mc_request *req)
706{
707 char *ptr = req->request.data;
708
709 ptr += strlen("sysrq");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700710 while (isspace(*ptr)) ptr++;
Jeff Dike4111b022006-01-06 00:19:05 -0800711
Jeff Dikeae2587e2007-10-16 01:26:57 -0700712 /*
713 * With 'b', the system will shut down without a chance to reply,
Jeff Dike4111b022006-01-06 00:19:05 -0800714 * so in this case, we reply first.
715 */
Jeff Dikeae2587e2007-10-16 01:26:57 -0700716 if (*ptr == 'b')
Jeff Dike4111b022006-01-06 00:19:05 -0800717 mconsole_reply(req, "", 0, 0);
718
719 with_console(req, sysrq_proc, ptr);
720}
721#else
722void mconsole_sysrq(struct mc_request *req)
723{
724 mconsole_reply(req, "Sysrq not compiled in", 1, 0);
725}
726#endif
727
Jeff Dike6f517d32006-01-06 00:19:04 -0800728static void stack_proc(void *arg)
729{
730 struct task_struct *from = current, *to = arg;
731
732 to->thread.saved_task = from;
733 switch_to(from, to, from);
734}
735
Jeff Dikeae2587e2007-10-16 01:26:57 -0700736/*
737 * Mconsole stack trace
Jeff Dike3eddddc2005-09-16 19:27:46 -0700738 * Added by Allan Graves, Jeff Dike
739 * Dumps a stacks registers to the linux console.
740 * Usage stack <pid>.
741 */
Jeff Dike42fda662007-10-16 01:26:50 -0700742void mconsole_stack(struct mc_request *req)
Jeff Dike3eddddc2005-09-16 19:27:46 -0700743{
Jeff Dike3a331a52006-01-06 00:19:05 -0800744 char *ptr = req->request.data;
745 int pid_requested= -1;
Jeff Dike3eddddc2005-09-16 19:27:46 -0700746 struct task_struct *to = NULL;
747
Jeff Dikeae2587e2007-10-16 01:26:57 -0700748 /*
749 * Would be nice:
Jeff Dike3a331a52006-01-06 00:19:05 -0800750 * 1) Send showregs output to mconsole.
Jeff Dike3eddddc2005-09-16 19:27:46 -0700751 * 2) Add a way to stack dump all pids.
752 */
753
Jeff Dike3a331a52006-01-06 00:19:05 -0800754 ptr += strlen("stack");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700755 while (isspace(*ptr))
756 ptr++;
Jeff Dike3eddddc2005-09-16 19:27:46 -0700757
Jeff Dikeae2587e2007-10-16 01:26:57 -0700758 /*
759 * Should really check for multiple pids or reject bad args here
760 */
Jeff Dike3a331a52006-01-06 00:19:05 -0800761 /* What do the arguments in mconsole_reply mean? */
Jeff Dikeae2587e2007-10-16 01:26:57 -0700762 if (sscanf(ptr, "%d", &pid_requested) == 0) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800763 mconsole_reply(req, "Please specify a pid", 1, 0);
764 return;
765 }
Jeff Dike3eddddc2005-09-16 19:27:46 -0700766
Jeff Dike6f517d32006-01-06 00:19:04 -0800767 to = find_task_by_pid(pid_requested);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700768 if ((to == NULL) || (pid_requested == 0)) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800769 mconsole_reply(req, "Couldn't find that pid", 1, 0);
770 return;
771 }
Jeff Dike6f517d32006-01-06 00:19:04 -0800772 with_console(req, stack_proc, to);
Jeff Dike3eddddc2005-09-16 19:27:46 -0700773}
Jeff Dike3eddddc2005-09-16 19:27:46 -0700774
Jeff Dikeae2587e2007-10-16 01:26:57 -0700775/*
776 * Changed by mconsole_setup, which is __setup, and called before SMP is
Linus Torvalds1da177e2005-04-16 15:20:36 -0700777 * active.
778 */
Jeff Diked50084a2006-01-06 00:18:50 -0800779static char *notify_socket = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700780
Jeff Dike97a1fcb2007-07-23 18:43:48 -0700781static int __init mconsole_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700782{
783 /* long to avoid size mismatch warnings from gcc */
784 long sock;
785 int err;
786 char file[256];
787
Jeff Dikeae2587e2007-10-16 01:26:57 -0700788 if (umid_file_name("mconsole", file, sizeof(file)))
789 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700790 snprintf(mconsole_socket_name, sizeof(file), "%s", file);
791
792 sock = os_create_unix_socket(file, sizeof(file), 1);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700793 if (sock < 0) {
794 printk(KERN_ERR "Failed to initialize management console\n");
795 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700796 }
Jeff Dike438ee672008-02-04 22:31:19 -0800797 if (os_set_fd_block(sock, 0))
798 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700799
800 register_reboot_notifier(&reboot_notifier);
801
802 err = um_request_irq(MCONSOLE_IRQ, sock, IRQ_READ, mconsole_interrupt,
Thomas Gleixnerbd6aa652006-07-01 19:29:27 -0700803 IRQF_DISABLED | IRQF_SHARED | IRQF_SAMPLE_RANDOM,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700804 "mconsole", (void *)sock);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700805 if (err) {
806 printk(KERN_ERR "Failed to get IRQ for management console\n");
Jeff Dike438ee672008-02-04 22:31:19 -0800807 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700808 }
809
Jeff Dikeae2587e2007-10-16 01:26:57 -0700810 if (notify_socket != NULL) {
Jeff Dike970d6e32006-01-06 00:18:48 -0800811 notify_socket = kstrdup(notify_socket, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700812 if (notify_socket != NULL)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700813 mconsole_notify(notify_socket, MCONSOLE_SOCKET,
Jeff Diked50084a2006-01-06 00:18:50 -0800814 mconsole_socket_name,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700815 strlen(mconsole_socket_name) + 1);
816 else printk(KERN_ERR "mconsole_setup failed to strdup "
817 "string\n");
818 }
819
Jeff Dikeae2587e2007-10-16 01:26:57 -0700820 printk(KERN_INFO "mconsole (version %d) initialized on %s\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700821 MCONSOLE_VERSION, mconsole_socket_name);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700822 return 0;
Jeff Dike438ee672008-02-04 22:31:19 -0800823
824 out:
825 os_close_file(sock);
826 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700827}
828
829__initcall(mconsole_init);
830
831static int write_proc_mconsole(struct file *file, const char __user *buffer,
832 unsigned long count, void *data)
833{
834 char *buf;
835
836 buf = kmalloc(count + 1, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700837 if (buf == NULL)
838 return -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700839
Jeff Dikeae2587e2007-10-16 01:26:57 -0700840 if (copy_from_user(buf, buffer, count)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700841 count = -EFAULT;
842 goto out;
843 }
844
845 buf[count] = '\0';
846
847 mconsole_notify(notify_socket, MCONSOLE_USER_NOTIFY, buf, count);
848 out:
849 kfree(buf);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700850 return count;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700851}
852
853static int create_proc_mconsole(void)
854{
855 struct proc_dir_entry *ent;
856
Jeff Dikeae2587e2007-10-16 01:26:57 -0700857 if (notify_socket == NULL)
858 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700859
860 ent = create_proc_entry("mconsole", S_IFREG | 0200, NULL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700861 if (ent == NULL) {
862 printk(KERN_INFO "create_proc_mconsole : create_proc_entry "
863 "failed\n");
864 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700865 }
866
867 ent->read_proc = NULL;
868 ent->write_proc = write_proc_mconsole;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700869 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700870}
871
872static DEFINE_SPINLOCK(notify_spinlock);
873
874void lock_notify(void)
875{
876 spin_lock(&notify_spinlock);
877}
878
879void unlock_notify(void)
880{
881 spin_unlock(&notify_spinlock);
882}
883
884__initcall(create_proc_mconsole);
885
Jeff Dike088bec42007-10-16 01:27:20 -0700886#define NOTIFY "notify:"
Linus Torvalds1da177e2005-04-16 15:20:36 -0700887
888static int mconsole_setup(char *str)
889{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700890 if (!strncmp(str, NOTIFY, strlen(NOTIFY))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700891 str += strlen(NOTIFY);
892 notify_socket = str;
893 }
894 else printk(KERN_ERR "mconsole_setup : Unknown option - '%s'\n", str);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700895 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700896}
897
Jeff Dike088bec42007-10-16 01:27:20 -0700898__setup("mconsole=", mconsole_setup);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700899
900__uml_help(mconsole_setup,
901"mconsole=notify:<socket>\n"
902" Requests that the mconsole driver send a message to the named Unix\n"
903" socket containing the name of the mconsole socket. This also serves\n"
904" to notify outside processes when UML has booted far enough to respond\n"
905" to mconsole requests.\n\n"
906);
907
908static int notify_panic(struct notifier_block *self, unsigned long unused1,
909 void *ptr)
910{
911 char *message = ptr;
912
Jeff Dikeae2587e2007-10-16 01:26:57 -0700913 if (notify_socket == NULL)
914 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700915
Jeff Diked50084a2006-01-06 00:18:50 -0800916 mconsole_notify(notify_socket, MCONSOLE_PANIC, message,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700917 strlen(message) + 1);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700918 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700919}
920
921static struct notifier_block panic_exit_notifier = {
922 .notifier_call = notify_panic,
923 .next = NULL,
924 .priority = 1
925};
926
927static int add_notifier(void)
928{
Alan Sterne041c682006-03-27 01:16:30 -0800929 atomic_notifier_chain_register(&panic_notifier_list,
930 &panic_exit_notifier);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700931 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700932}
933
934__initcall(add_notifier);
935
936char *mconsole_notify_socket(void)
937{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700938 return notify_socket;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700939}
940
941EXPORT_SYMBOL(mconsole_notify_socket);