blob: 19d579d74d27f18ae67bb1b5c9adcf93f963a288 [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
Jan Blunck4ac91372008-02-14 19:34:32 -0800162 file = dentry_open(nd.path.dentry, nd.path.mnt, O_RDONLY);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700163 if (IS_ERR(file)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700164 mconsole_reply(req, "Failed to open file", 1, 0);
165 goto out_kill;
166 }
167 /*END*/
168
169 buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700170 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700171 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
172 goto out_fput;
173 }
174
Jeff Dikeae2587e2007-10-16 01:26:57 -0700175 if ((file->f_op != NULL) && (file->f_op->read != NULL)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700176 do {
177 n = (*file->f_op->read)(file, buf, PAGE_SIZE - 1,
178 &file->f_pos);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700179 if (n >= 0) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700180 buf[n] = '\0';
181 mconsole_reply(req, buf, 0, (n > 0));
182 }
183 else {
184 mconsole_reply(req, "Read of file failed",
185 1, 0);
186 goto out_free;
187 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700188 } while (n > 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700189 }
190 else mconsole_reply(req, "", 0, 0);
191
192 out_free:
193 kfree(buf);
194 out_fput:
195 fput(file);
196 out_kill:
197 deactivate_super(super);
198 out: ;
199}
200#endif
201
202void mconsole_proc(struct mc_request *req)
203{
204 char path[64];
205 char *buf;
206 int len;
207 int fd;
208 int first_chunk = 1;
209 char *ptr = req->request.data;
210
211 ptr += strlen("proc");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700212 while (isspace(*ptr))
213 ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700214 snprintf(path, sizeof(path), "/proc/%s", ptr);
215
216 fd = sys_open(path, 0, 0);
217 if (fd < 0) {
218 mconsole_reply(req, "Failed to open file", 1, 0);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700219 printk(KERN_ERR "open %s: %d\n",path,fd);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700220 goto out;
221 }
222
223 buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700224 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700225 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
226 goto out_close;
227 }
228
229 for (;;) {
230 len = sys_read(fd, buf, PAGE_SIZE-1);
231 if (len < 0) {
232 mconsole_reply(req, "Read of file failed", 1, 0);
233 goto out_free;
234 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700235 /* Begin the file content on his own line. */
Linus Torvalds1da177e2005-04-16 15:20:36 -0700236 if (first_chunk) {
237 mconsole_reply(req, "\n", 0, 1);
238 first_chunk = 0;
239 }
240 if (len == PAGE_SIZE-1) {
241 buf[len] = '\0';
242 mconsole_reply(req, buf, 0, 1);
243 } else {
244 buf[len] = '\0';
245 mconsole_reply(req, buf, 0, 0);
246 break;
247 }
248 }
249
250 out_free:
251 kfree(buf);
252 out_close:
253 sys_close(fd);
254 out:
255 /* nothing */;
256}
257
258#define UML_MCONSOLE_HELPTEXT \
259"Commands: \n\
260 version - Get kernel version \n\
261 help - Print this message \n\
262 halt - Halt UML \n\
263 reboot - Reboot UML \n\
264 config <dev>=<config> - Add a new device to UML; \n\
265 same syntax as command line \n\
266 config <dev> - Query the configuration of a device \n\
267 remove <dev> - Remove a device from UML \n\
268 sysrq <letter> - Performs the SysRq action controlled by the letter \n\
Jeff Dikedb805812006-02-01 03:06:23 -0800269 cad - invoke the Ctrl-Alt-Del handler \n\
Linus Torvalds1da177e2005-04-16 15:20:36 -0700270 stop - pause the UML; it will do nothing until it receives a 'go' \n\
271 go - continue the UML after a 'stop' \n\
272 log <string> - make UML enter <string> into the kernel log\n\
273 proc <file> - returns the contents of the UML's /proc/<file>\n\
Jeff Dike3eddddc2005-09-16 19:27:46 -0700274 stack <pid> - returns the stack of the specified pid\n\
Linus Torvalds1da177e2005-04-16 15:20:36 -0700275"
276
277void mconsole_help(struct mc_request *req)
278{
279 mconsole_reply(req, UML_MCONSOLE_HELPTEXT, 0, 0);
280}
281
282void mconsole_halt(struct mc_request *req)
283{
284 mconsole_reply(req, "", 0, 0);
285 machine_halt();
286}
287
288void mconsole_reboot(struct mc_request *req)
289{
290 mconsole_reply(req, "", 0, 0);
291 machine_restart(NULL);
292}
293
Linus Torvalds1da177e2005-04-16 15:20:36 -0700294void mconsole_cad(struct mc_request *req)
295{
296 mconsole_reply(req, "", 0, 0);
297 ctrl_alt_del();
298}
299
300void mconsole_go(struct mc_request *req)
301{
302 mconsole_reply(req, "Not stopped", 1, 0);
303}
304
305void mconsole_stop(struct mc_request *req)
306{
307 deactivate_fd(req->originating_fd, MCONSOLE_IRQ);
308 os_set_fd_block(req->originating_fd, 1);
Al Viro3a512372006-10-24 11:15:29 +0100309 mconsole_reply(req, "stopped", 0, 0);
Karol Swietlickicc0be0f2008-02-04 22:31:25 -0800310 for (;;) {
311 if (!mconsole_get_request(req->originating_fd, req))
312 continue;
Al Viro3a512372006-10-24 11:15:29 +0100313 if (req->cmd->handler == mconsole_go)
314 break;
315 if (req->cmd->handler == mconsole_stop) {
316 mconsole_reply(req, "Already stopped", 1, 0);
317 continue;
318 }
319 if (req->cmd->handler == mconsole_sysrq) {
320 struct pt_regs *old_regs;
321 old_regs = set_irq_regs((struct pt_regs *)&req->regs);
322 mconsole_sysrq(req);
323 set_irq_regs(old_regs);
324 continue;
325 }
Linus Torvalds1da177e2005-04-16 15:20:36 -0700326 (*req->cmd->handler)(req);
327 }
328 os_set_fd_block(req->originating_fd, 0);
329 reactivate_fd(req->originating_fd, MCONSOLE_IRQ);
330 mconsole_reply(req, "", 0, 0);
331}
332
Jeff Dike84f48d42007-02-10 01:44:01 -0800333static DEFINE_SPINLOCK(mc_devices_lock);
Paolo 'Blaisorblade' Giarrusso42947cb2006-02-01 03:06:29 -0800334static LIST_HEAD(mconsole_devices);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700335
336void mconsole_register_dev(struct mc_device *new)
337{
Jeff Dike84f48d42007-02-10 01:44:01 -0800338 spin_lock(&mc_devices_lock);
339 BUG_ON(!list_empty(&new->list));
Linus Torvalds1da177e2005-04-16 15:20:36 -0700340 list_add(&new->list, &mconsole_devices);
Jeff Dike84f48d42007-02-10 01:44:01 -0800341 spin_unlock(&mc_devices_lock);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700342}
343
344static struct mc_device *mconsole_find_dev(char *name)
345{
346 struct list_head *ele;
347 struct mc_device *dev;
348
Jeff Dikeae2587e2007-10-16 01:26:57 -0700349 list_for_each(ele, &mconsole_devices) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700350 dev = list_entry(ele, struct mc_device, list);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700351 if (!strncmp(name, dev->name, strlen(dev->name)))
352 return dev;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700353 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700354 return NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700355}
356
Jeff Dike02dea082006-03-31 02:30:08 -0800357#define UNPLUGGED_PER_PAGE \
358 ((PAGE_SIZE - sizeof(struct list_head)) / sizeof(unsigned long))
359
360struct unplugged_pages {
361 struct list_head list;
362 void *pages[UNPLUGGED_PER_PAGE];
363};
364
Daniel Walkere98fa282008-02-04 22:31:27 -0800365static DEFINE_MUTEX(plug_mem_mutex);
Jeff Dike02dea082006-03-31 02:30:08 -0800366static unsigned long long unplugged_pages_count = 0;
Jeff Dikec59bce62007-02-10 01:44:04 -0800367static LIST_HEAD(unplugged_pages);
Jeff Dike02dea082006-03-31 02:30:08 -0800368static int unplug_index = UNPLUGGED_PER_PAGE;
369
Jeff Dikef28169d2007-02-10 01:43:53 -0800370static int mem_config(char *str, char **error_out)
Jeff Dike02dea082006-03-31 02:30:08 -0800371{
372 unsigned long long diff;
373 int err = -EINVAL, i, add;
374 char *ret;
375
Jeff Dikeae2587e2007-10-16 01:26:57 -0700376 if (str[0] != '=') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800377 *error_out = "Expected '=' after 'mem'";
Jeff Dike02dea082006-03-31 02:30:08 -0800378 goto out;
Jeff Dikef28169d2007-02-10 01:43:53 -0800379 }
Jeff Dike02dea082006-03-31 02:30:08 -0800380
381 str++;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700382 if (str[0] == '-')
Jeff Dike02dea082006-03-31 02:30:08 -0800383 add = 0;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700384 else if (str[0] == '+') {
Jeff Dike02dea082006-03-31 02:30:08 -0800385 add = 1;
386 }
Jeff Dikef28169d2007-02-10 01:43:53 -0800387 else {
388 *error_out = "Expected increment to start with '-' or '+'";
389 goto out;
390 }
Jeff Dike02dea082006-03-31 02:30:08 -0800391
392 str++;
393 diff = memparse(str, &ret);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700394 if (*ret != '\0') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800395 *error_out = "Failed to parse memory increment";
Jeff Dike02dea082006-03-31 02:30:08 -0800396 goto out;
Jeff Dikef28169d2007-02-10 01:43:53 -0800397 }
Jeff Dike02dea082006-03-31 02:30:08 -0800398
399 diff /= PAGE_SIZE;
400
Daniel Walkere98fa282008-02-04 22:31:27 -0800401 mutex_lock(&plug_mem_mutex);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700402 for (i = 0; i < diff; i++) {
Jeff Dike02dea082006-03-31 02:30:08 -0800403 struct unplugged_pages *unplugged;
404 void *addr;
405
Jeff Dikeae2587e2007-10-16 01:26:57 -0700406 if (add) {
407 if (list_empty(&unplugged_pages))
Jeff Dike02dea082006-03-31 02:30:08 -0800408 break;
409
410 unplugged = list_entry(unplugged_pages.next,
411 struct unplugged_pages, list);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700412 if (unplug_index > 0)
Jeff Dike02dea082006-03-31 02:30:08 -0800413 addr = unplugged->pages[--unplug_index];
414 else {
415 list_del(&unplugged->list);
416 addr = unplugged;
417 unplug_index = UNPLUGGED_PER_PAGE;
418 }
419
420 free_page((unsigned long) addr);
421 unplugged_pages_count--;
422 }
423 else {
424 struct page *page;
425
426 page = alloc_page(GFP_ATOMIC);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700427 if (page == NULL)
Jeff Dike02dea082006-03-31 02:30:08 -0800428 break;
429
430 unplugged = page_address(page);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700431 if (unplug_index == UNPLUGGED_PER_PAGE) {
Jeff Dike02dea082006-03-31 02:30:08 -0800432 list_add(&unplugged->list, &unplugged_pages);
433 unplug_index = 0;
434 }
435 else {
436 struct list_head *entry = unplugged_pages.next;
437 addr = unplugged;
438
439 unplugged = list_entry(entry,
440 struct unplugged_pages,
441 list);
Jeff Dike02dea082006-03-31 02:30:08 -0800442 err = os_drop_memory(addr, PAGE_SIZE);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700443 if (err) {
444 printk(KERN_ERR "Failed to release "
445 "memory - errno = %d\n", err);
Jeff Dikef28169d2007-02-10 01:43:53 -0800446 *error_out = "Failed to release memory";
Jeff Dike84f48d42007-02-10 01:44:01 -0800447 goto out_unlock;
Jeff Dikef28169d2007-02-10 01:43:53 -0800448 }
449 unplugged->pages[unplug_index++] = addr;
Jeff Dike02dea082006-03-31 02:30:08 -0800450 }
451
452 unplugged_pages_count++;
453 }
454 }
455
456 err = 0;
Jeff Dike84f48d42007-02-10 01:44:01 -0800457out_unlock:
Daniel Walkere98fa282008-02-04 22:31:27 -0800458 mutex_unlock(&plug_mem_mutex);
Jeff Dike02dea082006-03-31 02:30:08 -0800459out:
460 return err;
461}
462
463static int mem_get_config(char *name, char *str, int size, char **error_out)
464{
465 char buf[sizeof("18446744073709551615")];
466 int len = 0;
467
468 sprintf(buf, "%ld", uml_physmem);
469 CONFIG_CHUNK(str, size, len, buf, 1);
470
471 return len;
472}
473
474static int mem_id(char **str, int *start_out, int *end_out)
475{
476 *start_out = 0;
477 *end_out = 0;
478
479 return 0;
480}
481
Jeff Dikef28169d2007-02-10 01:43:53 -0800482static int mem_remove(int n, char **error_out)
Jeff Dike02dea082006-03-31 02:30:08 -0800483{
Jeff Dikef28169d2007-02-10 01:43:53 -0800484 *error_out = "Memory doesn't support the remove operation";
Jeff Dike02dea082006-03-31 02:30:08 -0800485 return -EBUSY;
486}
487
488static struct mc_device mem_mc = {
Jeff Dike84f48d42007-02-10 01:44:01 -0800489 .list = LIST_HEAD_INIT(mem_mc.list),
Jeff Dike02dea082006-03-31 02:30:08 -0800490 .name = "mem",
491 .config = mem_config,
492 .get_config = mem_get_config,
493 .id = mem_id,
494 .remove = mem_remove,
495};
496
Jeff Dike97a1fcb2007-07-23 18:43:48 -0700497static int __init mem_mc_init(void)
Jeff Dike02dea082006-03-31 02:30:08 -0800498{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700499 if (can_drop_memory())
Jeff Dike02dea082006-03-31 02:30:08 -0800500 mconsole_register_dev(&mem_mc);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700501 else printk(KERN_ERR "Can't release memory to the host - memory "
502 "hotplug won't be supported\n");
Jeff Dike02dea082006-03-31 02:30:08 -0800503 return 0;
504}
505
506__initcall(mem_mc_init);
507
Linus Torvalds1da177e2005-04-16 15:20:36 -0700508#define CONFIG_BUF_SIZE 64
509
Jeff Diked50084a2006-01-06 00:18:50 -0800510static void mconsole_get_config(int (*get_config)(char *, char *, int,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700511 char **),
512 struct mc_request *req, char *name)
513{
514 char default_buf[CONFIG_BUF_SIZE], *error, *buf;
515 int n, size;
516
Jeff Dikeae2587e2007-10-16 01:26:57 -0700517 if (get_config == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700518 mconsole_reply(req, "No get_config routine defined", 1, 0);
519 return;
520 }
521
522 error = NULL;
Jeff Dike91b165c2006-09-25 23:33:00 -0700523 size = ARRAY_SIZE(default_buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700524 buf = default_buf;
525
Jeff Dikeae2587e2007-10-16 01:26:57 -0700526 while (1) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700527 n = (*get_config)(name, buf, size, &error);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700528 if (error != NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700529 mconsole_reply(req, error, 1, 0);
530 goto out;
531 }
532
Jeff Dikeae2587e2007-10-16 01:26:57 -0700533 if (n <= size) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700534 mconsole_reply(req, buf, 0, 0);
535 goto out;
536 }
537
Jeff Dikeae2587e2007-10-16 01:26:57 -0700538 if (buf != default_buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700539 kfree(buf);
540
541 size = n;
542 buf = kmalloc(size, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700543 if (buf == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700544 mconsole_reply(req, "Failed to allocate buffer", 1, 0);
545 return;
546 }
547 }
548 out:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700549 if (buf != default_buf)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700550 kfree(buf);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700551}
552
553void mconsole_config(struct mc_request *req)
554{
555 struct mc_device *dev;
Jeff Dikef28169d2007-02-10 01:43:53 -0800556 char *ptr = req->request.data, *name, *error_string = "";
Linus Torvalds1da177e2005-04-16 15:20:36 -0700557 int err;
558
559 ptr += strlen("config");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700560 while (isspace(*ptr))
561 ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700562 dev = mconsole_find_dev(ptr);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700563 if (dev == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700564 mconsole_reply(req, "Bad configuration option", 1, 0);
565 return;
566 }
567
568 name = &ptr[strlen(dev->name)];
569 ptr = name;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700570 while ((*ptr != '=') && (*ptr != '\0'))
Linus Torvalds1da177e2005-04-16 15:20:36 -0700571 ptr++;
572
Jeff Dikeae2587e2007-10-16 01:26:57 -0700573 if (*ptr == '=') {
Jeff Dikef28169d2007-02-10 01:43:53 -0800574 err = (*dev->config)(name, &error_string);
575 mconsole_reply(req, error_string, err, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700576 }
577 else mconsole_get_config(dev->get_config, req, name);
578}
579
580void mconsole_remove(struct mc_request *req)
581{
Jeff Diked50084a2006-01-06 00:18:50 -0800582 struct mc_device *dev;
Jeff Dike29d56cf2005-06-25 14:55:25 -0700583 char *ptr = req->request.data, *err_msg = "";
Jeff Dike3a331a52006-01-06 00:19:05 -0800584 char error[256];
Jeff Dike29d56cf2005-06-25 14:55:25 -0700585 int err, start, end, n;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700586
587 ptr += strlen("remove");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700588 while (isspace(*ptr)) ptr++;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700589 dev = mconsole_find_dev(ptr);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700590 if (dev == NULL) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700591 mconsole_reply(req, "Bad remove option", 1, 0);
592 return;
593 }
Jeff Dike29d56cf2005-06-25 14:55:25 -0700594
Jeff Dike3a331a52006-01-06 00:19:05 -0800595 ptr = &ptr[strlen(dev->name)];
Jeff Dike29d56cf2005-06-25 14:55:25 -0700596
Jeff Dike3a331a52006-01-06 00:19:05 -0800597 err = 1;
598 n = (*dev->id)(&ptr, &start, &end);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700599 if (n < 0) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800600 err_msg = "Couldn't parse device number";
601 goto out;
602 }
Jeff Dikeae2587e2007-10-16 01:26:57 -0700603 else if ((n < start) || (n > end)) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800604 sprintf(error, "Invalid device number - must be between "
605 "%d and %d", start, end);
606 err_msg = error;
607 goto out;
608 }
Jeff Dike29d56cf2005-06-25 14:55:25 -0700609
Jeff Dikef28169d2007-02-10 01:43:53 -0800610 err_msg = NULL;
611 err = (*dev->remove)(n, &err_msg);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700612 switch(err) {
Jeff Diked40f6d72007-03-29 01:20:28 -0700613 case 0:
614 err_msg = "";
615 break;
Jeff Dike3a331a52006-01-06 00:19:05 -0800616 case -ENODEV:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700617 if (err_msg == NULL)
Jeff Dikef28169d2007-02-10 01:43:53 -0800618 err_msg = "Device doesn't exist";
Jeff Dike3a331a52006-01-06 00:19:05 -0800619 break;
620 case -EBUSY:
Jeff Dikeae2587e2007-10-16 01:26:57 -0700621 if (err_msg == NULL)
Jeff Dikef28169d2007-02-10 01:43:53 -0800622 err_msg = "Device is currently open";
Jeff Dike3a331a52006-01-06 00:19:05 -0800623 break;
624 default:
625 break;
626 }
627out:
Jeff Dike29d56cf2005-06-25 14:55:25 -0700628 mconsole_reply(req, err_msg, err, 0);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700629}
630
Jeff Dikef92afe52006-09-29 01:58:52 -0700631struct mconsole_output {
632 struct list_head list;
633 struct mc_request *req;
634};
635
Jeff Dike84f48d42007-02-10 01:44:01 -0800636static DEFINE_SPINLOCK(client_lock);
Jeff Dike6f517d32006-01-06 00:19:04 -0800637static LIST_HEAD(clients);
638static char console_buf[MCONSOLE_MAX_DATA];
Jeff Dike6f517d32006-01-06 00:19:04 -0800639
640static void console_write(struct console *console, const char *string,
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700641 unsigned int len)
Jeff Dike6f517d32006-01-06 00:19:04 -0800642{
643 struct list_head *ele;
644 int n;
645
Jeff Dikeae2587e2007-10-16 01:26:57 -0700646 if (list_empty(&clients))
Jeff Dike6f517d32006-01-06 00:19:04 -0800647 return;
648
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700649 while (len > 0) {
650 n = min((size_t) len, ARRAY_SIZE(console_buf));
651 strncpy(console_buf, string, n);
Jeff Dike6f517d32006-01-06 00:19:04 -0800652 string += n;
653 len -= n;
Jeff Dike6f517d32006-01-06 00:19:04 -0800654
Jeff Dikeae2587e2007-10-16 01:26:57 -0700655 list_for_each(ele, &clients) {
Jeff Dikef92afe52006-09-29 01:58:52 -0700656 struct mconsole_output *entry;
Jeff Dike6f517d32006-01-06 00:19:04 -0800657
Jeff Dikef92afe52006-09-29 01:58:52 -0700658 entry = list_entry(ele, struct mconsole_output, list);
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700659 mconsole_reply_len(entry->req, console_buf, n, 0, 1);
Jeff Dike6f517d32006-01-06 00:19:04 -0800660 }
Jeff Dike6f517d32006-01-06 00:19:04 -0800661 }
662}
663
664static struct console mc_console = { .name = "mc",
665 .write = console_write,
Jeff Dikea174b302006-01-11 12:17:28 -0800666 .flags = CON_ENABLED,
Jeff Dike6f517d32006-01-06 00:19:04 -0800667 .index = -1 };
668
669static int mc_add_console(void)
670{
671 register_console(&mc_console);
672 return 0;
673}
674
675late_initcall(mc_add_console);
676
677static void with_console(struct mc_request *req, void (*proc)(void *),
678 void *arg)
679{
Jeff Dikef92afe52006-09-29 01:58:52 -0700680 struct mconsole_output entry;
Jeff Dike6f517d32006-01-06 00:19:04 -0800681 unsigned long flags;
682
Jeff Dikef92afe52006-09-29 01:58:52 -0700683 entry.req = req;
Jeff Dike84f48d42007-02-10 01:44:01 -0800684 spin_lock_irqsave(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800685 list_add(&entry.list, &clients);
Jeff Dike84f48d42007-02-10 01:44:01 -0800686 spin_unlock_irqrestore(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800687
688 (*proc)(arg);
689
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700690 mconsole_reply_len(req, "", 0, 0, 0);
Jeff Dike6f517d32006-01-06 00:19:04 -0800691
Jeff Dike84f48d42007-02-10 01:44:01 -0800692 spin_lock_irqsave(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800693 list_del(&entry.list);
Jeff Dike84f48d42007-02-10 01:44:01 -0800694 spin_unlock_irqrestore(&client_lock, flags);
Jeff Dike6f517d32006-01-06 00:19:04 -0800695}
696
Jeff Dike4111b022006-01-06 00:19:05 -0800697#ifdef CONFIG_MAGIC_SYSRQ
Jeff Dike54fa0ba2007-10-16 01:27:17 -0700698
699#include <linux/sysrq.h>
700
Jeff Dike4111b022006-01-06 00:19:05 -0800701static void sysrq_proc(void *arg)
702{
703 char *op = arg;
Al Viro7bea96f2006-10-08 22:49:34 +0100704 handle_sysrq(*op, NULL);
Jeff Dike4111b022006-01-06 00:19:05 -0800705}
706
707void mconsole_sysrq(struct mc_request *req)
708{
709 char *ptr = req->request.data;
710
711 ptr += strlen("sysrq");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700712 while (isspace(*ptr)) ptr++;
Jeff Dike4111b022006-01-06 00:19:05 -0800713
Jeff Dikeae2587e2007-10-16 01:26:57 -0700714 /*
715 * With 'b', the system will shut down without a chance to reply,
Jeff Dike4111b022006-01-06 00:19:05 -0800716 * so in this case, we reply first.
717 */
Jeff Dikeae2587e2007-10-16 01:26:57 -0700718 if (*ptr == 'b')
Jeff Dike4111b022006-01-06 00:19:05 -0800719 mconsole_reply(req, "", 0, 0);
720
721 with_console(req, sysrq_proc, ptr);
722}
723#else
724void mconsole_sysrq(struct mc_request *req)
725{
726 mconsole_reply(req, "Sysrq not compiled in", 1, 0);
727}
728#endif
729
Jeff Dike6f517d32006-01-06 00:19:04 -0800730static void stack_proc(void *arg)
731{
732 struct task_struct *from = current, *to = arg;
733
734 to->thread.saved_task = from;
735 switch_to(from, to, from);
736}
737
Jeff Dikeae2587e2007-10-16 01:26:57 -0700738/*
739 * Mconsole stack trace
Jeff Dike3eddddc2005-09-16 19:27:46 -0700740 * Added by Allan Graves, Jeff Dike
741 * Dumps a stacks registers to the linux console.
742 * Usage stack <pid>.
743 */
Jeff Dike42fda662007-10-16 01:26:50 -0700744void mconsole_stack(struct mc_request *req)
Jeff Dike3eddddc2005-09-16 19:27:46 -0700745{
Jeff Dike3a331a52006-01-06 00:19:05 -0800746 char *ptr = req->request.data;
747 int pid_requested= -1;
Jeff Dike3eddddc2005-09-16 19:27:46 -0700748 struct task_struct *to = NULL;
749
Jeff Dikeae2587e2007-10-16 01:26:57 -0700750 /*
751 * Would be nice:
Jeff Dike3a331a52006-01-06 00:19:05 -0800752 * 1) Send showregs output to mconsole.
Jeff Dike3eddddc2005-09-16 19:27:46 -0700753 * 2) Add a way to stack dump all pids.
754 */
755
Jeff Dike3a331a52006-01-06 00:19:05 -0800756 ptr += strlen("stack");
Jeff Dikeae2587e2007-10-16 01:26:57 -0700757 while (isspace(*ptr))
758 ptr++;
Jeff Dike3eddddc2005-09-16 19:27:46 -0700759
Jeff Dikeae2587e2007-10-16 01:26:57 -0700760 /*
761 * Should really check for multiple pids or reject bad args here
762 */
Jeff Dike3a331a52006-01-06 00:19:05 -0800763 /* What do the arguments in mconsole_reply mean? */
Jeff Dikeae2587e2007-10-16 01:26:57 -0700764 if (sscanf(ptr, "%d", &pid_requested) == 0) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800765 mconsole_reply(req, "Please specify a pid", 1, 0);
766 return;
767 }
Jeff Dike3eddddc2005-09-16 19:27:46 -0700768
Jeff Dike827b3f62008-02-04 22:31:29 -0800769 to = find_task_by_pid_ns(pid_requested, &init_pid_ns);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700770 if ((to == NULL) || (pid_requested == 0)) {
Jeff Dike3a331a52006-01-06 00:19:05 -0800771 mconsole_reply(req, "Couldn't find that pid", 1, 0);
772 return;
773 }
Jeff Dike6f517d32006-01-06 00:19:04 -0800774 with_console(req, stack_proc, to);
Jeff Dike3eddddc2005-09-16 19:27:46 -0700775}
Jeff Dike3eddddc2005-09-16 19:27:46 -0700776
Jeff Dikeae2587e2007-10-16 01:26:57 -0700777/*
778 * Changed by mconsole_setup, which is __setup, and called before SMP is
Linus Torvalds1da177e2005-04-16 15:20:36 -0700779 * active.
780 */
Jeff Diked50084a2006-01-06 00:18:50 -0800781static char *notify_socket = NULL;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700782
Jeff Dike97a1fcb2007-07-23 18:43:48 -0700783static int __init mconsole_init(void)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700784{
785 /* long to avoid size mismatch warnings from gcc */
786 long sock;
787 int err;
788 char file[256];
789
Jeff Dikeae2587e2007-10-16 01:26:57 -0700790 if (umid_file_name("mconsole", file, sizeof(file)))
791 return -1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700792 snprintf(mconsole_socket_name, sizeof(file), "%s", file);
793
794 sock = os_create_unix_socket(file, sizeof(file), 1);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700795 if (sock < 0) {
796 printk(KERN_ERR "Failed to initialize management console\n");
797 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700798 }
Jeff Dike438ee672008-02-04 22:31:19 -0800799 if (os_set_fd_block(sock, 0))
800 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700801
802 register_reboot_notifier(&reboot_notifier);
803
804 err = um_request_irq(MCONSOLE_IRQ, sock, IRQ_READ, mconsole_interrupt,
Thomas Gleixnerbd6aa652006-07-01 19:29:27 -0700805 IRQF_DISABLED | IRQF_SHARED | IRQF_SAMPLE_RANDOM,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700806 "mconsole", (void *)sock);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700807 if (err) {
808 printk(KERN_ERR "Failed to get IRQ for management console\n");
Jeff Dike438ee672008-02-04 22:31:19 -0800809 goto out;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700810 }
811
Jeff Dikeae2587e2007-10-16 01:26:57 -0700812 if (notify_socket != NULL) {
Jeff Dike970d6e32006-01-06 00:18:48 -0800813 notify_socket = kstrdup(notify_socket, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700814 if (notify_socket != NULL)
Linus Torvalds1da177e2005-04-16 15:20:36 -0700815 mconsole_notify(notify_socket, MCONSOLE_SOCKET,
Jeff Diked50084a2006-01-06 00:18:50 -0800816 mconsole_socket_name,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700817 strlen(mconsole_socket_name) + 1);
818 else printk(KERN_ERR "mconsole_setup failed to strdup "
819 "string\n");
820 }
821
Jeff Dikeae2587e2007-10-16 01:26:57 -0700822 printk(KERN_INFO "mconsole (version %d) initialized on %s\n",
Linus Torvalds1da177e2005-04-16 15:20:36 -0700823 MCONSOLE_VERSION, mconsole_socket_name);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700824 return 0;
Jeff Dike438ee672008-02-04 22:31:19 -0800825
826 out:
827 os_close_file(sock);
828 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700829}
830
831__initcall(mconsole_init);
832
833static int write_proc_mconsole(struct file *file, const char __user *buffer,
834 unsigned long count, void *data)
835{
836 char *buf;
837
838 buf = kmalloc(count + 1, GFP_KERNEL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700839 if (buf == NULL)
840 return -ENOMEM;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700841
Jeff Dikeae2587e2007-10-16 01:26:57 -0700842 if (copy_from_user(buf, buffer, count)) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700843 count = -EFAULT;
844 goto out;
845 }
846
847 buf[count] = '\0';
848
849 mconsole_notify(notify_socket, MCONSOLE_USER_NOTIFY, buf, count);
850 out:
851 kfree(buf);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700852 return count;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700853}
854
855static int create_proc_mconsole(void)
856{
857 struct proc_dir_entry *ent;
858
Jeff Dikeae2587e2007-10-16 01:26:57 -0700859 if (notify_socket == NULL)
860 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700861
862 ent = create_proc_entry("mconsole", S_IFREG | 0200, NULL);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700863 if (ent == NULL) {
864 printk(KERN_INFO "create_proc_mconsole : create_proc_entry "
865 "failed\n");
866 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700867 }
868
869 ent->read_proc = NULL;
870 ent->write_proc = write_proc_mconsole;
Jeff Dikeae2587e2007-10-16 01:26:57 -0700871 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700872}
873
874static DEFINE_SPINLOCK(notify_spinlock);
875
876void lock_notify(void)
877{
878 spin_lock(&notify_spinlock);
879}
880
881void unlock_notify(void)
882{
883 spin_unlock(&notify_spinlock);
884}
885
886__initcall(create_proc_mconsole);
887
Jeff Dike088bec42007-10-16 01:27:20 -0700888#define NOTIFY "notify:"
Linus Torvalds1da177e2005-04-16 15:20:36 -0700889
890static int mconsole_setup(char *str)
891{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700892 if (!strncmp(str, NOTIFY, strlen(NOTIFY))) {
Linus Torvalds1da177e2005-04-16 15:20:36 -0700893 str += strlen(NOTIFY);
894 notify_socket = str;
895 }
896 else printk(KERN_ERR "mconsole_setup : Unknown option - '%s'\n", str);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700897 return 1;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700898}
899
Jeff Dike088bec42007-10-16 01:27:20 -0700900__setup("mconsole=", mconsole_setup);
Linus Torvalds1da177e2005-04-16 15:20:36 -0700901
902__uml_help(mconsole_setup,
903"mconsole=notify:<socket>\n"
904" Requests that the mconsole driver send a message to the named Unix\n"
905" socket containing the name of the mconsole socket. This also serves\n"
906" to notify outside processes when UML has booted far enough to respond\n"
907" to mconsole requests.\n\n"
908);
909
910static int notify_panic(struct notifier_block *self, unsigned long unused1,
911 void *ptr)
912{
913 char *message = ptr;
914
Jeff Dikeae2587e2007-10-16 01:26:57 -0700915 if (notify_socket == NULL)
916 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700917
Jeff Diked50084a2006-01-06 00:18:50 -0800918 mconsole_notify(notify_socket, MCONSOLE_PANIC, message,
Linus Torvalds1da177e2005-04-16 15:20:36 -0700919 strlen(message) + 1);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700920 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700921}
922
923static struct notifier_block panic_exit_notifier = {
924 .notifier_call = notify_panic,
925 .next = NULL,
926 .priority = 1
927};
928
929static int add_notifier(void)
930{
Alan Sterne041c682006-03-27 01:16:30 -0800931 atomic_notifier_chain_register(&panic_notifier_list,
932 &panic_exit_notifier);
Jeff Dikeae2587e2007-10-16 01:26:57 -0700933 return 0;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700934}
935
936__initcall(add_notifier);
937
938char *mconsole_notify_socket(void)
939{
Jeff Dikeae2587e2007-10-16 01:26:57 -0700940 return notify_socket;
Linus Torvalds1da177e2005-04-16 15:20:36 -0700941}
942
943EXPORT_SYMBOL(mconsole_notify_socket);