Skip to content

Commit f2c7fde

Browse files
committed
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 187de7c ("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: Jakub Kicinski <kuba@kernel.org>
2 parents ad1f18e + 79ba362 commit f2c7fde

8 files changed

Lines changed: 197 additions & 60 deletions

File tree

drivers/net/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,7 @@ config NETCONSOLE_DYNAMIC
341341
bool "Dynamic reconfiguration of logging targets"
342342
depends on NETCONSOLE && SYSFS && CONFIGFS_FS && \
343343
!(NETCONSOLE=y && CONFIGFS_FS=m)
344+
select PRINTK_EXECUTION_CTX
344345
help
345346
This option enables the ability to dynamically reconfigure target
346347
parameters (interface, IP addresses, port numbers, MAC addresses)

drivers/net/netconsole.c

Lines changed: 94 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,18 +1490,20 @@ static void populate_configfs_item(struct netconsole_target *nt,
14901490
init_target_config_group(nt, target_name);
14911491
}
14921492

1493-
static int sysdata_append_cpu_nr(struct netconsole_target *nt, int offset)
1493+
static int sysdata_append_cpu_nr(struct netconsole_target *nt, int offset,
1494+
struct nbcon_write_context *wctxt)
14941495
{
14951496
return scnprintf(&nt->sysdata[offset],
14961497
MAX_EXTRADATA_ENTRY_LEN, " cpu=%u\n",
1497-
raw_smp_processor_id());
1498+
wctxt->cpu);
14981499
}
14991500

1500-
static int sysdata_append_taskname(struct netconsole_target *nt, int offset)
1501+
static int sysdata_append_taskname(struct netconsole_target *nt, int offset,
1502+
struct nbcon_write_context *wctxt)
15011503
{
15021504
return scnprintf(&nt->sysdata[offset],
15031505
MAX_EXTRADATA_ENTRY_LEN, " taskname=%s\n",
1504-
current->comm);
1506+
wctxt->comm);
15051507
}
15061508

