Commit f2c7fdeb authored by Jakub Kicinski's avatar Jakub Kicinski
Browse files

Merge branch 'net-netconsole-convert-to-nbcon-console-infrastructure'

Breno Leitao says:

====================
net: netconsole: convert to NBCON console infrastructure

This series adds support for the nbcon (new buffer console) infrastructure
to netconsole, enabling lock-free, priority-based console operations that
are safer in crash scenarios.

The implementation is introduced in three steps:

0) Extend printk to expose CPU and taskname (task->comm) where the
   printk originated from. (Thanks John and Petr for the support in
   getting this done)
1) Refactor the message fragmentation logic into a reusable helper function
2) Extend nbcon support to non-extended (basic) consoles using the same
   infrastructure.

The initial discussion about it appeared a while ago in [1], in order to
solve Mike's HARDIRQ-safe -> HARDIRQ-unsafe lock order warning, and the root
cause is that some hosts were calling IRQ unsafe locks from inside console
lock.

At that time, we didn't have the CON_NBCON_ATOMIC_UNSAFE yet. John
kindly implemented CON_NBCON_ATOMIC_UNSAFE in 187de7c2 ("printk:
nbcon: Allow unsafe write_atomic() for panic"), and now we can
implement netconsole on top of nbcon.

Important to note that netconsole continues to call netpoll and the
network TX helpers with interrupt disable, given the TX are called with
target_list_lock.
====================

Link: https://patch.msgid.link/20260206-nbcon-v7-0-62bda69b1b41@debian.org


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parents ad1f18e9 79ba362b
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -341,6 +341,7 @@ config NETCONSOLE_DYNAMIC
	bool "Dynamic reconfiguration of logging targets"
	depends on NETCONSOLE && SYSFS && CONFIGFS_FS && \
			!(NETCONSOLE=y && CONFIGFS_FS=m)
	select PRINTK_EXECUTION_CTX
	help
	  This option enables the ability to dynamically reconfigure target
	  parameters (interface, IP addresses, port numbers, MAC addresses)
+94 −59
Original line number Diff line number Diff line
@@ -1490,18 +1490,20 @@ 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)
static int sysdata_append_cpu_nr(struct netconsole_target *nt, int offset,
				 struct nbcon_write_context *wctxt)
{
	return scnprintf(&nt->sysdata[offset],
			 MAX_EXTRADATA_ENTRY_LEN, " cpu=%u\n",
			 raw_smp_processor_id());
			 wctxt->cpu);
}

static int sysdata_append_taskname(struct netconsole_target *nt, int offset)
static int sysdata_append_taskname(struct netconsole_target *nt, int offset,
				   struct nbcon_write_context *wctxt)
{
	return scnprintf(&nt->sysdata[offset],
			 MAX_EXTRADATA_ENTRY_LEN, " taskname=%s\n",
			 current->comm);
			 wctxt->comm);
}

static int sysdata_append_release(struct netconsole_target *nt, int offset)
@@ -1522,8 +1524,10 @@ static int sysdata_append_msgid(struct netconsole_target *nt, int offset)
/*
 * prepare_sysdata - append sysdata in runtime
 * @nt: target to send message to
 * @wctxt: nbcon write context containing message metadata
 */
static int prepare_sysdata(struct netconsole_target *nt)
static int prepare_sysdata(struct netconsole_target *nt,
			   struct nbcon_write_context *wctxt)
{
	int sysdata_len = 0;

@@ -1531,9 +1535,9 @@ static int prepare_sysdata(struct netconsole_target *nt)
		goto out;

