Skip to content

Commit

Permalink
printk: extend console_lock for proper kthread support
Browse files Browse the repository at this point in the history
Currently threaded console printers synchronize against each
other using console_lock(). However, different console drivers
are unrelated and do not require any synchronization between
each other. Removing the synchronization between the threaded
console printers will allow each console to print at its own
speed.

But the threaded consoles printers do still need to synchronize
against console_lock() callers. Introduce a per-console mutex
and a new console flag CON_THD_BLOCKED to provide this
synchronization.

console_lock() is modified so that it must acquire the mutex
of each console in order to set the CON_THD_BLOCKED flag.
Console printing threads will acquire their mutex while
printing a record. If CON_THD_BLOCKED was set, the thread will
go back to sleep instead of printing.

The reason for the CON_THD_BLOCKED flag is so that
console_lock() callers do not need to acquire multiple console
mutexes simultaneously, which would introduce unnecessary
complexity due to nested mutex locking.

The per-console mutex is also used to synchronize setting and
checking the CON_ENABLED flag. A new console_lock() variant is
introduced, console_lock_single_hold(), that allows acquiring
@console_sem but only locking (and holding) the mutex of a
single console. This allows safely enabling and disabling
consoles without disturbing the other kthread printers. The
mutex and @console_sem are released with
console_unlock_single_release().

Console unregistering now uses console_lock_single_hold() to
stop the kthread. Thus con->thread is now synchronized by
the per-console mutex. This allows consoles to be unregistered
without disturbing the other kthread printers.

Threaded console printers also need to synchronize against
console_trylock() callers. Since console_trylock() may be
called from any context, the per-console mutex cannot be used
for this synchronization. (mutex_trylock() cannot be called
from atomic contexts.) Introduce a global atomic counter to
identify if any threaded printers are active. The threaded
printers will also check the atomic counter to identify if the
console has been locked by another task via console_trylock().

Note that @console_sem is still used to provide synchronization
between console_lock() and console_trylock() callers.

A locking overview for console_lock(), console_trylock(), and the
threaded printers is as follows (pseudo code):

console_lock()
{
        down(&console_sem);
        for_each_console(con) {
                mutex_lock(&con->lock);
                con->flags |= CON_THD_BLOCKED;
                mutex_unlock(&con->lock);
        }
        /* console_lock acquired */
}

console_trylock()
{
        if (down_trylock(&console_sem) == 0) {
                if (atomic_cmpxchg(&console_kthreads_active, 0, -1) == 0) {
                        /* console_lock acquired */
                }
        }
}

threaded_printer()
{
        mutex_lock(&con->lock);
        if (!(con->flags & CON_THD_BLOCKED)) {
		/* console_lock() callers blocked */

                if (atomic_inc_unless_negative(&console_kthreads_active)) {
                        /* console_trylock() callers blocked */

                        con->write();

                        atomic_dec(&console_lock_count);
                }
        }
        mutex_unlock(&con->lock);
}

The console owner and waiter logic now only applies between contexts
that have taken the console_lock via console_trylock(). Threaded
printers never take the console_lock, so they do not have a
console_lock to handover. Tasks that have used console_lock() will
block the threaded printers using a mutex and if the console_lock
is handed over to an atomic context, it would be unable to unblock
the threaded printers. However, the console_trylock() case is
really the only scenario that is interesting for handovers anyway.

@panic_console_dropped must change to atomic_t since it is no longer
protected exclusively by the console_lock.

Since threaded printers remain asleep if they see that the console
is locked, they now must be explicitly woken in __console_unlock().
This means wake_up_klogd() calls following a console_unlock() are
no longer necessary and are removed.

Also note that threaded printers no longer need to check
@console_suspended. The check for the CON_THD_BLOCKED flag
implicitly covers the suspended console case.

Signed-off-by: John Ogness <john.ogness@linutronix.de>
  • Loading branch information
jogness authored and intel-lab-lkp committed Apr 20, 2022
1 parent be4482f commit 5139c5b
Show file tree
Hide file tree
Showing 2 changed files with 242 additions and 68 deletions.
15 changes: 15 additions & 0 deletions include/linux/console.h
Expand Up @@ -16,6 +16,7 @@

#include <linux/atomic.h>
#include <linux/types.h>
#include <linux/mutex.h>

struct vc_data;
struct console_font_op;
Expand Down Expand Up @@ -136,6 +137,7 @@ static inline int con_debug_leave(void)
#define CON_ANYTIME (16) /* Safe to call when cpu is offline */
#define CON_BRL (32) /* Used for a braille device */
#define CON_EXTENDED (64) /* Use the extended output format a la /dev/kmsg */
#define CON_THD_BLOCKED (128) /* Thread blocked because console is locked */

struct console {
char name[16];
Expand All @@ -155,6 +157,19 @@ struct console {
unsigned long dropped;
struct task_struct *thread;

/*
* The per-console lock is used by printing kthreads to synchronize
* this console with callers of console_lock(). This is necessary in
* order to allow printing kthreads to run in parallel to each other,
* while each safely accessing their own @flags and synchronizing
* against direct printing via console_lock/console_unlock.
*
* Note: For synchronizing against direct printing via
* console_trylock/console_unlock, see the static global
* variable @console_kthreads_active.
*/
struct mutex lock;

void *data;
struct console *next;
};
Expand Down

0 comments on commit 5139c5b

Please sign in to comment.