lkml.org 
[lkml]   [2012]   [Feb]   [26]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
    Patch in this message
    /
    From
    Subject[PATCHv3 2/3] NETKGDB: Ethernet/UDP/IP KDB transport.
    Date
    From: Andrei Warkentin <andreiw@vmware.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@lists.sourceforge.net
    Cc: Jason Wessel <jason.wessel@windriver.com>
    Cc: Matt Mackall <mpm@selenic.com>
    Signed-off-by: Andrei Warkentin <andreiw@vmware.com>
    Signed-off-by: Andrei Warkentin <andrey.warkentin@gmail.com>
    ---
    Documentation/networking/netkgdb.txt | 104 +++++
    drivers/net/Kconfig | 8 +-
    drivers/net/Makefile | 1 +
    drivers/net/netkgdb.c | 729 ++++++++++++++++++++++++++++++++++
    4 files changed, 841 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@gmail.com>, 2012.02.24
    +
    +Please send bug reports to Andrey Warkentin <andrey.warkentin@gmail.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@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 <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..bd03cd7
    --- /dev/null
    +++ b/drivers/net/netkgdb.c
    @@ -0,0 +1,729 @@
    +/*
    + * linux/drivers/net/netkgdb.c
    + *
    + * Copyright (C) 2012 Andrei Warkentin <andreiw@vmware.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@vmware.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",
    + .flush = netkgdb_tx_flush,
    + .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


    \
     
     \ /
      Last update: 2012-02-27 05:33    [W:2.609 / U:0.332 seconds]
    ©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site