Skip to content

Commit

Permalink
printk: console: Support console-specific loglevels
Browse files Browse the repository at this point in the history
Consoles can have vastly different latencies and throughputs. For
example, writing a message to the serial console can take on the order
of tens of milliseconds to get the UART to successfully write a message.
While this might be fine for a single, one-off message, this can cause
significant application-level stalls in situations where the kernel
writes large amounts of information to the console.

This means that while you might want to send at least INFO level
messages to (for example) netconsole, which is relatively fast, you may
only want to send at least WARN level messages to the serial console.
Such an implementation would permit debugging using the serial console
in cases that netconsole doesn't receive messages during particularly
bad system issues, while still keeping the noise low enough to avoid
inducing latency in userspace applications. This patch adds such an
interface, extending the existing console loglevel controls to allow
each console to have its own loglevel.

One can't just disable the serial console, because one may actually need
it in situations where the machine is in a bad enough state that nothing
is received on netconsole. One also can't just bump the loglevel at
runtime after the issue, because usually the machine is already so
wedged by this point that it isn't responsive to such requests.

In terms of technical implementation, this patch embeds a device pointer
in the console struct, and registers each console using it so we can
expose attributes in sysfs. We currently expose the following
attributes:

    % ls -l /sys/class/console/ttyS/
    total 0
    lrwxrwxrwx 1 root root    0 Jul 20 17:37 subsystem -> ../../../../class/console/
    -r--r--r-- 1 root root 4096 Jul 20 17:38 effective_loglevel
    -r--r--r-- 1 root root 4096 Jul 20 17:38 effective_loglevel_source
    -r--r--r-- 1 root root 4096 Jul 20 17:38 enabled
    -rw-r--r-- 1 root root 4096 Jul 20 17:38 loglevel
    -rw-r--r-- 1 root root 4096 Jul 20 17:37 uevent

The lifecycle of this classdev looks like this on registration:

    register_console(con)/printk_late_init()
      console_register_device(con)
        device_initialize(con->classdev) # refcount++
        device_add(con->classdev)        # refcount++

At stable state, the refcount is two.

Console unregistration looks like this:

    [con->classdev refcount drops to 0]
        console_classdev_release(con->classdev)
            kfree(con->classdev)

    unregister_console(con)
        device_unregister(con->classdev)
            device_del(con->classdev) # refcount--
                device_remove_class_symlinks()
                    kernfs_remove_by_name_ns()
                        kernfs_drain()
                            kernfs_drain_open_files() # wait for close()
            put_device(con->classdev) # refcount--

We also deprecate the kernel.printk sysctl as it doesn't know about
per-console loglevels, and is generally pretty confusing.

For information on the precedence and application of the new controls,
see Documentation/ABI/testing/sysfs-class-console and
Documentation/admin-guide/per-console-loglevel.rst.

Signed-off-by: Chris Down <chris@chrisdown.name>
  • Loading branch information
cdown authored and intel-lab-lkp committed Jul 20, 2022
1 parent 6c872a2 commit fac1dc8
Show file tree
Hide file tree
Showing 11 changed files with 574 additions and 36 deletions.
43 changes: 43 additions & 0 deletions Documentation/ABI/testing/sysfs-class-console
@@ -0,0 +1,43 @@
What: /sys/class/console/
Date: May 2022
Contact: Chris Down <chris@chrisdown.name>
Description: Interface for viewing and setting per-console attributes, like
the per-console loglevel. For a high-level document describing
the motivations for this interface and related non-sysfs
controls, see
Documentation/admin-guide/per-console-loglevel.rst.

What: /sys/class/console/<C>/effective_loglevel
Date: May 2022
Contact: Chris Down <chris@chrisdown.name>
Description: Read only. The currently effective loglevel for this console.
All messages emitted with a loglevel below the effective value
will be emitted to the console.

What: /sys/class/console/<C>/effective_loglevel_source
Date: May 2022
Contact: Chris Down <chris@chrisdown.name>
Description: Read only. The currently effective loglevel source for this
console -- for example, whether it was set globally, or whether
it was set locally for this console. Possible values are:

local: The loglevel comes from the per-console loglevel.
global: The loglevel comes from the global loglevel.
ignore_loglevel: Both the per-console loglevel and global
loglevels are ignored as ignore_loglevel is
present on the kernel command line.

What: /sys/class/console/<C>/enabled
Date: May 2022
Contact: Chris Down <chris@chrisdown.name>
Description: Read only. "1" if the console is enabled, "0" otherwise.

