netconsole: convert to NBCON console infrastructure

Convert netconsole from the legacy console API to the NBCON framework.
NBCON provides threaded printing which unblocks printk()s and flushes in
a thread, decoupling network TX from printk() when netconsole is
in use.

Since netconsole relies on the network stack which cannot safely operate
from all atomic contexts, mark both consoles with
CON_NBCON_ATOMIC_UNSAFE. (See discussion in [1])

CON_NBCON_ATOMIC_UNSAFE restricts write_atomic() usage to emergency
scenarios (panic) where regular messages are sent in threaded mode.

Implementation changes:
- Unify write_ext_msg() and write_msg() into netconsole_write()
- Add device_lock/device_unlock callbacks to manage target_list_lock
- Use nbcon_enter_unsafe()/nbcon_exit_unsafe() around network
  operations.
  - If nbcon_enter_unsafe() fails, just return given netconsole lost
    the ownership of the console.
- Set write_thread and write_atomic callbacks (both use same function)

Link: https://lore.kernel.org/all/b2qps3uywhmjaym4mht2wpxul4yqtuuayeoq4iv4k3zf5wdgh3@tocu6c7mj4lt/ [1]
Reviewed-by: John Ogness <john.ogness@linutronix.de>
Reviewed-by: Petr Mladek <pmladek@suse.com>
Signed-off-by: Breno Leitao <leitao@debian.org>
Link: https://patch.msgid.link/20260206-nbcon-v7-3-62bda69b1b41@debian.org
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
Breno Leitao 2026-02-06 04:45:31 -08:00 committed by Jakub Kicinski
parent eaf35bc63b
commit 7eab73b186

View file

@ -1859,23 +1859,6 @@ static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg,
sysdata_len);
}
static void write_ext_msg(struct console *con, const char *msg,
unsigned int len)
{
struct netconsole_target *nt;
unsigned long flags;
if ((oops_only && !oops_in_progress) || list_empty(&target_list))
return;
spin_lock_irqsave(&target_list_lock, flags);
list_for_each_entry(nt, &target_list, list)
if (nt->extended && nt->state == STATE_ENABLED &&
netif_running(nt->np.dev))
send_ext_msg_udp(nt, msg, len);
spin_unlock_irqrestore(&target_list_lock, flags);
}
static void send_msg_udp(struct netconsole_target *nt, const char *msg,
unsigned int len)
{
@ -1890,30 +1873,64 @@ static void send_msg_udp(struct netconsole_target *nt, const char *msg,
}
}
static void write_msg(struct console *con, const char *msg, unsigned int len)
/**
* netconsole_write - Generic function to send a msg to all targets
* @wctxt: nbcon write context
* @extended: "true" for extended console mode
*
* Given an nbcon write context, send the message to the netconsole targets
*/
static void netconsole_write(struct nbcon_write_context *wctxt, bool extended)
{
unsigned long flags;
struct netconsole_target *nt;
if (oops_only && !oops_in_progress)
return;
/* Avoid taking lock and disabling interrupts unnecessarily */
if (list_empty(&target_list))
return;
spin_lock_irqsave(&target_list_lock, flags);
list_for_each_entry(nt, &target_list, list) {
if (!nt->extended && nt->state == STATE_ENABLED &&
netif_running(nt->np.dev)) {
/*
* We nest this inside the for-each-target loop above
* so that we're able to get as much logging out to
* at least one target if we die inside here, instead
* of unnecessarily keeping all targets in lock-step.
*/
send_msg_udp(nt, msg, len);
}
if (nt->extended != extended || nt->state != STATE_ENABLED ||
!netif_running(nt->np.dev))
continue;
/* If nbcon_enter_unsafe() fails, just return given netconsole
* lost the ownership, and iterating over the targets will not
* be able to re-acquire.
*/
if (!nbcon_enter_unsafe(wctxt))
return;
if (extended)
send_ext_msg_udp(nt, wctxt->outbuf, wctxt->len);
else
send_msg_udp(nt, wctxt->outbuf, wctxt->len);
nbcon_exit_unsafe(wctxt);
}
}
static void netconsole_write_ext(struct console *con __always_unused,
struct nbcon_write_context *wctxt)
{
netconsole_write(wctxt, true);
}
static void netconsole_write_basic(struct console *con __always_unused,
struct nbcon_write_context *wctxt)
{
netconsole_write(wctxt, false);
}
static void netconsole_device_lock(struct console *con __always_unused,
unsigned long *flags)
__acquires(&target_list_lock)
{
spin_lock_irqsave(&target_list_lock, *flags);
}
static void netconsole_device_unlock(struct console *con __always_unused,
unsigned long flags)
__releases(&target_list_lock)
{
spin_unlock_irqrestore(&target_list_lock, flags);
}
@ -2077,15 +2094,21 @@ static void free_param_target(struct netconsole_target *nt)
}
static struct console netconsole_ext = {
.name = "netcon_ext",
.flags = CON_ENABLED | CON_EXTENDED,
.write = write_ext_msg,
.name = "netcon_ext",
.flags = CON_ENABLED | CON_EXTENDED | CON_NBCON | CON_NBCON_ATOMIC_UNSAFE,
.write_thread = netconsole_write_ext,
.write_atomic = netconsole_write_ext,
.device_lock = netconsole_device_lock,
.device_unlock = netconsole_device_unlock,
};
static struct console netconsole = {
.name = "netcon",
.flags = CON_ENABLED,
.write = write_msg,
.name = "netcon",
.flags = CON_ENABLED | CON_NBCON | CON_NBCON_ATOMIC_UNSAFE,
.write_thread = netconsole_write_basic,
.write_atomic = netconsole_write_basic,
.device_lock = netconsole_device_lock,
.device_unlock = netconsole_device_unlock,
};
static int __init init_netconsole(void)