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