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); 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, static void send_msg_udp(struct netconsole_target *nt, const char *msg,
unsigned int len) 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; struct netconsole_target *nt;
if (oops_only && !oops_in_progress) if (oops_only && !oops_in_progress)
return; return;
/* Avoid taking lock and disabling interrupts unnecessarily */
if (list_empty(&target_list)) list_for_each_entry(nt, &target_list, list) {
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; return;
spin_lock_irqsave(&target_list_lock, flags); if (extended)
list_for_each_entry(nt, &target_list, list) { send_ext_msg_udp(nt, wctxt->outbuf, wctxt->len);
if (!nt->extended && nt->state == STATE_ENABLED && else
netif_running(nt->np.dev)) { send_msg_udp(nt, wctxt->outbuf, wctxt->len);
/*
* We nest this inside the for-each-target loop above nbcon_exit_unsafe(wctxt);
* 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);
}
} }
}
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); spin_unlock_irqrestore(&target_list_lock, flags);
} }
@ -2078,14 +2095,20 @@ static void free_param_target(struct netconsole_target *nt)
static struct console netconsole_ext = { static struct console netconsole_ext = {
.name = "netcon_ext", .name = "netcon_ext",
.flags = CON_ENABLED | CON_EXTENDED, .flags = CON_ENABLED | CON_EXTENDED | CON_NBCON | CON_NBCON_ATOMIC_UNSAFE,
.write = write_ext_msg, .write_thread = netconsole_write_ext,
.write_atomic = netconsole_write_ext,
.device_lock = netconsole_device_lock,
.device_unlock = netconsole_device_unlock,
}; };
static struct console netconsole = { static struct console netconsole = {
.name = "netcon", .name = "netcon",
.flags = CON_ENABLED, .flags = CON_ENABLED | CON_NBCON | CON_NBCON_ATOMIC_UNSAFE,
.write = write_msg, .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) static int __init init_netconsole(void)