Re: [RFC PATCH] printk: console: Allow each console to have its own loglevel

Chris Down
2022-05-18T14:33:00Z

(Cc'ing Sergey, Steven, and John who were mistakenly left out of cc.)

Chris Down writes:
>[Once the goals of this patch are generally agreed upon, it can be split
>out further with more detailed changelogs if desired.]
>
>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.
>
>In terms of technical implementation, this patch embeds a class device
>in the console struct, and registers each console using it so we can
>expose attributes in sysfs.
>
>The lifecycle of this classdev looks like this on registration:
>
> register_console(con)/printk_late_init()
> console_register_device(con)
> con->classdev->flags |= CON_CLASSDEV_ACTIVE
> 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)
> con->classdev->flags &= ~CON_CLASSDEV_ACTIVE
>
> 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--
> [spin on CON_CLASSDEV_ACTIVE before returning]
>
>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/admin-guide/per-console-loglevel.rst.
>
>Signed-off-by: Chris Down <chris@chrisdown.name>
>---
> .../admin-guide/kernel-parameters.txt | 18 +-
> .../admin-guide/per-console-loglevel.rst | 116 ++++++
> Documentation/admin-guide/serial-console.rst | 17 +-
> Documentation/core-api/printk-basics.rst | 34 +-
> Documentation/networking/netconsole.rst | 17 +
> include/linux/console.h | 27 ++
> include/linux/kern_levels.h | 2 +
> init/main.c | 2 +-
> kernel/printk/console_cmdline.h | 2 +
> kernel/printk/printk.c | 335 ++++++++++++++++--
> kernel/printk/sysctl.c | 95 ++++-
> 11 files changed, 615 insertions(+), 50 deletions(-)
> create mode 100644 Documentation/admin-guide/per-console-loglevel.rst
>
>diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
>index 1a9f89a835b3..77febe32d73b 100644
>--- a/Documentation/admin-guide/kernel-parameters.txt
>+++ b/Documentation/admin-guide/kernel-parameters.txt
>@@ -696,10 +696,11 @@
> ttyS<n>[,options]
> ttyUSB0[,options]
> Use the specified serial port. The options are of
>- the form "bbbbpnf", where "bbbb" is the baud rate,
>+ the form "bbbbpnf/l", 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".
>+ bits, "f" is flow control ("r" for RTS or omit it),
>+ and "l" is the minimum loglevel ([0,8], can be
>+ omitted). The default is "9600n8".
>
> See Documentation/admin-guide/serial-console.rst for more
> information. See
>@@ -2709,10 +2710,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 default 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
>diff --git a/Documentation/admin-guide/per-console-loglevel.rst b/Documentation/admin-guide/per-console-loglevel.rst
>new file mode 100644
>index 000000000000..f1b56a4daec7
>--- /dev/null
>+++ b/Documentation/admin-guide/per-console-loglevel.rst
>@@ -0,0 +1,116 @@
>+.. 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 signiifcant
>+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
>+~~~~~~
>+
>+In order of authority:
>+
>+* ``ignore_loglevel`` on the kernel command line: Emit all messages. Cannot be
>+ disabled without restarting the kernel. All other controls are ignored if
>+ this is present.
>+* ``loglevel=...`` on the kernel command line: Equivalent to sysctl
>+ ``kernel.default_console_loglevel``.
>+* ``kernel.minimum_console_loglevel`` sysctl: Clamp all consoles to emit
>+ messages beyond this loglevel.
>+* ``kernel.force_console_loglevel`` sysctl: Force all consoles to the given
>+ loglevel. If this value is lower than ``kernel.minimum_console_loglevel``,
>+ ``kernel.minimum_console_loglevel`` is respected. Can also be set to the
>+ special value "unset" which removes any existing forced level.
>+* ``kernel.default_console_loglevel`` sysctl: The default console loglevel if
>+ there is no local loglevel for the console, and
>+ ``kernel.force_console_loglevel`` is unset. If this value is lower than
>+ ``kernel.minimum_console_loglevel``, ``kernel.minimum_console_loglevel`` is
>+ respected.
>+* ``kernel.default_console_loglevel`` sysctl: The default console loglevel if
>+ there is no local loglevel for the console, and
>+ ``kernel.force_console_loglevel`` is unset. If this value is lower than
>+ ``kernel.minimum_console_loglevel``, ``kernel.minimum_console_loglevel`` is
>+ forced.
>+* ``kernel.default_message_loglevel`` sysctl: The default loglevel to send
>+ messages at if they are sent with no explicit loglevel.
>+
>+The default value for ``kernel.default_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 local 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 default loglevel
>+ (``kernel.default_console_loglevel``) is in effect. Set a console-specific
>+ loglevel to override it.
>+ * ``forced``: The global forced loglevel (``kernel.force_console_loglevel``)
>+ is in effect. Write "unset" to ``kernel.force_console_loglevel`` to disable
>+ it.
>+ * ``minimum``: The global minimum loglevel
>+ (``kernel.minimum_console_loglevel``) is in effect. Set a higher
>+ console-specific loglevel to override it.
>+ * ``forced_minimum``: The global minimum loglevel
>+ (``kernel.minimum_console_loglevel``) is in effect. Even if the local
>+ console-specific loglevel is higher, it is in effect because the global
>+ forced loglevel (``kernel.force_console_loglevel``) is present, but is
>+ below ``kernel.minimum_console_loglevel``. Write "unset" to
>+ ``kernel.force_console_loglevel`` to disable the forcing, and make sure
>+ ``kernel.minimum_console_loglevel`` is below the local console loglevel
>+ if you want the per-console loglevel to take effect.
>+ console-specific loglevel to override it.
>+ * ``ignore_loglevel``: ``ignore_loglevel`` was specified on the kernel
>+ command line. Restart without it to use other controls.
>+
>+* ``enabled`` (r): Whether the console is enabled.
>+* ``loglevel`` (rw): The local 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.
>+
>+Deprecated
>+~~~~~~~~~~
>+
>+* ``syslog(SYSLOG_ACTION_CONSOLE_*)``: This sets
>+ ``kernel.force_console_loglevel``. It is unaware of per-console loglevel
>+ semantics and is not recommended. A warning will be emitted if it is used
>+ while local loglevels are in effect.
>+* ``kernel.printk`` sysctl: this takes four values, setting
>+ ``kernel.force_console_loglevel``, ``kernel.default_message_loglevel``,
>+ ``kernel.minimum_console_loglevel``, and ``kernel.default_console_loglevel``
>+ respectively. The interface is generally considered to quite confusing, and
>+ is unaware of per-console loglevel semantics.
>+
>+Chris Down <chris@chrisdown.name>, 17-May-2020
>diff --git a/Documentation/admin-guide/serial-console.rst b/Documentation/admin-guide/serial-console.rst
>index 58b32832e50a..7329ef2b5f73 100644
>--- a/Documentation/admin-guide/serial-console.rst
>+++ b/Documentation/admin-guide/serial-console.rst
>@@ -17,7 +17,7 @@ use for console output.
>
> The format of this option is::
>
>- console=device,options
>+ console=device,options/loglevel
>
> device: tty0 for the foreground virtual console
> ttyX for any other virtual console
>@@ -32,15 +32,23 @@ The format of this option is::
> and F is flow control ('r' for RTS). Default is
> 9600n8. The maximum baudrate is 115200.
>
>+ loglevel: optional. a number can be provided 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/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).
>
>@@ -113,3 +121,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-2020
>diff --git a/Documentation/core-api/printk-basics.rst b/Documentation/core-api/printk-basics.rst
>index 2dde24ca7d9f..deeabbc8cad6 100644
>--- a/Documentation/core-api/printk-basics.rst
>+++ b/Documentation/core-api/printk-basics.rst
>@@ -54,32 +54,32 @@ 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 current loglevel for all consoles, simply write the desired level
>+to ``/proc/sys/kernel/forced_console_loglevel``. For example, to print all
>+messages to all consoles::
>
>-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/forced_console_loglevel
>
>- # echo 8 > /proc/sys/kernel/printk
>+This sets the console_loglevel to print KERN_WARNING (4) or more severe
>+messages to console.
>
>-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
>diff --git a/Documentation/networking/netconsole.rst b/Documentation/networking/netconsole.rst
>index 1f5c4a04027c..aa56f2420fb9 100644
>--- a/Documentation/networking/netconsole.rst
>+++ b/Documentation/networking/netconsole.rst
>@@ -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/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:
>
>diff --git a/include/linux/console.h b/include/linux/console.h
>index 7cd758a4f44e..ce5ba405285a 100644
>--- a/include/linux/console.h
>+++ b/include/linux/console.h
>@@ -15,6 +15,7 @@
> #define _LINUX_CONSOLE_H_ 1
>
> #include <linux/atomic.h>
>+#include <linux/device.h>
> #include <linux/types.h>
>
> struct vc_data;
>@@ -137,6 +138,30 @@ 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 a certain value permanently (ignore_loglevel);
>+ * 2. It can be forced temporarily to a value (sysctls kernel.printk
>+ * (deprecated), kernel.force_console_loglevel,
>+ * kernel.minimum_console_loglevel, magic sysrq)
>+ * 3. It can be locally set (console=.../N on kernel command line,
>+ * /sys/class/console/.../loglevel);
>+ * 4. It can be set by a global default
>+ * (CONFIG_CONSOLE_LOGLEVEL_{DEFAULT,QUIET}, loglevel= on kernel command
>+ * line, sysctl kernel.default_console_loglevel).
>+ *
>+ * If case 3 happens, even if another forced value in effect, CON_LOGLEVEL will
>+ * be set.
>+ */
>+#define CON_LOGLEVEL (128) /* Level set locally for this console */
>+
>+/*
>+ * Console has active class device, so may have active readers/writers from
>+ * /sys/class hierarchy.
>+ */
>+#define CON_CLASSDEV_ACTIVE (256)
>+
> struct console {
> char name[16];
> void (*write)(struct console *, const char *, unsigned);
>@@ -153,6 +178,8 @@ struct console {
> uint ospeed;
> void *data;
> struct console *next;
>+ int level;
>+ struct device classdev;
> };
>
> /*
>diff --git a/include/linux/kern_levels.h b/include/linux/kern_levels.h
>index bf2389c26ae3..38923956841b 100644
>--- a/include/linux/kern_levels.h
>+++ b/include/linux/kern_levels.h
>@@ -24,6 +24,8 @@
> #define KERN_CONT KERN_SOH "c"
>
> /* integer equivalents of KERN_<LEVEL> */
>+#define LOGLEVEL_INVALID -127 /* Invalid loglevel, used to indicate
>+ this loglevel is not used */
> #define LOGLEVEL_SCHED -2 /* Deferred messages from sched code
> * are set to this special level */
> #define LOGLEVEL_DEFAULT -1 /* default (or last) loglevel */
>diff --git a/init/main.c b/init/main.c
>index 560f45c27ffe..7f0216b273df 100644
>--- a/init/main.c
>+++ b/init/main.c
>@@ -255,7 +255,7 @@ static int __init loglevel(char *str)
> * are quite hard to debug
> */
> if (get_option(&str, &newlevel)) {
>- console_loglevel = newlevel;
>+ default_console_loglevel = newlevel;
> return 0;
> }
>
>diff --git a/kernel/printk/console_cmdline.h b/kernel/printk/console_cmdline.h
>index 3ca74ad391d6..40f1a1ff0965 100644
>--- a/kernel/printk/console_cmdline.h
>+++ b/kernel/printk/console_cmdline.h
>@@ -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
>diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
>index da03c15ecc89..ee3328f7b6fb 100644
>--- a/kernel/printk/printk.c
>+++ b/kernel/printk/printk.c
>@@ -44,6 +44,7 @@
> #include <linux/irq_work.h>
> #include <linux/ctype.h>
> #include <linux/uio.h>
>+#include <linux/device.h>
> #include <linux/sched/clock.h>
> #include <linux/sched/debug.h>
> #include <linux/sched/task_stack.h>
>@@ -61,7 +62,7 @@
> #include "internal.h"
>
> int console_printk[4] = {
>- CONSOLE_LOGLEVEL_DEFAULT, /* console_loglevel */
>+ LOGLEVEL_INVALID, /* console_loglevel (forced) */
> MESSAGE_LOGLEVEL_DEFAULT, /* default_message_loglevel */
> CONSOLE_LOGLEVEL_MIN, /* minimum_console_loglevel */
> CONSOLE_LOGLEVEL_DEFAULT, /* default_console_loglevel */
>@@ -87,6 +88,8 @@ static DEFINE_SEMAPHORE(console_sem);
> struct console *console_drivers;
> EXPORT_SYMBOL_GPL(console_drivers);
>
>+static struct class *console_class;
>+
> /*
> * System may need to suppress printk message under certain
> * circumstances, like after kernel panic happens.
>@@ -396,6 +399,30 @@ static struct latched_seq clear_seq = {
> .val[1] = 0,
> };
>
>+/*
>+ * When setting a console loglevel, we may not ultimately end up with that as
>+ * the effective level due to forced_console_loglevel,
>+ * minimum_console_loglevel, or ignore_loglevel. Always returning the level and
>+ * effective source allows us to keep the logic in one place.
>+ */
>+enum loglevel_source {
>+ LLS_GLOBAL,
>+ LLS_LOCAL,
>+ LLS_FORCED,
>+ LLS_MINIMUM,
>+ LLS_FORCED_MINIMUM,
>+ LLS_IGNORE_LOGLEVEL,
>+};
>+
>+static const char *const loglevel_source_names[] = {
>+ [LLS_GLOBAL] = "global",
>+ [LLS_LOCAL] = "local",
>+ [LLS_FORCED] = "forced",
>+ [LLS_MINIMUM] = "minimum",
>+ [LLS_FORCED_MINIMUM] = "forced_minimum",
>+ [LLS_IGNORE_LOGLEVEL] = "ignore_loglevel",
>+};
>+
> #ifdef CONFIG_PRINTK_CALLER
> #define PREFIX_MAX 48
> #else
>@@ -1199,9 +1226,68 @@ module_param(ignore_loglevel, bool, S_IRUGO | S_IWUSR);
> MODULE_PARM_DESC(ignore_loglevel,
> "ignore loglevel setting (prints all kernel messages to the console)");
>
>-static bool suppress_message_printing(int level)
>+/*
>+ * Hierarchy of loglevel authority:
>+ *
>+ * 1. ignore_loglevel. Cannot be changed after boot. Overrides absolutely
>+ * everything since it's used to debug.
>+ * 2. forced_console_loglevel. Optional, forces all consoles to the specified
>+ * loglevel.
>+ * 3. minimum_console_loglevel. Always present, in effect if it's greater than
>+ * the console's local loglevel (or the global level if that isn't set).
>+ * 4. con->level. Optional.
>+ * 5. default_console_loglevel. Always present.
>+ *
>+ * Callers typically only need the level _or_ the source, but they're both
>+ * emitted from this function so that the effective loglevel logic can be
>+ * kept in one place.
>+ */
>+static int console_effective_loglevel(const struct console *con,
>+ enum loglevel_source *source)
> {
>- return (level >= console_loglevel && !ignore_loglevel);
>+ enum loglevel_source lsource;
>+ int level;
>+
>+ if (ignore_loglevel) {
>+ *source = LLS_IGNORE_LOGLEVEL;
>+ return CONSOLE_LOGLEVEL_MOTORMOUTH;
>+ }
>+
>+ /* Everything below here must respect minimum_console_loglevel */
>+
>+ if (console_loglevel != LOGLEVEL_INVALID) {
>+ lsource = LLS_FORCED;
>+ level = console_loglevel;
>+ goto check_minimum;
>+ }
>+
>+ if (con && (con->flags & CON_LOGLEVEL)) {
>+ lsource = LLS_LOCAL;
>+ level = con->level;
>+ goto check_minimum;
>+ }
>+
>+ lsource = LLS_GLOBAL;
>+ level = default_console_loglevel;
>+
>+check_minimum:
>+ if (level < minimum_console_loglevel) {
>+ if (lsource == LLS_FORCED)
>+ lsource = LLS_FORCED_MINIMUM;
>+ else
>+ lsource = LLS_MINIMUM;
>+ level = minimum_console_loglevel;
>+ }
>+
>+ *source = lsource;
>+ return level;
>+}
>+
>+static bool suppress_message_printing(int level, struct console *con)
>+{
>+ enum loglevel_source source;
>+
>+ return level >= console_effective_loglevel(con, &source);
> }
>
> #ifdef CONFIG_BOOT_PRINTK_DELAY
>@@ -1233,7 +1319,7 @@ static void boot_delay_msec(int level)
> unsigned long timeout;
>
> if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
>- || suppress_message_printing(level)) {
>+ || suppress_message_printing(level, NULL)) {
> return;
> }
>
>@@ -1642,6 +1728,32 @@ static void syslog_clear(void)
> mutex_unlock(&syslog_lock);
> }
>
>+/*
>+ * Using the global klogctl/syslog API is unlikely to do what you want if you
>+ * also have console specific loglevels. Warn about it.
>+ */
>+static void warn_on_local_loglevel(void)
>+{
>+ struct console *con;
>+
>+ /*
>+ * If it's already forced, it's not like local console loglevels are
>+ * taking effect anyway.
>+ */
>+ if (console_loglevel != LOGLEVEL_INVALID)
>+ return;
>+
>+ console_lock();
>+ for_each_console(con) {
>+ if (con->flags & CON_LOGLEVEL) {
>+ pr_warn_ratelimited("%s (%d) used syslog(SYSLOG_ACTION_CONSOLE_*) with local console loglevels set. This overrides local console loglevels.\n",
>+ current->comm, current->pid);
>+ break;
>+ }
>+ }
>+ console_unlock();
>+}
>+
> int do_syslog(int type, char __user *buf, int len, int source)
> {
> struct printk_info info;
>@@ -1653,6 +1765,18 @@ int do_syslog(int type, char __user *buf, int len, int source)
> if (error)
> return error;
>
>+ /*
>+ * This is the old, global API -- syslog() has no way to indicate which
>+ * console the changes are supposed to go to. If you want per-console
>+ * loglevel controls, then use the sysfs API.
>+ *
>+ * SYSLOG_ACTION_CONSOLE_{ON,OFF,LEVEL} emulate the old behaviour by
>+ * forcing all consoles to the specified level. Note that this means
>+ * you cannot reasonably mix using per-console loglevels and
>+ * syslog(SYSLOG_ACTION_CONSOLE_*), since this will override your
>+ * per-console loglevel settings.
>+ */
>+
> switch (type) {
> case SYSLOG_ACTION_CLOSE: /* Close log */
> break;
>@@ -1687,12 +1811,14 @@ int do_syslog(int type, char __user *buf, int len, int source)
> break;
> /* Disable logging to console */
> case SYSLOG_ACTION_CONSOLE_OFF:
>+ warn_on_local_loglevel();
> if (saved_console_loglevel == LOGLEVEL_DEFAULT)
> saved_console_loglevel = console_loglevel;
> console_loglevel = minimum_console_loglevel;
> break;
> /* Enable logging to console */
> case SYSLOG_ACTION_CONSOLE_ON:
>+ warn_on_local_loglevel();
> if (saved_console_loglevel != LOGLEVEL_DEFAULT) {
> console_loglevel = saved_console_loglevel;
> saved_console_loglevel = LOGLEVEL_DEFAULT;
>@@ -1700,10 +1826,9 @@ int do_syslog(int type, char __user *buf, int len, int source)
> break;
> /* Set level of messages printed to console */
> case SYSLOG_ACTION_CONSOLE_LEVEL:
>+ warn_on_local_loglevel();
> if (len < 1 || len > 8)
> return -EINVAL;
>- if (len < minimum_console_loglevel)
>- len = minimum_console_loglevel;
> console_loglevel = len;
> /* Implicitly re-enable logging to console */
> saved_console_loglevel = LOGLEVEL_DEFAULT;
>@@ -1916,7 +2041,7 @@ static int console_trylock_spinning(void)
> * The console_lock must be held.
> */
> static void call_console_drivers(const char *ext_text, size_t ext_len,
>- const char *text, size_t len)
>+ const char *text, size_t len, int level)
> {
> static char dropped_text[64];
> size_t dropped_len = 0;
>@@ -1944,6 +2069,8 @@ static void call_console_drivers(const char *ext_text, size_t ext_len,
> if (!cpu_online(smp_processor_id()) &&
> !(con->flags & CON_ANYTIME))
> continue;
>+ if (suppress_message_printing(level, con))
>+ continue;
> if (con->flags & CON_EXTENDED)
> con->write(con, ext_text, ext_len);
> else {
>@@ -2326,7 +2453,7 @@ static ssize_t msg_print_ext_body(char *buf, size_t size,
> static void console_lock_spinning_enable(void) { }
> static int console_lock_spinning_disable_and_check(void) { return 0; }
> static void call_console_drivers(const char *ext_text, size_t ext_len,
>- const char *text, size_t len) {}
>+ const char *text, size_t len, int level) {}
> static bool suppress_message_printing(int level) { return false; }
>
> #endif /* CONFIG_PRINTK */
>@@ -2365,7 +2492,8 @@ static void set_user_specified(struct console_cmdline *c, bool user_specified)
> console_set_on_cmdline = 1;
> }
>
>-static int __add_preferred_console(char *name, int idx, char *options,
>+static int __add_preferred_console(char *name, int idx, int loglevel,
>+ short initial_flags, char *options,
> char *brl_options, bool user_specified)
> {
> struct console_cmdline *c;
>@@ -2394,6 +2522,8 @@ static int __add_preferred_console(char *name, int idx, char *options,
> set_user_specified(c, user_specified);
> braille_set_options(c, brl_options);
>
>+ c->level = loglevel;
>+ c->flags = initial_flags;
> c->index = idx;
> return 0;
> }
>@@ -2415,7 +2545,9 @@ __setup("console_msg_format=", console_msg_format_setup);
> static int __init console_setup(char *str)
> {
> char buf[sizeof(console_cmdline[0].name) + 4]; /* 4 for "ttyS" */
>- char *s, *options, *brl_options = NULL;
>+ char *s, *options, *sloglevel, *brl_options = NULL;
>+ int loglevel = default_console_loglevel;
>+ short initial_flags = 0;
> int idx;
>
> /*
>@@ -2424,7 +2556,8 @@ static int __init console_setup(char *str)
> * for exactly this purpose.
> */
> if (str[0] == 0 || strcmp(str, "null") == 0) {
>- __add_preferred_console("ttynull", 0, NULL, NULL, true);
>+ __add_preferred_console("ttynull", 0, 0, 0, NULL, NULL,
>+ true);
> return 1;
> }
>
>@@ -2444,6 +2577,17 @@ static int __init console_setup(char *str)
> options = strchr(str, ',');
> if (options)
> *(options++) = 0;
>+
>+ sloglevel = strchr(options ?: str, '/');
>+ if (sloglevel) {
>+ *(sloglevel++) = 0;
>+ if (kstrtoint(sloglevel, 10, &loglevel) == 0) {
>+ loglevel = clamp(loglevel, LOGLEVEL_EMERG,
>+ LOGLEVEL_DEBUG + 1);
>+ initial_flags |= CON_LOGLEVEL;
>+ }
>+ }
>+
> #ifdef __sparc__
> if (!strcmp(str, "ttya"))
> strcpy(buf, "ttyS0");
>@@ -2456,7 +2600,8 @@ static int __init console_setup(char *str)
> idx = simple_strtoul(s, NULL, 10);
> *s = 0;
>
>- __add_preferred_console(buf, idx, options, brl_options, true);
>+ __add_preferred_console(buf, idx, loglevel, initial_flags, options,
>+ brl_options, true);
> return 1;
> }
> __setup("console=", console_setup);
>@@ -2476,7 +2621,7 @@ __setup("console=", console_setup);
> */
> int add_preferred_console(char *name, int idx, char *options)
> {
>- return __add_preferred_console(name, idx, options, NULL, false);
>+ return __add_preferred_console(name, idx, 0, 0, options, NULL, false);
> }
>
> bool console_suspend_enabled = true;
>@@ -2706,8 +2851,10 @@ void console_unlock(void)
>
> for (;;) {
> size_t ext_len = 0;
>+ bool solicited = false;
> int handover;
> size_t len;
>+ struct console *con;
>
> skip:
> if (!prb_read_valid(prb, console_seq, &r))
>@@ -2722,12 +2869,18 @@ void console_unlock(void)
> }
> }
>
>- if (suppress_message_printing(r.info->level)) {
>- /*
>- * Skip record we have buffered and already printed
>- * directly to the console when we received it, and
>- * record that has level above the console loglevel.
>- */
>+ /*
>+ * Already checked per-console in call_console_drivers(), but
>+ * we should avoid spending time formatting the text at all if
>+ * no console wants the message in the first place.
>+ */
>+ for_each_console(con) {
>+ if (!suppress_message_printing(r.info->level, con)) {
>+ solicited = true;
>+ break;
>+ }
>+ }
>+ if (!solicited) {
> console_seq++;
> goto skip;
> }
>@@ -2771,7 +2924,8 @@ void console_unlock(void)
> console_lock_spinning_enable();
>
> stop_critical_timings(); /* don't trace print latency */
>- call_console_drivers(ext_text, ext_len, text, len);
>+ call_console_drivers(ext_text, ext_len, text, len,
>+ r.info->level);
> start_critical_timings();
>
> handover = console_lock_spinning_disable_and_check();
>@@ -2919,6 +3073,121 @@ static int __init keep_bootcon_setup(char *str)
>
> early_param("keep_bootcon", keep_bootcon_setup);
>
>+static ssize_t loglevel_show(struct device *dev, struct device_attribute *attr,
>+ char *buf)
>+{
>+ struct console *con = container_of(dev, struct console, classdev);
>+
>+ if (con->flags & CON_LOGLEVEL)
>+ return sprintf(buf, "%d\n", con->level);
>+ else
>+ return sprintf(buf, "unset\n");
>+}
>+
>+static ssize_t loglevel_store(struct device *dev, struct device_attribute *attr,
>+ const char *buf, size_t size)
>+{
>+ struct console *con = container_of(dev, struct console, classdev);
>+ ssize_t ret;
>+ int tmp;
>+
>+ if (!strcmp(buf, "unset") || !strcmp(buf, "unset\n")) {
>+ con->flags &= ~CON_LOGLEVEL;
>+ return size;
>+ }
>+
>+ ret = kstrtoint(buf, 10, &tmp);
>+ if (ret < 0)
>+ return ret;
>+
>+ if (tmp < LOGLEVEL_EMERG || tmp > LOGLEVEL_DEBUG + 1)
>+ return -ERANGE;
>+
>+ con->level = tmp;
>+ con->flags |= CON_LOGLEVEL;
>+
>+ return size;
>+}
>+
>+static DEVICE_ATTR_RW(loglevel);
>+
>+static ssize_t effective_loglevel_source_show(struct device *dev,
>+ struct device_attribute *attr,
>+ char *buf)
>+{
>+ struct console *con = container_of(dev, struct console, classdev);
>+ enum loglevel_source source;
>+
>+ console_effective_loglevel(con, &source);
>+ return sprintf(buf, "%s\n", loglevel_source_names[source]);
>+}
>+
>+static DEVICE_ATTR_RO(effective_loglevel_source);
>+
>+static ssize_t effective_loglevel_show(struct device *dev,
>+ struct device_attribute *attr,
>+ char *buf)
>+{
>+ struct console *con = container_of(dev, struct console, classdev);
>+ enum loglevel_source source;
>+
>+ return sprintf(buf, "%d\n", console_effective_loglevel(con, &source));
>+}
>+
>+static DEVICE_ATTR_RO(effective_loglevel);
>+
>+static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
>+ char *buf)
>+{
>+ struct console *con = container_of(dev, struct console, classdev);
>+
>+ return sprintf(buf, "%d\n", !!(con->flags & CON_ENABLED));
>+}
>+
>+static DEVICE_ATTR_RO(enabled);
>+
>+static struct attribute *console_sysfs_attrs[] = {
>+ &dev_attr_loglevel.attr,
>+ &dev_attr_effective_loglevel_source.attr,
>+ &dev_attr_effective_loglevel.attr,
>+ &dev_attr_enabled.attr,
>+ NULL,
>+};
>+
>+ATTRIBUTE_GROUPS(console_sysfs);
>+
>+static void console_classdev_release(struct device *dev)
>+{
>+ struct console *con = container_of(dev, struct console, classdev);
>+
>+ /*
>+ * `struct console' objects are statically allocated (or at the very
>+ * least managed outside of our lifecycle), nothing to do. Just set a
>+ * flag so that we know we are no longer waiting for anyone and can
>+ * return control in unregister_console().
>+ */
>+ con->flags &= ~CON_CLASSDEV_ACTIVE;
>+}
>+
>+static void console_register_device(struct console *new)
>+{
>+ /*
>+ * We might be called from register_console() before the class is
>+ * registered. If that happens, we'll take care of it in
>+ * printk_late_init.
>+ */
>+ if (IS_ERR_OR_NULL(console_class))
>+ return;
>+
>+ new->flags |= CON_CLASSDEV_ACTIVE;
>+ device_initialize(&new->classdev);
>+ dev_set_name(&new->classdev, "%s", new->name);
>+ new->classdev.release = console_classdev_release;
>+ new->classdev.class = console_class;
>+ if (WARN_ON(device_add(&new->classdev)))
>+ put_device(&new->classdev);
>+}
>+
> /*
> * This is called by register_console() to try to match
> * the newly registered console with any of the ones selected
>@@ -2951,6 +3220,10 @@ static int try_enable_preferred_console(struct console *newcon,
> if (newcon->index < 0)
> newcon->index = c->index;
>
>+ if (c->flags & CON_LOGLEVEL)
>+ newcon->level = c->level;
>+ newcon->flags |= c->flags;
>+
> if (_braille_register_console(newcon, c))
> return 0;
>
>@@ -3118,6 +3391,7 @@ void register_console(struct console *newcon)
> console_seq = syslog_seq;
> mutex_unlock(&syslog_lock);
> }
>+ console_register_device(newcon);
> console_unlock();
> console_sysfs_notify();
>
>@@ -3188,6 +3462,7 @@ int unregister_console(struct console *console)
> console_drivers->flags |= CON_CONSDEV;
>
> console->flags &= ~CON_ENABLED;
>+ device_unregister(&console->classdev);
> console_unlock();
> console_sysfs_notify();
>
>@@ -3200,6 +3475,14 @@ int unregister_console(struct console *console)
> console->flags &= ~CON_ENABLED;
> console_unlock();
>
>+ /*
>+ * Wait for anyone holding the classdev open to close it so that we
>+ * don't remove the module prematurely. Once classdev refcnt is 0,
>+ * CON_CLASSDEV_ACTIVE will be unset in console_classdev_release.
>+ */
>+ while (console->flags & CON_CLASSDEV_ACTIVE)
>+ schedule_timeout_uninterruptible(msecs_to_jiffies(1));
>+
> return res;
> }
> EXPORT_SYMBOL(unregister_console);
>@@ -3247,6 +3530,10 @@ void __init console_init(void)
> * To mitigate this problem somewhat, only unregister consoles whose memory
> * intersects with the init section. Note that all other boot consoles will
> * get unregistered when the real preferred console is registered.
>+ *
>+ * Early consoles will also have been registered before we had the
>+ * infrastructure to put them into /sys/class/console, so make sure they get
>+ * set up now that we're ready.
> */
> static int __init printk_late_init(void)
> {
>@@ -3280,6 +3567,14 @@ static int __init printk_late_init(void)
> console_cpu_notify, NULL);
> WARN_ON(ret < 0);
> printk_sysctl_init();
>+
>+ console_class = class_create(THIS_MODULE, "console");
>+ if (!WARN_ON(IS_ERR(console_class)))
>+ console_class->dev_groups = console_sysfs_groups;
>+
>+ for_each_console(con)
>+ console_register_device(con);
>+
> return 0;
> }
> late_initcall(printk_late_init);
>diff --git a/kernel/printk/sysctl.c b/kernel/printk/sysctl.c
>index c228343eeb97..28660ce90453 100644
>--- a/kernel/printk/sysctl.c
>+++ b/kernel/printk/sysctl.c
>@@ -7,9 +7,12 @@
> #include <linux/printk.h>
> #include <linux/capability.h>
> #include <linux/ratelimit.h>
>+#include <linux/console.h>
> #include "internal.h"
>
> static const int ten_thousand = 10000;
>+static const int min_loglevel = LOGLEVEL_EMERG;
>+static const int max_loglevel = LOGLEVEL_DEBUG + 1;
>
> static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write,
> void *buffer, size_t *lenp, loff_t *ppos)
>@@ -20,13 +23,71 @@ static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write,
> return proc_dointvec_minmax(table, write, buffer, lenp, ppos);
> }
>
>+static int printk_sysctl_deprecated(struct ctl_table *table, int write,
>+ void __user *buffer, size_t *lenp,
>+ loff_t *ppos)
>+{
>+ int res = proc_dointvec(table, write, buffer, lenp, ppos);
>+
>+ if (write)
>+ pr_warn_ratelimited(
>+ "printk: The kernel.printk sysctl is deprecated and will be removed soon. Use kernel.force_console_loglevel, kernel.default_message_loglevel, kernel.minimum_console_loglevel, or kernel.default_console_loglevel instead.\n"
>+ );
>+
>+ return res;
>+}
>+
>+#define FORCE_CONSOLE_LOGLEVEL_MAX_LEN (sizeof("unset\n") + 1)
>+
>+static int printk_force_console_loglevel(struct ctl_table *table, int write,
>+ void __user *buffer, size_t *lenp,
>+ loff_t *ppos)
>+{
>+
>+ char level[FORCE_CONSOLE_LOGLEVEL_MAX_LEN] = "unset";
>+ struct ctl_table ltable = *table;
>+ int ret, value;
>+
>+ ltable.data = level;
>+ ltable.maxlen = sizeof(level) - 1;
>+
>+ if (!write) {
>+ if (console_loglevel != LOGLEVEL_INVALID)
>+ snprintf(ltable.data,
>+ FORCE_CONSOLE_LOGLEVEL_MAX_LEN, "%d",
>+ console_loglevel);
>+ return proc_dostring(&ltable, write, buffer, lenp, ppos);
>+ }
>+
>+ /* We accept either a loglevel, or "unset". */
>+ ret = proc_dostring(&ltable, write, buffer, lenp, ppos);
>+ if (ret)
>+ return ret;
>+
>+ if (strncmp(ltable.data, "unset", sizeof("unset")) == 0) {
>+ console_loglevel = LOGLEVEL_INVALID;
>+ return 0;
>+ }
>+
>+ ret = kstrtoint(ltable.data, 10, &value);
>+ if (ret)
>+ return ret;
>+
>+ if (value < LOGLEVEL_EMERG || value > LOGLEVEL_DEBUG + 1)
>+ return -ERANGE;
>+
>+ console_loglevel = value;
>+
>+ return 0;
>+}
>+
> static struct ctl_table printk_sysctls[] = {
> {
> .procname = "printk",
> .data = &console_loglevel,
> .maxlen = 4*sizeof(int),
> .mode = 0644,
>- .proc_handler = proc_dointvec,
>+ .proc_handler = printk_sysctl_deprecated,
> },
> {
> .procname = "printk_ratelimit",
>@@ -76,6 +137,38 @@ static struct ctl_table printk_sysctls[] = {
> .extra1 = SYSCTL_ZERO,
> .extra2 = SYSCTL_TWO,
> },
>+ {
>+ .procname = "force_console_loglevel",
>+ .mode = 0644,
>+ .proc_handler = printk_force_console_loglevel,
>+ },
>+ {
>+ .procname = "default_message_loglevel",
>+ .data = &default_message_loglevel,
>+ .maxlen = sizeof(int),
>+ .mode = 0644,
>+ .proc_handler = proc_dointvec_minmax,
>+ .extra1 = (void *)&min_loglevel,
>+ .extra2 = (void *)&max_loglevel,
>+ },
>+ {
>+ .procname = "default_console_loglevel",
>+ .data = &default_console_loglevel,
>+ .maxlen = sizeof(int),
>+ .mode = 0644,
>+ .proc_handler = proc_dointvec_minmax,
>+ .extra1 = (void *)&min_loglevel,
>+ .extra2 = (void *)&max_loglevel,
>+ },
>+ {
>+ .procname = "minimum_console_loglevel",
>+ .data = &minimum_console_loglevel,
>+ .maxlen = sizeof(int),
>+ .mode = 0644,
>+ .proc_handler = proc_dointvec_minmax,
>+ .extra1 = (void *)&min_loglevel,
>+ .extra2 = (void *)&max_loglevel,
>+ },
> {}
> };
>
>
>base-commit: 3ef4ea3d84ca568dcd57816b9521e82e3bd94f08
>--
>2.36.0
>