lkml.org 
[lkml]   [2022]   [May]   [18]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
Patch in this message
/
Date
From
Subject[RFC PATCH] printk: console: Allow each console to have its own loglevel
[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

\
 
 \ /
  Last update: 2022-05-18 16:25    [W:0.203 / U:0.044 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site