diff options
Diffstat (limited to 'drivers/net/netconsole.c')
-rw-r--r-- | drivers/net/netconsole.c | 779 |
1 files changed, 610 insertions, 169 deletions
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index d7070dd4fe73..4289ccd3e41b 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -36,19 +36,21 @@ #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_USERDATA_ENTRY_LENGTH 256 -#define MAX_USERDATA_VALUE_LENGTH 200 +#define MAX_EXTRADATA_ENTRY_LEN 256 +#define MAX_EXTRADATA_VALUE_LEN 200 /* The number 3 comes from userdata entry format characters (' ', '=', '\n') */ -#define MAX_USERDATA_NAME_LENGTH (MAX_USERDATA_ENTRY_LENGTH - \ - MAX_USERDATA_VALUE_LENGTH - 3) -#define MAX_USERDATA_ITEMS 16 +#define MAX_EXTRADATA_NAME_LEN (MAX_EXTRADATA_ENTRY_LEN - \ + MAX_EXTRADATA_VALUE_LEN - 3) +#define MAX_EXTRADATA_ITEMS 16 #define MAX_PRINT_CHUNK 1000 static char config[MAX_PARAM_LENGTH]; @@ -72,9 +74,16 @@ __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 @@ -82,13 +91,34 @@ static DEFINE_SPINLOCK(target_list_lock); */ static struct console netconsole_ext; +struct netconsole_target_stats { + u64_stats_t xmit_drop_count; + u64_stats_t enomem_count; + struct u64_stats_sync syncp; +}; + +/* 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. + */ +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), +}; + /** * struct netconsole_target - Represents a configured netconsole target. * @list: Links this target into the target_list. * @group: Links us into the configfs subsystem hierarchy. * @userdata_group: Links to the userdata configfs hierarchy - * @userdata_complete: Cached, formatted string of append - * @userdata_length: String length of userdata_complete + * @extradata_complete: Cached, formatted string of append + * @userdata_length: String length of usedata in extradata_complete. + * @sysdata_fields: Sysdata features enabled. + * @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 @@ -107,19 +137,25 @@ 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_group group; struct config_group userdata_group; - char userdata_complete[MAX_USERDATA_ENTRY_LENGTH * MAX_USERDATA_ITEMS]; + char extradata_complete[MAX_EXTRADATA_ENTRY_LEN * MAX_EXTRADATA_ITEMS]; size_t userdata_length; + /* bit-wise with sysdata_feature bits */ + u32 sysdata_fields; #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 @@ -210,6 +246,33 @@ static struct netconsole_target *alloc_and_init(void) return nt; } +/* 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; + + /* The cleanup needs RTNL locked */ + ASSERT_RTNL(); + + 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); +} + #ifdef CONFIG_NETCONSOLE_DYNAMIC /* @@ -227,6 +290,7 @@ static struct netconsole_target *alloc_and_init(void) * | remote_ip * | local_mac * | remote_mac + * | transmit_errors * | userdata/ * | <key>/ * | value @@ -246,6 +310,19 @@ static struct netconsole_target *to_target(struct config_item *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) { @@ -323,6 +400,61 @@ static ssize_t remote_mac_show(struct config_item *item, char *buf) 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); +} + /* * This one is special -- targets created through the configfs interface * are not enabled (and the corresponding netpoll activated) by default. @@ -336,15 +468,15 @@ static ssize_t enabled_store(struct config_item *item, struct netconsole_target *nt = to_target(item); unsigned long flags; bool enabled; - int err; + ssize_t ret; mutex_lock(&dynamic_netconsole_mutex); - err = kstrtobool(buf, &enabled); - if (err) + ret = kstrtobool(buf, &enabled); + if (ret) goto out_unlock; - err = -EINVAL; - 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; @@ -365,29 +497,34 @@ static ssize_t enabled_store(struct config_item *item, */ netpoll_print_options(&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); } - nt->enabled = enabled; - - mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + ret = strnlen(buf, count); + /* Deferred cleanup */ + netconsole_process_cleanups(); out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return err; + return ret; } static ssize_t release_store(struct config_item *item, const char *buf, @@ -395,27 +532,26 @@ static ssize_t release_store(struct config_item *item, const char *buf, { struct netconsole_target *nt = to_target(item); bool release; - int err; + 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)); - err = -EINVAL; + ret = -EINVAL; goto out_unlock; } - err = kstrtobool(buf, &release); - if (err) + ret = kstrtobool(buf, &release); + if (ret) goto out_unlock; nt->release = release; - 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 extended_store(struct config_item *item, const char *buf, @@ -423,27 +559,25 @@ static ssize_t extended_store(struct config_item *item, const char *buf, { struct netconsole_target *nt = to_target(item); bool extended; - int err; + 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)); - err = -EINVAL; + ret = -EINVAL; goto out_unlock; } - err = kstrtobool(buf, &extended); - if (err) + ret = kstrtobool(buf, &extended); + if (ret) 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, @@ -470,7 +604,7 @@ 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) { @@ -479,21 +613,20 @@ static ssize_t local_port_store(struct config_item *item, const char *buf, 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) { @@ -502,20 +635,20 @@ static ssize_t remote_port_store(struct config_item *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; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { @@ -542,17 +675,17 @@ static ssize_t local_ip_store(struct config_item *item, const char *buf, goto out_unlock; } - 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; mutex_lock(&dynamic_netconsole_mutex); if (nt->enabled) { @@ -579,11 +712,32 @@ static ssize_t remote_ip_store(struct config_item *item, const char *buf, goto out_unlock; } - 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 extradata. + * This is important because the extradata_complete only supports + * MAX_EXTRADATA_ITEMS entries. Before enabling any new {user,sys}data + * feature, number of entries needs to checked for available space. + */ +static size_t count_extradata_entries(struct netconsole_target *nt) +{ + size_t entries; + + /* Userdata entries */ + entries = list_count_nodes(&nt->userdata_group.cg_children); + /* Plus sysdata entries */ + if (nt->sysdata_fields & SYSDATA_CPU_NR) + entries += 1; + if (nt->sysdata_fields & SYSDATA_TASKNAME) + entries += 1; + if (nt->sysdata_fields & SYSDATA_RELEASE) + entries += 1; + + return entries; } static ssize_t remote_mac_store(struct config_item *item, const char *buf, @@ -591,6 +745,7 @@ 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) { @@ -601,20 +756,19 @@ static ssize_t remote_mac_store(struct config_item *item, const char *buf, 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); - mutex_unlock(&dynamic_netconsole_mutex); - return strnlen(buf, count); + ret = strnlen(buf, count); out_unlock: mutex_unlock(&dynamic_netconsole_mutex); - return -EINVAL; + return ret; } struct userdatum { struct config_item item; - char value[MAX_USERDATA_VALUE_LENGTH]; + char value[MAX_EXTRADATA_VALUE_LEN]; }; static struct userdatum *to_userdatum(struct config_item *item) @@ -651,13 +805,13 @@ static void update_userdata(struct netconsole_target *nt) /* Clear the current string in case the last userdatum was deleted */ nt->userdata_length = 0; - nt->userdata_complete[0] = 0; + nt->extradata_complete[0] = 0; list_for_each(entry, &nt->userdata_group.cg_children) { struct userdatum *udm_item; struct config_item *item; - if (child_count >= MAX_USERDATA_ITEMS) + if (WARN_ON_ONCE(child_count >= MAX_EXTRADATA_ITEMS)) break; child_count++; @@ -665,19 +819,19 @@ static void update_userdata(struct netconsole_target *nt) udm_item = to_userdatum(item); /* Skip userdata with no value set */ - if (strnlen(udm_item->value, MAX_USERDATA_VALUE_LENGTH) == 0) + if (strnlen(udm_item->value, MAX_EXTRADATA_VALUE_LEN) == 0) continue; - /* This doesn't overflow userdata_complete since it will write - * one entry length (1/MAX_USERDATA_ITEMS long), entry count is + /* This doesn't overflow extradata_complete since it will write + * one entry length (1/MAX_EXTRADATA_ITEMS long), entry count is * checked to not exceed MAX items with child_count above */ - complete_idx += scnprintf(&nt->userdata_complete[complete_idx], - MAX_USERDATA_ENTRY_LENGTH, " %s=%s\n", + complete_idx += scnprintf(&nt->extradata_complete[complete_idx], + MAX_EXTRADATA_ENTRY_LEN, " %s=%s\n", item->ci_name, udm_item->value); } - nt->userdata_length = strnlen(nt->userdata_complete, - sizeof(nt->userdata_complete)); + nt->userdata_length = strnlen(nt->extradata_complete, + sizeof(nt->extradata_complete)); } static ssize_t userdatum_value_store(struct config_item *item, const char *buf, @@ -686,9 +840,9 @@ static ssize_t userdatum_value_store(struct config_item *item, const char *buf, struct userdatum *udm = to_userdatum(item); struct netconsole_target *nt; struct userdata *ud; - int ret; + ssize_t ret; - if (count > MAX_USERDATA_VALUE_LENGTH) + if (count > MAX_EXTRADATA_VALUE_LEN) return -EMSGSIZE; mutex_lock(&dynamic_netconsole_mutex); @@ -701,15 +855,138 @@ static ssize_t userdatum_value_store(struct config_item *item, const char *buf, ud = to_userdata(item->ci_parent); nt = userdata_to_target(ud); update_userdata(nt); + ret = count; +out_unlock: + mutex_unlock(&dynamic_netconsole_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->extradata_complete[nt->userdata_length] = 0; +} + +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(&dynamic_netconsole_mutex); + curr = !!(nt->sysdata_fields & SYSDATA_RELEASE); + if (release_enabled == curr) + goto unlock_ok; + + if (release_enabled && + count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS) { + ret = -ENOSPC; + goto unlock; + } + + if (release_enabled) + nt->sysdata_fields |= SYSDATA_RELEASE; + else + disable_sysdata_feature(nt, SYSDATA_RELEASE); + +unlock_ok: + ret = strnlen(buf, count); +unlock: mutex_unlock(&dynamic_netconsole_mutex); - return count; -out_unlock: + 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(&dynamic_netconsole_mutex); + curr = !!(nt->sysdata_fields & SYSDATA_TASKNAME); + if (taskname_enabled == curr) + goto unlock_ok; + + if (taskname_enabled && + count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS) { + ret = -ENOSPC; + goto unlock; + } + + if (taskname_enabled) + nt->sysdata_fields |= SYSDATA_TASKNAME; + else + disable_sysdata_feature(nt, SYSDATA_TASKNAME); + +unlock_ok: + ret = strnlen(buf, count); +unlock: + mutex_unlock(&dynamic_netconsole_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(&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 && + count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS) { + /* user wants the new feature, but there is no space in the + * buffer. + */ + ret = -ENOSPC; + goto unlock; + } + + if (cpu_nr_enabled) + nt->sysdata_fields |= SYSDATA_CPU_NR; + else + /* This is special because extradata_complete 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); +unlock: mutex_unlock(&dynamic_netconsole_mutex); return ret; } CONFIGFS_ATTR(userdatum_, value); +CONFIGFS_ATTR(sysdata_, cpu_nr_enabled); +CONFIGFS_ATTR(sysdata_, taskname_enabled); +CONFIGFS_ATTR(sysdata_, release_enabled); static struct configfs_attribute *userdatum_attrs[] = { &userdatum_attr_value, @@ -737,15 +1014,13 @@ static struct config_item *userdatum_make_item(struct config_group *group, struct netconsole_target *nt; struct userdatum *udm; struct userdata *ud; - size_t child_count; - if (strlen(name) > MAX_USERDATA_NAME_LENGTH) + if (strlen(name) > MAX_EXTRADATA_NAME_LEN) return ERR_PTR(-ENAMETOOLONG); ud = to_userdata(&group->cg_item); nt = userdata_to_target(ud); - child_count = list_count_nodes(&nt->userdata_group.cg_children); - if (child_count >= MAX_USERDATA_ITEMS) + if (count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS) return ERR_PTR(-ENOSPC); udm = kzalloc(sizeof(*udm), GFP_KERNEL); @@ -771,6 +1046,9 @@ static void userdatum_drop(struct config_group *group, struct config_item *item) } static struct configfs_attribute *userdata_attrs[] = { + &sysdata_attr_cpu_nr_enabled, + &sysdata_attr_taskname_enabled, + &sysdata_attr_release_enabled, NULL, }; @@ -779,7 +1057,7 @@ static struct configfs_group_operations userdata_ops = { .drop_item = userdatum_drop, }; -static struct config_item_type userdata_type = { +static const struct config_item_type userdata_type = { .ct_item_ops = &userdatum_ops, .ct_group_ops = &userdata_ops, .ct_attrs = userdata_attrs, @@ -796,6 +1074,7 @@ 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, @@ -808,6 +1087,7 @@ static struct configfs_attribute *netconsole_target_attrs[] = { &attr_remote_ip, &attr_local_mac, &attr_remote_mac, + &attr_transmit_errors, NULL, }; @@ -944,6 +1224,63 @@ static void populate_configfs_item(struct netconsole_target *nt, init_target_config_group(nt, target_name); } +static int sysdata_append_cpu_nr(struct netconsole_target *nt, int offset) +{ + /* Append cpu=%d at extradata_complete after userdata str */ + return scnprintf(&nt->extradata_complete[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->extradata_complete[offset], + MAX_EXTRADATA_ENTRY_LEN, " taskname=%s\n", + current->comm); +} + +static int sysdata_append_release(struct netconsole_target *nt, int offset) +{ + return scnprintf(&nt->extradata_complete[offset], + MAX_EXTRADATA_ENTRY_LEN, " release=%s\n", + init_utsname()->release); +} + +/* + * prepare_extradata - append sysdata at extradata_complete in runtime + * @nt: target to send message to + */ +static int prepare_extradata(struct netconsole_target *nt) +{ + u32 fields = SYSDATA_CPU_NR | SYSDATA_TASKNAME; + int extradata_len; + + /* userdata was appended when configfs write helper was called + * by update_userdata(). + */ + extradata_len = nt->userdata_length; + + if (!(nt->sysdata_fields & fields)) + goto out; + + if (nt->sysdata_fields & SYSDATA_CPU_NR) + extradata_len += sysdata_append_cpu_nr(nt, extradata_len); + if (nt->sysdata_fields & SYSDATA_TASKNAME) + extradata_len += sysdata_append_taskname(nt, extradata_len); + if (nt->sysdata_fields & SYSDATA_RELEASE) + extradata_len += sysdata_append_release(nt, extradata_len); + + WARN_ON_ONCE(extradata_len > + MAX_EXTRADATA_ENTRY_LEN * MAX_EXTRADATA_ITEMS); + +out: + return extradata_len; +} +#else /* CONFIG_NETCONSOLE_DYNAMIC not set */ +static int prepare_extradata(struct netconsole_target *nt) +{ + return 0; +} #endif /* CONFIG_NETCONSOLE_DYNAMIC */ /* Handle network interface device notifications */ @@ -951,7 +1288,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; @@ -959,9 +1296,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) { @@ -971,25 +1308,16 @@ 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"; @@ -1008,6 +1336,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; } @@ -1017,121 +1350,220 @@ 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) +{ + 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) { - static char buf[MAX_PRINT_CHUNK]; /* protected by target_list_lock */ - const char *header, *body; - int offset = 0; - int header_len, body_len; - const char *msg_ready = msg; + const char *extradata = NULL; const char *release; - int release_len = 0; - int userdata_len = 0; - char *userdata = NULL; #ifdef CONFIG_NETCONSOLE_DYNAMIC - userdata = nt->userdata_complete; - userdata_len = nt->userdata_length; + extradata = nt->extradata_complete; #endif - if (nt->release) { + if (release_len) { release = init_utsname()->release; - release_len = strlen(release) + 1; + + scnprintf(nt->buf, MAX_PRINT_CHUNK, "%s,%s", release, msg); + msg_len += release_len; + } else { + memcpy(nt->buf, msg, msg_len); } - if (msg_len + release_len + userdata_len <= MAX_PRINT_CHUNK) { - /* No fragmentation needed */ - if (nt->release) { - scnprintf(buf, MAX_PRINT_CHUNK, "%s,%s", release, msg); - msg_len += release_len; - } else { - memcpy(buf, msg, msg_len); - } + if (extradata) + msg_len += scnprintf(&nt->buf[msg_len], + MAX_PRINT_CHUNK - msg_len, + "%s", extradata); - if (userdata) - msg_len += scnprintf(&buf[msg_len], - MAX_PRINT_CHUNK - msg_len, - "%s", userdata); + send_udp(nt, nt->buf, msg_len); +} - msg_ready = buf; - netpoll_send_udp(&nt->np, msg_ready, msg_len); - return; - } +static void append_release(char *buf) +{ + const char *release; - /* need to insert extra header fields, detect header and body */ - header = msg; - body = memchr(msg, ';', msg_len); - if (WARN_ON_ONCE(!body)) - return; + release = init_utsname()->release; + scnprintf(buf, MAX_PRINT_CHUNK, "%s,", release); +} - header_len = body - header; - body_len = msg_len - header_len - 1; - body++; +static void send_fragmented_body(struct netconsole_target *nt, + const char *msgbody, int header_len, + int msgbody_len, int extradata_len) +{ + int sent_extradata, preceding_bytes; + const char *extradata = NULL; + int body_len, offset = 0; - /* - * Transfer multiple chunks with the following extra header. - * "ncfrag=<byte-offset>/<total-bytes>" +#ifdef CONFIG_NETCONSOLE_DYNAMIC + extradata = nt->extradata_complete; +#endif + + /* body_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 */ - if (nt->release) - scnprintf(buf, MAX_PRINT_CHUNK, "%s,", release); - memcpy(buf + release_len, header, header_len); - header_len += release_len; + body_len = msgbody_len + extradata_len; - while (offset < body_len + userdata_len) { + /* In each iteration of the while loop below, we send a packet + * containing the header and a portion of the body. The body is + * composed of two parts: msgbody and extradata. We keep track of how + * many bytes have been sent so far using the offset variable, which + * ranges from 0 to the total length of the body. + */ + while (offset < body_len) { int this_header = header_len; + bool msgbody_written = false; int this_offset = 0; int this_chunk = 0; - this_header += scnprintf(buf + this_header, - sizeof(buf) - this_header, + this_header += scnprintf(nt->buf + this_header, + MAX_PRINT_CHUNK - this_header, ",ncfrag=%d/%d;", offset, - body_len + userdata_len); + body_len); - /* Not all body data has been written yet */ - if (offset < body_len) { - this_chunk = min(body_len - offset, + /* Not all msgbody data has been written yet */ + if (offset < msgbody_len) { + this_chunk = min(msgbody_len - offset, MAX_PRINT_CHUNK - this_header); if (WARN_ON_ONCE(this_chunk <= 0)) return; - memcpy(buf + this_header, body + offset, this_chunk); + memcpy(nt->buf + this_header, msgbody + offset, + this_chunk); this_offset += this_chunk; } - /* Body is fully written and there is pending userdata to write, - * append userdata in this chunk + + /* msgbody was finally written, either in the previous + * messages and/or in the current buf. Time to write + * the extradata. + */ + msgbody_written |= offset + this_offset >= msgbody_len; + + /* Msg body is fully written and there is pending extradata to + * write, append extradata in this chunk */ - if (offset + this_offset >= body_len && - offset + this_offset < userdata_len + body_len) { - int sent_userdata = (offset + this_offset) - body_len; - int preceding_bytes = this_chunk + this_header; + if (msgbody_written && offset + this_offset < body_len) { + /* Track how much user data was already sent. First + * time here, sent_userdata is zero + */ + sent_extradata = (offset + this_offset) - msgbody_len; + /* offset of bytes used in current buf */ + preceding_bytes = this_chunk + this_header; - if (WARN_ON_ONCE(sent_userdata < 0)) + if (WARN_ON_ONCE(sent_extradata < 0)) return; - this_chunk = min(userdata_len - sent_userdata, + this_chunk = min(extradata_len - sent_extradata, MAX_PRINT_CHUNK - preceding_bytes); - if (WARN_ON_ONCE(this_chunk <= 0)) + if (WARN_ON_ONCE(this_chunk < 0)) + /* this_chunk could be zero if all the previous + * message used all the buffer. This is not a + * problem, extradata will be sent in the next + * iteration + */ return; - memcpy(buf + this_header + this_offset, - userdata + sent_userdata, + + memcpy(nt->buf + this_header + this_offset, + extradata + sent_extradata, this_chunk); this_offset += this_chunk; } - netpoll_send_udp(&nt->np, buf, this_header + this_offset); + send_udp(nt, nt->buf, this_header + this_offset); offset += this_offset; } } +static void send_msg_fragmented(struct netconsole_target *nt, + const char *msg, + int msg_len, + int release_len, + int extradata_len) +{ + int header_len, msgbody_len; + const char *msgbody; + + /* need to insert extra header fields, detect header and msgbody */ + msgbody = memchr(msg, ';', msg_len); + if (WARN_ON_ONCE(!msgbody)) + return; + + 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>" + */ + if (release_len) + append_release(nt->buf); + + /* Copy the header into the buffer */ + memcpy(nt->buf + release_len, msg, header_len); + header_len += release_len; + + /* for now on, the header will be persisted, and the msgbody + * will be replaced + */ + send_fragmented_body(nt, msgbody, header_len, msgbody_len, + extradata_len); +} + +/** + * 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 release_len = 0; + int extradata_len; + + extradata_len = prepare_extradata(nt); + + if (nt->release) + release_len = strlen(init_utsname()->release) + 1; + + if (msg_len + release_len + extradata_len <= MAX_PRINT_CHUNK) + return send_msg_no_fragmentation(nt, msg, msg_len, release_len); + + return send_msg_fragmented(nt, msg, msg_len, release_len, + extradata_len); +} + static void write_ext_msg(struct console *con, const char *msg, unsigned int len) { @@ -1173,7 +1605,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; } @@ -1216,11 +1648,18 @@ static struct netconsole_target *alloc_param_target(char *target_config, goto fail; err = netpoll_setup(&nt->np); - if (err) - goto fail; - + 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); - nt->enabled = true; return nt; @@ -1262,6 +1701,8 @@ static int __init init_netconsole(void) while ((target_config = strsep(&input, ";"))) { nt = alloc_param_target(target_config, count); if (IS_ERR(nt)) { + if (IS_ENABLED(CONFIG_NETCONSOLE_DYNAMIC)) + continue; err = PTR_ERR(nt); goto fail; } |