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(¬ify_spinlock); 808} 809 810void unlock_notify(void) 811{ 812 spin_unlock(¬ify_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