/* * linux/drivers/net/netconsole.c * * Copyright (C) 2001 Ingo Molnar * * This file contains the implementation of an IRQ-safe, crash-safe * kernel console implementation that outputs kernel messages to the * network. * * Modification history: * * 2001-09-17 started by Ingo Molnar. * 2003-08-11 2.6 port by Matt Mackall * simplified options * generic card hooks * works non-modular * 2003-09-07 rewritten with netpoll api */ /**************************************************************** * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * ****************************************************************/ #include #include #include #include #include #include #include MODULE_AUTHOR("Maintainer: Matt Mackall "); MODULE_DESCRIPTION("Console driver for network interfaces"); MODULE_LICENSE("GPL"); #define MAX_PARAM_LENGTH 256 #define MAX_PRINT_CHUNK 1000 static char config[MAX_PARAM_LENGTH]; module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0); MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@/[tgt-macaddr]\n"); #ifndef MODULE static int __init option_setup(char *opt) { strlcpy(config, opt, MAX_PARAM_LENGTH); return 1; } __setup("netconsole=", option_setup); #endif /* MODULE */ /* Linked list of all configured targets */ static LIST_HEAD(target_list); /* This needs to be a spinlock because write_msg() cannot sleep */ static DEFINE_SPINLOCK(target_list_lock); /** * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list. * @np: The netpoll structure for this target. */ struct netconsole_target { struct list_head list; struct netpoll np; }; /* Allocate new target and setup netpoll for it */ static struct netconsole_target *alloc_target(char *target_config) { int err = -ENOMEM; struct netconsole_target *nt; /* Allocate and initialize with defaults */ nt = kzalloc(sizeof(*nt), GFP_KERNEL); if (!nt) { printk(KERN_ERR "netconsole: failed to allocate memory\n"); goto fail; } nt->np.name = "netconsole"; strlcpy(nt->np.dev_name, "eth0", IFNAMSIZ); nt->np.local_port = 6665; nt->np.remote_port = 6666; memset(nt->np.remote_mac, 0xff, ETH_ALEN); /* Parse parameters and setup netpoll */ err = netpoll_parse_options(&nt->np, target_config); if (err) goto fail; err = netpoll_setup(&nt->np); if (err) goto fail; return nt; fail: kfree(nt); return ERR_PTR(err); } /* Cleanup netpoll for given target and free it */ static void free_target(struct netconsole_target *nt) { netpoll_cleanup(&nt->np); kfree(nt); } /* Handle network interface device notifications */ static int netconsole_netdev_event(struct notifier_block *this, unsigned long event, void *ptr) { unsigned long flags; struct netconsole_target *nt; struct net_device *dev = ptr; if (!(event == NETDEV_CHANGEADDR || event == NETDEV_CHANGENAME)) goto done; spin_lock_irqsave(&target_list_lock, flags); list_for_each_entry(nt, &target_list, list) { if (nt->np.dev == dev) { switch (event) { case NETDEV_CHANGEADDR: memcpy(nt->np.local_mac, dev->dev_addr, ETH_ALEN); break; case NETDEV_CHANGENAME: strlcpy(nt->np.dev_name, dev->name, IFNAMSIZ); break; } } } spin_unlock_irqrestore(&target_list_lock, flags); done: return NOTIFY_DONE; } static struct notifier_block netconsole_netdev_notifier = { .notifier_call = netconsole_netdev_event, }; static void write_msg(struct console *con, const char *msg, unsigned int len) { int frag, left; unsigned long flags; struct netconsole_target *nt; const char *tmp; /* Avoid taking lock and disabling interrupts unnecessarily */ if (list_empty(&target_list)) return; spin_lock_irqsave(&target_list_lock, flags); list_for_each_entry(nt, &target_list, list) { if (netif_running(nt->np.dev)) { /* * We nest this inside the for-each-target loop above * so that we're able to get as much logging out to * at least one target if we die inside here, instead * of unnecessarily keeping all targets in lock-step. */ tmp = msg; for (left = len; left;) { frag = min(left, MAX_PRINT_CHUNK); netpoll_send_udp(&nt->np, tmp, frag); tmp += frag; left -= frag; } } } spin_unlock_irqrestore(&target_list_lock, flags); } static struct console netconsole = { .name = "netcon", .flags = CON_ENABLED | CON_PRINTBUFFER, .write = write_msg, }; static int __init init_netconsole(void) { int err = 0; struct netconsole_target *nt, *tmp; unsigned long flags; char *target_config; char *input = config; if (!strnlen(input, MAX_PARAM_LENGTH)) { printk(KERN_INFO "netconsole: not configured, aborting\n"); goto out; } while ((target_config = strsep(&input, ";"))) { nt = alloc_target(target_config); if (IS_ERR(nt)) { err = PTR_ERR(nt); goto fail; } spin_lock_irqsave(&target_list_lock, flags); list_add(&nt->list, &target_list); spin_unlock_irqrestore(&target_list_lock, flags); } err = register_netdevice_notifier(&netconsole_netdev_notifier); if (err) goto fail; register_console(&netconsole); printk(KERN_INFO "netconsole: network logging started\n"); out: return err; fail: printk(KERN_ERR "netconsole: cleaning up\n"); /* * Remove all targets and destroy them. Skipping the list * lock is safe here, and netpoll_cleanup() will sleep. */ list_for_each_entry_safe(nt, tmp, &target_list, list) { list_del(&nt->list); free_target(nt); } return err; } static void __exit cleanup_netconsole(void) { struct netconsole_target *nt, *tmp; unregister_console(&netconsole); unregister_netdevice_notifier(&netconsole_netdev_notifier); /* * Remove all targets and destroy them. Skipping the list * lock is safe here, and netpoll_cleanup() will sleep. */ list_for_each_entry_safe(nt, tmp, &target_list, list) { list_del(&nt->list); free_target(nt); } } module_init(init_netconsole); module_exit(cleanup_netconsole);