From nobody Mon Apr 6 04:57:29 2026 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from vger.kernel.org (vger.kernel.org [23.128.96.18]) by smtp.lore.kernel.org (Postfix) with ESMTP id D2B14ECAAD3 for ; Sat, 10 Sep 2022 22:30:40 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S230032AbiIJWag (ORCPT ); Sat, 10 Sep 2022 18:30:36 -0400 Received: from lindbergh.monkeyblade.net ([23.128.96.19]:46066 "EHLO lindbergh.monkeyblade.net" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S230317AbiIJW3R (ORCPT ); Sat, 10 Sep 2022 18:29:17 -0400 Received: from galois.linutronix.de (Galois.linutronix.de [193.142.43.55]) by lindbergh.monkeyblade.net (Postfix) with ESMTPS id E5877459B3 for ; Sat, 10 Sep 2022 15:28:15 -0700 (PDT) Message-ID: <20220910222301.881787284@linutronix.de> DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020; t=1662848892; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: references:references; bh=ITvwnhbMRopkU0uxXaBKIS73OwMZbbp3Ns73njgnTns=; b=XQ6dwSdqrlUVAuW3dX/LuWdVHeZpXNuW3M3+eyEcQZdTBGwFiooRC8wSHLNwFpOv+L/ARH k58mDsb4ZRQsfEdfEA1L4xjPT0+WEClV3odQlhH7DxneySw58JPc4hjhIJfT45sFZ2F5gG +0KZHXokixbeDTskHf7UbF73+nTQY81EDmiqJqlzn2lUmEKHlDwy5GKahwxAL3qzZbugww 9TUaw6X6BXjKOyQODIa+M9XS5QqrJdwn14WjAprE/g5RLVLLTriqR4Ql7gzM5U6aZ0eoF4 rrLlX0dOjJNg+QviT12lCampD9AoI0OgSQnqAHdNyuZCY3qsQG1z6XRsu2hfdg== DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed; d=linutronix.de; s=2020e; t=1662848892; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:cc:mime-version:mime-version:content-type:content-type: references:references; bh=ITvwnhbMRopkU0uxXaBKIS73OwMZbbp3Ns73njgnTns=; b=v8KQzrfnlvNiRc91Fuxuq5tIQO3uH/GaInbYC1wywePeZeEix9WhTTbTfYFQL32ZzxigNg GUF9GXZn1tJrjADw== From: Thomas Gleixner To: LKML Cc: John Ogness , Petr Mladek , Sergey Senozhatsky , Steven Rostedt , Linus Torvalds , Peter Zijlstra , "Paul E. McKenney" , Daniel Vetter , Greg Kroah-Hartman , Helge Deller , Jason Wessel , Daniel Thompson , John Ogness Subject: [patch RFC 26/29] printk: Add threaded printing support References: <20220910221947.171557773@linutronix.de> MIME-Version: 1.0 Date: Sun, 11 Sep 2022 00:28:12 +0200 (CEST) Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" From: John Ogness Add the infrastructure to create a printer thread per console along with the required thread function which is takeover/handover aware. Signed-off-by: John Ogness Signed-off-by: Thomas Gleixner --- include/linux/console.h | 12 ++ kernel/printk/printk_nobkl.c | 207 ++++++++++++++++++++++++++++++++++++++= +++-- 2 files changed, 214 insertions(+), 5 deletions(-) --- a/include/linux/console.h +++ b/include/linux/console.h @@ -17,6 +17,7 @@ #include #include #include +#include #include =20 struct vc_data; @@ -357,6 +358,12 @@ struct cons_context_data { * * @atomic_state: State array for non-BKL consoles. Real and handover * @atomic_seq: Sequence for record tracking (32bit only) + * @kthread: Pointer to kernel thread + * @rcuwait: RCU wait for the kernel thread + * @kthread_running: Indicator whether the kthread is running + * @thread_txtbuf: Pointer to thread private buffer + * @write_atomic: Write callback for atomic context + * @write_thread: Write callback for threaded printing * @pcpu_data: Pointer to percpu context data * @ctxt_data: Builtin context data for early boot and threaded printing */ @@ -384,8 +391,13 @@ struct console { #ifndef CONFIG_64BIT atomic_t __private atomic_seq; #endif + struct task_struct *kthread; + struct rcuwait rcuwait; + atomic_t kthread_running; + struct cons_text_buf *thread_txtbuf; =20 bool (*write_atomic)(struct console *con, struct cons_write_context *wctx= t); + bool (*write_thread)(struct console *con, struct cons_write_context *wctx= t); =20 struct cons_context_data __percpu *pcpu_data; struct cons_context_data ctxt_data; --- a/kernel/printk/printk_nobkl.c +++ b/kernel/printk/printk_nobkl.c @@ -50,6 +50,8 @@ */ =20 #ifdef CONFIG_PRINTK +static bool printk_threads_enabled __ro_after_init; +static bool printk_force_atomic __initdata; =20 static bool cons_release(struct cons_context *ctxt); =20 @@ -238,8 +240,8 @@ static void cons_context_set_text_buf(st { struct console *con =3D ctxt->console; =20 - /* Early boot or allocation fail? */ - if (!con->pcpu_data) + /* Early boot, allocation fail or thread context? */ + if (!con->pcpu_data || ctxt->thread) ctxt->txtbuf =3D &con->ctxt_data.txtbuf; else ctxt->txtbuf =3D &(this_cpu_ptr(con->pcpu_data)->txtbuf); @@ -840,6 +842,8 @@ static bool __maybe_unused cons_release( { bool ret =3D __cons_release(ctxt); =20 + /* Invalidate the record pointer. It's not longer valid */ + ctxt->txtbuf =3D NULL; ctxt->state.atom =3D 0; return ret; } @@ -1022,10 +1026,9 @@ static bool cons_fill_outbuf(struct cons static bool cons_get_record(struct cons_write_context *wctxt) { struct cons_context *ctxt =3D &ACCESS_PRIVATE(wctxt, ctxt); - struct console *con =3D ctxt->console; struct cons_outbuf_desc desc =3D { .txtbuf =3D ctxt->txtbuf, - .extmsg =3D con->flags & CON_EXTENDED, + .extmsg =3D ctxt->console->flags & CON_EXTENDED, .seq =3D ctxt->newseq, .dropped =3D ctxt->dropped, }; @@ -1054,7 +1057,7 @@ static bool cons_get_record(struct cons_ * If it returns true @wctxt->ctxt.backlog indicates whether there are * still records pending in the ringbuffer, */ -static int __maybe_unused cons_emit_record(struct cons_write_context *wctx= t) +static bool cons_emit_record(struct cons_write_context *wctxt) { struct cons_context *ctxt =3D &ACCESS_PRIVATE(wctxt, ctxt); struct console *con =3D ctxt->console; @@ -1089,6 +1092,8 @@ static int __maybe_unused cons_emit_reco =20 if (!ctxt->thread && con->write_atomic) { done =3D con->write_atomic(con, wctxt); + } else if (ctxt->thread && con->write_thread) { + done =3D con->write_thread(con, wctxt); } else { cons_release(ctxt); WARN_ON_ONCE(1); @@ -1111,6 +1116,194 @@ static int __maybe_unused cons_emit_reco } =20 /** + * cons_kthread_should_run - Check whether the printk thread should run + * @con: Console to operate on + * @ctxt: The acquire context which contains the state + * at console_acquire() + */ +static bool cons_kthread_should_run(struct console *con, struct cons_conte= xt *ctxt) +{ + if (kthread_should_stop()) + return true; + + /* This reads state and sequence on 64bit. On 32bit only state */ + cons_state_read(con, STATE_REAL, &ctxt->state); + /* Bring the sequence in @ctxt up to date */ + cons_context_sequence_init(ctxt); + + if (!cons_state_ok(ctxt->state)) + return false; + + /* + * Atomic printing is running on some other CPU. The owner + * will wake the console thread on unlock if necessary. + */ + if (ctxt->state.locked) + return false; + + return prb_read_valid(prb, ctxt->oldseq, NULL); +} + +/** + * cons_kthread_func - The printk thread function + * @__console: Console to operate on + */ +static int cons_kthread_func(void *__console) +{ + struct console *con =3D __console; + struct cons_write_context wctxt =3D { + .ctxt.console =3D con, + .ctxt.prio =3D CONS_PRIO_NORMAL, + .ctxt.thread =3D 1, + }; + struct cons_context *ctxt =3D &ACCESS_PRIVATE(&wctxt, ctxt); + int ret; + + atomic_set(&con->kthread_running, 1); + + for (;;) { + atomic_dec(&con->kthread_running); + /* + * Provides a full memory barrier vs. cons_kthread_wake(). + */ + ret =3D rcuwait_wait_event(&con->rcuwait, cons_kthread_should_run(con, c= txt), + TASK_INTERRUPTIBLE); + + if (kthread_should_stop()) + break; + + atomic_inc(&con->kthread_running); + + /* Wait was interrupted by a spurious signal, go back to sleep */ + if (ret) + continue; + + for (;;) { + bool backlog; + + /* + * Ensure this stays on the CPU to make handover and + * takeover possible. + */ + migrate_disable(); + + /* + * Try to acquire the console without attempting to + * take over. If an atomic printer wants to hand + * back to the thread it simply wakes it up. + */ + if (!cons_try_acquire(ctxt)) + break; + + /* Stop when the console was handed/taken over */ + if (!cons_emit_record(&wctxt)) + break; + + backlog =3D ctxt->backlog; + + /* Stop when the console was handed/taken over */ + if (!cons_release(ctxt)) + break; + + /* Backlog done? */ + if (!backlog) + break; + + migrate_enable(); + cond_resched(); + } + migrate_enable(); + } + return 0; +} + +/** + * cons_kthread_wake - Wake up a printk thread + * @con: Console to operate on + */ +static inline void cons_kthread_wake(struct console *con) +{ + rcuwait_wake_up(&con->rcuwait); +} + +/** + * cons_kthread_stop - Stop a printk thread + * @con: Console to operate on + */ +static void cons_kthread_stop(struct console *con) +{ + struct task_struct *kt; + + lockdep_assert_held(&console_mutex); + + if (!con->kthread) + return; + + /* + * Nothing else than the thread itself can see @con->kthread + * anymore. @con is unhashed and all list walkers are synchronized. + */ + kt =3D con->kthread; + con->kthread =3D NULL; + kthread_stop(kt); + + kfree(con->thread_txtbuf); + con->thread_txtbuf =3D NULL; +} + +/** + * cons_kthread_create - Create a printk thread + * @con: Console to operate on + * + * If it fails, let the console proceed. The atomic part might + * be usable and useful. + */ +static void cons_kthread_create(struct console *con) +{ + struct task_struct *kt; + + lockdep_assert_held(&console_mutex); + + if (!(con->flags & CON_NO_BKL) || !con->write_thread) + return; + + if (!printk_threads_enabled || con->kthread) + return; + + con->thread_txtbuf =3D kmalloc(sizeof(*con->thread_txtbuf), GFP_KERNEL); + if (!con->thread_txtbuf) { + con_printk(KERN_ERR, con, "unable to allocate memory for printing thread= \n"); + return; + } + + kt =3D kthread_run(cons_kthread_func, con, "pr/%s%d", con->name, con->ind= ex); + if (IS_ERR(kt)) { + con_printk(KERN_ERR, con, "unable to start printing thread\n"); + kfree(con->thread_txtbuf); + con->thread_txtbuf =3D NULL; + return; + } + + con->kthread =3D kt; +} + +static int __init printk_setup_threads(void) +{ + struct console *con; + + if (printk_force_atomic) + return 0; + + console_list_lock(); + printk_threads_enabled =3D true; + for_each_registered_console(con) + cons_kthread_create(con); + console_list_unlock(); + return 0; +} +early_initcall(printk_setup_threads); + +/** * cons_nobkl_init - Initialize the NOBKL console state * @con: Console to initialize */ @@ -1123,7 +1316,10 @@ static void cons_nobkl_init(struct conso =20 cons_alloc_percpu_data(con); cons_forward_sequence(con); + rcuwait_init(&con->rcuwait); + cons_kthread_create(con); cons_state_set(con, STATE_REAL, &state); + cons_kthread_wake(con); } =20 /** @@ -1134,6 +1330,7 @@ static void cons_nobkl_cleanup(struct co { struct cons_state state =3D { }; =20 + cons_kthread_stop(con); cons_state_set(con, STATE_REAL, &state); cons_free_percpu_data(con); }