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