[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <1330308337-21365-3-git-send-email-andrey.warkentin@gmail.com>
Date: Sun, 26 Feb 2012 21:05:37 -0500
From: Andrei Warkentin <andrey.warkentin@...il.com>
To: netdev@...r.kernel.org, linux-kernel@...r.kernel.org
Cc: Andrei Warkentin <andreiw@...are.com>,
kgdb-bugreport@...ts.sourceforge.net,
Jason Wessel <jason.wessel@...driver.com>,
Matt Mackall <mpm@...enic.com>,
Andrei Warkentin <andrey.warkentin@...il.com>
Subject: [PATCHv2 2/2] NETKGDB: Ethernet/UDP/IP KDB transport.
From: Andrei Warkentin <andreiw@...are.com>
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@...ts.sourceforge.net
Cc: Jason Wessel <jason.wessel@...driver.com>
Cc: Matt Mackall <mpm@...enic.com>
Signed-off-by: Andrei Warkentin <andreiw@...are.com>
Signed-off-by: Andrei Warkentin <andrey.warkentin@...il.com>
---
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 <andrey.warkentin@...il.com>, 2012.02.24
+
+Please send bug reports to Andrey Warkentin <andrey.warkentin@...il.com>
+
+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]/[<dev>],[tgt-port]@<tgt-ip>/[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@...0.0.1/eth1,9353@...0.0.2/12:34:56:78:9a:bc
+
+ or
+
+ insmod netkgdb netkgdb=@...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 <port>' or 'nc -l -u <port>'.
+
+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 <file:Documentation/networking/netconsole.txt> for details.
+config NETKGDB
+ tristate "Network kgdb I/O backend"
+ ---help---
+ If you want to debug the kernel over the network, enable this.
+ See <file:Documentation/networking/netkgdb.txt> 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 <file:Documentation/networking/netconsole.txt> 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 <andreiw@...are.com>
+ *
+ * Based on Matt Mackall's netconsole and
+ * my FIQ/KGDB support code.
+ *
+ */
+
+#define pr_fmt(fmt) "netkgdb: " fmt
+
+#include <linux/mm.h>
+#include <linux/init.h>
+#include <linux/module.h>
+#include <linux/slab.h>
+#include <linux/moduleparam.h>
+#include <linux/string.h>
+#include <linux/netpoll.h>
+#include <linux/workqueue.h>
+#include <linux/inet.h>
+#include <linux/inetdevice.h>
+#include <linux/kgdb.h>
+
+MODULE_AUTHOR("Maintainer: Andrei Warkentin <andreiw@...are.com>");
+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-ip>/[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 netdev" in
the body of a message to majordomo@...r.kernel.org
More majordomo info at http://vger.kernel.org/majordomo-info.html
Powered by blists - more mailing lists