diff --git a/drivers/net/Kconfig b/drivers/net/Kconfig index 9c7953f8e637..7e9becad91df 100644 --- a/drivers/net/Kconfig +++ b/drivers/net/Kconfig @@ -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) diff --git a/drivers/net/netconsole.c b/drivers/net/netconsole.c index 82c232f9ede2..0f44ce5ccc0a 100644 --- a/drivers/net/netconsole.c +++ b/drivers/net/netconsole.c @@ -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, - unsigned int len) +static void send_msg_udp(struct netconsole_target *nt, const char *msg, + unsigned int len) { - struct netconsole_target *nt; - unsigned long flags; + const char *tmp = msg; + int frag, left = len; - 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); + 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. - */ - tmp = msg; - for (left = len; left;) { - frag = min(left, MAX_PRINT_CHUNK); - send_udp(nt, tmp, frag); - tmp += frag; - left -= frag; - } - } + 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); + 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); } @@ -2071,15 +2100,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) diff --git a/include/linux/console.h b/include/linux/console.h index fc9f5c5c1b04..cc5dc3bf58b6 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -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 }; /** diff --git a/kernel/printk/internal.h b/kernel/printk/internal.h index 5f5f626f4279..5fdea5682756 100644 --- a/kernel/printk/internal.h +++ b/kernel/printk/internal.h @@ -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, diff --git a/kernel/printk/nbcon.c b/kernel/printk/nbcon.c index 32fc12e53675..f6d22510fdc3 100644 --- a/kernel/printk/nbcon.c +++ b/kernel/printk/nbcon.c @@ -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 diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 1d765ad242b8..cf6b52861036 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -2131,12 +2131,40 @@ static inline void printk_delay(int level) } } +#define CALLER_ID_MASK 0x80000000 + static inline u32 printk_caller_id(void) { return in_task() ? task_pid_nr(current) : - 0x80000000 + smp_processor_id(); + CALLER_ID_MASK + smp_processor_id(); } +#ifdef CONFIG_PRINTK_EXECUTION_CTX +/* Store the opposite info than caller_id. */ +static u32 printk_caller_id2(void) +{ + return !in_task() ? task_pid_nr(current) : + CALLER_ID_MASK + smp_processor_id(); +} + +static pid_t printk_info_get_pid(const struct printk_info *info) +{ + u32 caller_id = info->caller_id; + u32 caller_id2 = info->caller_id2; + + return caller_id & CALLER_ID_MASK ? caller_id2 : caller_id; +} + +static int printk_info_get_cpu(const struct printk_info *info) +{ + u32 caller_id = info->caller_id; + u32 caller_id2 = info->caller_id2; + + return ((caller_id & CALLER_ID_MASK ? + caller_id : caller_id2) & ~CALLER_ID_MASK); +} +#endif + /** * printk_parse_prefix - Parse level and control flags. * @@ -2213,6 +2241,28 @@ static u16 printk_sprint(char *text, u16 size, int facility, return text_len; } +#ifdef CONFIG_PRINTK_EXECUTION_CTX +static void printk_store_execution_ctx(struct printk_info *info) +{ + info->caller_id2 = printk_caller_id2(); + get_task_comm(info->comm, current); +} + +static void pmsg_load_execution_ctx(struct printk_message *pmsg, + const struct printk_info *info) +{ + pmsg->cpu = printk_info_get_cpu(info); + pmsg->pid = printk_info_get_pid(info); + memcpy(pmsg->comm, info->comm, sizeof(pmsg->comm)); + static_assert(sizeof(pmsg->comm) == sizeof(info->comm)); +} +#else +static void printk_store_execution_ctx(struct printk_info *info) {} + +static void pmsg_load_execution_ctx(struct printk_message *pmsg, + const struct printk_info *info) {} +#endif + __printf(4, 0) int vprintk_store(int facility, int level, const struct dev_printk_info *dev_info, @@ -2320,6 +2370,7 @@ int vprintk_store(int facility, int level, r.info->caller_id = caller_id; if (dev_info) memcpy(&r.info->dev_info, dev_info, sizeof(r.info->dev_info)); + printk_store_execution_ctx(r.info); /* A message without a trailing newline can be continued. */ if (!(flags & LOG_NEWLINE)) @@ -3002,6 +3053,7 @@ bool printk_get_next_message(struct printk_message *pmsg, u64 seq, pmsg->seq = r.info->seq; pmsg->dropped = r.info->seq - seq; force_con = r.info->flags & LOG_FORCE_CON; + pmsg_load_execution_ctx(pmsg, r.info); /* * Skip records that are not forced to be printed on consoles and that diff --git a/kernel/printk/printk_ringbuffer.h b/kernel/printk/printk_ringbuffer.h index 4ef81349d9fb..1651b53ece34 100644 --- a/kernel/printk/printk_ringbuffer.h +++ b/kernel/printk/printk_ringbuffer.h @@ -23,6 +23,11 @@ struct printk_info { u8 flags:5; /* internal record flags */ u8 level:3; /* syslog level */ u32 caller_id; /* thread id or processor id */ +#ifdef CONFIG_PRINTK_EXECUTION_CTX + u32 caller_id2; /* caller_id complement */ + /* name of the task that generated the message */ + char comm[TASK_COMM_LEN]; +#endif struct dev_printk_info dev_info; }; diff --git a/lib/Kconfig.debug b/lib/Kconfig.debug index ba36939fda79..938d967ae3bf 100644 --- a/lib/Kconfig.debug +++ b/lib/Kconfig.debug @@ -35,6 +35,18 @@ config PRINTK_CALLER no option to enable/disable at the kernel command line parameter or sysfs interface. +config PRINTK_EXECUTION_CTX + bool + depends on PRINTK + help + This option extends struct printk_info to include extra execution + context in printk, such as task name and CPU number from where the + message originated. This is useful for correlating printk messages + with specific execution contexts. + + This is automatically enabled when a console driver that supports + execution context is selected. + config STACKTRACE_BUILD_ID bool "Show build ID information in stacktraces" depends on PRINTK