Return-Path: Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1753884Ab2B0DGI (ORCPT ); Sun, 26 Feb 2012 22:06:08 -0500 Received: from mail-qy0-f174.google.com ([209.85.216.174]:63561 "EHLO mail-qy0-f174.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1753601Ab2B0DFz (ORCPT ); Sun, 26 Feb 2012 22:05:55 -0500 From: Andrei Warkentin To: netdev@vger.kernel.org, linux-kernel@vger.kernel.org Cc: Andrei Warkentin , kgdb-bugreport@lists.sourceforge.net, Jason Wessel , Matt Mackall , Andrei Warkentin Subject: [PATCHv2 2/2] NETKGDB: Ethernet/UDP/IP KDB transport. Date: Sun, 26 Feb 2012 21:05:37 -0500 Message-Id: <1330308337-21365-3-git-send-email-andrey.warkentin@gmail.com> X-Mailer: git-send-email 1.7.8.3 In-Reply-To: <1330308337-21365-1-git-send-email-andrey.warkentin@gmail.com> References: <1330137851-4716-1-git-send-email-andrey.warkentin@gmail.com> <1330308337-21365-1-git-send-email-andrey.warkentin@gmail.com> Sender: linux-kernel-owner@vger.kernel.org List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Length: 24564 Lines: 913 From: Andrei Warkentin Allows debugging a crashed kernel, and breaking into a live kernel via a network interface. Useful in testing/QA farms where physical access is not necessarily easy/desired, and iLO-like solutions end up using USB HID and not i8042-based keyboard. Cc: kgdb-bugreport@lists.sourceforge.net Cc: Jason Wessel Cc: Matt Mackall Signed-off-by: Andrei Warkentin Signed-off-by: Andrei Warkentin --- Documentation/networking/netkgdb.txt | 104 +++++ drivers/net/Kconfig | 8 +- drivers/net/Makefile | 1 + drivers/net/netkgdb.c | 728 ++++++++++++++++++++++++++++++++++ 4 files changed, 840 insertions(+), 1 deletions(-) create mode 100644 Documentation/networking/netkgdb.txt create mode 100644 drivers/net/netkgdb.c diff --git a/Documentation/networking/netkgdb.txt b/Documentation/networking/netkgdb.txt new file mode 100644 index 0000000..b2c70c1 --- /dev/null +++ b/Documentation/networking/netkgdb.txt @@ -0,0 +1,104 @@ +(based off netconsole.txt) + +Started by Andrei Warkentin , 2012.02.24 + +Please send bug reports to Andrey Warkentin + +Introduction: +============= + +This module allows debugging a crashed kernel over the network, +where other means are not practical. + +It can be used either built-in or as a module. As a built-in, +netkgdb initializes immediately after NIC cards and will bring up +the specified interface as soon as possible. While this doesn't allow +early kernel debugging, it's useful for handling random crashes and +such. + +Sender and receiver configuration: +================================== + +It takes an optional string configuration parameter "netkgdb" in the +following format: + + netkgdb=[src-port]@[src-ip]/[],[tgt-port]@/[tgt-macaddr] + + where + src-port source for UDP packets (defaults to 7777) + src-ip source IP to use (interface address) + dev network interface (eth0) + tgt-port port for remote agent (7777) + tgt-ip IP address for remote agent + tgt-macaddr ethernet MAC address for remote agent (broadcast) + +Examples: + + linux netkgdb=4444@10.0.0.1/eth1,9353@10.0.0.2/12:34:56:78:9a:bc + + or + + insmod netkgdb netkgdb=@10.0.0.5/,@10.0.0.2/ + +Note: the parameter is optional and largely unneeded unless you +are running a listen server - netkgdb will accept connection from any +IP on all interfaces and will reconfigure itself appropriately if +the assigned interface IP address changes. This makes it useful +in an environment where it's not known ahead of time what computer +will connect to perform the crash analysis. + +Note: The format for the string is not netkgdb specific, and its +parsing is part of Linux netpoll support. The netpoll code does +need at least a local IP assigned, so the following will result +in an error and netkgdb will not load if you don't already have +an IP address assigned. + +Note: If you specify a local/remote netkgdb string as a kernel +parameter, with the idea of having a remote server listen on +a socket and wait for the kdb crash connection, don't expect +to be able to connect from the remote host. This is peculiar +behavior of the netpoll code - the interface is not *really* +configured and your packets will never reach netkgdb. You +will need to "ifconfig eth0 x.y.z.w' first. Or just wait +for the crash :-). + +insmod netkgdb netkgdb=@/,@10.0.0.2/ + +The remote host can run either 'netcat -u -l -p ' or 'nc -l -u '. + +Using: +====== + +If the kernel is running, then any input will result in following string +to be sent back - + Alive - '!!!BREAK!!!' to break in. + +This lets you know the machine is alive. + +Sending "!!!BREAK!!!", followed by a '\n' will result in breaking into +KGDB/KDB. + +Miscellaneous notes: +==================== + +The following notes are only relevant if you want the debugged +host to automatically send the KDB/KGDB data on a crash to a +host preconfigured with the netkgdb= parameter. + +WARNING: the default target ethernet setting uses the broadcast +ethernet address to send packets, which can cause increased load on +other systems on the same ethernet segment. After the first reply +connection, the target ethernet address is updated to match the source. + +TIP: some LAN switches may be configured to suppress ethernet broadcasts +so it is advised to explicitly specify the remote agents' MAC addresses +from the config parameters passed to netkgdb. + +TIP: to find out the MAC address of, say, 10.0.0.2, you may try using: + + ping -c 1 10.0.0.2 ; /sbin/arp -n | grep 10.0.0.2 + +TIP: in case the remote agent is on a separate LAN subnet than +the sender, it is suggested to try specifying the MAC address of the +default gateway (you may use /sbin/route -n to find it out) as the +remote MAC address instead. diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index b982854..8deb605 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -160,6 +160,12 @@ config NETCONSOLE If you want to log kernel messages over the network, enable this. See for details. +config NETKGDB + tristate "Network kgdb I/O backend" + ---help--- + If you want to debug the kernel over the network, enable this. + See for details. + config NETCONSOLE_DYNAMIC bool "Dynamic reconfiguration of logging targets" depends on NETCONSOLE && SYSFS && CONFIGFS_FS && \ @@ -171,7 +177,7 @@ config NETCONSOLE_DYNAMIC See for details. config NETPOLL - def_bool NETCONSOLE + def_bool NETCONSOLE || NETKGDB config NETPOLL_TRAP bool "Netpoll traffic trapping" diff --git a/drivers/net/Makefile b/drivers/net/Makefile index a6b8ce1..6eaa21f 100644 --- a/drivers/net/Makefile +++ b/drivers/net/Makefile @@ -15,6 +15,7 @@ obj-$(CONFIG_MII) += mii.o obj-$(CONFIG_MDIO) += mdio.o obj-$(CONFIG_NET) += Space.o loopback.o obj-$(CONFIG_NETCONSOLE) += netconsole.o +obj-$(CONFIG_NETKGDB) += netkgdb.o obj-$(CONFIG_PHYLIB) += phy/ obj-$(CONFIG_RIONET) += rionet.o obj-$(CONFIG_NET_TEAM) += team/ diff --git a/drivers/net/netkgdb.c b/drivers/net/netkgdb.c new file mode 100644 index 0000000..6a7bd90 --- /dev/null +++ b/drivers/net/netkgdb.c @@ -0,0 +1,728 @@ +/* + * linux/drivers/net/netkgdb.c + * + * Copyright (C) 2012 Andrei Warkentin + * + * Based on Matt Mackall's netconsole and + * my FIQ/KGDB support code. + * + */ + +#define pr_fmt(fmt) "netkgdb: " fmt + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +MODULE_AUTHOR("Maintainer: Andrei Warkentin "); +MODULE_DESCRIPTION("KGDB I/O driver for network interfaces"); +MODULE_LICENSE("GPL"); + +#define DEFAULT_PORT 7777 +#define MAX_PARAM_LENGTH 256 +#define MAX_RING_SIZE PAGE_SIZE + +static char config[MAX_PARAM_LENGTH]; +module_param_string(netkgdb, config, MAX_PARAM_LENGTH, 0); +MODULE_PARM_DESC(netkgdb, " netkgdb=[src-port]@[src-ip]/[dev],[tgt-port]@/[tgt-macaddr]"); + +static LIST_HEAD(netkgdb_nts); + +/* + * Protects netkgdb_nts list and access to stored netkgdb targets + * between the net notifiers. + */ +static DEFINE_MUTEX(netkgdb_nts_lock); + +/** + * struct netkgdb_target - Represents a configured netkgdb target. + * @list: Links this target into the netkgdb_nts. + * @tx_np: netpoll structue used for KGDB/KDB traffic. + * @rx_np: netpoll structure used to listed to inbound connections. + * @bp: work_struct to break into KGDB/KDB. + * @ping: work_struct to return alive message. + * @tx_ready: set if there is an outgoing IP address assigned. + */ +struct netkgdb_target { + struct list_head list; + struct netpoll tx_np; + struct netpoll rx_np; + struct work_struct bp; + struct work_struct ping; + bool tx_ready; +}; + +static struct netkgdb_ring { + u8 buf[MAX_RING_SIZE]; + int head; + int tail; +} netkgdb_rx_ring, netkgdb_tx_ring; +static int netkgdb_trapped = 0; + +/* + * Returns the offset of the first unconsumed character. + */ +static inline u8 *netkgdb_ring_off(struct netkgdb_ring *ring) +{ + return &ring->buf[ring->tail]; +} + +/* + * Returns the length of a contiguous buffer of unconsumed + * characters, to deal with ring buffer wraparound. + */ +static inline int netkgdb_ring_cont(struct netkgdb_ring *ring) +{ + if (ring->head < ring->tail) + return MAX_RING_SIZE - ring->tail; + else + return ring->head - ring->tail; +} + +/* + * Returns the number of characters present in the ring. + */ +static inline int netkgdb_ring_level(struct netkgdb_ring *ring) +{ + int level = ring->head - ring->tail; + + if (level < 0) + level = MAX_RING_SIZE + level; + + return level; +} + +/* + * Returns the number of characters that could be added to the ring. + */ +static inline int netkgdb_ring_room(struct netkgdb_ring *ring) +{ + return MAX_RING_SIZE - netkgdb_ring_level(ring) - 1; +} + +/* + * Returns the character at a specific position in the ring, + * without consuming it. + */ +static inline u8 netkgdb_ring_peek(struct netkgdb_ring *ring, int i) +{ + return ring->buf[(ring->tail + i) % MAX_RING_SIZE]; +} + +/* + * Consumes a number of characters (up to count) in the ring. + */ +static inline int ring_consume(struct netkgdb_ring *ring, int count) +{ + count = min(count, netkgdb_ring_level(ring)); + + ring->tail = (ring->tail + count) % MAX_RING_SIZE; + smp_mb(); + return count; +} + +/* + * Adds a character to the ring, updating the number of unconsumed + * characters. + */ +static inline int ring_push(struct netkgdb_ring *ring, u8 data) +{ + if (netkgdb_ring_room(ring) == 0) + return 0; + + ring->buf[ring->head] = data; + smp_mb(); + ring->head = (ring->head + 1) % MAX_RING_SIZE; + smp_mb(); + + return 1; +} + +/* + * Flushes the contents of the outgoing (TX) ring + * to all netkgdb targets that are up and have a remote + * host configured. + */ +static inline void netkgdb_tx_flush(void) +{ + int frag; + u8 *start; + struct netkgdb_target *nt; + + /* In interrupt context on one cpu. Forget about the locks. */ + while (netkgdb_ring_level(&netkgdb_tx_ring)) + { + start = netkgdb_ring_off(&netkgdb_tx_ring); + frag = netkgdb_ring_cont(&netkgdb_tx_ring); + + list_for_each_entry(nt, &netkgdb_nts, list) { + if (nt->tx_ready && + nt->tx_np.dev && + netif_running(nt->tx_np.dev)) + netpoll_send_udp(&nt->tx_np, start, frag); + } + + ring_consume(&netkgdb_tx_ring, frag); + } +} + +/* + * Called from KGDB/KDB, returns the the next character read + * from the network interface(s), while processing any + * connect() requests from remote hosts and flushing + * the TX ring. + */ +static int netkgdb_char_get(void) +{ + u8 c; + struct netkgdb_target *nt; + + /* In interrupt context on ONE cpu. Forget about the locks. */ + list_for_each_entry(nt, &netkgdb_nts, list) { + + /* + * Polls the devices, both for KGDB I/O and + * new incoming connections. + */ + if (netif_running(nt->rx_np.dev)) + netpoll_poll_dev(nt->rx_np.dev); + } + + /* Flush any output we didn't get to in char_put. */ + netkgdb_tx_flush(); + + if (!netkgdb_ring_level(&netkgdb_rx_ring)) + return NO_POLL_CHAR; + + c = netkgdb_ring_peek(&netkgdb_rx_ring, 0); + if (c == '\n') + c = '\r'; + ring_consume(&netkgdb_rx_ring, 1); + return c; +} + +/* + * Sends a character to remote hosts, taking care to + * avoid singe-character packets. Only flush the ring + * on a full ring or at the end of the line. Whatever + * didn't get flushed will get flushed in netkgdb_char_get. + * There is, of course, some potential for missed data, + * like if KDB/KGDB crashed while sending data, but it's + * probably insignificant given the likelyhood and the + * amount of lost data (less than a line). + */ +static void netkgdb_char_put(u8 c) +{ + while (!ring_push(&netkgdb_tx_ring, c)) + netkgdb_tx_flush(); + + if (c == '\n') + netkgdb_tx_flush(); +} + +/* + * Invoked before KDB/KGDB takes control. + */ +static void netkgdb_exp_pre(void) +{ + netpoll_set_trap(1); + netkgdb_trapped = 1; +} + +/* + * Invoked on KDB/KGDB exit. + */ +static void netkgdb_exp_post(void) +{ + netkgdb_trapped = 0; + netpoll_set_trap(0); +} + +static struct kgdb_io netkgdb_io_ops = { + .name = "netkgdb", + .read_char = netkgdb_char_get, + .write_char = netkgdb_char_put, + .pre_exception = netkgdb_exp_pre, + .post_exception = netkgdb_exp_post, +}; + +/* + * Handles receiving the '!!!BREAK!!!' string from + * a remote host. We cannot do this from the rx_hook + * itself, otherwise we'll deadlock due to reentering + * netpoll. + */ +void netkgdb_work_bp(struct work_struct *work) +{ + struct netkgdb_target *nt = container_of(work, + struct netkgdb_target, + bp); + + pr_emerg("breaking into KGDB from %pI4:%d\n", + &nt->tx_np.remote_ip, + nt->tx_np.remote_port); + kgdb_breakpoint(); +} + +/* + * Handles sending an alive message to a remote host, + * as a response to network messages while the local + * host is not crashed. Once again, cannot be done + * from rx_hook context, due to netpoll reentrance + * issue. + */ +void netkgdb_work_ping(struct work_struct *work) +{ + char break_string[] = "Alive - '!!!BREAK!!!' to break in.\n"; + struct netkgdb_target *nt = container_of(work, + struct netkgdb_target, + ping); + + if (nt->tx_ready && + nt->tx_np.dev && + netif_running(nt->tx_np.dev)) + netpoll_send_udp(&nt->tx_np, break_string, + sizeof(break_string) - 1); +} + +/* + * Invoked on every received UDP/IP message received from + * a known host, setup in netkgdb_nt_initial, or by + * netkgdb_in_rx_hook. If the host is not crashed, it will + * return an alive message and allow breaking in, otherwise + * it fills the RX ring with received data. Note that there + * is no locking on the RX ring - when we crash we are guaranteed + * to run on one CP. + */ +void netkgdb_io_rx_hook(struct netpoll *np, + u8 *h_source, + __be32 saddr, + struct udphdr *uh, + char *data, + int len) +{ + int count = 0; + char break_string[] = "!!!BREAK!!!\n"; + struct netkgdb_target *nt = container_of(np, + struct netkgdb_target, + tx_np); + + if (!netkgdb_trapped) { + if ((len == sizeof(break_string) - 1) && + !memcmp(data, break_string, + sizeof(break_string) - 1)) + schedule_work(&nt->bp); + else + schedule_work(&nt->ping); + return; + } + + while (count < len) + ring_push(&netkgdb_rx_ring, data[count++]); +} + +/* + * Invoked on every UDP/IP message received from a remote + * host. For a known host, which we configured for, does nothing. + * For an unknown host, modifies the KGDB I/O netpoll struct + * with the new remote address, so that netkgdb_io_rx_hook can + * receive messages and KDB can send data to the new adddress. + */ +void netkgdb_in_rx_hook(struct netpoll *np, + u8 *h_source, + __be32 saddr, + struct udphdr *uh, + char *data, + int len) +{ + struct netkgdb_target *nt = container_of(np, + struct netkgdb_target, + rx_np); + + if (!nt->tx_np.dev) + return; + + if (nt->tx_np.remote_ip != saddr || + nt->tx_np.remote_port != ntohs(uh->source) || + memcmp(np->remote_mac, h_source, ETH_ALEN)) { + + /* + * This is safe because npinfo->rx_lock is taken + * and is shared with tx_np. The npinfo->rx_lock + * also means this is safe with any up() or down() + * work going on. + */ + nt->tx_np.remote_ip = saddr; + nt->tx_np.remote_port = ntohs(uh->source); + memcpy(np->remote_mac, h_source, ETH_ALEN); + nt->tx_ready = true; + + pr_info("accepted from %pI4:%d\n", + &saddr, ntohs(uh->source)); + + netkgdb_io_rx_hook(&nt->tx_np, h_source, saddr, uh, data, len); + } +} + +/* + * Cleans up a netkgdb_target structure. + */ +static void netkgdb_nt_free(struct netkgdb_target *nt) +{ + nt->tx_ready = false; + flush_work(&nt->bp); + flush_work(&nt->ping); + if (nt->rx_np.dev) + netpoll_cleanup(&nt->rx_np); + if (nt->tx_np.dev) + netpoll_cleanup(&nt->tx_np); + kfree(nt); +} + +/* + * Creates a new netkgdb_target structure, filling in + * defaults. Does not add it to target list or register + * with netpoll. + */ +static struct netkgdb_target *netkgdb_nt_alloc(char *dev_name) +{ + struct netkgdb_target *nt; + + nt = kzalloc(sizeof(*nt), GFP_KERNEL); + if (!nt) { + pr_err("failed to allocate memory\n"); + return ERR_PTR(-ENOMEM); + } + + INIT_WORK(&nt->bp, netkgdb_work_bp); + INIT_WORK(&nt->ping, netkgdb_work_ping); + nt->tx_np.name = "netkgdb-io"; + strlcpy(nt->tx_np.dev_name, dev_name, IFNAMSIZ); + nt->tx_np.local_port = DEFAULT_PORT; + nt->tx_np.remote_port = DEFAULT_PORT; + nt->tx_np.rx_hook = netkgdb_io_rx_hook; + memset(nt->tx_np.remote_mac, 0xff, ETH_ALEN); + + nt->rx_np.name = "netkgdb-inbound"; + strlcpy(nt->rx_np.dev_name, dev_name, IFNAMSIZ); + nt->rx_np.local_port = DEFAULT_PORT; + nt->rx_np.rx_hook = netkgdb_in_rx_hook; + memset(nt->rx_np.remote_mac, 0xff, ETH_ALEN); + return nt; +} + +/* + * Called in response to network interface going down. + * __netpoll_cleanup must be used over netpoll_cleanup, due + * rtnl_lock being taken by network dev notifiers. + */ +static void netkgdb_nt_down(struct netkgdb_target *nt) +{ + + if (nt->tx_np.dev || + nt->rx_np.dev) + pr_info("down %s\n", nt->tx_np.dev->name); + + nt->tx_ready = false; + if (nt->tx_np.dev) { + __netpoll_cleanup(&nt->tx_np); + dev_put(nt->tx_np.dev); + nt->tx_np.dev = NULL; + } + if (nt->rx_np.dev) { + __netpoll_cleanup(&nt->rx_np); + dev_put(nt->rx_np.dev); + nt->rx_np.dev = NULL; + } +} + +/* + * Called in response to network interface going up. + * __netpoll_setup must be used over netpoll_setup, due + * rtnl_lock being taken by network dev notifiers. + */ +static int netkgdb_nt_up(struct netkgdb_target *nt, + struct in_ifaddr *ifa) +{ + int err; + + nt->tx_np.local_ip = ifa->ifa_local; + nt->tx_np.dev = ifa->ifa_dev->dev; + dev_hold(nt->tx_np.dev); + err = __netpoll_setup(&nt->tx_np); + if (err) + goto done; + + nt->rx_np.local_ip = ifa->ifa_local; + nt->rx_np.dev = ifa->ifa_dev->dev; + dev_hold(nt->rx_np.dev); + err = __netpoll_setup(&nt->rx_np); + if (err) { + netpoll_cleanup(&nt->tx_np); + goto done; + } + + if (nt->tx_np.remote_ip && nt->tx_np.remote_port) + nt->tx_ready = true; + +done: + if (!err) + pr_info("up %s (%pI4:%d)\n", + ifa->ifa_dev->dev->name, + &ifa->ifa_local, + nt->tx_np.local_port); + else { + pr_err("couldn't configure %s (%pI4)\n", + ifa->ifa_dev->dev->name, + &ifa->ifa_local); + if (nt->tx_np.dev) { + dev_put(nt->tx_np.dev); + nt->tx_np.dev = NULL; + } + if (nt->rx_np.dev) { + dev_put(nt->rx_np.dev); + nt->rx_np.dev = NULL; + } + } + return err; +} + +/* + * Allocates a netkgdb_target and sets it up + * for a new network interface coming up. + */ +static void netkgdb_nt_late(struct in_ifaddr *ifa) +{ + int err; + struct netkgdb_target *nt; + + nt = netkgdb_nt_alloc(ifa->ifa_dev->dev->name); + if (IS_ERR(nt)) + return; + + err = netkgdb_nt_up(nt, ifa); + + if (!err) + list_add(&nt->list, &netkgdb_nts); + + if (err) + netkgdb_nt_free(nt); +} + +/* + * Allocate a target (from boot/module param) and + * setup netpoll for it + */ +static int netkgdb_nt_initial(char *target_config) +{ + int err; + struct netkgdb_target *nt; + + nt = netkgdb_nt_alloc("eth0"); + if (IS_ERR(nt)) + return PTR_ERR(nt); + + /* Parse parameters and setup netpoll */ + err = netpoll_parse_options(&nt->tx_np, target_config); + if (err) + goto done; + + if (nt->tx_np.remote_ip && nt->tx_np.remote_port) + nt->tx_ready = true; + + err = netpoll_setup(&nt->tx_np); + if (err) + goto done; + + /* Synchronize inbound np with I/O np options. */ + strlcpy(nt->rx_np.dev_name, nt->tx_np.dev_name, IFNAMSIZ); + nt->rx_np.local_port = nt->tx_np.local_port; + nt->rx_np.local_ip = nt->tx_np.local_ip; + + err = netpoll_setup(&nt->rx_np); + if (err) + goto done; + + mutex_lock(&netkgdb_nts_lock); + list_add(&nt->list, &netkgdb_nts); + mutex_unlock(&netkgdb_nts_lock); + +done: + if (err) + netkgdb_nt_free(nt); + return err; +} + +/* + * Handle network address notifications. + */ +static int netkgdb_inetaddr_event(struct notifier_block *this, + unsigned long event, void *ptr) +{ + struct netkgdb_target *nt; + struct in_ifaddr *ifa = (struct in_ifaddr *)ptr; + struct net_device *dev = ifa->ifa_dev->dev; + bool found = false; + + mutex_lock(&netkgdb_nts_lock); + switch (event) { + case NETDEV_UP: + list_for_each_entry(nt, &netkgdb_nts, list) { + if (!strcmp(nt->tx_np.dev_name, dev->name)) { + found = true; + netkgdb_nt_down(nt); + netkgdb_nt_up(nt, ifa); + break; + } + } + if (!found) + netkgdb_nt_late(ifa); + break; + case NETDEV_DOWN: + list_for_each_entry(nt, &netkgdb_nts, list) { + if (nt->tx_np.dev == dev) + netkgdb_nt_down(nt); + } + break; + } + mutex_unlock(&netkgdb_nts_lock); + + return NOTIFY_DONE; +} + +/* + * Handle network interface device notifications. + */ +static int netkgdb_netdev_event(struct notifier_block *this, + unsigned long event, + void *ptr) +{ + struct netkgdb_target *nt; + struct net_device *dev = ptr; + + if (!(event == NETDEV_CHANGENAME || + event == NETDEV_UNREGISTER || + event == NETDEV_RELEASE || + event == NETDEV_JOIN)) + return NOTIFY_DONE; + + mutex_lock(&netkgdb_nts_lock); + list_for_each_entry(nt, &netkgdb_nts, list) { + if (nt->tx_np.dev == dev) { + switch (event) { + case NETDEV_CHANGENAME: + strlcpy(nt->tx_np.dev_name, dev->name, IFNAMSIZ); + strlcpy(nt->rx_np.dev_name, dev->name, IFNAMSIZ); + break; + case NETDEV_RELEASE: + case NETDEV_JOIN: + case NETDEV_UNREGISTER: + /* + * rtnl_lock already held + */ + netkgdb_nt_down(nt); + break; + } + } + } + mutex_unlock(&netkgdb_nts_lock); + return NOTIFY_DONE; +} + +static struct notifier_block netkgdb_netdev_notifier = { + .notifier_call = netkgdb_netdev_event, +}; + +static struct notifier_block netkgdb_inetaddr_notifier = { + .notifier_call = netkgdb_inetaddr_event, +}; + +static int __init netkgdb_init(void) +{ + int err; + struct netkgdb_target *nt, *tmp; + char *input = config; + + /* + * Not having a netkgdb= parameter itself is not + * a mistake, asn netkgdb_nt_late will take care + * of things. However, passing a bad parameter + * is a mistake (like not having a local IP address). + */ + if (strnlen(input, MAX_PARAM_LENGTH)) { + err = netkgdb_nt_initial(input); + if (err) + goto fail; + } + + err = register_netdevice_notifier(&netkgdb_netdev_notifier); + if (err) + goto fail; + + err = register_inetaddr_notifier(&netkgdb_inetaddr_notifier); + if (err) + goto undo_netdev; + + err = kgdb_register_io_module(&netkgdb_io_ops); + if (err) { + pr_err("failed to register I/O ops\n"); + goto undo_inetaddr; + } + + pr_info("started\n"); + return err; + +undo_inetaddr: + unregister_inetaddr_notifier(&netkgdb_inetaddr_notifier); + +undo_netdev: + unregister_netdevice_notifier(&netkgdb_netdev_notifier); + +fail: + pr_err("cleaning up\n"); + list_for_each_entry_safe(nt, tmp, &netkgdb_nts, list) { + list_del(&nt->list); + netkgdb_nt_free(nt); + } + + return err; +} + +static void __exit netkgdb_cleanup(void) +{ + struct netkgdb_target *nt, *tmp; + + kgdb_register_io_module(&netkgdb_io_ops); + unregister_netdevice_notifier(&netkgdb_netdev_notifier); + + list_for_each_entry_safe(nt, tmp, &netkgdb_nts, list) { + list_del(&nt->list); + netkgdb_nt_free(nt); + } +} + +/* + * Use late_initcall to ensure netkgdb is + * initialized after network device driver if built-in. + * + * late_initcall() and module_init() are identical if built as module. + */ +late_initcall(netkgdb_init); +module_exit(netkgdb_cleanup); + +#ifndef MODULE +static int __init option_setup(char *opt) +{ + strlcpy(config, opt, MAX_PARAM_LENGTH); + return 1; +} +__setup("netkgdb=", option_setup); +#endif /* MODULE */ -- 1.7.8.3 -- To unsubscribe from this list: send the line "unsubscribe linux-kernel" in the body of a message to majordomo@vger.kernel.org More majordomo info at http://vger.kernel.org/majordomo-info.html Please read the FAQ at http://www.tux.org/lkml/