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