What: /sys/class/console/<C>/loglevel
Date: May 2022
Contact: Chris Down <chris@chrisdown.name>
Description: Read write. The current per-console loglevel, which will take
effect if not overridden by other non-sysfs controls (see
Documentation/admin-guide/per-console-loglevel.rst). Bounds are
0 (LOGLEVEL_EMERG) to 8 (LOGLEVEL_DEBUG + 1) inclusive. Also
takes the special value "unset" to indicate that no per-console
loglevel is set, and we should defer to the global controls.
28 changes: 18 additions & 10 deletions Documentation/admin-guide/kernel-parameters.txt
Expand Up @@ -702,13 +702,18 @@
ttyS<n>[,options]
ttyUSB0[,options]
Use the specified serial port. The options are of
the form "bbbbpnf", where "bbbb" is the baud rate,
"p" is parity ("n", "o", or "e"), "n" is number of
bits, and "f" is flow control ("r" for RTS or
omit it). Default is "9600n8".
the form "bbbbpnf,extra", where "bbbb" is the baud
rate, "p" is parity ("n", "o", or "e"), "n" is
number of bits, and "f" is flow control ("r" for RTS
or omit it). Default is "9600n8".

See Documentation/admin-guide/serial-console.rst for more
information. See
At present the only extra option is "loglevel" to
set the per-console loglevel. For example:

console=ttyS0,9600n8,loglevel:3

See Documentation/admin-guide/serial-console.rst for
more information. See
Documentation/networking/netconsole.rst for an
alternative.

Expand Down Expand Up @@ -2806,10 +2811,13 @@
logibm.irq= [HW,MOUSE] Logitech Bus Mouse Driver
Format: <irq>

loglevel= All Kernel Messages with a loglevel smaller than the
console loglevel will be printed to the console. It can
also be changed with klogd or other programs. The
loglevels are defined as follows:
loglevel= Sets the global loglevel. All messages with a loglevel
smaller than the console loglevel will be printed to
the console. Note that this can be overridden
per-console, see
Documentation/admin-guide/per-console-loglevel.rst.

The loglevels are defined as follows:

0 (KERN_EMERG) system is unusable
1 (KERN_ALERT) action must be taken immediately
Expand Down
92 changes: 92 additions & 0 deletions Documentation/admin-guide/per-console-loglevel.rst
@@ -0,0 +1,92 @@
.. SPDX-License-Identifier: GPL-2.0
.. _per_console_loglevel:

Per-console loglevel support
============================

Motivation
----------

Consoles can have vastly different latencies and throughputs. For example,
writing a message to the serial console can take on the order of tens of
milliseconds to get the UART to successfully write a message. While this might
be fine for a single, one-off message, this can cause significant
application-level stalls in situations where the kernel writes large amounts of
information to the console.

This means that while you might want to send at least INFO level messages to
(for example) netconsole, which is relatively fast, you may only want to send
at least WARN level messages to the serial console. This permits debugging
using the serial console in cases that netconsole doesn't receive messages
during particularly bad system issues, while still keeping the noise low enough
to avoid inducing latency in userspace applications.

Tunables
--------

In order to allow tuning this, the following controls exist:

Global
~~~~~~

The global loglevel is set by the ``kernel.console_loglevel`` sysctl, which can
also be set as ``loglevel=`` on the kernel command line.

The printk module also takes two parameters which modify this behaviour
further:

* ``ignore_loglevel`` on the kernel command line or set in printk parameters:
Emit all messages. All other controls are ignored if this is present.
* ``ignore_per_console_loglevel`` on the kernel command line or set in printk
parameters: Ignore all per-console loglevels and use the global loglevel.

The default value for ``kernel.console_loglevel`` comes from
``CONFIG_CONSOLE_LOGLEVEL_DEFAULT``, or ``CONFIG_CONSOLE_LOGLEVEL_QUIET`` if
``quiet`` is passed on the kernel command line.

Console attributes
~~~~~~~~~~~~~~~~~~

Registered consoles are exposed at ``/sys/class/console``. For example, if you
are using ``ttyS0``, the console backing it can be viewed at
``/sys/class/console/ttyS/``. The following files are available:

* ``effective_loglevel`` (r): The effective loglevel after considering all
loglevel authorities. For example, if the console-specific loglevel is 3, but
the global minimum console loglevel[*]_ is 5, then the value will be 5.
* ``effective_loglevel_source`` (r): The loglevel authority which resulted in
the effective loglevel being set. The following values can be present:

* ``local``: The console-specific loglevel is in effect.
* ``global``: The global loglevel (``kernel.console_loglevel``) is in
effect. Set a console-specific loglevel to override it.
* ``ignore_loglevel``: ``ignore_loglevel`` was specified on the kernel
command line or at ``/sys/module/printk/parameters/ignore_loglevel``.
Disable it to use level controls.
* ``ignore_per_console_loglevel``: ``ignore_per_console_loglevel`` was
specified on the kernel command line or at
``/sys/module/printk/parameters/ignore_per_console_loglevel``. Disable it
to use per-console level controls.

* ``enabled`` (r): Whether the console is enabled.
* ``loglevel`` (rw): The local, console-specific loglevel for this console.
This will be in effect if no other global control overrides it. Look at
``effective_loglevel`` and ``effective_loglevel_source`` to verify that.

.. [*] The existence of a minimum console loglevel is generally considered to
be a confusing and rarely used interface, and as such is not exposed through
the modern printk sysctl APIs that obsoleted ``kernel.printk``. Use the
legacy ``kernel.printk`` sysctl to control it if you have a rare use case
that requires changing it. The default value is ``CONSOLE_LOGLEVEL_MIN``.
Deprecated
~~~~~~~~~~

* ``kernel.printk`` sysctl: this takes four values, setting
``kernel.console_loglevel``, ``kernel.default_message_loglevel``, the minimum
console loglevel, and a fourth unused value. The interface is generally
considered to quite confusing, doesn't perform checks on the values given,
and is unaware of per-console loglevel semantics.

Chris Down <chris@chrisdown.name>, 17-May-2022
17 changes: 14 additions & 3 deletions Documentation/admin-guide/serial-console.rst
Expand Up @@ -32,15 +32,25 @@ The format of this option is::
and F is flow control ('r' for RTS). Default is
9600n8. The maximum baudrate is 115200.

One can also specify the per-console loglevel for this
console by providing a loglevel parameter, for example
"loglevel:4" to set this console's loglevel to 4. The
value provided can be from 0 (LOGLEVEL_EMERG) to 8
(LOGLEVEL_DEBUG + 1), and messages below that will be
emitted onto the console as they become available.

You can specify multiple console= options on the kernel command line.
Output will appear on all of them. The last device will be used when
you open ``/dev/console``. So, for example::

console=ttyS1,9600 console=tty0
console=ttyS1,9600,loglevel:5 console=tty0

defines that opening ``/dev/console`` will get you the current foreground
virtual console, and kernel messages will appear on both the VGA
console and the 2nd serial port (ttyS1 or COM2) at 9600 baud.
virtual console, and kernel messages will appear on both the VGA console and
the 2nd serial port (ttyS1 or COM2) at 9600 baud. The optional loglevel "5"
indicates that this console will emit messages more serious than
LOGLEVEL_NOTICE (that is, LOGLEVEL_WARNING and below, since more serious
messages have lower ordering).

Note that you can only define one console per device type (serial, video).

Expand Down Expand Up @@ -113,3 +123,4 @@ Replace the sample values as needed.
the integration of these patches into m68k, ppc and alpha.

Miquel van Smoorenburg <miquels@cistron.nl>, 11-Jun-2000
Chris Down <chris@chrisdown.name>, 17-May-2022
35 changes: 18 additions & 17 deletions Documentation/core-api/printk-basics.rst
Expand Up @@ -54,32 +54,33 @@ string, the log level is not a separate argument). The available log levels are:

The log level specifies the importance of a message. The kernel decides whether
to show the message immediately (printing it to the current console) depending
on its log level and the current *console_loglevel* (a kernel variable). If the
message priority is higher (lower log level value) than the *console_loglevel*
the message will be printed to the console.
on its log level and the current global *console_loglevel* or local per-console
loglevel (kernel variables). If the message priority is higher (lower log level
value) than the effective loglevel the message will be printed to the console.

If the log level is omitted, the message is printed with ``KERN_DEFAULT``
level.

You can check the current *console_loglevel* with::
You can check the current console's loglevel -- for example if you want to
check the loglevel for serial consoles:

$ cat /proc/sys/kernel/printk
4 4 1 7
$ cat /sys/class/console/ttyS/effective_loglevel
6
$ cat /sys/class/console/ttyS/effective_loglevel_source
local

The result shows the *current*, *default*, *minimum* and *boot-time-default* log
levels.
To change the default loglevel for all consoles, simply write the desired level
to ``/proc/sys/kernel/console_loglevel``. For example::

To change the current console_loglevel simply write the desired level to
``/proc/sys/kernel/printk``. For example, to print all messages to the console::
# echo 5 > /proc/sys/kernel/console_loglevel

