[PATCH RFC net-next 2/2] netconsole: add CONFIG_NETCONSOLE_NBCON for nbcon support

Breno Leitao posted 2 patches 1 week, 3 days ago
[PATCH RFC net-next 2/2] netconsole: add CONFIG_NETCONSOLE_NBCON for nbcon support
Posted by Breno Leitao 1 week, 3 days ago
Add optional support for the nbcon infrastructure to netconsole via a new
CONFIG_NETCONSOLE_NBCON compile-time option.

The nbcon infrastructure provides a lock-free, priority-based console
system that supports atomic printing from any context including NMI,
with safe handover mechanisms between different priority levels. This
makes it particularly suitable for crash-safe kernel logging.

When disabled (default), netconsole uses the legacy console callbacks,
maintaining full backward compatibility.

PS: .write_atomic and .write_thread uses the same callback, given that
there is no safe .write_atomic, so .write_atomic is called as the last
resource. This is what CON_NBCON_ATOMIC_UNSAFE is telling nbcon.

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 drivers/net/Kconfig      | 14 ++++++++++
 drivers/net/netconsole.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 82 insertions(+)

diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig
index ac12eaf11755..aa8771b5b723 100644
--- a/drivers/net/Kconfig
+++ b/drivers/net/Kconfig
@@ -369,6 +369,20 @@ config NETCONSOLE_PREPEND_RELEASE
 	  message.  See <file:Documentation/networking/netconsole.rst> for
 	  details.
 
+config NETCONSOLE_NBCON
+	bool "Use nbcon infrastructure (EXPERIMENTAL)"
+	depends on NETCONSOLE
+	default n
+	help
+	  Enable nbcon support for netconsole. This uses the new lock-free
+	  console infrastructure which supports threaded and atomic printing.
+	  Given that netconsole does not support atomic operations, the current
+	  implementation focuses on threaded callbacks, unless the host is
+	  crashing, then it uses an unsafe atomic callbacks. This feature is
+	  available for both extended and non-extended consoles.
+
+	  If unsure, say N to use the legacy console infrastructure.
+
 config NETPOLL
 	def_bool NETCONSOLE
 
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c
index f4b1706fb081..2943f00b83f6 100644
--- a/drivers/net/netconsole.c
+++ b/drivers/net/netconsole.c
@@ -1724,6 +1724,57 @@ static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg,
 				   extradata_len);
 }
 
+#ifdef CONFIG_NETCONSOLE_NBCON
+static void netcon_write_nbcon(struct console *con,
+			       struct nbcon_write_context *wctxt,
+			       bool extended)
+{
+	struct netconsole_target *nt;
+
+	lockdep_assert_held(&target_list_lock);
+
+	list_for_each_entry(nt, &target_list, list) {
+		if (nt->extended != extended || !nt->enabled ||
+		    !netif_running(nt->np.dev))
+			continue;
+
+		if (!nbcon_enter_unsafe(wctxt))
+			continue;
+
+		if (extended)
+			send_ext_msg_udp(nt, wctxt->outbuf, wctxt->len);
+		else
+			write_msg_target(nt, wctxt->outbuf, wctxt->len);
+
+		nbcon_exit_unsafe(wctxt);
+	}
+}
+
+static void netcon_write_nbcon_ext(struct console *con,
+				   struct nbcon_write_context *wctxt)
+{
+	netcon_write_nbcon(con, wctxt, true);
+}
+
+static void netcon_write_nbcon_basic(struct console *con,
+				     struct nbcon_write_context *wctxt)
+{
+	netcon_write_nbcon(con, wctxt, false);
+}
+
+static void netconsole_device_lock(struct console *con, unsigned long *flags)
+{
+	/* protects all the targets at the same time */
+	spin_lock_irqsave(&target_list_lock, *flags);
+}
+
+static void netconsole_device_unlock(struct console *con, unsigned long flags)
+{
+	spin_unlock_irqrestore(&target_list_lock, flags);
+}
+
+#else
+
 static void write_ext_msg(struct console *con, const char *msg,
 			  unsigned int len)
 {
@@ -1765,6 +1816,7 @@ static void write_msg(struct console *con, const char *msg, unsigned int len)
 	}
 	spin_unlock_irqrestore(&target_list_lock, flags);
 }
