+#define NETCONSOLE_TARGET_ATTR_RW(_name) \
+static struct netconsole_target_attr netconsole_target_##_name = \
+ __CONFIGFS_ATTR(_name, S_IRUGO | S_IWUSR, show_##_name, store_##_name)
+
+NETCONSOLE_TARGET_ATTR_RW(enabled);
+NETCONSOLE_TARGET_ATTR_RW(dev_name);
+NETCONSOLE_TARGET_ATTR_RW(local_port);
+NETCONSOLE_TARGET_ATTR_RW(remote_port);
+NETCONSOLE_TARGET_ATTR_RW(local_ip);
+NETCONSOLE_TARGET_ATTR_RW(remote_ip);
+NETCONSOLE_TARGET_ATTR_RO(local_mac);
+NETCONSOLE_TARGET_ATTR_RW(remote_mac);
+
+static struct configfs_attribute *netconsole_target_attrs[] = {
+ &netconsole_target_enabled.attr,
+ &netconsole_target_dev_name.attr,
+ &netconsole_target_local_port.attr,
+ &netconsole_target_remote_port.attr,
+ &netconsole_target_local_ip.attr,
+ &netconsole_target_remote_ip.attr,
+ &netconsole_target_local_mac.attr,
+ &netconsole_target_remote_mac.attr,
+ NULL,
+};
+
+/*
+ * Item operations and type for netconsole_target.
+ */
+
+static void netconsole_target_release(struct config_item *item)
+{
+ kfree(to_target(item));
+}
+
+static ssize_t netconsole_target_attr_show(struct config_item *item,
+ struct configfs_attribute *attr,
+ char *buf)
+{
+ ssize_t ret = -EINVAL;
+ struct netconsole_target *nt = to_target(item);
+ struct netconsole_target_attr *na =
+ container_of(attr, struct netconsole_target_attr, attr);
+
+ if (na->show)
+ ret = na->show(nt, buf);
+
+ return ret;
+}
+
+static ssize_t netconsole_target_attr_store(struct config_item *item,
+ struct configfs_attribute *attr,
+ const char *buf,
+ size_t count)
+{
+ ssize_t ret = -EINVAL;
+ struct netconsole_target *nt = to_target(item);
+ struct netconsole_target_attr *na =
+ container_of(attr, struct netconsole_target_attr, attr);
+
+ if (na->store)
+ ret = na->store(nt, buf, count);
+
+ return ret;
+}
+
+static struct configfs_item_operations netconsole_target_item_ops = {
+ .release = netconsole_target_release,
+ .show_attribute = netconsole_target_attr_show,
+ .store_attribute = netconsole_target_attr_store,
+};
+
+static struct config_item_type netconsole_target_type = {
+ .ct_attrs = netconsole_target_attrs,
+ .ct_item_ops = &netconsole_target_item_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/*
+ * Group operations and type for netconsole_subsys.
+ */
+
+static struct config_item *make_netconsole_target(struct config_group *group,
+ const char *name)
+{
+ unsigned long flags;
+ struct netconsole_target *nt;
+
+ /*
+ * Allocate and initialize with defaults.
+ * Target is disabled at creation (enabled == 0).
+ */
+ nt = kzalloc(sizeof(*nt), GFP_KERNEL);
+ if (!nt) {
+ printk(KERN_ERR "netconsole: failed to allocate memory\n");
+ return NULL;
+ }
+
+ 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);
+
+ /* Initialize the config_item member */
+ config_item_init_type_name(&nt->item, name, &netconsole_target_type);
+
+ /* Adding, but it is disabled */
+ spin_lock_irqsave(&target_list_lock, flags);
+ list_add(&nt->list, &target_list);
+ spin_unlock_irqrestore(&target_list_lock, flags);
+
+ return &nt->item;
+}
+
+static void drop_netconsole_target(struct config_group *group,
+ struct config_item *item)
+{
+ unsigned long flags;
+ struct netconsole_target *nt = to_target(item);
+
+ spin_lock_irqsave(&target_list_lock, flags);
+ list_del(&nt->list);
+ spin_unlock_irqrestore(&target_list_lock, flags);
+
+ /*
+ * The target may have never been enabled, or was manually disabled
+ * before being removed so netpoll may have already been cleaned up.
+ */
+ if (nt->enabled)
+ netpoll_cleanup(&nt->np);
+
+ config_item_put(&nt->item);
+}
+
+static struct configfs_group_operations netconsole_subsys_group_ops = {
+ .make_item = make_netconsole_target,
+ .drop_item = drop_netconsole_target,
+};
+
+static struct config_item_type netconsole_subsys_type = {
+ .ct_group_ops = &netconsole_subsys_group_ops,
+ .ct_owner = THIS_MODULE,
+};
+
+/* The netconsole configfs subsystem */
+static struct configfs_subsystem netconsole_subsys = {
+ .su_group = {
+ .cg_item = {
+ .ci_namebuf = "netconsole",
+ .ci_type = &netconsole_subsys_type,
+ },
+ },
+};
+
+#endif /* CONFIG_NETCONSOLE_DYNAMIC */
+
+/* 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) {
+ netconsole_target_get(nt);
+ 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;
+ }
+ }
+ netconsole_target_put(nt);
+ }
+ 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) {
+ netconsole_target_get(nt);
+ if (nt->enabled && 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;
+ }
+ }
+ netconsole_target_put(nt);
+ }
+ 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;
+ struct netconsole_target *nt, *tmp;
+ unsigned long flags;
+ char *target_config;
+ char *input = config;
+
+ if (strnlen(input, MAX_PARAM_LENGTH)) {
+ while ((target_config = strsep(&input, ";"))) {
+ nt = alloc_param_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;
+
+ err = dynamic_netconsole_init();