# echo 8 > /proc/sys/kernel/printk
This sets the console_loglevel to print KERN_WARNING (4) or more severe
messages to console. Consoles with a per-console loglevel set will ignore it
unless ``ignore_per_console_loglevel`` is set on the kernel command line or at
``/sys/module/printk/parameters/ignore_per_console_loglevel``.

Another way, using ``dmesg``::

# dmesg -n 5

sets the console_loglevel to print KERN_WARNING (4) or more severe messages to
console. See ``dmesg(1)`` for more information.
For more information on per-console loglevels, see
Documentation/admin-guide/per-console-loglevel.rst.

As an alternative to printk() you can use the ``pr_*()`` aliases for
logging. This family of macros embed the log level in the macro names. For
Expand Down
17 changes: 17 additions & 0 deletions Documentation/networking/netconsole.rst
Expand Up @@ -67,6 +67,23 @@ Built-in netconsole starts immediately after the TCP stack is
initialized and attempts to bring up the supplied dev at the supplied
address.

You can also set a loglevel at boot time on the kernel command line::

console=netcon0,loglevel:2

This can also be changed at runtime::

$ ls -l /sys/class/console/netcon/
total 0
lrwxrwxrwx 1 root root 0 May 18 13:28 subsystem -> ../../../../class/console/
-r--r--r-- 1 root root 4096 May 18 13:28 effective_loglevel
-r--r--r-- 1 root root 4096 May 18 13:28 effective_loglevel_source
-r--r--r-- 1 root root 4096 May 18 13:28 enabled
-rw-r--r-- 1 root root 4096 May 18 13:28 loglevel
-rw-r--r-- 1 root root 4096 May 18 13:28 uevent

See Documentation/admin-guide/per-console-loglevel.rst for more information.

The remote host has several options to receive the kernel messages,
for example:

Expand Down
3 changes: 3 additions & 0 deletions MAINTAINERS
Expand Up @@ -16044,6 +16044,9 @@ R: Steven Rostedt <rostedt@goodmis.org>
R: John Ogness <john.ogness@linutronix.de>
S: Maintained
T: git git://git.kernel.org/pub/scm/linux/kernel/git/printk/linux.git
F: Documentation/ABI/testing/sysfs-class-console
F: Documentation/admin-guide/per-console-loglevel.rst
F: Documentation/core-api/printk-basics.rst
F: include/linux/printk.h
F: kernel/printk/

Expand Down
24 changes: 24 additions & 0 deletions include/linux/console.h
Expand Up @@ -15,6 +15,7 @@
#define _LINUX_CONSOLE_H_ 1

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

struct vc_data;
Expand Down Expand Up @@ -137,6 +138,22 @@ static inline int con_debug_leave(void)
#define CON_BRL (32) /* Used for a braille device */
#define CON_EXTENDED (64) /* Use the extended output format a la /dev/kmsg */

/*
* The loglevel for a console can be set in many places:
*
* 1. It can be forced to emit everything (ignore_loglevel);
* 2. It can be set globally (sysctls kernel.printk (deprecated),
* kernel.console_loglevel, magic sysrq, loglevel= on kernel command line);
* 3. It can be locally set for this specific console (console=...,loglevel:N on
* kernel command line, /sys/class/console/.../loglevel);
* 4. It can be set by a compile-time default
* (CONFIG_CONSOLE_LOGLEVEL_{DEFAULT,QUIET})
*
* If case 3 happens, even if another global value in effect, CON_LOGLEVEL will
* be set.
*/
#define CON_LOGLEVEL (128) /* Level set locally for this console */

struct console {
char name[16];
void (*write)(struct console *, const char *, unsigned);
Expand All @@ -155,8 +172,15 @@ struct console {
unsigned long dropped;
void *data;
struct console *next;
int level;
struct device *classdev;
};

static inline struct console *classdev_to_console(struct device *dev)
{
return dev_get_drvdata(dev);
}

/*
* for_each_console() allows you to iterate on each console
*/
Expand Down
2 changes: 2 additions & 0 deletions kernel/printk/console_cmdline.h
Expand Up @@ -6,6 +6,8 @@ struct console_cmdline
{
char name[16]; /* Name of the driver */
int index; /* Minor dev. to use */
int level; /* Log level to use */
short flags; /* Initial flags */
bool user_specified; /* Specified by command line vs. platform */
char *options; /* Options for the driver */
#ifdef CONFIG_A11Y_BRAILLE_CONSOLE
Expand Down

0 comments on commit fac1dc8

Please sign in to comment.