	if (nt->sysdata_fields & SYSDATA_CPU_NR)
		sysdata_len += sysdata_append_cpu_nr(nt, sysdata_len);
		sysdata_len += sysdata_append_cpu_nr(nt, sysdata_len, wctxt);
	if (nt->sysdata_fields & SYSDATA_TASKNAME)
		sysdata_len += sysdata_append_taskname(nt, sysdata_len);
		sysdata_len += sysdata_append_taskname(nt, sysdata_len, wctxt);
	if (nt->sysdata_fields & SYSDATA_RELEASE)
		sysdata_len += sysdata_append_release(nt, sysdata_len);
	if (nt->sysdata_fields & SYSDATA_MSGID)
@@ -1831,83 +1835,108 @@ static void send_msg_fragmented(struct netconsole_target *nt,
/**
 * 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
 * @wctxt: nbcon write context containing message and metadata
 *
 * Transfer extended log @msg to @nt.  If @msg is longer than
 * Transfer extended log message to @nt.  If message 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)
static void send_ext_msg_udp(struct netconsole_target *nt,
			     struct nbcon_write_context *wctxt)
{
	int userdata_len = 0;
	int release_len = 0;
	int sysdata_len = 0;
	int len;

#ifdef CONFIG_NETCONSOLE_DYNAMIC
	sysdata_len = prepare_sysdata(nt);
	sysdata_len = prepare_sysdata(nt, wctxt);
	userdata_len = nt->userdata_length;
#endif
	if (nt->release)
		release_len = strlen(init_utsname()->release) + 1;

	if (msg_len + release_len + sysdata_len + userdata_len <= MAX_PRINT_CHUNK)
		return send_msg_no_fragmentation(nt, msg, msg_len, release_len);
	len = wctxt->len + release_len + sysdata_len + userdata_len;
	if (len <= MAX_PRINT_CHUNK)
		return send_msg_no_fragmentation(nt, wctxt->outbuf,
						 wctxt->len, release_len);

	return send_msg_fragmented(nt, msg, msg_len, release_len,
	return send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len,
				   sysdata_len);
}

static void write_ext_msg(struct console *con, const char *msg,
static void send_msg_udp(struct netconsole_target *nt, const char *msg,
			 unsigned int len)
{
	struct netconsole_target *nt;
	unsigned long flags;

	if ((oops_only && !oops_in_progress) || list_empty(&target_list))
		return;
	const char *tmp = msg;
	int frag, left = len;

	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);
	while (left > 0) {
		frag = min(left, MAX_PRINT_CHUNK);
		send_udp(nt, tmp, frag);
		tmp += frag;
		left -= frag;
	}
}

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)
{
	int frag, left;
	unsigned long flags;
	struct netconsole_target *nt;
	const char *tmp;

	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.
		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.
		 */
			tmp = msg;
			for (left = len; left;) {
				frag = min(left, MAX_PRINT_CHUNK);
				send_udp(nt, tmp, frag);
				tmp += frag;
				left -= frag;
		if (!nbcon_enter_unsafe(wctxt))
			return;

		if (extended)
			send_ext_msg_udp(nt, wctxt);
		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);
}

@@ -2072,14 +2101,20 @@ 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,
	.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,
	.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)
+8 −0
Original line number Diff line number Diff line
@@ -298,12 +298,20 @@ struct nbcon_context {
 * @outbuf:		Pointer to the text buffer for output
 * @len:		Length to write
 * @unsafe_takeover:	If a hostile takeover in an unsafe state has occurred
 * @cpu:		CPU on which the message was generated
 * @pid:		PID of the task that generated the message
 * @comm:		Name of the task that generated the message
 */
struct nbcon_write_context {
	struct nbcon_context	__private ctxt;
	char			*outbuf;
	unsigned int		len;
	bool			unsafe_takeover;
#ifdef CONFIG_PRINTK_EXECUTION_CTX
	int			cpu;
	pid_t			pid;
	char			comm[TASK_COMM_LEN];
#endif
};

/**
+8 −0
Original line number Diff line number Diff line
@@ -281,12 +281,20 @@ struct printk_buffers {
 *		nothing to output and this record should be skipped.
 * @seq:	The sequence number of the record used for @pbufs->outbuf.
 * @dropped:	The number of dropped records from reading @seq.
 * @cpu:	CPU on which the message was generated.
 * @pid:	PID of the task that generated the message
 * @comm:	Name of the task that generated the message.
 */
struct printk_message {
	struct printk_buffers	*pbufs;
	unsigned int		outbuf_len;
	u64			seq;
	unsigned long		dropped;
#ifdef CONFIG_PRINTK_EXECUTION_CTX
	int			cpu;
	pid_t			pid;
	char			comm[TASK_COMM_LEN];
#endif
};

bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
+16 −0
Original line number Diff line number Diff line
@@ -946,6 +946,20 @@ void nbcon_reacquire_nobuf(struct nbcon_write_context *wctxt)
}
EXPORT_SYMBOL_GPL(nbcon_reacquire_nobuf);

#ifdef CONFIG_PRINTK_EXECUTION_CTX
static void wctxt_load_execution_ctx(struct nbcon_write_context *wctxt,
				     struct printk_message *pmsg)
{
	wctxt->cpu = pmsg->cpu;
	wctxt->pid = pmsg->pid;
	memcpy(wctxt->comm, pmsg->comm, sizeof(wctxt->comm));
	static_assert(sizeof(wctxt->comm) == sizeof(pmsg->comm));
}
#else
static void wctxt_load_execution_ctx(struct nbcon_write_context *wctxt,
				     struct printk_message *pmsg) {}
#endif

/**
 * nbcon_emit_next_record - Emit a record in the acquired context
 * @wctxt:	The write context that will be handed to the write function
@@ -1048,6 +1062,8 @@ static bool nbcon_emit_next_record(struct nbcon_write_context *wctxt, bool use_a
	/* Initialize the write context for driver callbacks. */
	nbcon_write_context_set_buf(wctxt, &pmsg.pbufs->outbuf[0], pmsg.outbuf_len);

	wctxt_load_execution_ctx(wctxt, &pmsg);

	if (use_atomic)
		con->write_atomic(con, wctxt);
	else
Loading