__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;
 };
 
-static struct netconsole_target default_target = {
-       .np             = {
-               .name           = "netconsole",
-               .dev_name       = "eth0",
-               .local_port     = 6665,
-               .remote_port    = 6666,
-               .remote_mac     = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
-       },
-};
+/* 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;
-       struct netconsole_target *nt = &default_target;
 
-       if (nt->np.dev == dev) {
-               switch (event) {
-               case NETDEV_CHANGEADDR:
-                       memcpy(nt->np.local_mac, dev->dev_addr, ETH_ALEN);
-                       break;
+       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;
+                       case NETDEV_CHANGENAME:
+                               strlcpy(nt->np.dev_name, dev->name, IFNAMSIZ);
+                               break;
+                       }
                }
        }
+       spin_unlock_irqrestore(&target_list_lock, flags);
 
+done:
        return NOTIFY_DONE;
 }
 
 {
        int frag, left;
        unsigned long flags;
-       struct netconsole_target *nt = &default_target;
-
-       if (netif_running(nt->np.dev)) {
-               local_irq_save(flags);
-               for (left = len; left;) {
-                       frag = min(left, MAX_PRINT_CHUNK);
-                       netpoll_send_udp(&nt->np, msg, frag);
-                       msg += frag;
-                       left -= frag;
+       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;
+                       }
                }
-               local_irq_restore(flags);
        }
+       spin_unlock_irqrestore(&target_list_lock, flags);
 }
 
 static struct console netconsole = {
 static int __init init_netconsole(void)
 {
        int err = 0;
-       struct netconsole_target *nt = &default_target;
+       struct netconsole_target *nt, *tmp;
+       unsigned long flags;
+       char *target_config;
+       char *input = config;
 
-       if (!strnlen(config, MAX_PARAM_LENGTH)) {
+       if (!strnlen(input, MAX_PARAM_LENGTH)) {
                printk(KERN_INFO "netconsole: not configured, aborting\n");
                goto out;
        }
 
-       err = netpoll_parse_options(&nt->np, config);
-       if (err)
-               goto out;
-
-       err = netpoll_setup(&nt->np);
-       if (err)
-               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 out;
+               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 = &default_target;
+       struct netconsole_target *nt, *tmp;
 
        unregister_console(&netconsole);
        unregister_netdevice_notifier(&netconsole_netdev_notifier);
-       netpoll_cleanup(&nt->np);
+
+       /*
+        * 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);