From: Breno Leitao <leitao@debian.org>
To: Andrew Lunn <andrew+netdev@lunn.ch>,
"David S. Miller" <davem@davemloft.net>,
Eric Dumazet <edumazet@google.com>,
Jakub Kicinski <kuba@kernel.org>,
Paolo Abeni <pabeni@redhat.com>, Shuah Khan <shuah@kernel.org>
Cc: netdev@vger.kernel.org, linux-kernel@vger.kernel.org,
linux-kselftest@vger.kernel.org, gustavold@gmail.com,
asantostc@gmail.com, Breno Leitao <leitao@debian.org>,
kernel-team@meta.com
Subject: [PATCH net-next v2 1/2] netconsole: publish the userdata payload with RCU
Date: Wed, 05 Aug 2026 03:58:19 -0700 [thread overview]
Message-ID: <20260805-netcons-userdata-rcu-v2-1-c5e80f6a7a46@debian.org> (raw)
In-Reply-To: <20260805-netcons-userdata-rcu-v2-0-c5e80f6a7a46@debian.org>
update_userdata() takes target_list_lock to swap nt->userdata and
nt->userdata_length, then frees the old buffer. Since commit
7eab73b18630 ("netconsole: convert to NBCON console infrastructure")
that lock is also the console's device_lock, so writing a userdata value
from configfs serialises against the printk core emitting messages.
The buffer is immutable once published, which is what RCU is for. Move
the string and its length into a single netcons_userdata object and
publish it with rcu_replace_pointer(), freeing the old one with
kfree_rcu().
New userdata design:
0) Unify the userdata fields into a struct netcons_userdata
1) update_userdata() no longer needs target_list_lock.
2) writers stay serialised by dynamic_netconsole_mutex.
3) reading userdata needs an RCU read lock.
No functional change intended.
Signed-off-by: Breno Leitao <leitao@debian.org>
---
drivers/net/netconsole.c | 92 +++++++++++++++++++++++++++++-------------------
1 file changed, 56 insertions(+), 36 deletions(-)
diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c
index 862001d09aa84..14092fb15f6d0 100644
--- a/drivers/net/netconsole.c
+++ b/drivers/net/netconsole.c
@@ -135,13 +135,27 @@ enum target_state {
STATE_DEACTIVATED,
};
+/**
+ * struct netcons_userdata - Formatted userdata payload of a target.
+ * @rcu: Used to free the payload after a grace period.
+ * @length: Length of @data, excluding the NUL terminator.
+ * @data: Formatted " key=value\n" entries, NUL terminated.
+ *
+ * Immutable once published, so the transmit path never observes @data and
+ * @length disagreeing.
+ */
+struct netcons_userdata {
+ struct rcu_head rcu;
+ size_t length;
+ char data[];
+};
+
/**
* struct netconsole_target - Represents a configured netconsole target.
* @list: Links this target into the target_list.
* @group: Links us into the configfs subsystem hierarchy.
* @userdata_group: Links to the userdata configfs hierarchy
- * @userdata: Cached, formatted string of append
- * @userdata_length: String length of userdata.
+ * @userdata: Cached, formatted userdata payload. RCU protected.
* @sysdata: Cached, formatted string of append
* @sysdata_fields: Sysdata features enabled.
* @msgcounter: Message sent counter.
@@ -176,8 +190,7 @@ struct netconsole_target {
#ifdef CONFIG_NETCONSOLE_DYNAMIC
struct config_group group;
struct config_group userdata_group;
- char *userdata;
- size_t userdata_length;
+ struct netcons_userdata __rcu *userdata;
char sysdata[MAX_EXTRADATA_ENTRY_LEN * MAX_SYSDATA_ITEMS];
/* bit-wise with sysdata_feature bits */
@@ -1059,12 +1072,11 @@ static int calc_userdata_len(struct netconsole_target *nt)
static int update_userdata(struct netconsole_target *nt)
{
+ struct netcons_userdata *new = NULL;
+ struct netcons_userdata *old;
struct userdatum *udm_item;
struct config_item *item;
struct list_head *entry;
- char *old_buf = NULL;
- char *new_buf = NULL;
- unsigned long flags;
int offset = 0;
int len;
@@ -1076,8 +1088,8 @@ static int update_userdata(struct netconsole_target *nt)
/* Allocate new buffer */
if (len) {
- new_buf = kmalloc(len + 1, GFP_KERNEL);
- if (!new_buf)
+ new = kmalloc_flex(*new, data, len + 1);
+ if (!new)
return -ENOMEM;
}
@@ -1087,22 +1099,21 @@ static int update_userdata(struct netconsole_target *nt)
udm_item = to_userdatum(item);
/* Skip userdata with no value set */
if (udm_item->value[0]) {
- offset += scnprintf(&new_buf[offset], len + 1 - offset,
+ offset += scnprintf(&new->data[offset],
+ len + 1 - offset,
" %s=%s\n", item->ci_name,
udm_item->value);
}
}
WARN_ON_ONCE(offset != len);
+ if (new)
+ new->length = offset;
- /* Switch to new buffer and free old buffer */
- spin_lock_irqsave(&target_list_lock, flags);
- old_buf = nt->userdata;
- nt->userdata = new_buf;
- nt->userdata_length = offset;
- spin_unlock_irqrestore(&target_list_lock, flags);
-
- kfree(old_buf);
+ /* Writers are serialized by dynamic_netconsole_mutex. */
+ old = rcu_replace_pointer(nt->userdata, new,
+ lockdep_is_held(&dynamic_netconsole_mutex));
+ kfree_rcu(old, rcu);
return 0;
}
@@ -1392,7 +1403,7 @@ static void netconsole_target_release(struct config_item *item)
{
struct netconsole_target *nt = to_target(item);
- kfree(nt->userdata);
+ kfree(rcu_access_pointer(nt->userdata));
kfree(nt);
}
@@ -1926,14 +1937,13 @@ static void send_udp(struct netconsole_target *nt, const char *msg, int len)
static void send_msg_no_fragmentation(struct netconsole_target *nt,
const char *msg,
int msg_len,
- int release_len)
+ int release_len,
+ const struct netcons_userdata *userdata)
{
- const char *userdata = NULL;
const char *sysdata = NULL;
const char *release;
#ifdef CONFIG_NETCONSOLE_DYNAMIC
- userdata = nt->userdata;
sysdata = nt->sysdata;
#endif
@@ -1950,7 +1960,7 @@ static void send_msg_no_fragmentation(struct netconsole_target *nt,
if (userdata)
msg_len += scnprintf(&nt->buf[msg_len],
sizeof(nt->buf) - msg_len, "%s",
- userdata);
+ userdata->data);
if (sysdata)
msg_len += scnprintf(&nt->buf[msg_len],
@@ -1970,7 +1980,8 @@ static void append_release(char *buf)
static void send_fragmented_body(struct netconsole_target *nt,
const char *msgbody_ptr, int header_len,
- int msgbody_len, int sysdata_len)
+ int msgbody_len, int sysdata_len,
+ const struct netcons_userdata *userdata)
{
const char *userdata_ptr = NULL;
const char *sysdata_ptr = NULL;
@@ -1981,12 +1992,12 @@ static void send_fragmented_body(struct netconsole_target *nt,
int userdata_len = 0;
#ifdef CONFIG_NETCONSOLE_DYNAMIC
- userdata_ptr = nt->userdata;
sysdata_ptr = nt->sysdata;
- userdata_len = nt->userdata_length;
#endif
- if (WARN_ON_ONCE(!userdata_ptr && userdata_len != 0))
- return;
+ if (userdata) {
+ userdata_ptr = userdata->data;
+ userdata_len = userdata->length;
+ }
if (WARN_ON_ONCE(!sysdata_ptr && sysdata_len != 0))
return;
@@ -2063,7 +2074,8 @@ static void send_msg_fragmented(struct netconsole_target *nt,
const char *msg,
int msg_len,
int release_len,
- int sysdata_len)
+ int sysdata_len,
+ const struct netcons_userdata *userdata)
{
int header_len, msgbody_len;
const char *msgbody;
@@ -2092,7 +2104,7 @@ static void send_msg_fragmented(struct netconsole_target *nt,
* will be replaced
*/
send_fragmented_body(nt, msgbody, header_len, msgbody_len,
- sysdata_len);
+ sysdata_len, userdata);
}
/**
@@ -2107,25 +2119,33 @@ static void send_msg_fragmented(struct netconsole_target *nt,
static void send_ext_msg_udp(struct netconsole_target *nt,
struct nbcon_write_context *wctxt)
{
+ const struct netcons_userdata *userdata = NULL;
int userdata_len = 0;
int release_len = 0;
int sysdata_len = 0;
int len;
+ /* Keeps the payload picked below alive until the last send_udp(). */
+ rcu_read_lock();
+
#ifdef CONFIG_NETCONSOLE_DYNAMIC
sysdata_len = prepare_sysdata(nt, wctxt);
- userdata_len = nt->userdata_length;
+ userdata = rcu_dereference(nt->userdata);
+ if (userdata)
+ userdata_len = userdata->length;
#endif
if (nt->release)
release_len = strlen(init_utsname()->release) + 1;
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);
+ send_msg_no_fragmentation(nt, wctxt->outbuf, wctxt->len,
+ release_len, userdata);
+ else
+ send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len,
+ sysdata_len, userdata);
- return send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len,
- sysdata_len);
+ rcu_read_unlock();
}
static void send_msg_udp(struct netconsole_target *nt, const char *msg,
@@ -2357,7 +2377,7 @@ static void free_param_target(struct netconsole_target *nt)
cancel_work_sync(&nt->resume_wq);
netpoll_cleanup(&nt->np);
#ifdef CONFIG_NETCONSOLE_DYNAMIC
- kfree(nt->userdata);
+ kfree(rcu_access_pointer(nt->userdata));
#endif
kfree(nt);
}
--
2.53.0-Meta
next prev parent reply other threads:[~2026-08-05 10:59 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-08-05 10:58 [PATCH net-next v2 0/2] netconsole: replace target_list_lock by RCU on userdata hot path Breno Leitao
2026-08-05 10:58 ` Breno Leitao [this message]
2026-08-05 14:22 ` [PATCH net-next v2 1/2] netconsole: publish the userdata payload with RCU Gustavo Luiz Duarte
2026-08-05 10:58 ` [PATCH net-next v2 2/2] selftests: netconsole: add a userdata torture test Breno Leitao
2026-08-05 14:22 ` Gustavo Luiz Duarte
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260805-netcons-userdata-rcu-v2-1-c5e80f6a7a46@debian.org \
--to=leitao@debian.org \
--cc=andrew+netdev@lunn.ch \
--cc=asantostc@gmail.com \
--cc=davem@davemloft.net \
--cc=edumazet@google.com \
--cc=gustavold@gmail.com \
--cc=kernel-team@meta.com \
--cc=kuba@kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-kselftest@vger.kernel.org \
--cc=netdev@vger.kernel.org \
--cc=pabeni@redhat.com \
--cc=shuah@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox