diff options
Diffstat (limited to 'drivers/net/netconsole.c')
| -rw-r--r-- | drivers/net/netconsole.c | 1519 |
1 files changed, 1289 insertions, 230 deletions
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 4f4f79532c6c..9cb4dfc242f5 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -36,22 +36,33 @@ #include <linux/inet.h> #include <linux/configfs.h> #include <linux/etherdevice.h> +#include <linux/u64_stats_sync.h> +#include <linux/utsname.h> +#include <linux/rtnetlink.h> -MODULE_AUTHOR("Maintainer: Matt Mackall <mpm@selenic.com>"); +MODULE_AUTHOR("Matt Mackall <mpm@selenic.com>"); MODULE_DESCRIPTION("Console driver for network interfaces"); MODULE_LICENSE("GPL"); -#define MAX_PARAM_LENGTH 256 -#define MAX_PRINT_CHUNK 1000 +#define MAX_PARAM_LENGTH 256 +#define MAX_EXTRADATA_ENTRY_LEN 256 +#define MAX_EXTRADATA_VALUE_LEN 200 +/* The number 3 comes from userdata entry format characters (' ', '=', '\n') */ +#define MAX_EXTRADATA_NAME_LEN (MAX_EXTRADATA_ENTRY_LEN - \ + MAX_EXTRADATA_VALUE_LEN - 3) +#define MAX_USERDATA_ITEMS 256 +#define MAX_PRINT_CHUNK 1000 static char config[MAX_PARAM_LENGTH]; module_param_string(netconsole, config, MAX_PARAM_LENGTH, 0); MODULE_PARM_DESC(netconsole, " netconsole=[src-port]@[src-ip]/[dev],[tgt-port]@<tgt-ip>/[tgt-macaddr]"); -static bool oops_only = false; +static bool oops_only; module_param(oops_only, bool, 0600); MODULE_PARM_DESC(oops_only, "Only log oops messages"); +#define NETCONSOLE_PARAM_TARGET_PREFIX "cmdline" + #ifndef MODULE static int __init option_setup(char *opt) { @@ -63,20 +74,62 @@ __setup("netconsole=", option_setup); /* Linked list of all configured targets */ static LIST_HEAD(target_list); +/* target_cleanup_list is used to track targets that need to be cleaned outside + * of target_list_lock. It should be cleaned in the same function it is + * populated. + */ +static LIST_HEAD(target_cleanup_list); /* This needs to be a spinlock because write_msg() cannot sleep */ static DEFINE_SPINLOCK(target_list_lock); +/* This needs to be a mutex because netpoll_cleanup might sleep */ +static DEFINE_MUTEX(target_cleanup_list_lock); /* - * Console driver for extended netconsoles. Registered on the first use to - * avoid unnecessarily enabling ext message formatting. + * Console driver for netconsoles. Register only consoles that have + * an associated target of the same type. + */ +static struct console netconsole_ext, netconsole; + +struct netconsole_target_stats { + u64_stats_t xmit_drop_count; + u64_stats_t enomem_count; + struct u64_stats_sync syncp; +}; + +enum console_type { + CONS_BASIC = BIT(0), + CONS_EXTENDED = BIT(1), +}; + +/* Features enabled in sysdata. Contrary to userdata, this data is populated by + * the kernel. The fields are designed as bitwise flags, allowing multiple + * features to be set in sysdata_fields. */ -static struct console netconsole_ext; +enum sysdata_feature { + /* Populate the CPU that sends the message */ + SYSDATA_CPU_NR = BIT(0), + /* Populate the task name (as in current->comm) in sysdata */ + SYSDATA_TASKNAME = BIT(1), + /* Kernel release/version as part of sysdata */ + SYSDATA_RELEASE = BIT(2), + /* Include a per-target message ID as part of sysdata */ + SYSDATA_MSGID = BIT(3), + /* Sentinel: highest bit position */ + MAX_SYSDATA_ITEMS = 4, +}; /** * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list. - * @item: Links us into the configfs subsystem hierarchy. + * @group: Links us into the configfs subsystem hierarchy. + * @userdata_group: Links to the userdata configfs hierarchy + * @userdata: Cached, formatted string of append + * @userdata_length: String length of userdata. + * @sysdata: Cached, formatted string of append + * @sysdata_fields: Sysdata features enabled. + * @msgcounter: Message sent counter. + * @stats: Packet send stats for the target. Used for debugging. * @enabled: On / off knob to enable / disable target. * Visible from userspace (read-write). * We maintain a strict 1:1 correspondence between this and @@ -84,6 +137,8 @@ static struct console netconsole_ext; * Also, other parameters of a target may be modified at * runtime only when it is disabled (enabled == 0). * @extended: Denotes whether console is extended or not. + * @release: Denotes whether kernel release version should be prepended + * to the message. Depends on extended console. * @np: The netpoll structure for this target. * Contains the other userspace visible parameters: * dev_name (read-write) @@ -93,15 +148,29 @@ static struct console netconsole_ext; * remote_ip (read-write) * local_mac (read-only) * remote_mac (read-write) + * @buf: The buffer used to send the full msg to the network stack */ struct netconsole_target { struct list_head list; #ifdef CONFIG_NETCONSOLE_DYNAMIC - struct config_item item; + struct config_group group; + struct config_group userdata_group; + char *userdata; + size_t userdata_length; + char sysdata[MAX_EXTRADATA_ENTRY_LEN * MAX_SYSDATA_ITEMS]; + + /* bit-wise with sysdata_feature bits */ + u32 sysdata_fields; + /* protected by target_list_lock */ + u32 msgcounter; #endif + struct netconsole_target_stats stats; bool enabled; bool extended; + bool release; struct netpoll np; + /* protected by target_list_lock */ + char buf[MAX_PRINT_CHUNK]; }; #ifdef CONFIG_NETCONSOLE_DYNAMIC @@ -128,14 +197,14 @@ static void __exit dynamic_netconsole_exit(void) */ static void netconsole_target_get(struct netconsole_target *nt) { - if (config_item_name(&nt->item)) - config_item_get(&nt->item); + if (config_item_name(&nt->group.cg_item)) + config_group_get(&nt->group); } static void netconsole_target_put(struct netconsole_target *nt) { - if (config_item_name(&nt->item)) - config_item_put(&nt->item); + if (config_item_name(&nt->group.cg_item)) + config_group_put(&nt->group); } #else /* !CONFIG_NETCONSOLE_DYNAMIC */ @@ -161,21 +230,27 @@ static void netconsole_target_put(struct netconsole_target *nt) { } +static void populate_configfs_item(struct netconsole_target *nt, + int cmdline_count) +{ +} #endif /* CONFIG_NETCONSOLE_DYNAMIC */ -/* Allocate new target (from boot/module param) and setup netpoll for it */ -static struct netconsole_target *alloc_param_target(char *target_config) +/* Allocate and initialize with defaults. + * Note that these targets get their config_item fields zeroed-out. + */ +static struct netconsole_target *alloc_and_init(void) { - int err = -ENOMEM; struct netconsole_target *nt; - /* - * Allocate and initialize with defaults. - * Note that these targets get their config_item fields zeroed-out. - */ nt = kzalloc(sizeof(*nt), GFP_KERNEL); if (!nt) - goto fail; + return nt; + + if (IS_ENABLED(CONFIG_NETCONSOLE_EXTENDED_LOG)) + nt->extended = true; + if (IS_ENABLED(CONFIG_NETCONSOLE_PREPEND_RELEASE)) + nt->release = true; nt->np.name = "netconsole"; strscpy(nt->np.dev_name, "eth0", IFNAMSIZ); @@ -183,34 +258,78 @@ static struct netconsole_target *alloc_param_target(char *target_config) nt->np.remote_port = 6666; eth_broadcast_addr(nt->np.remote_mac); - if (*target_config == '+') { - nt->extended = true; - target_config++; - } - - /* Parse parameters and setup netpoll */ - err = netpoll_parse_options(&nt->np, target_config); - if (err) - goto fail; + return nt; +} - err = netpoll_setup(&nt->np); - if (err) - goto fail; +/* Clean up every target in the cleanup_list and move the clean targets back to + * the main target_list. + */ +static void netconsole_process_cleanups_core(void) +{ + struct netconsole_target *nt, *tmp; + unsigned long flags; - nt->enabled = true; + /* The cleanup needs RTNL locked */ + ASSERT_RTNL(); - return nt; + mutex_lock(&target_cleanup_list_lock); + list_for_each_entry_safe(nt, tmp, &target_cleanup_list, list) { + /* all entries in the cleanup_list needs to be disabled */ + WARN_ON_ONCE(nt->enabled); + do_netpoll_cleanup(&nt->np); + /* moved the cleaned target to target_list. Need to hold both + * locks + */ + spin_lock_irqsave(&target_list_lock, flags); + list_move(&nt->list, &target_list); + spin_unlock_irqrestore(&target_list_lock, flags); + } + WARN_ON_ONCE(!list_empty(&target_cleanup_list)); + mutex_unlock(&target_cleanup_list_lock); +} -fail: - kfree(nt); - return ERR_PTR(err); +static void netconsole_print_banner(struct netpoll *np) +{ + np_info(np, "local port %d\n", np->local_port); + if (np->ipv6) + np_info(np, "local IPv6 address %pI6c\n", &np->local_ip.in6); + else + np_info(np, "local IPv4 address %pI4\n", &np->local_ip.ip); + np_info(np, "interface name '%s'\n", np->dev_name); + np_info(np, "local ethernet address '%pM'\n", np->dev_mac); + np_info(np, "remote port %d\n", np->remote_port); + if (np->ipv6) + np_info(np, "remote IPv6 address %pI6c\n", &np->remote_ip.in6); + else + np_info(np, "remote IPv4 address %pI4\n", &np->remote_ip.ip); + np_info(np, "remote ethernet address %pM\n", np->remote_mac); } -/* Cleanup netpoll for given target (from boot/module param) and free it */ -static void free_param_target(struct netconsole_target *nt) +/* Parse the string and populate the `inet_addr` union. Return 0 if IPv4 is + * populated, 1 if IPv6 is populated, and -1 upon failure. + */ +static int netpoll_parse_ip_addr(const char *str, union inet_addr *addr) { - netpoll_cleanup(&nt->np); - kfree(nt); + const char *end = NULL; + int len; + + len = strlen(str); + if (!len) + return -1; + + if (str[len - 1] == '\n') + len -= 1; + + if (in4_pton(str, len, (void *)addr, -1, &end) > 0 && + (!end || *end == 0 || *end == '\n')) + return 0; + + if (IS_ENABLED(CONFIG_IPV6) && + in6_pton(str, len, (void *)addr, -1, &end) > 0 && + (!end || *end == 0 || *end == '\n')) + return 1; + + return -1; } #ifdef CONFIG_NETCONSOLE_DYNAMIC @@ -222,6 +341,7 @@ static void free_param_target(struct netconsole_target *nt) * | * <target>/ * | enabled + * | release * | dev_name * | local_port * | remote_port @@ -229,15 +349,47 @@ static void free_param_target(struct netconsole_target *nt) * | remote_ip * | local_mac * | remote_mac + * | transmit_errors + * | userdata/ + * | <key>/ + * | value + * | ... * | * <target>/... */ static struct netconsole_target *to_target(struct config_item *item) { - return item ? - container_of(item, struct netconsole_target, item) : - NULL; + struct config_group *cfg_group; + + cfg_group = to_config_group(item); + if (!cfg_group) + return NULL; + return container_of(to_config_group(item), + struct netconsole_target, group); +} + +/* Do the list cleanup with the rtnl lock hold. rtnl lock is necessary because + * netdev might be cleaned-up by calling __netpoll_cleanup(), + */ +static void netconsole_process_cleanups(void) +{ + /* rtnl lock is called here, because it has precedence over + * target_cleanup_list_lock mutex and target_cleanup_list + */ + rtnl_lock(); + netconsole_process_cleanups_core(); + rtnl_unlock(); +} + +/* Get rid of possible trailing newline, returning the new length */ +static void trim_newline(char *s, size_t maxlen) +{ + size_t len; + + len = strnlen(s, maxlen); + if (s[len - 1] == '\n') + s[len - 1] = '\0'; } /* @@ -246,27 +398,32 @@ static struct netconsole_target *to_target(struct config_item *item) static ssize_t enabled_show(struct config_item *item, char *buf) { - return snprintf(buf, PAGE_SIZE, "%d\n", to_target(item)->enabled); + return sysfs_emit(buf, "%d\n", to_target(item)->enabled); } static ssize_t extended_show(struct config_item *item, char *buf) { - return snprintf(buf, PAGE_SIZE, "%d\n", to_target(item)->extended); + return sysfs_emit(buf, "%d\n", to_target(item)->extended); +} + +static ssize_t release_show(struct config_item *item, char *buf) +{ + return sysfs_emit(buf, "%d\n", to_target(item)->release); } static ssize_t dev_name_show(struct config_item *item, char *buf) { - return snprintf(buf, PAGE_SIZE, "%s\n", to_target(item)->np.dev_name); + return sysfs_emit(buf, "%s\n", to_target(item)->np.dev_name); } static ssize_t local_port_show(struct config_item *item, char *buf) { - return snprintf(buf, PAGE_SIZE, "%d\n", to_target(item)->np.local_port); + return sysfs_emit(buf, "%d\n", to_target(item)->np.local_port); } static ssize_t remote_port_show(struct config_item *item, char *buf) { - return snprintf(buf, PAGE_SIZE, "%d\n", to_target(item)->np.remote_port); + return sysfs_emit(buf, "%d\n", to_target(item)->np.remote_port); } static ssize_t local_ip_show(struct config_item *item, char *buf) @@ -274,9 +431,9 @@ static ssize_t local_ip_show(struct config_item *item, char *buf) struct netconsole_target *nt = to_target(item); if (nt->np.ipv6) - return snprintf(buf, PAGE_SIZE, "%pI6c\n", &nt->np.local_ip.in6); + return sysfs_emit(buf, "%pI6c\n", &nt->np.local_ip.in6); else - return snprintf(buf, PAGE_SIZE, "%pI4\n", &nt->np.local_ip); + return sysfs_emit(buf, "%pI4\n", &nt->np.local_ip); } static ssize_t remote_ip_show(struct config_item *item, char *buf) @@ -284,9 +441,9 @@ static ssize_t remote_ip_show(struct config_item *item, char *buf) struct netconsole_target *nt = to_target(item); if (nt->np.ipv6) - return snprintf(buf, PAGE_SIZE, "%pI6c\n", &nt->np.remote_ip.in6); + return sysfs_emit(buf, "%pI6c\n", &nt->np.remote_ip.in6); else - return snprintf(buf, PAGE_SIZE, "%pI4\n", &nt->np.remote_ip); + return sysfs_emit(buf, "%pI4\n", &nt->np.remote_ip); } static ssize_t local_mac_show(struct config_item *item, char *buf) @@ -294,12 +451,107 @@ static ssize_t local_mac_show(struct config_item *item, char *buf) struct net_device *dev = to_target(item)->np.dev; static const u8 bcast[ETH_ALEN] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; - return snprintf(buf, PAGE_SIZE, "%pM\n", dev ? dev->dev_addr : bcast); + return sysfs_emit(buf, "%pM\n", dev ? dev->dev_addr : bcast); } static ssize_t remote_mac_show(struct config_item *item, char *buf) { - return snprintf(buf, PAGE_SIZE, "%pM\n", to_target(item)->np.remote_mac); + return sysfs_emit(buf, "%pM\n", to_target(item)->np.remote_mac); +} + +static ssize_t transmit_errors_show(struct config_item *item, char *buf) +{ + struct netconsole_target *nt = to_target(item); + u64 xmit_drop_count, enomem_count; + unsigned int start; + + do { + start = u64_stats_fetch_begin(&nt->stats.syncp); + xmit_drop_count = u64_stats_read(&nt->stats.xmit_drop_count); + enomem_count = u64_stats_read(&nt->stats.enomem_count); + } while (u64_stats_fetch_retry(&nt->stats.syncp, start)); + + return sysfs_emit(buf, "%llu\n", xmit_drop_count + enomem_count); +} + +/* configfs helper to display if cpu_nr sysdata feature is enabled */ +static ssize_t sysdata_cpu_nr_enabled_show(struct config_item *item, char *buf) +{ + struct netconsole_target *nt = to_target(item->ci_parent); + bool cpu_nr_enabled; + + mutex_lock(&dynamic_netconsole_mutex); + cpu_nr_enabled = !!(nt->sysdata_fields & SYSDATA_CPU_NR); + mutex_unlock(&dynamic_netconsole_mutex); + + return sysfs_emit(buf, "%d\n", cpu_nr_enabled); +} + +/* configfs helper to display if taskname sysdata feature is enabled */ +static ssize_t sysdata_taskname_enabled_show(struct config_item *item, + char *buf) +{ + struct netconsole_target *nt = to_target(item->ci_parent); + bool taskname_enabled; + + mutex_lock(&dynamic_netconsole_mutex); + taskname_enabled = !!(nt->sysdata_fields & SYSDATA_TASKNAME); + mutex_unlock(&dynamic_netconsole_mutex); + + return sysfs_emit(buf, "%d\n", taskname_enabled); +} + +static ssize_t sysdata_release_enabled_show(struct config_item *item, + char *buf) +{ + struct netconsole_target *nt = to_target(item->ci_parent); + bool release_enabled; + + mutex_lock(&dynamic_netconsole_mutex); + release_enabled = !!(nt->sysdata_fields & SYSDATA_TASKNAME); + mutex_unlock(&dynamic_netconsole_mutex); + + return sysfs_emit(buf, "%d\n", release_enabled); +} + +/* Iterate in the list of target, and make sure we don't have any console + * register without targets of the same type + */ +static void unregister_netcons_consoles(void) +{ + struct netconsole_target *nt; + u32 console_type_needed = 0; + unsigned long flags; + + spin_lock_irqsave(&target_list_lock, flags); + list_for_each_entry(nt, &target_list, list) { + if (nt->extended) + console_type_needed |= CONS_EXTENDED; + else + console_type_needed |= CONS_BASIC; + } + spin_unlock_irqrestore(&target_list_lock, flags); + + if (!(console_type_needed & CONS_EXTENDED) && + console_is_registered(&netconsole_ext)) + unregister_console(&netconsole_ext); + + if (!(console_type_needed & CONS_BASIC) && + console_is_registered(&netconsole)) + unregister_console(&netconsole); +} + +static ssize_t sysdata_msgid_enabled_show(struct config_item *item, + char *buf) +{ + struct netconsole_target *nt = to_target(item->ci_parent); + bool msgid_enabled; + + mutex_lock(&dynamic_netconsole_mutex); + msgid_enabled = !!(nt->sysdata_fields & SYSDATA_MSGID); + mutex_unlock(&dynamic_netconsole_mutex); + + return sysfs_emit(buf, "%d\n", msgid_enabled); } /* @@ -314,110 +566,148 @@ static ssize_t enabled_store(struct config_item *item, { struct netconsole_target *nt = to_target(item); unsigned long flags; - int enabled; - int err; + bool enabled; + ssize_t ret; mutex_lock(&dynamic_netconsole_mutex); - err = kstrtoint(buf, 10, &enabled); - if (err < 0) + ret = kstrtobool(buf, &enabled); + if (ret) goto out_unlock; - err = -EINVAL; - if (enabled < 0 || enabled > 1) - goto out_unlock; - if ((bool)enabled == nt->enabled) { + ret = -EINVAL; + if (enabled == nt->enabled) { pr_info("network logging has already %s\n", nt->enabled ? "started" : "stopped"); goto out_unlock; } if (enabled) { /* true */ - if (nt->extended && !console_is_registered(&netconsole_ext)) + if (nt->release && !nt->extended) { + pr_err("Not enabling netconsole. Release feature requires extended log message"); + goto out_unlock; + } + + if (nt->extended && !console_is_registered(&netconsole_ext)) { + netconsole_ext.flags |= CON_ENABLED; register_console(&netconsole_ext); + } + + /* User might be enabling the basic format target for the very + * first time, make sure the console is registered. + */ + if (!nt->extended && !console_is_registered(&netconsole)) { + netconsole.flags |= CON_ENABLED; + register_console(&netconsole); + } /* - * Skip netpoll_parse_options() -- all the attributes are + * Skip netconsole_parser_cmdline() -- all the attributes are * already configured via configfs. Just print them out. */ - netpoll_print_options(&nt->np); + netconsole_print_banner(&nt->np); - err = netpoll_setup(&nt->np); - if (err) + ret = netpoll_setup(&nt->np); + if (ret) goto out_unlock; + nt->enabled = true; pr_info("network logging started\n"); } else { /* false */ /* We need to disable the netconsole before cleaning it up * otherwise we might end up in write_msg() with * nt->np.dev == NULL and nt->enabled == true */ + mutex_lock(&target_cleanup_list_lock); spin_lock_irqsave(&target_list_lock, flags); nt->enabled = false; + /* Remove the target from the list, while holding + * target_list_lock + */ + list_move(&nt->list, &target_cleanup_list); spin_unlock_irqrestore(&target_list_lock, flags); - netpoll_cleanup(&nt->np); + mutex_unlock(&target_cleanup_list_lock); + /* Unregister consoles, whose the last target of that type got + * disabled. + */ + unregister_netcons_consoles(); } - nt->enabled = enabled; - + ret = strnlen(buf, count); + /* Deferred cleanup */ + netconsole_process_cleanups(); +out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + return ret; +} + +static ssize_t release_store(struct config_item *item, const char *buf, + size_t count) +{ + struct netconsole_target *nt = to_target(item); + bool release; + ssize_t ret; + + mutex_lock(&dynamic_netconsole_mutex); + if (nt->enabled) { + pr_err("target (%s) is enabled, disable to update parameters\n", + config_item_name(&nt->group.cg_item)); + ret = -EINVAL; + goto out_unlock; + } + + ret = kstrtobool(buf, &release); + if (ret) + goto out_unlock; + + nt->release = release; + + ret = strnlen(buf, count); out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return err; + return ret; } static ssize_t extended_store(struct config_item *item, const char *buf, size_t count) { struct netconsole_target *nt = to_target(item); - int extended; - int err; + bool extended; + ssize_t ret; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { pr_err("target (%s) is enabled, disable to update parameters\n", - config_item_name(&nt->item)); - err = -EINVAL; + config_item_name(&nt->group.cg_item)); + ret = -EINVAL; goto out_unlock; } - err = kstrtoint(buf, 10, &extended); - if (err < 0) + ret = kstrtobool(buf, &extended); + if (ret) goto out_unlock; - if (extended < 0 || extended > 1) { - err = -EINVAL; - goto out_unlock; - } nt->extended = extended; - - mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + ret = strnlen(buf, count); out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return err; + return ret; } static ssize_t dev_name_store(struct config_item *item, const char *buf, size_t count) { struct netconsole_target *nt = to_target(item); - size_t len; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { pr_err("target (%s) is enabled, disable to update parameters\n", - config_item_name(&nt->item)); + config_item_name(&nt->group.cg_item)); mutex_unlock(&dynamic_netconsole_mutex); return -EINVAL; } strscpy(nt->np.dev_name, buf, IFNAMSIZ); - - /* Get rid of possible trailing newline from echo(1) */ - len = strnlen(nt->np.dev_name, IFNAMSIZ); - if (nt->np.dev_name[len - 1] == '\n') - nt->np.dev_name[len - 1] = '\0'; + trim_newline(nt->np.dev_name, IFNAMSIZ); mutex_unlock(&dynamic_netconsole_mutex); return strnlen(buf, count); @@ -427,118 +717,104 @@ static ssize_t local_port_store(struct config_item *item, const char *buf, size_t count) { struct netconsole_target *nt = to_target(item); - int rv = -EINVAL; + ssize_t ret = -EINVAL; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { pr_err("target (%s) is enabled, disable to update parameters\n", - config_item_name(&nt->item)); + config_item_name(&nt->group.cg_item)); goto out_unlock; } - rv = kstrtou16(buf, 10, &nt->np.local_port); - if (rv < 0) + ret = kstrtou16(buf, 10, &nt->np.local_port); + if (ret < 0) goto out_unlock; - mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + ret = strnlen(buf, count); out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return rv; + return ret; } static ssize_t remote_port_store(struct config_item *item, const char *buf, size_t count) { struct netconsole_target *nt = to_target(item); - int rv = -EINVAL; + ssize_t ret = -EINVAL; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { pr_err("target (%s) is enabled, disable to update parameters\n", - config_item_name(&nt->item)); + config_item_name(&nt->group.cg_item)); goto out_unlock; } - rv = kstrtou16(buf, 10, &nt->np.remote_port); - if (rv < 0) + ret = kstrtou16(buf, 10, &nt->np.remote_port); + if (ret < 0) goto out_unlock; - mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + ret = strnlen(buf, count); out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return rv; + return ret; } static ssize_t local_ip_store(struct config_item *item, const char *buf, size_t count) { struct netconsole_target *nt = to_target(item); + ssize_t ret = -EINVAL; + int ipv6; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { pr_err("target (%s) is enabled, disable to update parameters\n", - config_item_name(&nt->item)); + config_item_name(&nt->group.cg_item)); goto out_unlock; } - if (strnchr(buf, count, ':')) { - const char *end; - if (in6_pton(buf, count, nt->np.local_ip.in6.s6_addr, -1, &end) > 0) { - if (*end && *end != '\n') { - pr_err("invalid IPv6 address at: <%c>\n", *end); - goto out_unlock; - } - nt->np.ipv6 = true; - } else - goto out_unlock; - } else { - if (!nt->np.ipv6) { - nt->np.local_ip.ip = in_aton(buf); - } else - goto out_unlock; - } + ipv6 = netpoll_parse_ip_addr(buf, &nt->np.local_ip); + if (ipv6 == -1) + goto out_unlock; + nt->np.ipv6 = !!ipv6; - mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + ret = strnlen(buf, count); out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return -EINVAL; + return ret; } static ssize_t remote_ip_store(struct config_item *item, const char *buf, size_t count) { struct netconsole_target *nt = to_target(item); + ssize_t ret = -EINVAL; + int ipv6; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { pr_err("target (%s) is enabled, disable to update parameters\n", - config_item_name(&nt->item)); + config_item_name(&nt->group.cg_item)); goto out_unlock; } - if (strnchr(buf, count, ':')) { - const char *end; - if (in6_pton(buf, count, nt->np.remote_ip.in6.s6_addr, -1, &end) > 0) { - if (*end && *end != '\n') { - pr_err("invalid IPv6 address at: <%c>\n", *end); - goto out_unlock; - } - nt->np.ipv6 = true; - } else - goto out_unlock; - } else { - if (!nt->np.ipv6) { - nt->np.remote_ip.ip = in_aton(buf); - } else - goto out_unlock; - } + ipv6 = netpoll_parse_ip_addr(buf, &nt->np.remote_ip); + if (ipv6 == -1) + goto out_unlock; + nt->np.ipv6 = !!ipv6; - mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + ret = strnlen(buf, count); out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return -EINVAL; + return ret; +} + +/* Count number of entries we have in userdata. + * This is important because userdata only supports MAX_USERDATA_ITEMS + * entries. Before enabling any new userdata feature, number of entries needs + * to checked for available space. + */ +static size_t count_userdata_entries(struct netconsole_target *nt) +{ + return list_count_nodes(&nt->userdata_group.cg_children); } static ssize_t remote_mac_store(struct config_item *item, const char *buf, @@ -546,27 +822,378 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf, { struct netconsole_target *nt = to_target(item); u8 remote_mac[ETH_ALEN]; + ssize_t ret = -EINVAL; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { pr_err("target (%s) is enabled, disable to update parameters\n", - config_item_name(&nt->item)); + config_item_name(&nt->group.cg_item)); goto out_unlock; } if (!mac_pton(buf, remote_mac)) goto out_unlock; - if (buf[3 * ETH_ALEN - 1] && buf[3 * ETH_ALEN - 1] != '\n') + if (buf[MAC_ADDR_STR_LEN] && buf[MAC_ADDR_STR_LEN] != '\n') goto out_unlock; memcpy(nt->np.remote_mac, remote_mac, ETH_ALEN); + ret = strnlen(buf, count); +out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + return ret; +} + +struct userdatum { + struct config_item item; + char value[MAX_EXTRADATA_VALUE_LEN]; +}; + +static struct userdatum *to_userdatum(struct config_item *item) +{ + return container_of(item, struct userdatum, item); +} + +struct userdata { + struct config_group group; +}; + +static struct userdata *to_userdata(struct config_item *item) +{ + return container_of(to_config_group(item), struct userdata, group); +} + +static struct netconsole_target *userdata_to_target(struct userdata *ud) +{ + struct config_group *netconsole_group; + + netconsole_group = to_config_group(ud->group.cg_item.ci_parent); + return to_target(&netconsole_group->cg_item); +} + +static ssize_t userdatum_value_show(struct config_item *item, char *buf) +{ + return sysfs_emit(buf, "%s\n", &(to_userdatum(item)->value[0])); +} + +/* Navigate configfs and calculate the lentgh of the formatted string + * representing userdata. + * Must be called holding netconsole_subsys.su_mutex + */ +static int calc_userdata_len(struct netconsole_target *nt) +{ + struct userdatum *udm_item; + struct config_item *item; + struct list_head *entry; + int len = 0; + + list_for_each(entry, &nt->userdata_group.cg_children) { + item = container_of(entry, struct config_item, ci_entry); + udm_item = to_userdatum(item); + /* Skip userdata with no value set */ + if (udm_item->value[0]) { + len += snprintf(NULL, 0, " %s=%s\n", item->ci_name, + udm_item->value); + } + } + return len; +} + +static int update_userdata(struct netconsole_target *nt) +{ + struct userdatum *udm_item; + struct config_item *item; + struct list_head *entry; + char *old_buf = NULL; + char *new_buf = NULL; + unsigned long flags; + int offset = 0; + int len; + + /* Calculate required buffer size */ + len = calc_userdata_len(nt); + + if (WARN_ON_ONCE(len > MAX_EXTRADATA_ENTRY_LEN * MAX_USERDATA_ITEMS)) + return -ENOSPC; + + /* Allocate new buffer */ + if (len) { + new_buf = kmalloc(len + 1, GFP_KERNEL); + if (!new_buf) + return -ENOMEM; + } + + /* Write userdata to new buffer */ + list_for_each(entry, &nt->userdata_group.cg_children) { + item = container_of(entry, struct config_item, ci_entry); + udm_item = to_userdatum(item); + /* Skip userdata with no value set */ + if (udm_item->value[0]) { + offset += scnprintf(&new_buf[offset], len + 1 - offset, + " %s=%s\n", item->ci_name, + udm_item->value); + } + } + + WARN_ON_ONCE(offset != len); + + /* Switch to new buffer and free old buffer */ + spin_lock_irqsave(&target_list_lock, flags); + old_buf = nt->userdata; + nt->userdata = new_buf; + nt->userdata_length = offset; + spin_unlock_irqrestore(&target_list_lock, flags); + + kfree(old_buf); + + return 0; +} + +static ssize_t userdatum_value_store(struct config_item *item, const char *buf, + size_t count) +{ + struct userdatum *udm = to_userdatum(item); + struct netconsole_target *nt; + struct userdata *ud; + ssize_t ret; + + if (count > MAX_EXTRADATA_VALUE_LEN) + return -EMSGSIZE; + + mutex_lock(&netconsole_subsys.su_mutex); + mutex_lock(&dynamic_netconsole_mutex); + + ret = strscpy(udm->value, buf, sizeof(udm->value)); + if (ret < 0) + goto out_unlock; + trim_newline(udm->value, sizeof(udm->value)); + + ud = to_userdata(item->ci_parent); + nt = userdata_to_target(ud); + ret = update_userdata(nt); + if (ret < 0) + goto out_unlock; + ret = count; out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return -EINVAL; + mutex_unlock(&netconsole_subsys.su_mutex); + return ret; } +/* disable_sysdata_feature - Disable sysdata feature and clean sysdata + * @nt: target that is disabling the feature + * @feature: feature being disabled + */ +static void disable_sysdata_feature(struct netconsole_target *nt, + enum sysdata_feature feature) +{ + nt->sysdata_fields &= ~feature; + nt->sysdata[0] = 0; +} + +static ssize_t sysdata_msgid_enabled_store(struct config_item *item, + const char *buf, size_t count) +{ + struct netconsole_target *nt = to_target(item->ci_parent); + bool msgid_enabled, curr; + ssize_t ret; + + ret = kstrtobool(buf, &msgid_enabled); + if (ret) + return ret; + + mutex_lock(&netconsole_subsys.su_mutex); + mutex_lock(&dynamic_netconsole_mutex); + curr = !!(nt->sysdata_fields & SYSDATA_MSGID); + if (msgid_enabled == curr) + goto unlock_ok; + + if (msgid_enabled) + nt->sysdata_fields |= SYSDATA_MSGID; + else + disable_sysdata_feature(nt, SYSDATA_MSGID); + +unlock_ok: + ret = strnlen(buf, count); + mutex_unlock(&dynamic_netconsole_mutex); + mutex_unlock(&netconsole_subsys.su_mutex); + return ret; +} + +static ssize_t sysdata_release_enabled_store(struct config_item *item, + const char *buf, size_t count) +{ + struct netconsole_target *nt = to_target(item->ci_parent); + bool release_enabled, curr; + ssize_t ret; + + ret = kstrtobool(buf, &release_enabled); + if (ret) + return ret; + + mutex_lock(&netconsole_subsys.su_mutex); + mutex_lock(&dynamic_netconsole_mutex); + curr = !!(nt->sysdata_fields & SYSDATA_RELEASE); + if (release_enabled == curr) + goto unlock_ok; + + if (release_enabled) + nt->sysdata_fields |= SYSDATA_RELEASE; + else + disable_sysdata_feature(nt, SYSDATA_RELEASE); + +unlock_ok: + ret = strnlen(buf, count); + mutex_unlock(&dynamic_netconsole_mutex); + mutex_unlock(&netconsole_subsys.su_mutex); + return ret; +} + +static ssize_t sysdata_taskname_enabled_store(struct config_item *item, + const char *buf, size_t count) +{ + struct netconsole_target *nt = to_target(item->ci_parent); + bool taskname_enabled, curr; + ssize_t ret; + + ret = kstrtobool(buf, &taskname_enabled); + if (ret) + return ret; + + mutex_lock(&netconsole_subsys.su_mutex); + mutex_lock(&dynamic_netconsole_mutex); + curr = !!(nt->sysdata_fields & SYSDATA_TASKNAME); + if (taskname_enabled == curr) + goto unlock_ok; + + if (taskname_enabled) + nt->sysdata_fields |= SYSDATA_TASKNAME; + else + disable_sysdata_feature(nt, SYSDATA_TASKNAME); + +unlock_ok: + ret = strnlen(buf, count); + mutex_unlock(&dynamic_netconsole_mutex); + mutex_unlock(&netconsole_subsys.su_mutex); + return ret; +} + +/* configfs helper to sysdata cpu_nr feature */ +static ssize_t sysdata_cpu_nr_enabled_store(struct config_item *item, + const char *buf, size_t count) +{ + struct netconsole_target *nt = to_target(item->ci_parent); + bool cpu_nr_enabled, curr; + ssize_t ret; + + ret = kstrtobool(buf, &cpu_nr_enabled); + if (ret) + return ret; + + mutex_lock(&netconsole_subsys.su_mutex); + mutex_lock(&dynamic_netconsole_mutex); + curr = !!(nt->sysdata_fields & SYSDATA_CPU_NR); + if (cpu_nr_enabled == curr) + /* no change requested */ + goto unlock_ok; + + if (cpu_nr_enabled) + nt->sysdata_fields |= SYSDATA_CPU_NR; + else + /* This is special because sysdata might have remaining data + * from previous sysdata, and it needs to be cleaned. + */ + disable_sysdata_feature(nt, SYSDATA_CPU_NR); + +unlock_ok: + ret = strnlen(buf, count); + mutex_unlock(&dynamic_netconsole_mutex); + mutex_unlock(&netconsole_subsys.su_mutex); + return ret; +} + +CONFIGFS_ATTR(userdatum_, value); +CONFIGFS_ATTR(sysdata_, cpu_nr_enabled); +CONFIGFS_ATTR(sysdata_, taskname_enabled); +CONFIGFS_ATTR(sysdata_, release_enabled); +CONFIGFS_ATTR(sysdata_, msgid_enabled); + +static struct configfs_attribute *userdatum_attrs[] = { + &userdatum_attr_value, + NULL, +}; + +static void userdatum_release(struct config_item *item) +{ + kfree(to_userdatum(item)); +} + +static struct configfs_item_operations userdatum_ops = { + .release = userdatum_release, +}; + +static const struct config_item_type userdatum_type = { + .ct_item_ops = &userdatum_ops, + .ct_attrs = userdatum_attrs, + .ct_owner = THIS_MODULE, +}; + +static struct config_item *userdatum_make_item(struct config_group *group, + const char *name) +{ + struct netconsole_target *nt; + struct userdatum *udm; + struct userdata *ud; + + if (strlen(name) > MAX_EXTRADATA_NAME_LEN) + return ERR_PTR(-ENAMETOOLONG); + + ud = to_userdata(&group->cg_item); + nt = userdata_to_target(ud); + if (count_userdata_entries(nt) >= MAX_USERDATA_ITEMS) + return ERR_PTR(-ENOSPC); + + udm = kzalloc(sizeof(*udm), GFP_KERNEL); + if (!udm) + return ERR_PTR(-ENOMEM); + + config_item_init_type_name(&udm->item, name, &userdatum_type); + return &udm->item; +} + +static void userdatum_drop(struct config_group *group, struct config_item *item) +{ + struct netconsole_target *nt; + struct userdata *ud; + + ud = to_userdata(&group->cg_item); + nt = userdata_to_target(ud); + + mutex_lock(&dynamic_netconsole_mutex); + update_userdata(nt); + config_item_put(item); + mutex_unlock(&dynamic_netconsole_mutex); +} + +static struct configfs_attribute *userdata_attrs[] = { + &sysdata_attr_cpu_nr_enabled, + &sysdata_attr_taskname_enabled, + &sysdata_attr_release_enabled, + &sysdata_attr_msgid_enabled, + NULL, +}; + +static struct configfs_group_operations userdata_ops = { + .make_item = userdatum_make_item, + .drop_item = userdatum_drop, +}; + +static const struct config_item_type userdata_type = { + .ct_item_ops = &userdatum_ops, + .ct_group_ops = &userdata_ops, + .ct_attrs = userdata_attrs, + .ct_owner = THIS_MODULE, +}; + CONFIGFS_ATTR(, enabled); CONFIGFS_ATTR(, extended); CONFIGFS_ATTR(, dev_name); @@ -576,10 +1203,13 @@ CONFIGFS_ATTR(, local_ip); CONFIGFS_ATTR(, remote_ip); CONFIGFS_ATTR_RO(, local_mac); CONFIGFS_ATTR(, remote_mac); +CONFIGFS_ATTR(, release); +CONFIGFS_ATTR_RO(, transmit_errors); static struct configfs_attribute *netconsole_target_attrs[] = { &attr_enabled, &attr_extended, + &attr_release, &attr_dev_name, &attr_local_port, &attr_remote_port, @@ -587,6 +1217,7 @@ static struct configfs_attribute *netconsole_target_attrs[] = { &attr_remote_ip, &attr_local_mac, &attr_remote_mac, + &attr_transmit_errors, NULL, }; @@ -596,7 +1227,10 @@ static struct configfs_attribute *netconsole_target_attrs[] = { static void netconsole_target_release(struct config_item *item) { - kfree(to_target(item)); + struct netconsole_target *nt = to_target(item); + + kfree(nt->userdata); + kfree(nt); } static struct configfs_item_operations netconsole_target_item_ops = { @@ -609,39 +1243,68 @@ static const struct config_item_type netconsole_target_type = { .ct_owner = THIS_MODULE, }; +static void init_target_config_group(struct netconsole_target *nt, + const char *name) +{ + config_group_init_type_name(&nt->group, name, &netconsole_target_type); + config_group_init_type_name(&nt->userdata_group, "userdata", + &userdata_type); + configfs_add_default_group(&nt->userdata_group, &nt->group); +} + +static struct netconsole_target *find_cmdline_target(const char *name) +{ + struct netconsole_target *nt, *ret = NULL; + unsigned long flags; + + spin_lock_irqsave(&target_list_lock, flags); + list_for_each_entry(nt, &target_list, list) { + if (!strcmp(nt->group.cg_item.ci_name, name)) { + ret = nt; + break; + } + } + spin_unlock_irqrestore(&target_list_lock, flags); + + return ret; +} + /* * Group operations and type for netconsole_subsys. */ -static struct config_item *make_netconsole_target(struct config_group *group, - const char *name) +static struct config_group *make_netconsole_target(struct config_group *group, + const char *name) { - unsigned long flags; struct netconsole_target *nt; + unsigned long flags; - /* - * Allocate and initialize with defaults. - * Target is disabled at creation (!enabled). + /* Checking if a target by this name was created at boot time. If so, + * attach a configfs entry to that target. This enables dynamic + * control. */ - nt = kzalloc(sizeof(*nt), GFP_KERNEL); + if (!strncmp(name, NETCONSOLE_PARAM_TARGET_PREFIX, + strlen(NETCONSOLE_PARAM_TARGET_PREFIX))) { + nt = find_cmdline_target(name); + if (nt) { + init_target_config_group(nt, name); + return &nt->group; + } + } + + nt = alloc_and_init(); if (!nt) return ERR_PTR(-ENOMEM); - nt->np.name = "netconsole"; - strscpy(nt->np.dev_name, "eth0", IFNAMSIZ); - nt->np.local_port = 6665; - nt->np.remote_port = 6666; - eth_broadcast_addr(nt->np.remote_mac); - - /* Initialize the config_item member */ - config_item_init_type_name(&nt->item, name, &netconsole_target_type); + /* Initialize the config_group member */ + init_target_config_group(nt, name); /* 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; + return &nt->group; } static void drop_netconsole_target(struct config_group *group, @@ -661,11 +1324,11 @@ static void drop_netconsole_target(struct config_group *group, if (nt->enabled) netpoll_cleanup(&nt->np); - config_item_put(&nt->item); + config_item_put(&nt->group.cg_item); } static struct configfs_group_operations netconsole_subsys_group_ops = { - .make_item = make_netconsole_target, + .make_group = make_netconsole_target, .drop_item = drop_netconsole_target, }; @@ -684,6 +1347,71 @@ static struct configfs_subsystem netconsole_subsys = { }, }; +static void populate_configfs_item(struct netconsole_target *nt, + int cmdline_count) +{ + char target_name[16]; + + snprintf(target_name, sizeof(target_name), "%s%d", + NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count); + init_target_config_group(nt, target_name); +} + +static int sysdata_append_cpu_nr(struct netconsole_target *nt, int offset) +{ + return scnprintf(&nt->sysdata[offset], + MAX_EXTRADATA_ENTRY_LEN, " cpu=%u\n", + raw_smp_processor_id()); +} + +static int sysdata_append_taskname(struct netconsole_target *nt, int offset) +{ + return scnprintf(&nt->sysdata[offset], + MAX_EXTRADATA_ENTRY_LEN, " taskname=%s\n", + current->comm); +} + +static int sysdata_append_release(struct netconsole_target *nt, int offset) +{ + return scnprintf(&nt->sysdata[offset], + MAX_EXTRADATA_ENTRY_LEN, " release=%s\n", + init_utsname()->release); +} + +static int sysdata_append_msgid(struct netconsole_target *nt, int offset) +{ + wrapping_assign_add(nt->msgcounter, 1); + return scnprintf(&nt->sysdata[offset], + MAX_EXTRADATA_ENTRY_LEN, " msgid=%u\n", + nt->msgcounter); +} + +/* + * prepare_sysdata - append sysdata in runtime + * @nt: target to send message to + */ +static int prepare_sysdata(struct netconsole_target *nt) +{ + int sysdata_len = 0; + + if (!nt->sysdata_fields) + goto out; + + if (nt->sysdata_fields & SYSDATA_CPU_NR) + sysdata_len += sysdata_append_cpu_nr(nt, sysdata_len); + if (nt->sysdata_fields & SYSDATA_TASKNAME) + sysdata_len += sysdata_append_taskname(nt, sysdata_len); + if (nt->sysdata_fields & SYSDATA_RELEASE) + sysdata_len += sysdata_append_release(nt, sysdata_len); + if (nt->sysdata_fields & SYSDATA_MSGID) + sysdata_len += sysdata_append_msgid(nt, sysdata_len); + + WARN_ON_ONCE(sysdata_len > + MAX_EXTRADATA_ENTRY_LEN * MAX_SYSDATA_ITEMS); + +out: + return sysdata_len; +} #endif /* CONFIG_NETCONSOLE_DYNAMIC */ /* Handle network interface device notifications */ @@ -691,7 +1419,7 @@ static int netconsole_netdev_event(struct notifier_block *this, unsigned long event, void *ptr) { unsigned long flags; - struct netconsole_target *nt; + struct netconsole_target *nt, *tmp; struct net_device *dev = netdev_notifier_info_to_dev(ptr); bool stopped = false; @@ -699,9 +1427,9 @@ static int netconsole_netdev_event(struct notifier_block *this, event == NETDEV_RELEASE || event == NETDEV_JOIN)) goto done; + mutex_lock(&target_cleanup_list_lock); spin_lock_irqsave(&target_list_lock, flags); -restart: - list_for_each_entry(nt, &target_list, list) { + list_for_each_entry_safe(nt, tmp, &target_list, list) { netconsole_target_get(nt); if (nt->np.dev == dev) { switch (event) { @@ -711,27 +1439,19 @@ restart: case NETDEV_RELEASE: case NETDEV_JOIN: case NETDEV_UNREGISTER: - /* rtnl_lock already held - * we might sleep in __netpoll_cleanup() - */ - spin_unlock_irqrestore(&target_list_lock, flags); - - __netpoll_cleanup(&nt->np); - - spin_lock_irqsave(&target_list_lock, flags); - netdev_put(nt->np.dev, &nt->np.dev_tracker); - nt->np.dev = NULL; nt->enabled = false; + list_move(&nt->list, &target_cleanup_list); stopped = true; - netconsole_target_put(nt); - goto restart; } } netconsole_target_put(nt); } spin_unlock_irqrestore(&target_list_lock, flags); + mutex_unlock(&target_cleanup_list_lock); + if (stopped) { const char *msg = "had an event"; + switch (event) { case NETDEV_UNREGISTER: msg = "unregistered"; @@ -747,6 +1467,11 @@ restart: dev->name, msg); } + /* Process target_cleanup_list entries. By the end, target_cleanup_list + * should be empty + */ + netconsole_process_cleanups_core(); + done: return NOTIFY_DONE; } @@ -756,63 +1481,232 @@ static struct notifier_block netconsole_netdev_notifier = { }; /** - * send_ext_msg_udp - send extended log message to target + * send_udp - Wrapper for netpoll_send_udp that counts errors * @nt: target to send message to - * @msg: extended log message to send - * @msg_len: length of message + * @msg: message to send + * @len: length of message * - * Transfer extended log @msg to @nt. If @msg is longer than - * MAX_PRINT_CHUNK, it'll be split and transmitted in multiple chunks with - * ncfrag header field added to identify them. + * Calls netpoll_send_udp and classifies the return value. If an error + * occurred it increments statistics in nt->stats accordingly. + * Only calls netpoll_send_udp if CONFIG_NETCONSOLE_DYNAMIC is disabled. */ -static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg, - int msg_len) +static void send_udp(struct netconsole_target *nt, const char *msg, int len) { - static char buf[MAX_PRINT_CHUNK]; /* protected by target_list_lock */ - const char *header, *body; - int offset = 0; - int header_len, body_len; + int result = netpoll_send_udp(&nt->np, msg, len); + + if (IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) { + if (result == NET_XMIT_DROP) { + u64_stats_update_begin(&nt->stats.syncp); + u64_stats_inc(&nt->stats.xmit_drop_count); + u64_stats_update_end(&nt->stats.syncp); + } else if (result == -ENOMEM) { + u64_stats_update_begin(&nt->stats.syncp); + u64_stats_inc(&nt->stats.enomem_count); + u64_stats_update_end(&nt->stats.syncp); + } + } +} + +static void send_msg_no_fragmentation(struct netconsole_target *nt, + const char *msg, + int msg_len, + int release_len) +{ + const char *userdata = NULL; + const char *sysdata = NULL; + const char *release; + +#ifdef CONFIG_NETCONSOLE_DYNAMIC + userdata = nt->userdata; + sysdata = nt->sysdata; +#endif + + if (release_len) { + release = init_utsname()->release; - if (msg_len <= MAX_PRINT_CHUNK) { - netpoll_send_udp(&nt->np, msg, msg_len); + scnprintf(nt->buf, MAX_PRINT_CHUNK, "%s,%s", release, msg); + msg_len += release_len; + } else { + memcpy(nt->buf, msg, msg_len); + } + + if (userdata) + msg_len += scnprintf(&nt->buf[msg_len], + MAX_PRINT_CHUNK - msg_len, "%s", + userdata); + + if (sysdata) + msg_len += scnprintf(&nt->buf[msg_len], + MAX_PRINT_CHUNK - msg_len, "%s", + sysdata); + + send_udp(nt, nt->buf, msg_len); +} + +static void append_release(char *buf) +{ + const char *release; + + release = init_utsname()->release; + scnprintf(buf, MAX_PRINT_CHUNK, "%s,", release); +} + +static void send_fragmented_body(struct netconsole_target *nt, + const char *msgbody_ptr, int header_len, + int msgbody_len, int sysdata_len) +{ + const char *userdata_ptr = NULL; + const char *sysdata_ptr = NULL; + int data_len, data_sent = 0; + int userdata_offset = 0; + int sysdata_offset = 0; + int msgbody_offset = 0; + int userdata_len = 0; + +#ifdef CONFIG_NETCONSOLE_DYNAMIC + userdata_ptr = nt->userdata; + sysdata_ptr = nt->sysdata; + userdata_len = nt->userdata_length; +#endif + if (WARN_ON_ONCE(!userdata_ptr && userdata_len != 0)) + return; + + if (WARN_ON_ONCE(!sysdata_ptr && sysdata_len != 0)) return; + + /* data_len represents the number of bytes that will be sent. This is + * bigger than MAX_PRINT_CHUNK, thus, it will be split in multiple + * packets + */ + data_len = msgbody_len + userdata_len + sysdata_len; + + /* In each iteration of the while loop below, we send a packet + * containing the header and a portion of the data. The data is + * composed of three parts: msgbody, userdata, and sysdata. + * We keep track of how many bytes have been sent from each part using + * the *_offset variables. + * We keep track of how many bytes have been sent overall using the + * data_sent variable, which ranges from 0 to the total bytes to be + * sent. + */ + while (data_sent < data_len) { + int userdata_left = userdata_len - userdata_offset; + int sysdata_left = sysdata_len - sysdata_offset; + int msgbody_left = msgbody_len - msgbody_offset; + int buf_offset = 0; + int this_chunk = 0; + + /* header is already populated in nt->buf, just append to it */ + buf_offset = header_len; + + buf_offset += scnprintf(nt->buf + buf_offset, + MAX_PRINT_CHUNK - buf_offset, + ",ncfrag=%d/%d;", data_sent, + data_len); + + /* append msgbody first */ + this_chunk = min(msgbody_left, MAX_PRINT_CHUNK - buf_offset); + memcpy(nt->buf + buf_offset, msgbody_ptr + msgbody_offset, + this_chunk); + msgbody_offset += this_chunk; + buf_offset += this_chunk; + data_sent += this_chunk; + + /* after msgbody, append userdata */ + if (userdata_ptr && userdata_left) { + this_chunk = min(userdata_left, + MAX_PRINT_CHUNK - buf_offset); + memcpy(nt->buf + buf_offset, + userdata_ptr + userdata_offset, this_chunk); + userdata_offset += this_chunk; + buf_offset += this_chunk; + data_sent += this_chunk; + } + + /* after userdata, append sysdata */ + if (sysdata_ptr && sysdata_left) { + this_chunk = min(sysdata_left, + MAX_PRINT_CHUNK - buf_offset); + memcpy(nt->buf + buf_offset, + sysdata_ptr + sysdata_offset, this_chunk); + sysdata_offset += this_chunk; + buf_offset += this_chunk; + data_sent += this_chunk; + } + + /* if all is good, send the packet out */ + if (WARN_ON_ONCE(data_sent > data_len)) + return; + + send_udp(nt, nt->buf, buf_offset); } +} + +static void send_msg_fragmented(struct netconsole_target *nt, + const char *msg, + int msg_len, + int release_len, + int sysdata_len) +{ + int header_len, msgbody_len; + const char *msgbody; - /* need to insert extra header fields, detect header and body */ - header = msg; - body = memchr(msg, ';', msg_len); - if (WARN_ON_ONCE(!body)) + /* need to insert extra header fields, detect header and msgbody */ + msgbody = memchr(msg, ';', msg_len); + if (WARN_ON_ONCE(!msgbody)) return; - header_len = body - header; - body_len = msg_len - header_len - 1; - body++; + header_len = msgbody - msg; + msgbody_len = msg_len - header_len - 1; + msgbody++; /* * Transfer multiple chunks with the following extra header. * "ncfrag=<byte-offset>/<total-bytes>" */ - memcpy(buf, header, header_len); + if (release_len) + append_release(nt->buf); - while (offset < body_len) { - int this_header = header_len; - int this_chunk; + /* Copy the header into the buffer */ + memcpy(nt->buf + release_len, msg, header_len); + header_len += release_len; - this_header += scnprintf(buf + this_header, - sizeof(buf) - this_header, - ",ncfrag=%d/%d;", offset, body_len); + /* for now on, the header will be persisted, and the msgbody + * will be replaced + */ + send_fragmented_body(nt, msgbody, header_len, msgbody_len, + sysdata_len); +} - this_chunk = min(body_len - offset, - MAX_PRINT_CHUNK - this_header); - if (WARN_ON_ONCE(this_chunk <= 0)) - return; +/** + * send_ext_msg_udp - send extended log message to target + * @nt: target to send message to + * @msg: extended log message to send + * @msg_len: length of message + * + * Transfer extended log @msg to @nt. If @msg is longer than + * MAX_PRINT_CHUNK, it'll be split and transmitted in multiple chunks with + * ncfrag header field added to identify them. + */ +static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg, + int msg_len) +{ + int userdata_len = 0; + int release_len = 0; + int sysdata_len = 0; - memcpy(buf + this_header, body + offset, this_chunk); +#ifdef CONFIG_NETCONSOLE_DYNAMIC + sysdata_len = prepare_sysdata(nt); + userdata_len = nt->userdata_length; +#endif + if (nt->release) + release_len = strlen(init_utsname()->release) + 1; - netpoll_send_udp(&nt->np, buf, this_header + this_chunk); + if (msg_len + release_len + sysdata_len + userdata_len <= MAX_PRINT_CHUNK) + return send_msg_no_fragmentation(nt, msg, msg_len, release_len); - offset += this_chunk; - } + return send_msg_fragmented(nt, msg, msg_len, release_len, + sysdata_len); } static void write_ext_msg(struct console *con, const char *msg, @@ -856,7 +1750,7 @@ static void write_msg(struct console *con, const char *msg, unsigned int len) tmp = msg; for (left = len; left;) { frag = min(left, MAX_PRINT_CHUNK); - netpoll_send_udp(&nt->np, tmp, frag); + send_udp(nt, tmp, frag); tmp += frag; left -= frag; } @@ -865,6 +1759,164 @@ static void write_msg(struct console *con, const char *msg, unsigned int len) spin_unlock_irqrestore(&target_list_lock, flags); } +static int netconsole_parser_cmdline(struct netpoll *np, char *opt) +{ + bool ipversion_set = false; + char *cur = opt; + char *delim; + int ipv6; + + if (*cur != '@') { + delim = strchr(cur, '@'); + if (!delim) + goto parse_failed; + *delim = 0; + if (kstrtou16(cur, 10, &np->local_port)) + goto parse_failed; + cur = delim; + } + cur++; + + if (*cur != '/') { + ipversion_set = true; + delim = strchr(cur, '/'); + if (!delim) + goto parse_failed; + *delim = 0; + ipv6 = netpoll_parse_ip_addr(cur, &np->local_ip); + if (ipv6 < 0) + goto parse_failed; + else + np->ipv6 = (bool)ipv6; + cur = delim; + } + cur++; + + if (*cur != ',') { + /* parse out dev_name or dev_mac */ + delim = strchr(cur, ','); + if (!delim) + goto parse_failed; + *delim = 0; + + np->dev_name[0] = '\0'; + eth_broadcast_addr(np->dev_mac); + if (!strchr(cur, ':')) + strscpy(np->dev_name, cur, sizeof(np->dev_name)); + else if (!mac_pton(cur, np->dev_mac)) + goto parse_failed; + + cur = delim; + } + cur++; + + if (*cur != '@') { + /* dst port */ + delim = strchr(cur, '@'); + if (!delim) + goto parse_failed; + *delim = 0; + if (*cur == ' ' || *cur == '\t') + np_info(np, "warning: whitespace is not allowed\n"); + if (kstrtou16(cur, 10, &np->remote_port)) + goto parse_failed; + cur = delim; + } + cur++; + + /* dst ip */ + delim = strchr(cur, '/'); + if (!delim) + goto parse_failed; + *delim = 0; + ipv6 = netpoll_parse_ip_addr(cur, &np->remote_ip); + if (ipv6 < 0) + goto parse_failed; + else if (ipversion_set && np->ipv6 != (bool)ipv6) + goto parse_failed; + else + np->ipv6 = (bool)ipv6; + cur = delim + 1; + + if (*cur != 0) { + /* MAC address */ + if (!mac_pton(cur, np->remote_mac)) + goto parse_failed; + } + + netconsole_print_banner(np); + + return 0; + + parse_failed: + np_info(np, "couldn't parse config at '%s'!\n", cur); + return -1; +} + +/* Allocate new target (from boot/module param) and setup netpoll for it */ +static struct netconsole_target *alloc_param_target(char *target_config, + int cmdline_count) +{ + struct netconsole_target *nt; + int err; + + nt = alloc_and_init(); + if (!nt) { + err = -ENOMEM; + goto fail; + } + + if (*target_config == '+') { + nt->extended = true; + target_config++; + } + + if (*target_config == 'r') { + if (!nt->extended) { + pr_err("Netconsole configuration error. Release feature requires extended log message"); + err = -EINVAL; + goto fail; + } + nt->release = true; + target_config++; + } + + /* Parse parameters and setup netpoll */ + err = netconsole_parser_cmdline(&nt->np, target_config); + if (err) + goto fail; + + err = netpoll_setup(&nt->np); + if (err) { + pr_err("Not enabling netconsole for %s%d. Netpoll setup failed\n", + NETCONSOLE_PARAM_TARGET_PREFIX, cmdline_count); + if (!IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) + /* only fail if dynamic reconfiguration is set, + * otherwise, keep the target in the list, but disabled. + */ + goto fail; + } else { + nt->enabled = true; + } + populate_configfs_item(nt, cmdline_count); + + return nt; + +fail: + kfree(nt); + return ERR_PTR(err); +} + +/* Cleanup netpoll for given target (from boot/module param) and free it */ +static void free_param_target(struct netconsole_target *nt) +{ + netpoll_cleanup(&nt->np); +#ifdef CONFIG_NETCONSOLE_DYNAMIC + kfree(nt->userdata); +#endif + kfree(nt); +} + static struct console netconsole_ext = { .name = "netcon_ext", .flags = CON_ENABLED | CON_EXTENDED, @@ -881,29 +1933,34 @@ static int __init init_netconsole(void) { int err; struct netconsole_target *nt, *tmp; - bool extended = false; + u32 console_type_needed = 0; + unsigned int count = 0; 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); + nt = alloc_param_target(target_config, count); if (IS_ERR(nt)) { + if (IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) + continue; err = PTR_ERR(nt); goto fail; } /* Dump existing printks when we register */ if (nt->extended) { - extended = true; + console_type_needed |= CONS_EXTENDED; netconsole_ext.flags |= CON_PRINTBUFFER; } else { + console_type_needed |= CONS_BASIC; netconsole.flags |= CON_PRINTBUFFER; } spin_lock_irqsave(&target_list_lock, flags); list_add(&nt->list, &target_list); spin_unlock_irqrestore(&target_list_lock, flags); + count++; } } @@ -915,9 +1972,10 @@ static int __init init_netconsole(void) if (err) goto undonotifier; - if (extended) + if (console_type_needed & CONS_EXTENDED) register_console(&netconsole_ext); - register_console(&netconsole); + if (console_type_needed & CONS_BASIC) + register_console(&netconsole); pr_info("network logging started\n"); return err; @@ -947,7 +2005,8 @@ static void __exit cleanup_netconsole(void) if (console_is_registered(&netconsole_ext)) unregister_console(&netconsole_ext); - unregister_console(&netconsole); + if (console_is_registered(&netconsole)) + unregister_console(&netconsole); dynamic_netconsole_exit(); unregister_netdevice_notifier(&netconsole_netdev_notifier); |
