diff options
Diffstat (limited to 'drivers/net/netconsole.c')
| -rw-r--r-- | drivers/net/netconsole.c | 270 | 
1 files changed, 258 insertions, 12 deletions
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 176935a8645f..e3722de08ea9 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -86,10 +86,10 @@ static DEFINE_SPINLOCK(target_list_lock);  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; +static struct console netconsole_ext, netconsole;  struct netconsole_target_stats  {  	u64_stats_t xmit_drop_count; @@ -97,6 +97,11 @@ struct netconsole_target_stats  {  	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. @@ -108,6 +113,8 @@ enum sysdata_feature {  	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),  };  /** @@ -118,6 +125,7 @@ enum sysdata_feature {   * @extradata_complete:	Cached, formatted string of append   * @userdata_length:	String length of usedata in extradata_complete.   * @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). @@ -148,6 +156,8 @@ struct netconsole_target {  	size_t			userdata_length;  	/* bit-wise with sysdata_feature bits */  	u32			sysdata_fields; +	/* protected by target_list_lock */ +	u32			msgcounter;  #endif  	struct netconsole_target_stats stats;  	bool			enabled; @@ -273,6 +283,23 @@ static void netconsole_process_cleanups_core(void)  	mutex_unlock(&target_cleanup_list_lock);  } +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); +} +  #ifdef	CONFIG_NETCONSOLE_DYNAMIC  /* @@ -455,6 +482,46 @@ static ssize_t sysdata_release_enabled_show(struct config_item *item,  	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); +} +  /*   * This one is special -- targets created through the configfs interface   * are not enabled (and the corresponding netpoll activated) by default. @@ -488,14 +555,24 @@ static ssize_t enabled_store(struct config_item *item,  			goto out_unlock;  		} -		if (nt->extended && !console_is_registered(&netconsole_ext)) +		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);  		ret = netpoll_setup(&nt->np);  		if (ret) @@ -517,6 +594,10 @@ static ssize_t enabled_store(struct config_item *item,  		list_move(&nt->list, &target_cleanup_list);  		spin_unlock_irqrestore(&target_list_lock, flags);  		mutex_unlock(&target_cleanup_list_lock); +		/* Unregister consoles, whose the last target of that type got +		 * disabled. +		 */ +		unregister_netcons_consoles();  	}  	ret = strnlen(buf, count); @@ -736,6 +817,8 @@ static size_t count_extradata_entries(struct netconsole_target *nt)  		entries += 1;  	if (nt->sysdata_fields & SYSDATA_RELEASE)  		entries += 1; +	if (nt->sysdata_fields & SYSDATA_MSGID) +		entries += 1;  	return entries;  } @@ -872,6 +955,40 @@ static void disable_sysdata_feature(struct netconsole_target *nt,  	nt->extradata_complete[nt->userdata_length] = 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(&dynamic_netconsole_mutex); +	curr = !!(nt->sysdata_fields & SYSDATA_MSGID); +	if (msgid_enabled == curr) +		goto unlock_ok; + +	if (msgid_enabled && +	    count_extradata_entries(nt) >= MAX_EXTRADATA_ITEMS) { +		ret = -ENOSPC; +		goto unlock; +	} + +	if (msgid_enabled) +		nt->sysdata_fields |= SYSDATA_MSGID; +	else +		disable_sysdata_feature(nt, SYSDATA_MSGID); + +unlock_ok: +	ret = strnlen(buf, count); +unlock: +	mutex_unlock(&dynamic_netconsole_mutex); +	return ret; +} +  static ssize_t sysdata_release_enabled_store(struct config_item *item,  					     const char *buf, size_t count)  { @@ -987,6 +1104,7 @@ 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, @@ -1049,6 +1167,7 @@ static struct configfs_attribute *userdata_attrs[] = {  	&sysdata_attr_cpu_nr_enabled,  	&sysdata_attr_taskname_enabled,  	&sysdata_attr_release_enabled, +	&sysdata_attr_msgid_enabled,  	NULL,  }; @@ -1246,6 +1365,14 @@ static int sysdata_append_release(struct netconsole_target *nt, int offset)  			 init_utsname()->release);  } +static int sysdata_append_msgid(struct netconsole_target *nt, int offset) +{ +	wrapping_assign_add(nt->msgcounter, 1); +	return scnprintf(&nt->extradata_complete[offset], +			 MAX_EXTRADATA_ENTRY_LEN, " msgid=%u\n", +			 nt->msgcounter); +} +  /*   * prepare_extradata - append sysdata at extradata_complete in runtime   * @nt: target to send message to @@ -1268,6 +1395,8 @@ static int prepare_extradata(struct netconsole_target *nt)  		extradata_len += sysdata_append_taskname(nt, extradata_len);  	if (nt->sysdata_fields & SYSDATA_RELEASE)  		extradata_len += sysdata_append_release(nt, extradata_len); +	if (nt->sysdata_fields & SYSDATA_MSGID) +		extradata_len += sysdata_append_msgid(nt, extradata_len);  	WARN_ON_ONCE(extradata_len >  		     MAX_EXTRADATA_ENTRY_LEN * MAX_EXTRADATA_ITEMS); @@ -1613,6 +1742,120 @@ static void write_msg(struct console *con, const char *msg, unsigned int len)  	spin_unlock_irqrestore(&target_list_lock, flags);  } +static int netpoll_parse_ip_addr(const char *str, union inet_addr *addr) +{ +	const char *end; + +	if (!strchr(str, ':') && +	    in4_pton(str, -1, (void *)addr, -1, &end) > 0) { +		if (!*end) +			return 0; +	} +	if (in6_pton(str, -1, addr->in6.s6_addr, -1, &end) > 0) { +#if IS_ENABLED(CONFIG_IPV6) +		if (!*end) +			return 1; +#else +		return -1; +#endif +	} +	return -1; +} + +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) @@ -1642,7 +1885,7 @@ static struct netconsole_target *alloc_param_target(char *target_config,  	}  	/* Parse parameters and setup netpoll */ -	err = netpoll_parse_options(&nt->np, target_config); +	err = netconsole_parser_cmdline(&nt->np, target_config);  	if (err)  		goto fail; @@ -1690,8 +1933,8 @@ static int __init init_netconsole(void)  {  	int err;  	struct netconsole_target *nt, *tmp; +	u32 console_type_needed = 0;  	unsigned int count = 0; -	bool extended = false;  	unsigned long flags;  	char *target_config;  	char *input = config; @@ -1707,9 +1950,10 @@ static int __init init_netconsole(void)  			}  			/* 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;  			} @@ -1728,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; @@ -1760,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);  | 
