1/*
2 * Copyright (C) 2001 Lennert Buytenhek (buytenh@gnu.org)
3 * Copyright (C) 2001 - 2003 Jeff Dike (jdike@addtoit.com)
4 * Licensed under the GPL
5 */
6
7#include "linux/kernel.h"
8#include "linux/slab.h"
9#include "linux/init.h"
10#include "linux/notifier.h"
11#include "linux/reboot.h"
12#include "linux/utsname.h"
13#include "linux/ctype.h"
14#include "linux/interrupt.h"
15#include "linux/sysrq.h"
16#include "linux/workqueue.h"
17#include "linux/module.h"
18#include "linux/file.h"
19#include "linux/fs.h"
20#include "linux/namei.h"
21#include "linux/proc_fs.h"
22#include "linux/syscalls.h"
23#include "linux/list.h"
24#include "linux/mm.h"
25#include "linux/console.h"
26#include "asm/irq.h"
27#include "asm/uaccess.h"
28#include "kern_util.h"
29#include "kern.h"
30#include "mconsole.h"
31#include "mconsole_kern.h"
32#include "irq_user.h"
33#include "init.h"
34#include "os.h"
35#include "irq_kern.h"
36#include "choose-mode.h"
37
38static int do_unlink_socket(struct notifier_block *notifier,
39			    unsigned long what, void *data)
40{
41	return(mconsole_unlink_socket());
42}
43
44
45static struct notifier_block reboot_notifier = {
46	.notifier_call		= do_unlink_socket,
47	.priority		= 0,
48};
49
50/* Safe without explicit locking for now.  Tasklets provide their own
51 * locking, and the interrupt handler is safe because it can't interrupt
52 * itself and it can only happen on CPU 0.
53 */
54
55static LIST_HEAD(mc_requests);
56
57static void mc_work_proc(struct work_struct *unused)
58{
59	struct mconsole_entry *req;
60	unsigned long flags;
61
62	while(!list_empty(&mc_requests)){
63		local_irq_save(flags);
64		req = list_entry(mc_requests.next, struct mconsole_entry,
65				 list);
66		list_del(&req->list);
67		local_irq_restore(flags);
68		req->request.cmd->handler(&req->request);
69		kfree(req);
70	}
71}
72
73static DECLARE_WORK(mconsole_work, mc_work_proc);
74
75static irqreturn_t mconsole_interrupt(int irq, void *dev_id)
76{
77	/* long to avoid size mismatch warnings from gcc */
78	long fd;
79	struct mconsole_entry *new;
80	static struct mc_request req;	/* that's OK */
81
82	fd = (long) dev_id;
83	while (mconsole_get_request(fd, &req)){
84		if(req.cmd->context == MCONSOLE_INTR)
85			(*req.cmd->handler)(&req);
86		else {
87			new = kmalloc(sizeof(*new), GFP_NOWAIT);
88			if(new == NULL)
89				mconsole_reply(&req, "Out of memory", 1, 0);
90			else {
91				new->request = req;
92				new->request.regs = get_irq_regs()->regs;
93				list_add(&new->list, &mc_requests);
94			}
95		}
96	}
97	if(!list_empty(&mc_requests))
98		schedule_work(&mconsole_work);
99	reactivate_fd(fd, MCONSOLE_IRQ);
100	return(IRQ_HANDLED);
101}
102
103void mconsole_version(struct mc_request *req)
104{
105	char version[256];
106
107	sprintf(version, "%s %s %s %s %s", utsname()->sysname,
108		utsname()->nodename, utsname()->release,
109		utsname()->version, utsname()->machine);
110	mconsole_reply(req, version, 0, 0);
111}
112
113void mconsole_log(struct mc_request *req)
114{
115	int len;
116	char *ptr = req->request.data;
117
118	ptr += strlen("log ");
119
120	len = req->len - (ptr - req->request.data);
121	printk("%.*s", len, ptr);
122	mconsole_reply(req, "", 0, 0);
123}
124
125/* This is a more convoluted version of mconsole_proc, which has some stability
126 * problems; however, we need it fixed, because it is expected that UML users
127 * mount HPPFS instead of procfs on /proc. And we want mconsole_proc to still
128 * show the real procfs content, not the ones from hppfs.*/
129
130void mconsole_proc(struct mc_request *req)
131{
132	char path[64];
133	char *buf;
134	int len;
135	int fd;
136	int first_chunk = 1;
137	char *ptr = req->request.data;
138
139	ptr += strlen("proc");
140	while(isspace(*ptr)) ptr++;
141	snprintf(path, sizeof(path), "/proc/%s", ptr);
142
143	fd = sys_open(path, 0, 0);
144	if (fd < 0) {
145		mconsole_reply(req, "Failed to open file", 1, 0);
146		printk("open %s: %d\n",path,fd);
147		goto out;
148	}
149
150	buf = kmalloc(PAGE_SIZE, GFP_KERNEL);
151	if(buf == NULL){
152		mconsole_reply(req, "Failed to allocate buffer", 1, 0);
153		goto out_close;
154	}
155
156	for (;;) {
157		len = sys_read(fd, buf, PAGE_SIZE-1);
158		if (len < 0) {
159			mconsole_reply(req, "Read of file failed", 1, 0);
160			goto out_free;
161		}
162		/*Begin the file content on his own line.*/
163		if (first_chunk) {
164			mconsole_reply(req, "\n", 0, 1);
165			first_chunk = 0;
166		}
167		if (len == PAGE_SIZE-1) {
168			buf[len] = '\0';
169			mconsole_reply(req, buf, 0, 1);
170		} else {
171			buf[len] = '\0';
172			mconsole_reply(req, buf, 0, 0);
173			break;
174		}
175	}
176
177 out_free:
178	kfree(buf);
179 out_close:
180	sys_close(fd);
181 out:
182	/* nothing */;
183}
184
185#define UML_MCONSOLE_HELPTEXT \
186"Commands: \n\
187    version - Get kernel version \n\
188    help - Print this message \n\
189    halt - Halt UML \n\
190    reboot - Reboot UML \n\
191    config <dev>=<config> - Add a new device to UML;  \n\
192	same syntax as command line \n\
193    config <dev> - Query the configuration of a device \n\
194    remove <dev> - Remove a device from UML \n\
195    sysrq <letter> - Performs the SysRq action controlled by the letter \n\
196    cad - invoke the Ctrl-Alt-Del handler \n\
197    stop - pause the UML; it will do nothing until it receives a 'go' \n\
198    go - continue the UML after a 'stop' \n\
199    log <string> - make UML enter <string> into the kernel log\n\
200    proc <file> - returns the contents of the UML's /proc/<file>\n\
201    stack <pid> - returns the stack of the specified pid\n\
202"
203
204void mconsole_help(struct mc_request *req)
205{
206	mconsole_reply(req, UML_MCONSOLE_HELPTEXT, 0, 0);
207}
208
209void mconsole_halt(struct mc_request *req)
210{
211	mconsole_reply(req, "", 0, 0);
212	machine_halt();
213}
214
215void mconsole_reboot(struct mc_request *req)
216{
217	mconsole_reply(req, "", 0, 0);
218	machine_restart(NULL);
219}
220
221void mconsole_cad(struct mc_request *req)
222{
223	mconsole_reply(req, "", 0, 0);
224	ctrl_alt_del();
225}
226
227void mconsole_go(struct mc_request *req)
228{
229	mconsole_reply(req, "Not stopped", 1, 0);
230}
231
232void mconsole_stop(struct mc_request *req)
233{
234	deactivate_fd(req->originating_fd, MCONSOLE_IRQ);
235	os_set_fd_block(req->originating_fd, 1);
236	mconsole_reply(req, "stopped", 0, 0);
237	while (mconsole_get_request(req->originating_fd, req)) {
238		if (req->cmd->handler == mconsole_go)
239			break;
240		if (req->cmd->handler == mconsole_stop) {
241			mconsole_reply(req, "Already stopped", 1, 0);
242			continue;
243		}
244		if (req->cmd->handler == mconsole_sysrq) {
245			struct pt_regs *old_regs;
246			old_regs = set_irq_regs((struct pt_regs *)&req->regs);
247			mconsole_sysrq(req);
248			set_irq_regs(old_regs);
249			continue;
250		}
251		(*req->cmd->handler)(req);
252	}
253	os_set_fd_block(req->originating_fd, 0);
254	reactivate_fd(req->originating_fd, MCONSOLE_IRQ);
255	mconsole_reply(req, "", 0, 0);
256}
257
258static DEFINE_SPINLOCK(mc_devices_lock);
259static LIST_HEAD(mconsole_devices);
260
261void mconsole_register_dev(struct mc_device *new)
262{
263	spin_lock(&mc_devices_lock);
264	BUG_ON(!list_empty(&new->list));
265	list_add(&new->list, &mconsole_devices);
266	spin_unlock(&mc_devices_lock);
267}
268
269static struct mc_device *mconsole_find_dev(char *name)
270{
271	struct list_head *ele;
272	struct mc_device *dev;
273
274	list_for_each(ele, &mconsole_devices){
275		dev = list_entry(ele, struct mc_device, list);
276		if(!strncmp(name, dev->name, strlen(dev->name)))
277			return(dev);
278	}
279	return(NULL);
280}
281
282#define UNPLUGGED_PER_PAGE \
283	((PAGE_SIZE - sizeof(struct list_head)) / sizeof(unsigned long))
284
285struct unplugged_pages {
286	struct list_head list;
287	void *pages[UNPLUGGED_PER_PAGE];
288};
289
290static DECLARE_MUTEX(plug_mem_mutex);
291static unsigned long long unplugged_pages_count = 0;
292static LIST_HEAD(unplugged_pages);
293static int unplug_index = UNPLUGGED_PER_PAGE;
294
295static int mem_config(char *str, char **error_out)
296{
297	unsigned long long diff;
298	int err = -EINVAL, i, add;
299	char *ret;
300
301	if(str[0] != '='){
302		*error_out = "Expected '=' after 'mem'";
303		goto out;
304	}
305
306	str++;
307	if(str[0] == '-')
308		add = 0;
309	else if(str[0] == '+'){
310		add = 1;
311	}
312	else {
313		*error_out = "Expected increment to start with '-' or '+'";
314		goto out;
315	}
316
317	str++;
318	diff = memparse(str, &ret);
319	if(*ret != '\0'){
320		*error_out = "Failed to parse memory increment";
321		goto out;
322	}
323
324	diff /= PAGE_SIZE;
325
326	down(&plug_mem_mutex);
327	for(i = 0; i < diff; i++){
328		struct unplugged_pages *unplugged;
329		void *addr;
330
331		if(add){
332			if(list_empty(&unplugged_pages))
333				break;
334
335			unplugged = list_entry(unplugged_pages.next,
336					       struct unplugged_pages, list);
337			if(unplug_index > 0)
338				addr = unplugged->pages[--unplug_index];
339			else {
340				list_del(&unplugged->list);
341				addr = unplugged;
342				unplug_index = UNPLUGGED_PER_PAGE;
343			}
344
345			free_page((unsigned long) addr);
346			unplugged_pages_count--;
347		}
348		else {
349			struct page *page;
350
351			page = alloc_page(GFP_ATOMIC);
352			if(page == NULL)
353				break;
354
355			unplugged = page_address(page);
356			if(unplug_index == UNPLUGGED_PER_PAGE){
357				list_add(&unplugged->list, &unplugged_pages);
358				unplug_index = 0;
359			}
360			else {
361				struct list_head *entry = unplugged_pages.next;
362				addr = unplugged;
363
364				unplugged = list_entry(entry,
365						       struct unplugged_pages,
366						       list);
367				err = os_drop_memory(addr, PAGE_SIZE);
368				if(err){
369					printk("Failed to release memory - "
370					       "errno = %d\n", err);
371					*error_out = "Failed to release memory";
372					goto out_unlock;
373				}
374				unplugged->pages[unplug_index++] = addr;
375			}
376
377			unplugged_pages_count++;
378		}
379	}
380
381	err = 0;
382out_unlock:
383	up(&plug_mem_mutex);
384out:
385	return err;
386}
387
388static int mem_get_config(char *name, char *str, int size, char **error_out)
389{
390	char buf[sizeof("18446744073709551615")];
391	int len = 0;
392
393	sprintf(buf, "%ld", uml_physmem);
394	CONFIG_CHUNK(str, size, len, buf, 1);
395
396	return len;
397}
398
399static int mem_id(char **str, int *start_out, int *end_out)
400{
401	*start_out = 0;
402	*end_out = 0;
403
404	return 0;
405}
406
407static int mem_remove(int n, char **error_out)
408{
409	*error_out = "Memory doesn't support the remove operation";
410	return -EBUSY;
411}
412
413static struct mc_device mem_mc = {
414	.list		= LIST_HEAD_INIT(mem_mc.list),
415	.name		= "mem",
416	.config		= mem_config,
417	.get_config	= mem_get_config,
418	.id		= mem_id,
419	.remove		= mem_remove,
420};
421
422static int mem_mc_init(void)
423{
424	if(can_drop_memory())
425		mconsole_register_dev(&mem_mc);
426	else printk("Can't release memory to the host - memory hotplug won't "
427		    "be supported\n");
428	return 0;
429}
430
431__initcall(mem_mc_init);
432
433#define CONFIG_BUF_SIZE 64
434
435static void mconsole_get_config(int (*get_config)(char *, char *, int,
436						  char **),
437				struct mc_request *req, char *name)
438{
439	char default_buf[CONFIG_BUF_SIZE], *error, *buf;
440	int n, size;
441
442	if(get_config == NULL){
443		mconsole_reply(req, "No get_config routine defined", 1, 0);
444		return;
445	}
446
447	error = NULL;
448	size = ARRAY_SIZE(default_buf);
449	buf = default_buf;
450
451	while(1){
452		n = (*get_config)(name, buf, size, &error);
453		if(error != NULL){
454			mconsole_reply(req, error, 1, 0);
455			goto out;
456		}
457
458		if(n <= size){
459			mconsole_reply(req, buf, 0, 0);
460			goto out;
461		}
462
463		if(buf != default_buf)
464			kfree(buf);
465
466		size = n;
467		buf = kmalloc(size, GFP_KERNEL);
468		if(buf == NULL){
469			mconsole_reply(req, "Failed to allocate buffer", 1, 0);
470			return;
471		}
472	}
473 out:
474	if(buf != default_buf)
475		kfree(buf);
476}
477
478void mconsole_config(struct mc_request *req)
479{
480	struct mc_device *dev;
481	char *ptr = req->request.data, *name, *error_string = "";
482	int err;
483
484	ptr += strlen("config");
485	while(isspace(*ptr)) ptr++;
486	dev = mconsole_find_dev(ptr);
487	if(dev == NULL){
488		mconsole_reply(req, "Bad configuration option", 1, 0);
489		return;
490	}
491
492	name = &ptr[strlen(dev->name)];
493	ptr = name;
494	while((*ptr != '=') && (*ptr != '\0'))
495		ptr++;
496
497	if(*ptr == '='){
498		err = (*dev->config)(name, &error_string);
499		mconsole_reply(req, error_string, err, 0);
500	}
501	else mconsole_get_config(dev->get_config, req, name);
502}
503
504void mconsole_remove(struct mc_request *req)
505{
506	struct mc_device *dev;
507	char *ptr = req->request.data, *err_msg = "";
508	char error[256];
509	int err, start, end, n;
510
511	ptr += strlen("remove");
512	while(isspace(*ptr)) ptr++;
513	dev = mconsole_find_dev(ptr);
514	if(dev == NULL){
515		mconsole_reply(req, "Bad remove option", 1, 0);
516		return;
517	}
518
519	ptr = &ptr[strlen(dev->name)];
520
521	err = 1;
522	n = (*dev->id)(&ptr, &start, &end);
523	if(n < 0){
524		err_msg = "Couldn't parse device number";
525		goto out;
526	}
527	else if((n < start) || (n > end)){
528		sprintf(error, "Invalid device number - must be between "
529			"%d and %d", start, end);
530		err_msg = error;
531		goto out;
532	}
533
534	err_msg = NULL;
535	err = (*dev->remove)(n, &err_msg);
536	switch(err){
537	case 0:
538		err_msg = "";
539		break;
540	case -ENODEV:
541		if(err_msg == NULL)
542			err_msg = "Device doesn't exist";
543		break;
544	case -EBUSY:
545		if(err_msg == NULL)
546			err_msg = "Device is currently open";
547		break;
548	default:
549		break;
550	}
551out:
552	mconsole_reply(req, err_msg, err, 0);
553}
554
555struct mconsole_output {
556	struct list_head list;
557	struct mc_request *req;
558};
559
560static DEFINE_SPINLOCK(client_lock);
561static LIST_HEAD(clients);
562static char console_buf[MCONSOLE_MAX_DATA];
563static int console_index = 0;
564
565static void console_write(struct console *console, const char *string,
566			  unsigned len)
567{
568	struct list_head *ele;
569	int n;
570
571	if(list_empty(&clients))
572		return;
573
574	while(1){
575		n = min((size_t) len, ARRAY_SIZE(console_buf) - console_index);
576		strncpy(&console_buf[console_index], string, n);
577		console_index += n;
578		string += n;
579		len -= n;
580		if(len == 0)
581			return;
582
583		list_for_each(ele, &clients){
584			struct mconsole_output *entry;
585
586			entry = list_entry(ele, struct mconsole_output, list);
587			mconsole_reply_len(entry->req, console_buf,
588					   console_index, 0, 1);
589		}
590
591		console_index = 0;
592	}
593}
594
595static struct console mc_console = { .name	= "mc",
596				     .write	= console_write,
597				     .flags	= CON_ENABLED,
598				     .index	= -1 };
599
600static int mc_add_console(void)
601{
602	register_console(&mc_console);
603	return 0;
604}
605
606late_initcall(mc_add_console);
607
608static void with_console(struct mc_request *req, void (*proc)(void *),
609			 void *arg)
610{
611	struct mconsole_output entry;
612	unsigned long flags;
613
614	entry.req = req;
615	spin_lock_irqsave(&client_lock, flags);
616	list_add(&entry.list, &clients);
617	spin_unlock_irqrestore(&client_lock, flags);
618
619	(*proc)(arg);
620
621	mconsole_reply_len(req, console_buf, console_index, 0, 0);
622	console_index = 0;
623
624	spin_lock_irqsave(&client_lock, flags);
625	list_del(&entry.list);
626	spin_unlock_irqrestore(&client_lock, flags);
627}
628
629#ifdef CONFIG_MAGIC_SYSRQ
630static void sysrq_proc(void *arg)
631{
632	char *op = arg;
633	handle_sysrq(*op, NULL);
634}
635
636void mconsole_sysrq(struct mc_request *req)
637{
638	char *ptr = req->request.data;
639
640	ptr += strlen("sysrq");
641	while(isspace(*ptr)) ptr++;
642
643	/* With 'b', the system will shut down without a chance to reply,
644	 * so in this case, we reply first.
645	 */
646	if(*ptr == 'b')
647		mconsole_reply(req, "", 0, 0);
648
649	with_console(req, sysrq_proc, ptr);
650}
651#else
652void mconsole_sysrq(struct mc_request *req)
653{
654	mconsole_reply(req, "Sysrq not compiled in", 1, 0);
655}
656#endif
657
658#ifdef CONFIG_MODE_SKAS
659
660static void stack_proc(void *arg)
661{
662	struct task_struct *from = current, *to = arg;
663
664	to->thread.saved_task = from;
665	switch_to(from, to, from);
666}
667
668/* Mconsole stack trace
669 *  Added by Allan Graves, Jeff Dike
670 *  Dumps a stacks registers to the linux console.
671 *  Usage stack <pid>.
672 */
673static void do_stack_trace(struct mc_request *req)
674{
675	char *ptr = req->request.data;
676	int pid_requested= -1;
677	struct task_struct *from = NULL;
678	struct task_struct *to = NULL;
679
680	/* Would be nice:
681	 * 1) Send showregs output to mconsole.
682	 * 2) Add a way to stack dump all pids.
683	 */
684
685	ptr += strlen("stack");
686	while(isspace(*ptr)) ptr++;
687
688	/* Should really check for multiple pids or reject bad args here */
689	/* What do the arguments in mconsole_reply mean? */
690	if(sscanf(ptr, "%d", &pid_requested) == 0){
691		mconsole_reply(req, "Please specify a pid", 1, 0);
692		return;
693	}
694
695	from = current;
696
697	to = find_task_by_pid(pid_requested);
698	if((to == NULL) || (pid_requested == 0)) {
699		mconsole_reply(req, "Couldn't find that pid", 1, 0);
700		return;
701	}
702	with_console(req, stack_proc, to);
703}
704#endif /* CONFIG_MODE_SKAS */
705
706void mconsole_stack(struct mc_request *req)
707{
708	/* This command doesn't work in TT mode, so let's check and then
709	 * get out of here
710	 */
711	CHOOSE_MODE(mconsole_reply(req, "Sorry, this doesn't work in TT mode",
712				   1, 0),
713		    do_stack_trace(req));
714}
715
716/* Changed by mconsole_setup, which is __setup, and called before SMP is
717 * active.
718 */
719static char *notify_socket = NULL;
720
721static int mconsole_init(void)
722{
723	/* long to avoid size mismatch warnings from gcc */
724	long sock;
725	int err;
726	char file[256];
727
728	if(umid_file_name("mconsole", file, sizeof(file))) return(-1);
729	snprintf(mconsole_socket_name, sizeof(file), "%s", file);
730
731	sock = os_create_unix_socket(file, sizeof(file), 1);
732	if (sock < 0){
733		printk("Failed to initialize management console\n");
734		return(1);
735	}
736
737	register_reboot_notifier(&reboot_notifier);
738
739	err = um_request_irq(MCONSOLE_IRQ, sock, IRQ_READ, mconsole_interrupt,
740			     IRQF_DISABLED | IRQF_SHARED | IRQF_SAMPLE_RANDOM,
741			     "mconsole", (void *)sock);
742	if (err){
743		printk("Failed to get IRQ for management console\n");
744		return(1);
745	}
746
747	if(notify_socket != NULL){
748		notify_socket = kstrdup(notify_socket, GFP_KERNEL);
749		if(notify_socket != NULL)
750			mconsole_notify(notify_socket, MCONSOLE_SOCKET,
751					mconsole_socket_name,
752					strlen(mconsole_socket_name) + 1);
753		else printk(KERN_ERR "mconsole_setup failed to strdup "
754			    "string\n");
755	}
756
757	printk("mconsole (version %d) initialized on %s\n",
758	       MCONSOLE_VERSION, mconsole_socket_name);
759	return(0);
760}
761
762__initcall(mconsole_init);
763
764static int write_proc_mconsole(struct file *file, const char __user *buffer,
765			       unsigned long count, void *data)
766{
767	char *buf;
768
769	buf = kmalloc(count + 1, GFP_KERNEL);
770	if(buf == NULL)
771		return(-ENOMEM);
772
773	if(copy_from_user(buf, buffer, count)){
774		count = -EFAULT;
775		goto out;
776	}
777
778	buf[count] = '\0';
779
780	mconsole_notify(notify_socket, MCONSOLE_USER_NOTIFY, buf, count);
781 out:
782	kfree(buf);
783	return(count);
784}
785
786static int create_proc_mconsole(void)
787{
788	struct proc_dir_entry *ent;
789
790	if(notify_socket == NULL) return(0);
791
792	ent = create_proc_entry("mconsole", S_IFREG | 0200, NULL);
793	if(ent == NULL){
794		printk(KERN_INFO "create_proc_mconsole : create_proc_entry failed\n");
795		return(0);
796	}
797
798	ent->read_proc = NULL;
799	ent->write_proc = write_proc_mconsole;
800	return(0);
801}
802
803static DEFINE_SPINLOCK(notify_spinlock);
804
805void lock_notify(void)
806{
807	spin_lock(&notify_spinlock);
808}
809
810void unlock_notify(void)
811{
812	spin_unlock(&notify_spinlock);
813}
814
815__initcall(create_proc_mconsole);
816
817#define NOTIFY "=notify:"
818
819static int mconsole_setup(char *str)
820{
821	if(!strncmp(str, NOTIFY, strlen(NOTIFY))){
822		str += strlen(NOTIFY);
823		notify_socket = str;
824	}
825	else printk(KERN_ERR "mconsole_setup : Unknown option - '%s'\n", str);
826	return(1);
827}
828
829__setup("mconsole", mconsole_setup);
830
831__uml_help(mconsole_setup,
832"mconsole=notify:<socket>\n"
833"    Requests that the mconsole driver send a message to the named Unix\n"
834"    socket containing the name of the mconsole socket.  This also serves\n"
835"    to notify outside processes when UML has booted far enough to respond\n"
836"    to mconsole requests.\n\n"
837);
838
839static int notify_panic(struct notifier_block *self, unsigned long unused1,
840			void *ptr)
841{
842	char *message = ptr;
843
844	if(notify_socket == NULL) return(0);
845
846	mconsole_notify(notify_socket, MCONSOLE_PANIC, message,
847			strlen(message) + 1);
848	return(0);
849}
850
851static struct notifier_block panic_exit_notifier = {
852	.notifier_call 		= notify_panic,
853	.next 			= NULL,
854	.priority 		= 1
855};
856
857static int add_notifier(void)
858{
859	atomic_notifier_chain_register(&panic_notifier_list,
860			&panic_exit_notifier);
861	return(0);
862}
863
864__initcall(add_notifier);
865
866char *mconsole_notify_socket(void)
867{
868	return(notify_socket);
869}
870
871EXPORT_SYMBOL(mconsole_notify_socket);
872