+#endif
 
 static int netconsole_parser_cmdline(struct netpoll *np, char *opt)
 {
@@ -1923,14 +1975,30 @@ static void free_param_target(struct netconsole_target *nt)
 
 static struct console netconsole_ext = {
 	.name	= "netcon_ext",
+#ifdef CONFIG_NETCONSOLE_NBCON
+	.flags = CON_ENABLED | CON_EXTENDED | CON_NBCON | CON_NBCON_ATOMIC_UNSAFE,
+	.write_thread = netcon_write_nbcon_ext,
+	.write_atomic = netcon_write_nbcon_ext,
+	.device_lock = netconsole_device_lock,
+	.device_unlock = netconsole_device_unlock,
+#else
 	.flags	= CON_ENABLED | CON_EXTENDED,
 	.write	= write_ext_msg,
+#endif
 };
 
 static struct console netconsole = {
 	.name	= "netcon",
+#ifdef CONFIG_NETCONSOLE_NBCON
+	.flags = CON_ENABLED | CON_NBCON | CON_NBCON_ATOMIC_UNSAFE,
+	.write_thread = netcon_write_nbcon_basic,
+	.write_atomic = netcon_write_nbcon_basic,
+	.device_lock = netconsole_device_lock,
+	.device_unlock = netconsole_device_unlock,
+#else
 	.flags	= CON_ENABLED,
 	.write	= write_msg,
+#endif
 };
 
 static int __init init_netconsole(void)

-- 
2.47.3
Re: [PATCH RFC net-next 2/2] netconsole: add CONFIG_NETCONSOLE_NBCON for nbcon support
Posted by Petr Mladek 1 week ago
On Fri 2025-11-21 03:26:08, Breno Leitao wrote:
> Add optional support for the nbcon infrastructure to netconsole via a new
> CONFIG_NETCONSOLE_NBCON compile-time option.
> 
> The nbcon infrastructure provides a lock-free, priority-based console
> system that supports atomic printing from any context including NMI,
> with safe handover mechanisms between different priority levels. This
> makes it particularly suitable for crash-safe kernel logging.
> 
> When disabled (default), netconsole uses the legacy console callbacks,
> maintaining full backward compatibility.
> 
> PS: .write_atomic and .write_thread uses the same callback, given that
> there is no safe .write_atomic, so .write_atomic is called as the last
> resource. This is what CON_NBCON_ATOMIC_UNSAFE is telling nbcon.

Makes sense. CON_NBCON_ATOMIC_UNSAFE also explains why target_list_lock
need not be synchronized with nbcon context locking [*]. The _unsafe_
.write_atomic() callback might be called only by the final
nbcon_atomic_flush_unsafe() when even the nbcon context
synchronization can be ignored.

[*] For example, see how port->lock is synchronized with the nbcon
    context by uart_port_lock() wrapper.

> --- a/drivers/net/Kconfig
> +++ b/drivers/net/Kconfig
> @@ -369,6 +369,20 @@ config NETCONSOLE_PREPEND_RELEASE
>  	  message.  See <file:Documentation/networking/netconsole.rst> for
>  	  details.
>  
> +config NETCONSOLE_NBCON
> +	bool "Use nbcon infrastructure (EXPERIMENTAL)"
> +	depends on NETCONSOLE
> +	default n
> +	help
> +	  Enable nbcon support for netconsole. This uses the new lock-free

Strictly speaking, it is not lock-free. The main feature is that it is
threaded so that it does not block the printk() caller.

Nbcon consoles also support synchronous flushing in emergecy situations.
But it does not work with netconsoles because they do not support
atomic operations. They are flushed only by the final desperate flush
in panic() when all locks are ignored.

> +	  console infrastructure which supports threaded and atomic printing.
> +	  Given that netconsole does not support atomic operations, the current
> +	  implementation focuses on threaded callbacks, unless the host is
> +	  crashing, then it uses an unsafe atomic callbacks. This feature is
> +	  available for both extended and non-extended consoles.
> +
> +	  If unsure, say N to use the legacy console infrastructure.
> +
>  config NETPOLL
>  	def_bool NETCONSOLE
>  
> diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c
> index f4b1706fb081..2943f00b83f6 100644
> --- a/drivers/net/netconsole.c
> +++ b/drivers/net/netconsole.c
> @@ -1724,6 +1724,57 @@ static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg,
>  				   extradata_len);
>  }
>  
> +#ifdef CONFIG_NETCONSOLE_NBCON
> +static void netcon_write_nbcon(struct console *con,
> +			       struct nbcon_write_context *wctxt,
> +			       bool extended)
> +{
> +	struct netconsole_target *nt;
> +
> +	lockdep_assert_held(&target_list_lock);
> +
> +	list_for_each_entry(nt, &target_list, list) {
> +		if (nt->extended != extended || !nt->enabled ||
> +		    !netif_running(nt->np.dev))
> +			continue;
> +
> +		if (!nbcon_enter_unsafe(wctxt))
> +			continue;
> +
> +		if (extended)
> +			send_ext_msg_udp(nt, wctxt->outbuf, wctxt->len);
> +		else
> +			write_msg_target(nt, wctxt->outbuf, wctxt->len);

If you accepted the rename in the 1st patch then this would be ;-)

		if (extended)
			send_ext_msg_udp(nt, wctxt->outbuf, wctxt->len);
		else
			send_msg_udp(nt, wctxt->outbuf, wctxt->len);

> +
> +		nbcon_exit_unsafe(wctxt);
> +	}
> +}

Otherwise, it looks good from my POV.

Best Regards,
Petr