blob: 16d3b3789a50ad6dc37c758d78922e1d11fe147e [file] [log] [blame]
Linus Torvalds1da177e2005-04-16 15:20:36 -07001/*
2 * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org)
Jeff Dike827b3f62008-02-04 22:31:29 -08003 * Copyright (C) 2001 - 2008 Jeff Dike (jdike@{addtoit,linux.intel}.com)
Linus Torvalds1da177e2005-04-16 15:20:36 -07004 * Licensed under the GPL
5 */
6
Jeff Dike827b3f62008-02-04 22:31:29 -08007#include <linux/console.h>
8#include <linux/ctype.h>
9#include <linux/interrupt.h>
10#include <linux/list.h>
11#include <linux/mm.h>
12#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>
20#include <linux/mutex.h>
21#include <asm/uaccess.h>
22
Jeff Dikeae2587e2007-10-16 01:26:57 -070023#include "init.h"
24#include "irq_kern.h"
25#include "irq_user.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070026#include "kern_util.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070027#include "mconsole.h"
28#include "mconsole_kern.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070029#include "os.h"
Linus Torvalds1da177e2005-04-16 15:20:36 -070030
Jeff Diked50084a2006-01-06 00:18:50 -080031static int do_unlink_socket(struct notifier_block *notifier,
Linus Torvalds1da177e2005-04-16 15:20:36 -070032 unsigned long what, void *data)
33{
Jeff Dikeae2587e2007-10-16 01:26:57 -070034 return mconsole_unlink_socket();
Linus Torvalds1da177e2005-04-16 15:20:36 -070035}
36
37
38static struct notifier_block reboot_notifier = {
39 .notifier_call = do_unlink_socket,
40 .priority = 0,
41};
42
Jeff Diked50084a2006-01-06 00:18:50 -080043/* Safe without explicit locking for now. Tasklets provide their own
Linus Torvalds1da177e2005-04-16 15:20:36 -070044 * locking, and the interrupt handler is safe because it can't interrupt
45 * itself and it can only happen on CPU 0.
46 */
47
Jeff Dike90107722006-01-06 00:18:54 -080048static LIST_HEAD(mc_requests);
Linus Torvalds1da177e2005-04-16 15:20:36 -070049
David Howells6d5aefb2006-12-05 19:36:26 +000050static void mc_work_proc(struct work_struct *unused)
Linus Torvalds1da177e2005-04-16 15:20:36 -070051{
52 struct mconsole_entry *req;
53 unsigned long flags;
54
Jeff Dikeae2587e2007-10-16 01:26:57 -070055 while (!list_empty(&mc_requests)) {
Paolo 'Blaisorblade' Giarrussodbdb4c02006-04-10 22:53:39 -070056 local_irq_save(flags);
Jeff Dikeae2587e2007-10-16 01:26:57 -070057 req = list_entry(mc_requests.next, struct mconsole_entry, list);
Linus Torvalds1da177e2005-04-16 15:20:36 -070058 list_del(&req->list);
59 local_irq_restore(flags);
60 req->request.cmd->handler(&req->request);
61 kfree(req);
62 }
63}
64
David Howells6d5aefb2006-12-05 19:36:26 +000065static DECLARE_WORK(mconsole_work, mc_work_proc);
Linus Torvalds1da177e2005-04-16 15:20:36 -070066
Al Viro7bea96f2006-10-08 22:49:34 +010067static irqreturn_t mconsole_interrupt(int irq, void *dev_id)
Linus Torvalds1da177e2005-04-16 15:20:36 -070068{
69 /* long to avoid size mismatch warnings from gcc */
70 long fd;
71 struct mconsole_entry *new;
Al Viro3a512372006-10-24 11:15:29 +010072 static struct mc_request req; /* that's OK */
Linus Torvalds1da177e2005-04-16 15:20:36 -070073
74 fd = (long) dev_id;
Jeff Dikeae2587e2007-10-16 01:26:57 -070075 while (mconsole_get_request(fd, &req)) {
76 if (req.cmd->context == MCONSOLE_INTR)
Linus Torvalds1da177e2005-04-16 15:20:36 -070077 (*req.cmd->handler)(&req);
78 else {
Jeff Dike60baa152006-04-10 22:53:28 -070079 new = kmalloc(sizeof(*new), GFP_NOWAIT);
Jeff Dikeae2587e2007-10-16 01:26:57 -070080 if (new == NULL)
Linus Torvalds1da177e2005-04-16 15:20:36 -070081 mconsole_reply(&req, "Out of memory", 1, 0);
82 else {
83 new->request = req;
Al Viro3a512372006-10-24 11:15:29 +010084 new->request.regs = get_irq_regs()->regs;
Linus Torvalds1da177e2005-04-16 15:20:36 -070085 list_add(&new->list, &mc_requests);
86 }
87 }
88 }
Jeff Dikeae2587e2007-10-16 01:26:57 -070089 if (!list_empty(&mc_requests))
Linus Torvalds1da177e2005-04-16 15:20:36 -070090 schedule_work(&mconsole_work);
91 reactivate_fd(fd, MCONSOLE_IRQ);
Jeff Dikeae2587e2007-10-16 01:26:57 -070092 return IRQ_HANDLED;
Linus Torvalds1da177e2005-04-16 15:20:36 -070093}
94
95void mconsole_version(struct mc_request *req)
96{
97 char version[256];
98
Serge E. Hallyne9ff3992006-10-02 02:18:11 -070099 sprintf(version, "%s %s %s %s %s", utsname()->sysname,
Jeff Dikeae2587e2007-10-16 01:26:57 -0700100 utsname()->nodename, utsname()->release, utsname()->version,
101 utsname()->machine);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700102 mconsole_reply(req, version, 0, 0);
103}
104
105void mconsole_log(struct mc_request *req)
106{
107 int len;
108 char *ptr = req->request.data;
109
110 ptr += strlen("log ");
111
112 len = req->len - (ptr - req->request.data);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700113 printk(KERN_WARNING "%.*s", len, ptr);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700114 mconsole_reply(req, "", 0, 0);
115}
116
117/* This is a more convoluted version of mconsole_proc, which has some stability
118 * problems; however, we need it fixed, because it is expected that UML users
119 * mount HPPFS instead of procfs on /proc. And we want mconsole_proc to still
120 * show the real procfs content, not the ones from hppfs.*/
121#if 0
122void mconsole_proc(struct mc_request *req)
123{
124 struct nameidata nd;
125 struct file_system_type *proc;
126 struct super_block *super;
127 struct file *file;
128 int n, err;
129 char *ptr = req->request.data, *buf;
130
131 ptr += strlen("proc");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700132 while (isspace(*ptr)) ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700133
134 proc = get_fs_type("proc");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700135 if (proc == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700136 mconsole_reply(req, "procfs not registered", 1, 0);
137 goto out;
138 }
139
140 super = (*proc->get_sb)(proc, 0, NULL, NULL);
141 put_filesystem(proc);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700142 if (super == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700143 mconsole_reply(req, "Failed to get procfs superblock", 1, 0);
144 goto out;
145 }
146 up_write(&super->s_umount);
147
Jan Blunck4ac91372008-02-14 19:34:32 -0800148 nd.path.dentry = super->s_root;
149 nd.path.mnt = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700150 nd.flags = O_RDONLY + 1;
151 nd.last_type = LAST_ROOT;
152
153 /* START: it was experienced that the stability problems are closed
154 * if commenting out these two calls + the below read cycle. To
155 * make UML crash again, it was enough to readd either one.*/
156 err = link_path_walk(ptr, &nd);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700157 if (err) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700158 mconsole_reply(req, "Failed to look up file", 1, 0);
159 goto out_kill;
160 }
161
David Howells745ca242008-11-14 10:39:22 +1100162 file = dentry_open(nd.path.dentry, nd.path.mnt, O_RDONLY,
163 current_cred());
Jeff Dikeae2587e2007-10-16 01:26:57 -0700164 if (IS_ERR(file)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700165 mconsole_reply(req, "Failed to open file", 1, 0);
166 goto out_kill;
167 }
168 /*END*/
169
170 buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700171 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700172 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
173 goto out_fput;
174 }
175
Jeff Dikeae2587e2007-10-16 01:26:57 -0700176 if ((file->f_op != NULL) && (file->f_op->read != NULL)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700177 do {
178 n = (*file->f_op->read)(file, buf, PAGE_SIZE - 1,
179 &file->f_pos);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700180 if (n >= 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700181 buf[n] = '\0';
182 mconsole_reply(req, buf, 0, (n > 0));
183 }
184 else {
185 mconsole_reply(req, "Read of file failed",
186 1, 0);
187 goto out_free;
188 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700189 } while (n > 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700190 }
191 else mconsole_reply(req, "", 0, 0);
192
193 out_free:
194 kfree(buf);
195 out_fput:
196 fput(file);
197 out_kill:
198 deactivate_super(super);
199 out: ;
200}
201#endif
202
203void mconsole_proc(struct mc_request *req)
204{
205 char path[64];
206 char *buf;
207 int len;
208 int fd;
209 int first_chunk = 1;
210 char *ptr = req->request.data;
211
212 ptr += strlen("proc");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700213 while (isspace(*ptr))
214 ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700215 snprintf(path, sizeof(path), "/proc/%s", ptr);
216
217 fd = sys_open(path, 0, 0);
218 if (fd < 0) {
219 mconsole_reply(req, "Failed to open file", 1, 0);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700220 printk(KERN_ERR "open %s: %d\n",path,fd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700221 goto out;
222 }
223
224 buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700225 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700226 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
227 goto out_close;
228 }
229
230 for (;;) {
231 len = sys_read(fd, buf, PAGE_SIZE-1);
232 if (len < 0) {
233 mconsole_reply(req, "Read of file failed", 1, 0);
234 goto out_free;
235 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700236 /* Begin the file content on his own line. */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700237 if (first_chunk) {
238 mconsole_reply(req, "\n", 0, 1);
239 first_chunk = 0;
240 }
241 if (len == PAGE_SIZE-1) {
242 buf[len] = '\0';
243 mconsole_reply(req, buf, 0, 1);
244 } else {
245 buf[len] = '\0';
246 mconsole_reply(req, buf, 0, 0);
247 break;
248 }
249 }
250
251 out_free:
252 kfree(buf);
253 out_close:
254 sys_close(fd);
255 out:
256 /* nothing */;
257}
258
259#define UML_MCONSOLE_HELPTEXT \
260"Commands: \n\
261 version - Get kernel version \n\
262 help - Print this message \n\
263 halt - Halt UML \n\
264 reboot - Reboot UML \n\
265 config <dev>=<config> - Add a new device to UML; \n\
266 same syntax as command line \n\
267 config <dev> - Query the configuration of a device \n\
268 remove <dev> - Remove a device from UML \n\
269 sysrq <letter> - Performs the SysRq action controlled by the letter \n\
Jeff Dikedb805812006-02-01 03:06:23 -0800270 cad - invoke the Ctrl-Alt-Del handler \n\
Linus Torvalds1da177e2005-04-16 15:20:36 -0700271 stop - pause the UML; it will do nothing until it receives a 'go' \n\
272 go - continue the UML after a 'stop' \n\
273 log <string> - make UML enter <string> into the kernel log\n\
274 proc <file> - returns the contents of the UML's /proc/<file>\n\
Jeff Dike3eddddc2005-09-16 19:27:46 -0700275 stack <pid> - returns the stack of the specified pid\n\
Linus Torvalds1da177e2005-04-16 15:20:36 -0700276"
277
278void mconsole_help(struct mc_request *req)
279{
280 mconsole_reply(req, UML_MCONSOLE_HELPTEXT, 0, 0);
281}
282
283void mconsole_halt(struct mc_request *req)
284{
285 mconsole_reply(req, "", 0, 0);
286 machine_halt();
287}
288
289void mconsole_reboot(struct mc_request *req)
290{
291 mconsole_reply(req, "", 0, 0);
292 machine_restart(NULL);
293}
294
Linus Torvalds1da177e2005-04-16 15:20:36 -0700295void mconsole_cad(struct mc_request *req)
296{
297 mconsole_reply(req, "", 0, 0);
298 ctrl_alt_del();
299}
300
301void mconsole_go(struct mc_request *req)
302{
303 mconsole_reply(req, "Not stopped", 1, 0);
304}
305
306void mconsole_stop(struct mc_request *req)
307{
308 deactivate_fd(req->originating_fd, MCONSOLE_IRQ);
309 os_set_fd_block(req->originating_fd, 1);
Al Viro3a512372006-10-24 11:15:29 +0100310 mconsole_reply(req, "stopped", 0, 0);
Karol Swietlickicc0be0f2008-02-04 22:31:25 -0800311 for (;;) {
312 if (!mconsole_get_request(req->originating_fd, req))
313 continue;
Al Viro3a512372006-10-24 11:15:29 +0100314 if (req->cmd->handler == mconsole_go)
315 break;
316 if (req->cmd->handler == mconsole_stop) {
317 mconsole_reply(req, "Already stopped", 1, 0);
318 continue;
319 }
320 if (req->cmd->handler == mconsole_sysrq) {
321 struct pt_regs *old_regs;
322 old_regs = set_irq_regs((struct pt_regs *)&req->regs);
323 mconsole_sysrq(req);
324 set_irq_regs(old_regs);
325 continue;
326 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700327 (*req->cmd->handler)(req);
328 }
329 os_set_fd_block(req->originating_fd, 0);
330 reactivate_fd(req->originating_fd, MCONSOLE_IRQ);
331 mconsole_reply(req, "", 0, 0);
332}
333
Jeff Dike84f48d42007-02-10 01:44:01 -0800334static DEFINE_SPINLOCK(mc_devices_lock);
Paolo 'Blaisorblade' Giarrusso42947cb2006-02-01 03:06:29 -0800335static LIST_HEAD(mconsole_devices);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700336
337void mconsole_register_dev(struct mc_device *new)
338{
Jeff Dike84f48d42007-02-10 01:44:01 -0800339 spin_lock(&mc_devices_lock);
340 BUG_ON(!list_empty(&new->list));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700341 list_add(&new->list, &mconsole_devices);
Jeff Dike84f48d42007-02-10 01:44:01 -0800342 spin_unlock(&mc_devices_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700343}
344
345static struct mc_device *mconsole_find_dev(char *name)
346{
347 struct list_head *ele;
348 struct mc_device *dev;
349
Jeff Dikeae2587e2007-10-16 01:26:57 -0700350 list_for_each(ele, &mconsole_devices) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700351 dev = list_entry(ele, struct mc_device, list);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700352 if (!strncmp(name, dev->name, strlen(dev->name)))
353 return dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700354 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700355 return NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700356}
357
Jeff Dike02dea082006-03-31 02:30:08 -0800358#define UNPLUGGED_PER_PAGE \
359 ((PAGE_SIZE - sizeof(struct list_head)) / sizeof(unsigned long))
360
361struct unplugged_pages {
362 struct list_head list;
363 void *pages[UNPLUGGED_PER_PAGE];
364};
365
Daniel Walkere98fa282008-02-04 22:31:27 -0800366static DEFINE_MUTEX(plug_mem_mutex);
Jeff Dike02dea082006-03-31 02:30:08 -0800367static unsigned long long unplugged_pages_count = 0;
Jeff Dikec59bce62007-02-10 01:44:04 -0800368static LIST_HEAD(unplugged_pages);
Jeff Dike02dea082006-03-31 02:30:08 -0800369static int unplug_index = UNPLUGGED_PER_PAGE;
370
Jeff Dikef28169d2007-02-10 01:43:53 -0800371static int mem_config(char *str, char **error_out)
Jeff Dike02dea082006-03-31 02:30:08 -0800372{
373 unsigned long long diff;
374 int err = -EINVAL, i, add;
375 char *ret;
376
Jeff Dikeae2587e2007-10-16 01:26:57 -0700377 if (str[0] != '=') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800378 *error_out = "Expected '=' after 'mem'";
Jeff Dike02dea082006-03-31 02:30:08 -0800379 goto out;
Jeff Dikef28169d2007-02-10 01:43:53 -0800380 }
Jeff Dike02dea082006-03-31 02:30:08 -0800381
382 str++;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700383 if (str[0] == '-')
Jeff Dike02dea082006-03-31 02:30:08 -0800384 add = 0;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700385 else if (str[0] == '+') {
Jeff Dike02dea082006-03-31 02:30:08 -0800386 add = 1;
387 }
Jeff Dikef28169d2007-02-10 01:43:53 -0800388 else {
389 *error_out = "Expected increment to start with '-' or '+'";
390 goto out;
391 }
Jeff Dike02dea082006-03-31 02:30:08 -0800392
393 str++;
394 diff = memparse(str, &ret);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700395 if (*ret != '\0') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800396 *error_out = "Failed to parse memory increment";
Jeff Dike02dea082006-03-31 02:30:08 -0800397 goto out;
Jeff Dikef28169d2007-02-10 01:43:53 -0800398 }
Jeff Dike02dea082006-03-31 02:30:08 -0800399
400 diff /= PAGE_SIZE;
401
Daniel Walkere98fa282008-02-04 22:31:27 -0800402 mutex_lock(&plug_mem_mutex);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700403 for (i = 0; i < diff; i++) {
Jeff Dike02dea082006-03-31 02:30:08 -0800404 struct unplugged_pages *unplugged;
405 void *addr;
406
Jeff Dikeae2587e2007-10-16 01:26:57 -0700407 if (add) {
408 if (list_empty(&unplugged_pages))
Jeff Dike02dea082006-03-31 02:30:08 -0800409 break;
410
411 unplugged = list_entry(unplugged_pages.next,
412 struct unplugged_pages, list);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700413 if (unplug_index > 0)
Jeff Dike02dea082006-03-31 02:30:08 -0800414 addr = unplugged->pages[--unplug_index];
415 else {
416 list_del(&unplugged->list);
417 addr = unplugged;
418 unplug_index = UNPLUGGED_PER_PAGE;
419 }
420
421 free_page((unsigned long) addr);
422 unplugged_pages_count--;
423 }
424 else {
425 struct page *page;
426
427 page = alloc_page(GFP_ATOMIC);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700428 if (page == NULL)
Jeff Dike02dea082006-03-31 02:30:08 -0800429 break;
430
431 unplugged = page_address(page);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700432 if (unplug_index == UNPLUGGED_PER_PAGE) {
Jeff Dike02dea082006-03-31 02:30:08 -0800433 list_add(&unplugged->list, &unplugged_pages);
434 unplug_index = 0;
435 }
436 else {
437 struct list_head *entry = unplugged_pages.next;
438 addr = unplugged;
439
440 unplugged = list_entry(entry,
441 struct unplugged_pages,
442 list);
Jeff Dike02dea082006-03-31 02:30:08 -0800443 err = os_drop_memory(addr, PAGE_SIZE);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700444 if (err) {
445 printk(KERN_ERR "Failed to release "
446 "memory - errno = %d\n", err);
Jeff Dikef28169d2007-02-10 01:43:53 -0800447 *error_out = "Failed to release memory";
Jeff Dike84f48d42007-02-10 01:44:01 -0800448 goto out_unlock;
Jeff Dikef28169d2007-02-10 01:43:53 -0800449 }
450 unplugged->pages[unplug_index++] = addr;
Jeff Dike02dea082006-03-31 02:30:08 -0800451 }
452
453 unplugged_pages_count++;
454 }
455 }
456
457 err = 0;
Jeff Dike84f48d42007-02-10 01:44:01 -0800458out_unlock:
Daniel Walkere98fa282008-02-04 22:31:27 -0800459 mutex_unlock(&plug_mem_mutex);
Jeff Dike02dea082006-03-31 02:30:08 -0800460out:
461 return err;
462}
463
464static int mem_get_config(char *name, char *str, int size, char **error_out)
465{
466 char buf[sizeof("18446744073709551615")];
467 int len = 0;
468
469 sprintf(buf, "%ld", uml_physmem);
470 CONFIG_CHUNK(str, size, len, buf, 1);
471
472 return len;
473}
474
475static int mem_id(char **str, int *start_out, int *end_out)
476{
477 *start_out = 0;
478 *end_out = 0;
479
480 return 0;
481}
482
Jeff Dikef28169d2007-02-10 01:43:53 -0800483static int mem_remove(int n, char **error_out)
Jeff Dike02dea082006-03-31 02:30:08 -0800484{
Jeff Dikef28169d2007-02-10 01:43:53 -0800485 *error_out = "Memory doesn't support the remove operation";
Jeff Dike02dea082006-03-31 02:30:08 -0800486 return -EBUSY;
487}
488
489static struct mc_device mem_mc = {
Jeff Dike84f48d42007-02-10 01:44:01 -0800490 .list = LIST_HEAD_INIT(mem_mc.list),
Jeff Dike02dea082006-03-31 02:30:08 -0800491 .name = "mem",
492 .config = mem_config,
493 .get_config = mem_get_config,
494 .id = mem_id,
495 .remove = mem_remove,
496};
497
Jeff Dike97a1fcb2007-07-23 18:43:48 -0700498static int __init mem_mc_init(void)
Jeff Dike02dea082006-03-31 02:30:08 -0800499{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700500 if (can_drop_memory())
Jeff Dike02dea082006-03-31 02:30:08 -0800501 mconsole_register_dev(&mem_mc);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700502 else printk(KERN_ERR "Can't release memory to the host - memory "
503 "hotplug won't be supported\n");
Jeff Dike02dea082006-03-31 02:30:08 -0800504 return 0;
505}
506
507__initcall(mem_mc_init);
508
Linus Torvalds1da177e2005-04-16 15:20:36 -0700509#define CONFIG_BUF_SIZE 64
510
Jeff Diked50084a2006-01-06 00:18:50 -0800511static void mconsole_get_config(int (*get_config)(char *, char *, int,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700512 char **),
513 struct mc_request *req, char *name)
514{
515 char default_buf[CONFIG_BUF_SIZE], *error, *buf;
516 int n, size;
517
Jeff Dikeae2587e2007-10-16 01:26:57 -0700518 if (get_config == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700519 mconsole_reply(req, "No get_config routine defined", 1, 0);
520 return;
521 }
522
523 error = NULL;
Jeff Dike91b165c2006-09-25 23:33:00 -0700524 size = ARRAY_SIZE(default_buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700525 buf = default_buf;
526
Jeff Dikeae2587e2007-10-16 01:26:57 -0700527 while (1) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700528 n = (*get_config)(name, buf, size, &error);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700529 if (error != NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700530 mconsole_reply(req, error, 1, 0);
531 goto out;
532 }
533
Jeff Dikeae2587e2007-10-16 01:26:57 -0700534 if (n <= size) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700535 mconsole_reply(req, buf, 0, 0);
536 goto out;
537 }
538
Jeff Dikeae2587e2007-10-16 01:26:57 -0700539 if (buf != default_buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700540 kfree(buf);
541
542 size = n;
543 buf = kmalloc(size, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700544 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700545 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
546 return;
547 }
548 }
549 out:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700550 if (buf != default_buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700551 kfree(buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700552}
553
554void mconsole_config(struct mc_request *req)
555{
556 struct mc_device *dev;
Jeff Dikef28169d2007-02-10 01:43:53 -0800557 char *ptr = req->request.data, *name, *error_string = "";
Linus Torvalds1da177e2005-04-16 15:20:36 -0700558 int err;
559
560 ptr += strlen("config");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700561 while (isspace(*ptr))
562 ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700563 dev = mconsole_find_dev(ptr);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700564 if (dev == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700565 mconsole_reply(req, "Bad configuration option", 1, 0);
566 return;
567 }
568
569 name = &ptr[strlen(dev->name)];
570 ptr = name;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700571 while ((*ptr != '=') && (*ptr != '\0'))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700572 ptr++;
573
Jeff Dikeae2587e2007-10-16 01:26:57 -0700574 if (*ptr == '=') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800575 err = (*dev->config)(name, &error_string);
576 mconsole_reply(req, error_string, err, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700577 }
578 else mconsole_get_config(dev->get_config, req, name);
579}
580
581void mconsole_remove(struct mc_request *req)
582{
Jeff Diked50084a2006-01-06 00:18:50 -0800583 struct mc_device *dev;
Jeff Dike29d56cf2005-06-25 14:55:25 -0700584 char *ptr = req->request.data, *err_msg = "";
Jeff Dike3a331a52006-01-06 00:19:05 -0800585 char error[256];
Jeff Dike29d56cf2005-06-25 14:55:25 -0700586 int err, start, end, n;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700587
588 ptr += strlen("remove");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700589 while (isspace(*ptr)) ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700590 dev = mconsole_find_dev(ptr);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700591 if (dev == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700592 mconsole_reply(req, "Bad remove option", 1, 0);
593 return;
594 }
Jeff Dike29d56cf2005-06-25 14:55:25 -0700595
Jeff Dike3a331a52006-01-06 00:19:05 -0800596 ptr = &ptr[strlen(dev->name)];
Jeff Dike29d56cf2005-06-25 14:55:25 -0700597
Jeff Dike3a331a52006-01-06 00:19:05 -0800598 err = 1;
599 n = (*dev->id)(&ptr, &start, &end);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700600 if (n < 0) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800601 err_msg = "Couldn't parse device number";
602 goto out;
603 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700604 else if ((n < start) || (n > end)) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800605 sprintf(error, "Invalid device number - must be between "
606 "%d and %d", start, end);
607 err_msg = error;
608 goto out;
609 }
Jeff Dike29d56cf2005-06-25 14:55:25 -0700610
Jeff Dikef28169d2007-02-10 01:43:53 -0800611 err_msg = NULL;
612 err = (*dev->remove)(n, &err_msg);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700613 switch(err) {
Jeff Diked40f6d72007-03-29 01:20:28 -0700614 case 0:
615 err_msg = "";
616 break;
Jeff Dike3a331a52006-01-06 00:19:05 -0800617 case -ENODEV:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700618 if (err_msg == NULL)
Jeff Dikef28169d2007-02-10 01:43:53 -0800619 err_msg = "Device doesn't exist";
Jeff Dike3a331a52006-01-06 00:19:05 -0800620 break;
621 case -EBUSY:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700622 if (err_msg == NULL)
Jeff Dikef28169d2007-02-10 01:43:53 -0800623 err_msg = "Device is currently open";
Jeff Dike3a331a52006-01-06 00:19:05 -0800624 break;
625 default:
626 break;
627 }
628out:
Jeff Dike29d56cf2005-06-25 14:55:25 -0700629 mconsole_reply(req, err_msg, err, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700630}
631
Jeff Dikef92afe52006-09-29 01:58:52 -0700632struct mconsole_output {
633 struct list_head list;
634 struct mc_request *req;
635};
636
Jeff Dike84f48d42007-02-10 01:44:01 -0800637static DEFINE_SPINLOCK(client_lock);
Jeff Dike6f517d32006-01-06 00:19:04 -0800638static LIST_HEAD(clients);
639static char console_buf[MCONSOLE_MAX_DATA];
Jeff Dike6f517d32006-01-06 00:19:04 -0800640
641static void console_write(struct console *console, const char *string,
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700642 unsigned int len)
Jeff Dike6f517d32006-01-06 00:19:04 -0800643{
644 struct list_head *ele;
645 int n;
646
Jeff Dikeae2587e2007-10-16 01:26:57 -0700647 if (list_empty(&clients))
Jeff Dike6f517d32006-01-06 00:19:04 -0800648 return;
649
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700650 while (len > 0) {
651 n = min((size_t) len, ARRAY_SIZE(console_buf));
652 strncpy(console_buf, string, n);
Jeff Dike6f517d32006-01-06 00:19:04 -0800653 string += n;
654 len -= n;
Jeff Dike6f517d32006-01-06 00:19:04 -0800655
Jeff Dikeae2587e2007-10-16 01:26:57 -0700656 list_for_each(ele, &clients) {
Jeff Dikef92afe52006-09-29 01:58:52 -0700657 struct mconsole_output *entry;
Jeff Dike6f517d32006-01-06 00:19:04 -0800658
Jeff Dikef92afe52006-09-29 01:58:52 -0700659 entry = list_entry(ele, struct mconsole_output, list);
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700660 mconsole_reply_len(entry->req, console_buf, n, 0, 1);
Jeff Dike6f517d32006-01-06 00:19:04 -0800661 }
Jeff Dike6f517d32006-01-06 00:19:04 -0800662 }
663}
664
665static struct console mc_console = { .name = "mc",
666 .write = console_write,
Jeff Dikea174b302006-01-11 12:17:28 -0800667 .flags = CON_ENABLED,
Jeff Dike6f517d32006-01-06 00:19:04 -0800668 .index = -1 };
669
670static int mc_add_console(void)
671{
672 register_console(&mc_console);
673 return 0;
674}
675
676late_initcall(mc_add_console);
677
678static void with_console(struct mc_request *req, void (*proc)(void *),
679 void *arg)
680{
Jeff Dikef92afe52006-09-29 01:58:52 -0700681 struct mconsole_output entry;
Jeff Dike6f517d32006-01-06 00:19:04 -0800682 unsigned long flags;
683
Jeff Dikef92afe52006-09-29 01:58:52 -0700684 entry.req = req;
Jeff Dike84f48d42007-02-10 01:44:01 -0800685 spin_lock_irqsave(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800686 list_add(&entry.list, &clients);
Jeff Dike84f48d42007-02-10 01:44:01 -0800687 spin_unlock_irqrestore(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800688
689 (*proc)(arg);
690
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700691 mconsole_reply_len(req, "", 0, 0, 0);
Jeff Dike6f517d32006-01-06 00:19:04 -0800692
Jeff Dike84f48d42007-02-10 01:44:01 -0800693 spin_lock_irqsave(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800694 list_del(&entry.list);
Jeff Dike84f48d42007-02-10 01:44:01 -0800695 spin_unlock_irqrestore(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800696}
697
Jeff Dike4111b022006-01-06 00:19:05 -0800698#ifdef CONFIG_MAGIC_SYSRQ
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700699
700#include <linux/sysrq.h>
701
Jeff Dike4111b022006-01-06 00:19:05 -0800702static void sysrq_proc(void *arg)
703{
704 char *op = arg;
Al Viro7bea96f2006-10-08 22:49:34 +0100705 handle_sysrq(*op, NULL);
Jeff Dike4111b022006-01-06 00:19:05 -0800706}
707
708void mconsole_sysrq(struct mc_request *req)
709{
710 char *ptr = req->request.data;
711
712 ptr += strlen("sysrq");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700713 while (isspace(*ptr)) ptr++;
Jeff Dike4111b022006-01-06 00:19:05 -0800714
Jeff Dikeae2587e2007-10-16 01:26:57 -0700715 /*
716 * With 'b', the system will shut down without a chance to reply,
Jeff Dike4111b022006-01-06 00:19:05 -0800717 * so in this case, we reply first.
718 */
Jeff Dikeae2587e2007-10-16 01:26:57 -0700719 if (*ptr == 'b')
Jeff Dike4111b022006-01-06 00:19:05 -0800720 mconsole_reply(req, "", 0, 0);
721
722 with_console(req, sysrq_proc, ptr);
723}
724#else
725void mconsole_sysrq(struct mc_request *req)
726{
727 mconsole_reply(req, "Sysrq not compiled in", 1, 0);
728}
729#endif
730
Jeff Dike6f517d32006-01-06 00:19:04 -0800731static void stack_proc(void *arg)
732{
733 struct task_struct *from = current, *to = arg;
734
735 to->thread.saved_task = from;
736 switch_to(from, to, from);
737}
738
Jeff Dikeae2587e2007-10-16 01:26:57 -0700739/*
740 * Mconsole stack trace
Jeff Dike3eddddc2005-09-16 19:27:46 -0700741 * Added by Allan Graves, Jeff Dike
742 * Dumps a stacks registers to the linux console.
743 * Usage stack <pid>.
744 */
Jeff Dike42fda662007-10-16 01:26:50 -0700745void mconsole_stack(struct mc_request *req)
Jeff Dike3eddddc2005-09-16 19:27:46 -0700746{
Jeff Dike3a331a52006-01-06 00:19:05 -0800747 char *ptr = req->request.data;
748 int pid_requested= -1;
Jeff Dike3eddddc2005-09-16 19:27:46 -0700749 struct task_struct *to = NULL;
750
Jeff Dikeae2587e2007-10-16 01:26:57 -0700751 /*
752 * Would be nice:
Jeff Dike3a331a52006-01-06 00:19:05 -0800753 * 1) Send showregs output to mconsole.
Jeff Dike3eddddc2005-09-16 19:27:46 -0700754 * 2) Add a way to stack dump all pids.
755 */
756
Jeff Dike3a331a52006-01-06 00:19:05 -0800757 ptr += strlen("stack");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700758 while (isspace(*ptr))
759 ptr++;
Jeff Dike3eddddc2005-09-16 19:27:46 -0700760
Jeff Dikeae2587e2007-10-16 01:26:57 -0700761 /*
762 * Should really check for multiple pids or reject bad args here
763 */
Jeff Dike3a331a52006-01-06 00:19:05 -0800764 /* What do the arguments in mconsole_reply mean? */
Jeff Dikeae2587e2007-10-16 01:26:57 -0700765 if (sscanf(ptr, "%d", &pid_requested) == 0) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800766 mconsole_reply(req, "Please specify a pid", 1, 0);
767 return;
768 }
Jeff Dike3eddddc2005-09-16 19:27:46 -0700769
Jeff Dike827b3f62008-02-04 22:31:29 -0800770 to = find_task_by_pid_ns(pid_requested, &init_pid_ns);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700771 if ((to == NULL) || (pid_requested == 0)) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800772 mconsole_reply(req, "Couldn't find that pid", 1, 0);
773 return;
774 }
Jeff Dike6f517d32006-01-06 00:19:04 -0800775 with_console(req, stack_proc, to);
Jeff Dike3eddddc2005-09-16 19:27:46 -0700776}
Jeff Dike3eddddc2005-09-16 19:27:46 -0700777
Jeff Dikeae2587e2007-10-16 01:26:57 -0700778/*
779 * Changed by mconsole_setup, which is __setup, and called before SMP is
Linus Torvalds1da177e2005-04-16 15:20:36 -0700780 * active.
781 */
Jeff Diked50084a2006-01-06 00:18:50 -0800782static char *notify_socket = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700783
Jeff Dike97a1fcb2007-07-23 18:43:48 -0700784static int __init mconsole_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700785{
786 /* long to avoid size mismatch warnings from gcc */
787 long sock;
788 int err;
789 char file[256];
790
Jeff Dikeae2587e2007-10-16 01:26:57 -0700791 if (umid_file_name("mconsole", file, sizeof(file)))
792 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700793 snprintf(mconsole_socket_name, sizeof(file), "%s", file);
794
795 sock = os_create_unix_socket(file, sizeof(file), 1);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700796 if (sock < 0) {
797 printk(KERN_ERR "Failed to initialize management console\n");
798 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700799 }
Jeff Dike438ee672008-02-04 22:31:19 -0800800 if (os_set_fd_block(sock, 0))
801 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700802
803 register_reboot_notifier(&reboot_notifier);
804
805 err = um_request_irq(MCONSOLE_IRQ, sock, IRQ_READ, mconsole_interrupt,
Thomas Gleixnerbd6aa652006-07-01 19:29:27 -0700806 IRQF_DISABLED | IRQF_SHARED | IRQF_SAMPLE_RANDOM,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700807 "mconsole", (void *)sock);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700808 if (err) {
809 printk(KERN_ERR "Failed to get IRQ for management console\n");
Jeff Dike438ee672008-02-04 22:31:19 -0800810 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700811 }
812
Jeff Dikeae2587e2007-10-16 01:26:57 -0700813 if (notify_socket != NULL) {
Jeff Dike970d6e32006-01-06 00:18:48 -0800814 notify_socket = kstrdup(notify_socket, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700815 if (notify_socket != NULL)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700816 mconsole_notify(notify_socket, MCONSOLE_SOCKET,
Jeff Diked50084a2006-01-06 00:18:50 -0800817 mconsole_socket_name,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700818 strlen(mconsole_socket_name) + 1);
819 else printk(KERN_ERR "mconsole_setup failed to strdup "
820 "string\n");
821 }
822
Jeff Dikeae2587e2007-10-16 01:26:57 -0700823 printk(KERN_INFO "mconsole (version %d) initialized on %s\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700824 MCONSOLE_VERSION, mconsole_socket_name);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700825 return 0;
Jeff Dike438ee672008-02-04 22:31:19 -0800826
827 out:
828 os_close_file(sock);
829 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700830}
831
832__initcall(mconsole_init);
833
834static int write_proc_mconsole(struct file *file, const char __user *buffer,
835 unsigned long count, void *data)
836{
837 char *buf;
838
839 buf = kmalloc(count + 1, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700840 if (buf == NULL)
841 return -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700842
Jeff Dikeae2587e2007-10-16 01:26:57 -0700843 if (copy_from_user(buf, buffer, count)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700844 count = -EFAULT;
845 goto out;
846 }
847
848 buf[count] = '\0';
849
850 mconsole_notify(notify_socket, MCONSOLE_USER_NOTIFY, buf, count);
851 out:
852 kfree(buf);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700853 return count;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700854}
855
856static int create_proc_mconsole(void)
857{
858 struct proc_dir_entry *ent;
859
Jeff Dikeae2587e2007-10-16 01:26:57 -0700860 if (notify_socket == NULL)
861 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700862
863 ent = create_proc_entry("mconsole", S_IFREG | 0200, NULL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700864 if (ent == NULL) {
865 printk(KERN_INFO "create_proc_mconsole : create_proc_entry "
866 "failed\n");
867 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700868 }
869
870 ent->read_proc = NULL;
871 ent->write_proc = write_proc_mconsole;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700872 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700873}
874
875static DEFINE_SPINLOCK(notify_spinlock);
876
877void lock_notify(void)
878{
879 spin_lock(&notify_spinlock);
880}
881
882void unlock_notify(void)
883{
884 spin_unlock(&notify_spinlock);
885}
886
887__initcall(create_proc_mconsole);
888
Jeff Dike088bec42007-10-16 01:27:20 -0700889#define NOTIFY "notify:"
Linus Torvalds1da177e2005-04-16 15:20:36 -0700890
891static int mconsole_setup(char *str)
892{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700893 if (!strncmp(str, NOTIFY, strlen(NOTIFY))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700894 str += strlen(NOTIFY);
895 notify_socket = str;
896 }
897 else printk(KERN_ERR "mconsole_setup : Unknown option - '%s'\n", str);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700898 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700899}
900
Jeff Dike088bec42007-10-16 01:27:20 -0700901__setup("mconsole=", mconsole_setup);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700902
903__uml_help(mconsole_setup,
904"mconsole=notify:<socket>\n"
905" Requests that the mconsole driver send a message to the named Unix\n"
906" socket containing the name of the mconsole socket. This also serves\n"
907" to notify outside processes when UML has booted far enough to respond\n"
908" to mconsole requests.\n\n"
909);
910
911static int notify_panic(struct notifier_block *self, unsigned long unused1,
912 void *ptr)
913{
914 char *message = ptr;
915
Jeff Dikeae2587e2007-10-16 01:26:57 -0700916 if (notify_socket == NULL)
917 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700918
Jeff Diked50084a2006-01-06 00:18:50 -0800919 mconsole_notify(notify_socket, MCONSOLE_PANIC, message,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700920 strlen(message) + 1);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700921 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700922}
923
924static struct notifier_block panic_exit_notifier = {
925 .notifier_call = notify_panic,
926 .next = NULL,
927 .priority = 1
928};
929
930static int add_notifier(void)
931{
Alan Sterne041c682006-03-27 01:16:30 -0800932 atomic_notifier_chain_register(&panic_notifier_list,
933 &panic_exit_notifier);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700934 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700935}
936
937__initcall(add_notifier);
938
939char *mconsole_notify_socket(void)
940{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700941 return notify_socket;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700942}
943
944EXPORT_SYMBOL(mconsole_notify_socket);