15071509
static int sysdata_append_release(struct netconsole_target *nt, int offset)
@@ -1522,18 +1524,20 @@ static int sysdata_append_msgid(struct netconsole_target *nt, int offset)
15221524
/*
15231525
* prepare_sysdata - append sysdata in runtime
15241526
* @nt: target to send message to
1527+
* @wctxt: nbcon write context containing message metadata
15251528
*/
1526-
static int prepare_sysdata(struct netconsole_target *nt)
1529+
static int prepare_sysdata(struct netconsole_target *nt,
1530+
struct nbcon_write_context *wctxt)
15271531
{
15281532
int sysdata_len = 0;
15291533

15301534
if (!nt->sysdata_fields)
15311535
goto out;
15321536

15331537
if (nt->sysdata_fields & SYSDATA_CPU_NR)
1534-
sysdata_len += sysdata_append_cpu_nr(nt, sysdata_len);
1538+
sysdata_len += sysdata_append_cpu_nr(nt, sysdata_len, wctxt);
15351539
if (nt->sysdata_fields & SYSDATA_TASKNAME)
1536-
sysdata_len += sysdata_append_taskname(nt, sysdata_len);
1540+
sysdata_len += sysdata_append_taskname(nt, sysdata_len, wctxt);
15371541
if (nt->sysdata_fields & SYSDATA_RELEASE)
15381542
sysdata_len += sysdata_append_release(nt, sysdata_len);
15391543
if (nt->sysdata_fields & SYSDATA_MSGID)
@@ -1831,83 +1835,108 @@ static void send_msg_fragmented(struct netconsole_target *nt,
18311835
/**
18321836
* send_ext_msg_udp - send extended log message to target
18331837
* @nt: target to send message to
1834-
* @msg: extended log message to send
1835-
* @msg_len: length of message
1838+
* @wctxt: nbcon write context containing message and metadata
18361839
*
1837-
* Transfer extended log @msg to @nt. If @msg is longer than
1840+
* Transfer extended log message to @nt. If message is longer than
18381841
* MAX_PRINT_CHUNK, it'll be split and transmitted in multiple chunks with
18391842
* ncfrag header field added to identify them.
18401843
*/
1841-
static void send_ext_msg_udp(struct netconsole_target *nt, const char *msg,
1842-
int msg_len)
1844+
static void send_ext_msg_udp(struct netconsole_target *nt,
1845+
struct nbcon_write_context *wctxt)
18431846
{
18441847
int userdata_len = 0;
18451848
int release_len = 0;
18461849
int sysdata_len = 0;
1850+
int len;
18471851

18481852
#ifdef CONFIG_NETCONSOLE_DYNAMIC
1849-
sysdata_len = prepare_sysdata(nt);
1853+
sysdata_len = prepare_sysdata(nt, wctxt);
18501854
userdata_len = nt->userdata_length;
18511855
#endif
18521856
if (nt->release)
18531857
release_len = strlen(init_utsname()->release) + 1;
18541858

1855-
if (msg_len + release_len + sysdata_len + userdata_len <= MAX_PRINT_CHUNK)
1856-
return send_msg_no_fragmentation(nt, msg, msg_len, release_len);
1859+
len = wctxt->len + release_len + sysdata_len + userdata_len;
1860+
if (len <= MAX_PRINT_CHUNK)
1861+
return send_msg_no_fragmentation(nt, wctxt->outbuf,
1862+
wctxt->len, release_len);
18571863

1858-
return send_msg_fragmented(nt, msg, msg_len, release_len,
1864+
return send_msg_fragmented(nt, wctxt->outbuf, wctxt->len, release_len,
18591865
sysdata_len);
18601866
}
18611867

1862-
static void write_ext_msg(struct console *con, const char *msg,
1863-
unsigned int len)
1868+
static void send_msg_udp(struct netconsole_target *nt, const char *msg,
1869+
unsigned int len)
18641870
{
1865-
struct netconsole_target *nt;
1866-
unsigned long flags;
1867-
1868-
if ((oops_only && !oops_in_progress) || list_empty(&target_list))
1869-
return;
1871+
const char *tmp = msg;
1872+
int frag, left = len;
18701873

1871-
spin_lock_irqsave(&target_list_lock, flags);
1872-
list_for_each_entry(nt, &target_list, list)
1873-
if (nt->extended && nt->state == STATE_ENABLED &&
1874-
netif_running(nt->np.dev))
1875-
send_ext_msg_udp(nt, msg, len);
1876-
spin_unlock_irqrestore(&target_list_lock, flags);
1874+
while (left > 0) {
1875+
frag = min(left, MAX_PRINT_CHUNK);
1876+
send_udp(nt, tmp, frag);
1877+
tmp += frag;
1878+
left -= frag;
1879+
}
18771880
}
18781881

1879-
static void write_msg(struct console *con, const char *msg, unsigned int len)
1882+
/**
1883+
* netconsole_write - Generic function to send a msg to all targets
1884+
* @wctxt: nbcon write context
1885+
* @extended: "true" for extended console mode
1886+
*
1887+
* Given an nbcon write context, send the message to the netconsole targets
1888+
*/
1889+
static void netconsole_write(struct nbcon_write_context *wctxt, bool extended)
18801890
{
1881-
int frag, left;
1882-
unsigned long flags;
18831891
struct netconsole_target *nt;
1884-
const char *tmp;
18851892

18861893
if (oops_only && !oops_in_progress)
18871894
return;
1888-
/* Avoid taking lock and disabling interrupts unnecessarily */
1889-
if (list_empty(&target_list))
1890-
return;
18911895

1892-
spin_lock_irqsave(&target_list_lock, flags);
18931896
list_for_each_entry(nt, &target_list, list) {
1894-
if (!nt->extended && nt->state == STATE_ENABLED &&
1895-
netif_running(nt->np.dev)) {
1896-
/*
1897-
* We nest this inside the for-each-target loop above
1898-
* so that we're able to get as much logging out to
1899-
* at least one target if we die inside here, instead
1900-
* of unnecessarily keeping all targets in lock-step.
1901-
*/
1902-
tmp = msg;
1903-
for (left = len; left;) {
1904-
frag = min(left, MAX_PRINT_CHUNK);
1905-
send_udp(nt, tmp, frag);
1906-
tmp += frag;
1907-
left -= frag;
1908-
}
1909-
}
1897+
if (nt->extended != extended || nt->state != STATE_ENABLED ||
1898+
!netif_running(nt->np.dev))
1899+
continue;
1900+
1901+
/* If nbcon_enter_unsafe() fails, just return given netconsole
1902+
* lost the ownership, and iterating over the targets will not
1903+
* be able to re-acquire.
1904+
*/
1905+
if (!nbcon_enter_unsafe(wctxt))
1906+
return;
1907+
1908+
if (extended)
1909+
send_ext_msg_udp(nt, wctxt);
1910+
else
1911+
send_msg_udp(nt, wctxt->outbuf, wctxt->len);
1912+
1913+
nbcon_exit_unsafe(wctxt);
19101914
}
1915+
}
1916+
1917+
static void netconsole_write_ext(struct console *con __always_unused,
1918+
struct nbcon_write_context *wctxt)
1919+
{
1920+
netconsole_write(wctxt, true);
1921+
}
1922+
1923+
static void netconsole_write_basic(struct console *con __always_unused,
1924+
struct nbcon_write_context *wctxt)
1925+
{
1926+
netconsole_write(wctxt, false);
1927+
}
1928+
1929+
static void netconsole_device_lock(struct console *con __always_unused,
1930+
unsigned long *flags)
1931+
__acquires(&target_list_lock)
1932+
{
1933+
spin_lock_irqsave(&target_list_lock, *flags);
1934+
}
1935+
1936+
static void netconsole_device_unlock(struct console *con __always_unused,
1937+
unsigned long flags)
1938+
__releases(&target_list_lock)
1939+
{
19111940
spin_unlock_irqrestore(&target_list_lock, flags);
19121941
}
19131942

@@ -2071,15 +2100,21 @@ static void free_param_target(struct netconsole_target *nt)
20712100
}
20722101

20732102
static struct console netconsole_ext = {
2074-
.name = "netcon_ext",
2075-
.flags = CON_ENABLED | CON_EXTENDED,
2076-
.write = write_ext_msg,
2103+
.name = "netcon_ext",
2104+
.flags = CON_ENABLED | CON_EXTENDED | CON_NBCON | CON_NBCON_ATOMIC_UNSAFE,
2105+
.write_thread = netconsole_write_ext,
2106+
.write_atomic = netconsole_write_ext,
2107+
.device_lock = netconsole_device_lock,
2108+
.device_unlock = netconsole_device_unlock,
20772109
};
20782110

20792111
static struct console netconsole = {
2080-
.name = "netcon",
2081-
.flags = CON_ENABLED,
2082-
.write = write_msg,
2112+
.name = "netcon",
2113+
.flags = CON_ENABLED | CON_NBCON | CON_NBCON_ATOMIC_UNSAFE,
2114+
.write_thread = netconsole_write_basic,
2115+
.write_atomic = netconsole_write_basic,
2116+
.device_lock = netconsole_device_lock,
2117+
.device_unlock = netconsole_device_unlock,
20832118
};
20842119

20852120
static int __init init_netconsole(void)

include/linux/console.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,20 @@ struct nbcon_context {
298298
* @outbuf: Pointer to the text buffer for output
299299
* @len: Length to write
300300
* @unsafe_takeover: If a hostile takeover in an unsafe state has occurred
301+
* @cpu: CPU on which the message was generated
302+
* @pid: PID of the task that generated the message
303+
* @comm: Name of the task that generated the message
301304
*/
302305
struct nbcon_write_context {
303306
struct nbcon_context __private ctxt;
304307
char *outbuf;
305308
unsigned int len;
306309
bool unsafe_takeover;
310+
#ifdef CONFIG_PRINTK_EXECUTION_CTX
311+
int cpu;
312+
pid_t pid;
313+
char comm[TASK_COMM_LEN];
314+
#endif
307315
};
308316

309317
/**

kernel/printk/internal.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,12 +281,20 @@ struct printk_buffers {
281281
* nothing to output and this record should be skipped.
282282
* @seq: The sequence number of the record used for @pbufs->outbuf.
283283
* @dropped: The number of dropped records from reading @seq.
284+
* @cpu: CPU on which the message was generated.
285+
* @pid: PID of the task that generated the message
286+
* @comm: Name of the task that generated the message.
284287
*/
285288
struct printk_message {
286289
struct printk_buffers *pbufs;
287290
unsigned int outbuf_len;
288291
u64 seq;
289292
unsigned long dropped;
293+
#ifdef CONFIG_PRINTK_EXECUTION_CTX
294+
int cpu;
295+
pid_t pid;
296+
char comm[TASK_COMM_LEN];
297+
#endif
290298
};
291299

292300
bool printk_get_next_message(struct printk_message *pmsg, u64 seq,

kernel/printk/nbcon.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,20 @@ void nbcon_reacquire_nobuf(struct nbcon_write_context *wctxt)
946946
}
947947
EXPORT_SYMBOL_GPL(nbcon_reacquire_nobuf);
948948

949+
#ifdef CONFIG_PRINTK_EXECUTION_CTX
950+
static void wctxt_load_execution_ctx(struct nbcon_write_context *wctxt,
951+
struct printk_message *pmsg)
952+
{
953+
wctxt->cpu = pmsg->cpu;
954+
wctxt->pid = pmsg->pid;
955+
memcpy(wctxt->comm, pmsg->comm, sizeof(wctxt->comm));
956+
static_assert(sizeof(wctxt->comm) == sizeof(pmsg->comm));
957+
}
958+
#else
959+
static void wctxt_load_execution_ctx(struct nbcon_write_context *wctxt,
960+
struct printk_message *pmsg) {}
961+
#endif
962+
949963
/**
950964
* nbcon_emit_next_record - Emit a record in the acquired context
951965
* @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
10481062
/* Initialize the write context for driver callbacks. */
10491063
nbcon_write_context_set_buf(wctxt, &pmsg.pbufs->outbuf[0], pmsg.outbuf_len);
10501064

1065+
wctxt_load_execution_ctx(wctxt, &pmsg);
1066+
10511067
if (use_atomic)
10521068
con->write_atomic(con, wctxt);
10531069
else

kernel/printk/printk.c

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2131,11 +2131,39 @@ static inline void printk_delay(int level)
21312131
}
21322132
}
21332133

2134+
#define CALLER_ID_MASK 0x80000000
2135+
21342136
static inline u32 printk_caller_id(void)
21352137
{
21362138
return in_task() ? task_pid_nr(current) :
2137-
0x80000000 + smp_processor_id();
2139+
CALLER_ID_MASK + smp_processor_id();
2140+
}
2141+
2142+
#ifdef CONFIG_PRINTK_EXECUTION_CTX
2143+
/* Store the opposite info than caller_id. */
2144+
static u32 printk_caller_id2(void)
2145+
{
2146+
return !in_task() ? task_pid_nr(current) :
2147+
CALLER_ID_MASK + smp_processor_id();
2148+
}
2149+
2150+
static pid_t printk_info_get_pid(const struct printk_info *info)
2151+
{
2152+
u32 caller_id = info->caller_id;
2153+
u32 caller_id2 = info->caller_id2;
2154+
2155+
return caller_id & CALLER_ID_MASK ? caller_id2 : caller_id;
2156+
}
2157+
2158+
static int printk_info_get_cpu(const struct printk_info *info)
2159+
{
2160+
u32 caller_id = info->caller_id;
2161+
u32 caller_id2 = info->caller_id2;
2162+
2163+
return ((caller_id & CALLER_ID_MASK ?
2164+
caller_id : caller_id2) & ~CALLER_ID_MASK);
21382165
}
2166+
#endif
21392167

21402168
/**
21412169
* printk_parse_prefix - Parse level and control flags.
@@ -2213,6 +2241,28 @@ static u16 printk_sprint(char *text, u16 size, int facility,
22132241
return text_len;
22142242
}
22152243

2244+
#ifdef CONFIG_PRINTK_EXECUTION_CTX
2245+
static void printk_store_execution_ctx(struct printk_info *info)
2246+
{
2247+
info->caller_id2 = printk_caller_id2();
2248+
get_task_comm(info->comm, current);
2249+
}
2250+
2251+
static void pmsg_load_execution_ctx(struct printk_message *pmsg,
2252+
const struct printk_info *info)
2253+
{
2254+
pmsg->cpu = printk_info_get_cpu(info);
2255+
pmsg->pid = printk_info_get_pid(info);
2256+
memcpy(pmsg->comm, info->comm, sizeof(pmsg->comm));
2257+
static_assert(sizeof(pmsg->comm) == sizeof(info->comm));
2258+
}
2259+
#else
2260+
static void printk_store_execution_ctx(struct printk_info *info) {}
2261+
2262+
static void pmsg_load_execution_ctx(struct printk_message *pmsg,
2263+
const struct printk_info *info) {}
2264+
#endif
2265+
22162266
__printf(4, 0)
22172267
int vprintk_store(int facility, int level,
22182268
const struct dev_printk_info *dev_info,
@@ -2320,6 +2370,7 @@ int vprintk_store(int facility, int level,
23202370
r.info->caller_id = caller_id;
23212371
if (dev_info)
23222372
memcpy(&r.info->dev_info, dev_info, sizeof(r.info->dev_info));
2373+
printk_store_execution_ctx(r.info);
23232374

23242375
/* A message without a trailing newline can be continued. */
23252376
if (!(flags & LOG_NEWLINE))
@@ -3002,6 +3053,7 @@ bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
30023053
pmsg->seq = r.info->seq;
30033054
pmsg->dropped = r.info->seq - seq;
30043055
force_con = r.info->flags & LOG_FORCE_CON;
3056+
pmsg_load_execution_ctx(pmsg, r.info);
30053057

30063058
/*
30073059
* Skip records that are not forced to be printed on consoles and that

0 commit comments

Comments
 (0)