2023-04-20 12:40:22

by Chris Down

[permalink] [raw]
Subject: [PATCH v4 0/2] printk: console: Per-console loglevels

v4:

- Change base to Linus' master
- Use SRCU iterators for console walks
- Override per-console loglevels on magic sysrq
- Fix htmldocs
- Fix mistaken __user annotation in sysctl callbacks
- Consistently use indexed names (eg. ttyS0 instead of ttyS)
- Remove "The loglevel for a console can be set in many places" comment
- Remove CON_LOGLEVEL flag and infer based on >0
- Open code our dev_get_drvdata console stashing
- Split out console_effective_loglevel functions per Petr's suggestion
- Make boot_delay_msec/printk_delay check if it would be emitted
- Simplify warning on SYSLOG_ACTION_CONSOLE_LEVEL
- Save/restore ignore_per_console_loglevel on SYSLOG_ACTION_CONSOLE_{ON,OFF}
- Unify min/max level checks across sysfs/proc/syslog
- Add find_and_remove_console_option to avoid affecting io/mmio options

v3:

- Update to work with John's kthread patches
- Remove force_console_loglevel, now we only have global and local levels
- Remove minimum_console_loglevel control and document how to change it
- The minimum loglevel is now only honoured on setting global/local level
- Add ignore_per_console_loglevel
- Return -EINVAL if trying to set below minimum console level
- Add parser for named console= options
- Fix docs around ignore_loglevel: it can be changed at runtime
- Fix ordering in "in order of authority" docs
- Remove duplicated default_console_loglevel doc
- Only warn once on syslog() use

v2:

- Dynamically allocate struct device*
- Document sysfs attributes in Documentation/ABI/
- Use sysfs_emit() instead of sprintf() in dev sysfs files
- Remove WARN_ON() for device_add/IS_ERR(console_class)
- Remove "soon" comment for kernel.printk
- Fix !CONFIG_PRINTK build
- Fix device_unregister() NULL dereference if called before class setup
- Add new documentation to MAINTAINERS

Chris Down (2):
printk: Do not delay messages which aren't solicited by any console
printk: console: Support console-specific loglevels

Documentation/ABI/testing/sysfs-class-console | 47 ++
Documentation/admin-guide/index.rst | 1 +
.../admin-guide/kernel-parameters.txt | 28 +-
.../admin-guide/per-console-loglevel.rst | 92 ++++
Documentation/admin-guide/serial-console.rst | 17 +-
Documentation/core-api/printk-basics.rst | 35 +-
Documentation/networking/netconsole.rst | 17 +
MAINTAINERS | 3 +
drivers/tty/sysrq.c | 15 +
include/linux/console.h | 3 +
include/linux/printk.h | 9 +
kernel/printk/console_cmdline.h | 2 +
kernel/printk/printk.c | 415 +++++++++++++++++-
kernel/printk/sysctl.c | 57 ++-
14 files changed, 691 insertions(+), 50 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-console
create mode 100644 Documentation/admin-guide/per-console-loglevel.rst


base-commit: cb0856346a60fe3eb837ba5e73588a41f81ac05f
--
2.40.0


2023-04-20 12:40:24

by Chris Down

[permalink] [raw]
Subject: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console

Boot-delayed printk messages already have this checked up front, and
it's odd that we don't do the same check for global printk delays, since
there's no reason to delay if we are not going to emit anything.

Signed-off-by: Chris Down <[email protected]>
---
kernel/printk/printk.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index fd0c9f913940..06f16a5f1516 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -1289,15 +1289,13 @@ static int __init boot_delay_setup(char *str)
}
early_param("boot_delay", boot_delay_setup);

-static void boot_delay_msec(int level)
+static void boot_delay_msec(void)
{
unsigned long long k;
unsigned long timeout;

if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
- || suppress_message_printing(level)) {
return;
- }

k = (unsigned long long)loops_per_msec * boot_delay;

@@ -1316,7 +1314,7 @@ static void boot_delay_msec(int level)
}
}
#else
-static inline void boot_delay_msec(int level)
+static inline void boot_delay_msec(void)
{
}
#endif
@@ -2064,7 +2062,10 @@ int printk_delay_msec __read_mostly;

static inline void printk_delay(int level)
{
- boot_delay_msec(level);
+ if (suppress_message_printing(level))
+ return;
+
+ boot_delay_msec();

if (unlikely(printk_delay_msec)) {
int m = printk_delay_msec;
--
2.40.0

2023-04-20 12:41:36

by Chris Down

[permalink] [raw]
Subject: [PATCH v4 2/2] printk: console: Support console-specific loglevels

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 <[email protected]>
---
Documentation/ABI/testing/sysfs-class-console | 47 ++
Documentation/admin-guide/index.rst | 1 +
.../admin-guide/kernel-parameters.txt | 28 +-
.../admin-guide/per-console-loglevel.rst | 92 ++++
Documentation/admin-guide/serial-console.rst | 17 +-
Documentation/core-api/printk-basics.rst | 35 +-
Documentation/networking/netconsole.rst | 17 +
MAINTAINERS | 3 +
drivers/tty/sysrq.c | 15 +
include/linux/console.h | 3 +
include/linux/printk.h | 9 +
kernel/printk/console_cmdline.h | 2 +
kernel/printk/printk.c | 410 +++++++++++++++++-
kernel/printk/sysctl.c | 57 ++-
14 files changed, 688 insertions(+), 48 deletions(-)
create mode 100644 Documentation/ABI/testing/sysfs-class-console
create mode 100644 Documentation/admin-guide/per-console-loglevel.rst

diff --git a/Documentation/ABI/testing/sysfs-class-console b/Documentation/ABI/testing/sysfs-class-console
new file mode 100644
index 000000000000..35a117d521cf
--- /dev/null
+++ b/Documentation/ABI/testing/sysfs-class-console
@@ -0,0 +1,47 @@
+What: /sys/class/console/
+Date: April 2023
+Contact: Chris Down <[email protected]>
+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: April 2023
+Contact: Chris Down <[email protected]>
+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: April 2023
+Contact: Chris Down <[email protected]>
+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: April 2023
+Contact: Chris Down <[email protected]>
+Description: Read only. "1" if the console is enabled, "0" otherwise.
+
+What: /sys/class/console/<C>/loglevel
+Date: April 2023
+Contact: Chris Down <[email protected]>
+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 "-1" to indicate that no per-console
+ loglevel is set, and we should defer to the global controls.
diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
index 09a563bbe3e7..875845b02556 100644
--- a/Documentation/admin-guide/index.rst
+++ b/Documentation/admin-guide/index.rst
@@ -116,6 +116,7 @@ configure specific aspects of kernel behavior to your liking.
namespaces/index
numastat
parport
+ per-console-loglevel
perf-security
pm/index
pnp
diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
index 7016cb12dc4e..902f018a5c27 100644
--- a/Documentation/admin-guide/kernel-parameters.txt
+++ b/Documentation/admin-guide/kernel-parameters.txt
@@ -722,13 +722,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.

@@ -2880,10 +2885,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
diff --git a/Documentation/admin-guide/per-console-loglevel.rst b/Documentation/admin-guide/per-console-loglevel.rst
new file mode 100644
index 000000000000..4a7ecce7ba8a
--- /dev/null
+++ b/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/ttyS0/``. 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 <[email protected]>, 27-April-2023
diff --git a/Documentation/admin-guide/serial-console.rst b/Documentation/admin-guide/serial-console.rst
index 58b32832e50a..4e204115fe4a 100644
--- a/Documentation/admin-guide/serial-console.rst
+++ b/Documentation/admin-guide/serial-console.rst
@@ -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).

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

Miquel van Smoorenburg <[email protected]>, 11-Jun-2000
+Chris Down <[email protected]>, 27-April-2023
diff --git a/Documentation/core-api/printk-basics.rst b/Documentation/core-api/printk-basics.rst
index 2dde24ca7d9f..bfad359505bb 100644
--- a/Documentation/core-api/printk-basics.rst
+++ b/Documentation/core-api/printk-basics.rst
@@ -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/ttyS0/effective_loglevel
+ 6
+ $ cat /sys/class/console/ttyS0/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
diff --git a/Documentation/networking/netconsole.rst b/Documentation/networking/netconsole.rst
index dd0518e002f6..e2c094a96f16 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,loglevel:2
+
+This can also be changed at runtime::
+
+ $ ls -l /sys/class/console/netcon0/
+ 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/MAINTAINERS b/MAINTAINERS
index 0e64787aace8..74a9a31264d2 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -16808,6 +16808,9 @@ R: Steven Rostedt <[email protected]>
R: John Ogness <[email protected]>
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/

diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
index b6e70c5cfa17..f62beafd5d49 100644
--- a/drivers/tty/sysrq.c
+++ b/drivers/tty/sysrq.c
@@ -51,6 +51,7 @@
#include <linux/syscalls.h>
#include <linux/of.h>
#include <linux/rcupdate.h>
+#include <linux/console.h>

#include <asm/ptrace.h>
#include <asm/irq_regs.h>
@@ -101,12 +102,26 @@ __setup("sysrq_always_enabled", sysrq_always_enabled_setup);
static void sysrq_handle_loglevel(int key)
{
int i;
+ int cookie;
+ int warned = 0;
+ struct console *con;

i = key - '0';
console_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
pr_info("Loglevel set to %d\n", i);
console_loglevel = i;
+
+ cookie = console_srcu_read_lock();
+ for_each_console_srcu(con) {
+ if (!warned && per_console_loglevel_is_set(con)) {
+ warned = 1;
+ pr_warn("Overriding per-console loglevel from sysrq\n");
+ }
+ con->level = -1;
+ }
+ console_srcu_read_unlock(cookie);
}
+
static const struct sysrq_key_op sysrq_loglevel_op = {
.handler = sysrq_handle_loglevel,
.help_msg = "loglevel(0-9)",
diff --git a/include/linux/console.h b/include/linux/console.h
index d3195664baa5..77052f863571 100644
--- a/include/linux/console.h
+++ b/include/linux/console.h
@@ -17,6 +17,7 @@
#include <linux/atomic.h>
#include <linux/bits.h>
#include <linux/rculist.h>
+#include <linux/device.h>
#include <linux/types.h>

struct vc_data;
@@ -203,6 +204,8 @@ struct console {
unsigned long dropped;
void *data;
struct hlist_node node;
+ int level;
+ struct device *classdev;
};

#ifdef CONFIG_LOCKDEP
diff --git a/include/linux/printk.h b/include/linux/printk.h
index 8ef499ab3c1e..25ba0873dee8 100644
--- a/include/linux/printk.h
+++ b/include/linux/printk.h
@@ -69,6 +69,8 @@ extern int console_printk[];

extern void console_verbose(void);

+int clamp_loglevel(int level);
+
/* strlen("ratelimit") + 1 */
#define DEVKMSG_STR_MAX_SIZE 10
extern char devkmsg_log_str[];
@@ -139,6 +141,7 @@ void early_printk(const char *s, ...) { }
#endif

struct dev_printk_info;
+struct console;

#ifdef CONFIG_PRINTK
asmlinkage __printf(4, 0)
@@ -192,6 +195,8 @@ void show_regs_print_info(const char *log_lvl);
extern asmlinkage void dump_stack_lvl(const char *log_lvl) __cold;
extern asmlinkage void dump_stack(void) __cold;
void printk_trigger_flush(void);
+
+bool per_console_loglevel_is_set(const struct console *con);
#else
static inline __printf(1, 0)
int vprintk(const char *s, va_list args)
@@ -271,6 +276,10 @@ static inline void dump_stack(void)
static inline void printk_trigger_flush(void)
{
}
+static inline bool per_console_loglevel_is_set(const struct console *con)
+{
+ return false;
+}
#endif

#ifdef CONFIG_SMP
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 06f16a5f1516..dbc686dfe15e 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>
@@ -199,6 +200,12 @@ static int __init control_devkmsg(char *str)
}
__setup("printk.devkmsg=", control_devkmsg);

+int clamp_loglevel(int level)
+{
+ return clamp(level, minimum_console_loglevel,
+ CONSOLE_LOGLEVEL_MOTORMOUTH);
+}
+
char devkmsg_log_str[DEVKMSG_STR_MAX_SIZE] = "ratelimit";
#if defined(CONFIG_PRINTK) && defined(CONFIG_SYSCTL)
int devkmsg_sysctl_set_loglvl(struct ctl_table *table, int write,
@@ -466,6 +473,14 @@ static struct latched_seq clear_seq = {
.val[1] = 0,
};

+static struct class *console_class;
+
+enum loglevel_source {
+ LLS_GLOBAL,
+ LLS_LOCAL,
+ LLS_IGNORE_LOGLEVEL,
+};
+
#define LOG_LEVEL(v) ((v) & 0x07)
#define LOG_FACILITY(v) ((v) >> 3 & 0xff)

@@ -696,7 +711,8 @@ static ssize_t msg_print_ext_body(char *buf, size_t size,
return len;
}

-static bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
+static bool printk_get_next_message(struct printk_message *pmsg,
+ struct console *con, u64 seq,
bool is_extended, bool may_supress);

/* /dev/kmsg - userspace message inject/listen interface */
@@ -799,7 +815,8 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
if (ret)
return ret;

- if (!printk_get_next_message(&pmsg, atomic64_read(&user->seq), true, false)) {
+ if (!printk_get_next_message(&pmsg, NULL, atomic64_read(&user->seq),
+ true, false)) {
if (file->f_flags & O_NONBLOCK) {
ret = -EAGAIN;
goto out;
@@ -816,8 +833,8 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
* This pairs with __wake_up_klogd:A.
*/
ret = wait_event_interruptible(log_wait,
- printk_get_next_message(&pmsg, atomic64_read(&user->seq), true,
- false)); /* LMM(devkmsg_read:A) */
+ printk_get_next_message(&pmsg, NULL, atomic64_read(&user->seq),
+ true, false)); /* LMM(devkmsg_read:A) */
if (ret)
goto out;
}
@@ -1261,9 +1278,119 @@ 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)
+static bool __read_mostly ignore_per_console_loglevel;
+
+static int __init ignore_per_console_loglevel_setup(char *str)
+{
+ ignore_per_console_loglevel = true;
+ return 0;
+}
+
+early_param("ignore_per_console_loglevel", ignore_per_console_loglevel_setup);
+module_param(ignore_per_console_loglevel, bool, 0644);
+MODULE_PARM_DESC(
+ ignore_per_console_loglevel,
+ "ignore per-console loglevel setting (only respect global console loglevel)");
+
+bool per_console_loglevel_is_set(const struct console *con)
+{
+ return !ignore_per_console_loglevel && con && (con->level > 0);
+}
+
+/*
+ * Hierarchy of loglevel authority:
+ *
+ * 1. con->level. The locally set, console-specific loglevel. Optional, only
+ * valid if >0.
+ * 2. console_loglevel. The default global console loglevel, always present.
+ *
+ * The behaviour can be further changed by the following printk module
+ * parameters:
+ *
+ * 1. ignore_loglevel. Can be set at boot or at runtime with
+ * /sys/module/printk/parameters/ignore_loglevel. Overrides absolutely
+ * everything since it's used to debug.
+ * 2. ignore_per_console_loglevel. Existing per-console loglevel values are left
+ * intact, but are ignored in favour of console_loglevel as long as this is
+ * true. Also manipulated through syslog(SYSLOG_ACTION_CONSOLE_{ON,OFF}).
+ */
+static enum loglevel_source
+console_effective_loglevel_source(const struct console *con)
+{
+ if (WARN_ON_ONCE(!con))
+ return LLS_GLOBAL;
+
+ if (ignore_loglevel)
+ return LLS_IGNORE_LOGLEVEL;
+
+ if (per_console_loglevel_is_set(con))
+ return LLS_LOCAL;
+
+ return LLS_GLOBAL;
+}
+
+static int console_effective_loglevel(const struct console *con)
{
- return (level >= console_loglevel && !ignore_loglevel);
+ enum loglevel_source source;
+ int level;
+
+ if (WARN_ON_ONCE(!con))
+ return default_console_loglevel;
+
+ source = console_effective_loglevel_source(con);
+
+ switch (source) {
+ case LLS_IGNORE_LOGLEVEL:
+ level = CONSOLE_LOGLEVEL_MOTORMOUTH;
+ break;
+ case LLS_LOCAL:
+ level = con->level;
+ break;
+ case LLS_GLOBAL:
+ level = console_loglevel;
+ break;
+ default:
+ pr_warn("Unhandled console loglevel source: %d", source);
+ level = default_console_loglevel;
+ break;
+ }
+
+ return level;
+}
+
+static const char *
+console_effective_loglevel_source_str(const struct console *con)
+{
+ enum loglevel_source source;
+ const char *str;
+
+ if (WARN_ON_ONCE(!con))
+ return "unknown";
+
+ source = console_effective_loglevel_source(con);
+
+ switch (source) {
+ case LLS_IGNORE_LOGLEVEL:
+ str = "ignore_loglevel";
+ break;
+ case LLS_LOCAL:
+ str = "local";
+ break;
+ case LLS_GLOBAL:
+ str = "global";
+ break;
+ default:
+ pr_warn("Unhandled console loglevel source: %d", source);
+ str = "unknown";
+ break;
+ }
+
+ return str;
+}
+
+static bool suppress_message_printing(int level, struct console *con)
+{
+ return level >= console_effective_loglevel(con);
}

#ifdef CONFIG_BOOT_PRINTK_DELAY
@@ -1718,6 +1845,7 @@ int do_syslog(int type, char __user *buf, int len, int source)
struct printk_info info;
bool clear = false;
static int saved_console_loglevel = LOGLEVEL_DEFAULT;
+ static int saved_ignore_per_console_loglevel;
int error;

error = check_syslog_permissions(type, source);
@@ -1758,19 +1886,28 @@ int do_syslog(int type, char __user *buf, int len, int source)
break;
/* Disable logging to console */
case SYSLOG_ACTION_CONSOLE_OFF:
- if (saved_console_loglevel == LOGLEVEL_DEFAULT)
+ if (saved_console_loglevel == LOGLEVEL_DEFAULT) {
saved_console_loglevel = console_loglevel;
+ saved_ignore_per_console_loglevel =
+ ignore_per_console_loglevel;
+ }
console_loglevel = minimum_console_loglevel;
+ ignore_per_console_loglevel = true;
break;
/* Enable logging to console */
case SYSLOG_ACTION_CONSOLE_ON:
if (saved_console_loglevel != LOGLEVEL_DEFAULT) {
console_loglevel = saved_console_loglevel;
+ ignore_per_console_loglevel =
+ saved_ignore_per_console_loglevel;
saved_console_loglevel = LOGLEVEL_DEFAULT;
}
break;
/* Set level of messages printed to console */
case SYSLOG_ACTION_CONSOLE_LEVEL:
+ if (!ignore_per_console_loglevel)
+ pr_warn_once(
+ "SYSLOG_ACTION_CONSOLE_LEVEL is ignored by consoles with an explicitly set per-console loglevel, see Documentation/admin-guide/per-console-loglevel\n");
if (len < 1 || len > 8)
return -EINVAL;
if (len < minimum_console_loglevel)
@@ -2060,11 +2197,8 @@ static u8 *__printk_recursion_counter(void)

int printk_delay_msec __read_mostly;

-static inline void printk_delay(int level)
+static inline void printk_delay(void)
{
- if (suppress_message_printing(level))
- return;
-
boot_delay_msec();

if (unlikely(printk_delay_msec)) {
@@ -2278,7 +2412,9 @@ asmlinkage int vprintk_emit(int facility, int level,
const char *fmt, va_list args)
{
int printed_len;
+ int cookie;
bool in_sched = false;
+ struct console *con;

/* Suppress unimportant messages after panic happens */
if (unlikely(suppress_printk))
@@ -2293,7 +2429,14 @@ asmlinkage int vprintk_emit(int facility, int level,
in_sched = true;
}

- printk_delay(level);
+ cookie = console_srcu_read_lock();
+ for_each_console_srcu(con) {
+ if (!suppress_message_printing(level, con)) {
+ printk_delay();
+ break;
+ }
+ }
+ console_srcu_read_unlock(cookie);

printed_len = vprintk_store(facility, level, dev_info, fmt, args);

@@ -2370,7 +2513,10 @@ static ssize_t msg_print_ext_body(char *buf, size_t size,
struct dev_printk_info *dev_info) { return 0; }
static void console_lock_spinning_enable(void) { }
static int console_lock_spinning_disable_and_check(int cookie) { return 0; }
-static bool suppress_message_printing(int level) { return false; }
+static bool suppress_message_printing(int level, struct console *con)
+{
+ return false;
+}
static bool pr_flush(int timeout_ms, bool reset_on_progress) { return true; }
static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progress) { return true; }

@@ -2410,11 +2556,78 @@ static void set_user_specified(struct console_cmdline *c, bool user_specified)
console_set_on_cmdline = 1;
}

+static bool find_and_remove_console_option(char *options, const char *key,
+ char *val_buf, size_t val_buf_size)
+{
+ bool found = false, first = true;
+ char *option, *next = options;
+
+ while ((option = strsep(&next, ","))) {
+ char *value;
+
+ value = strchr(option, ':');
+ if (value)
+ *(value++) = '\0';
+
+ if (strcmp(option, key) == 0) {
+ found = true;
+ if (value) {
+ if (strlen(value) > val_buf_size - 1) {
+ pr_warn("Can't copy console option value for %s:%s: not enough space (%zu)\n",
+ option, value, val_buf_size);
+ found = false;
+ } else {
+ strscpy(val_buf, value, val_buf_size);
+ }
+ } else
+ *val_buf = '\0';
+ }
+
+ if (found)
+ break;
+
+ if (next)
+ *(next - 1) = ',';
+ if (value)
+ *(value - 1) = ':';
+
+ first = false;
+ }
+
+ if (found) {
+ if (next)
+ memmove(option, next, strlen(next) + 1);
+ else if (first)
+ *option = '\0';
+ else
+ *--option = '\0';
+ }
+
+ return found;
+}
+
+static int find_and_remove_loglevel_option(char *options)
+{
+ char val[3];
+ int loglevel;
+
+ if (!find_and_remove_console_option(options, "loglevel", val,
+ sizeof(val)))
+ return -ENOENT;
+
+ if (kstrtoint(val, 10, &loglevel)) {
+ pr_warn("Invalid console loglevel, ignoring: %s\n", val);
+ return -EINVAL;
+ }
+
+ return clamp_loglevel(loglevel);
+}
+
static int __add_preferred_console(char *name, int idx, char *options,
char *brl_options, bool user_specified)
{
struct console_cmdline *c;
- int i;
+ int i, ret;

/*
* See if this tty is not yet registered, and
@@ -2435,6 +2648,11 @@ static int __add_preferred_console(char *name, int idx, char *options,
if (!brl_options)
preferred_console = i;
strscpy(c->name, name, sizeof(c->name));
+
+ ret = find_and_remove_loglevel_option(options);
+ if (ret >= 0)
+ c->level = ret;
+
c->options = options;
set_user_specified(c, user_specified);
braille_set_options(c, brl_options);
@@ -2764,7 +2982,8 @@ static void console_prepend_dropped(struct printk_message *pmsg, unsigned long d
* of @pmsg are valid. (See the documentation of struct printk_message
* for information about the @pmsg fields.)
*/
-static bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
+static bool printk_get_next_message(struct printk_message *pmsg,
+ struct console *con, u64 seq,
bool is_extended, bool may_suppress)
{
static int panic_console_dropped;
@@ -2808,7 +3027,7 @@ static bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
}

/* Skip record that has level above the console loglevel. */
- if (may_suppress && suppress_message_printing(r.info->level))
+ if (may_suppress && suppress_message_printing(r.info->level, con))
goto out;

if (is_extended) {
@@ -2851,7 +3070,7 @@ static bool console_emit_next_record(struct console *con, bool *handover, int co

*handover = false;

- if (!printk_get_next_message(&pmsg, con->seq, is_extended, true))
+ if (!printk_get_next_message(&pmsg, con, con->seq, is_extended, true))
return false;

con->dropped += pmsg.dropped;
@@ -3199,6 +3418,144 @@ static int __init keep_bootcon_setup(char *str)

early_param("keep_bootcon", keep_bootcon_setup);

+#ifdef CONFIG_PRINTK
+static ssize_t loglevel_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct console *con = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", con->level);
+}
+
+static ssize_t loglevel_store(struct device *dev, struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct console *con = dev_get_drvdata(dev);
+ ssize_t ret;
+ int level;
+
+ ret = kstrtoint(buf, 10, &level);
+ if (ret < 0)
+ return ret;
+
+ if (level == -1) {
+ con->level = level;
+ return size;
+ }
+
+ if (clamp_loglevel(level) != level)
+ return -ERANGE;
+
+ con->level = level;
+
+ 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 = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%s\n",
+ console_effective_loglevel_source_str(con));
+}
+
+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 = dev_get_drvdata(dev);
+
+ return sysfs_emit(buf, "%d\n", console_effective_loglevel(con));
+}
+
+static DEVICE_ATTR_RO(effective_loglevel);
+
+static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct console *con = dev_get_drvdata(dev);
+
+ return sysfs_emit(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)
+{
+ kfree(dev);
+}
+
+static void console_register_device(struct console *con)
+{
+ /*
+ * 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;
+
+ if (WARN_ON(con->classdev))
+ return;
+
+ con->classdev = kzalloc(sizeof(struct device), GFP_KERNEL);
+ if (!con->classdev)
+ return;
+
+ device_initialize(con->classdev);
+ dev_set_name(con->classdev, "%s%d", con->name, con->index);
+ dev_set_drvdata(con->classdev, con);
+ con->classdev->release = console_classdev_release;
+ con->classdev->class = console_class;
+ if (device_add(con->classdev))
+ put_device(con->classdev);
+}
+
+static void console_setup_class(void)
+{
+ struct console *con;
+ int cookie;
+
+ /*
+ * printk exists for the lifetime of the kernel, it cannot be unloaded,
+ * so we should never end up back in here.
+ */
+ if (WARN_ON(console_class))
+ return;
+
+ console_class = class_create(THIS_MODULE, "console");
+ if (!IS_ERR(console_class))
+ console_class->dev_groups = console_sysfs_groups;
+
+ cookie = console_srcu_read_lock();
+ for_each_console_srcu(con)
+ console_register_device(con);
+ console_srcu_read_unlock(cookie);
+}
+#else /* CONFIG_PRINTK */
+static void console_register_device(struct console *new)
+{
+}
+static void console_setup_class(void)
+{
+}
+#endif
+
/*
* This is called by register_console() to try to match
* the newly registered console with any of the ones selected
@@ -3231,6 +3588,14 @@ static int try_enable_preferred_console(struct console *newcon,
if (newcon->index < 0)
newcon->index = c->index;

+ if (c->level > 0)
+ newcon->level = c->level;
+ else
+ newcon->level = -1;
+
+ newcon->flags |= c->flags;
+ newcon->classdev = NULL;
+
if (_braille_register_console(newcon, c))
return 0;

@@ -3456,6 +3821,7 @@ void register_console(struct console *newcon)
* register_console() completes.
*/

+ console_register_device(newcon);
console_sysfs_notify();

/*
@@ -3523,6 +3889,9 @@ static int unregister_console_locked(struct console *console)
*/
synchronize_srcu(&console_srcu);

+ if (console->classdev)
+ device_unregister(console->classdev);
+
console_sysfs_notify();

if (console->exit)
@@ -3627,6 +3996,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)
{
@@ -3664,6 +4037,9 @@ static int __init printk_late_init(void)
console_cpu_notify, NULL);
WARN_ON(ret < 0);
printk_sysctl_init();
+
+ console_setup_class();
+
return 0;
}
late_initcall(printk_late_init);
diff --git a/kernel/printk/sysctl.c b/kernel/printk/sysctl.c
index c228343eeb97..688e1ebb7066 100644
--- a/kernel/printk/sysctl.c
+++ b/kernel/printk/sysctl.c
@@ -7,10 +7,14 @@
#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 int min_loglevel = LOGLEVEL_EMERG;
+static int max_loglevel = CONSOLE_LOGLEVEL_MOTORMOUTH;
+
static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write,
void *buffer, size_t *lenp, loff_t *ppos)
{
@@ -20,13 +24,48 @@ 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 *buffer, size_t *lenp, loff_t *ppos)
+{
+ int res = proc_dointvec(table, write, buffer, lenp, ppos);
+
+ if (write)
+ pr_warn_once(
+ "printk: The kernel.printk sysctl is deprecated. Consider using kernel.console_loglevel or kernel.default_message_loglevel instead.\n");
+
+ return res;
+}
+
+static int printk_console_loglevel(struct ctl_table *table, int write,
+ void *buffer, size_t *lenp, loff_t *ppos)
+{
+ struct ctl_table ltable = *table;
+ int ret, level;
+
+ if (!write)
+ return proc_dointvec(table, write, buffer, lenp, ppos);
+
+ ltable.data = &level;
+
+ ret = proc_dointvec(&ltable, write, buffer, lenp, ppos);
+ if (ret)
+ return ret;
+
+ if (level != -1 && level != clamp_loglevel(level))
+ return -ERANGE;
+
+ console_loglevel = level;
+
+ 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 +115,22 @@ static struct ctl_table printk_sysctls[] = {
.extra1 = SYSCTL_ZERO,
.extra2 = SYSCTL_TWO,
},
+ {
+ .procname = "console_loglevel",
+ .data = &console_loglevel,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = printk_console_loglevel,
+ },
+ {
+ .procname = "default_message_loglevel",
+ .data = &default_message_loglevel,
+ .maxlen = sizeof(int),
+ .mode = 0644,
+ .proc_handler = proc_dointvec_minmax,
+ .extra1 = &min_loglevel,
+ .extra2 = &max_loglevel,
+ },
{}
};

--
2.40.0

2023-04-20 13:35:20

by John Ogness

[permalink] [raw]
Subject: Re: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console

On 2023-04-20, Chris Down <[email protected]> wrote:
> diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
> index fd0c9f913940..06f16a5f1516 100644
> --- a/kernel/printk/printk.c
> +++ b/kernel/printk/printk.c
> @@ -1289,15 +1289,13 @@ static int __init boot_delay_setup(char *str)
> }
> early_param("boot_delay", boot_delay_setup);
>
> -static void boot_delay_msec(int level)
> +static void boot_delay_msec(void)
> {
> unsigned long long k;
> unsigned long timeout;
>
> if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)

^----- you will need to remove this paren as well

> - || suppress_message_printing(level)) {
> return;
> - }

John Ogness

2023-04-20 13:58:22

by Chris Down

[permalink] [raw]
Subject: Re: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console

John Ogness writes:
>On 2023-04-20, Chris Down <[email protected]> wrote:
>> diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
>> index fd0c9f913940..06f16a5f1516 100644
>> --- a/kernel/printk/printk.c
>> +++ b/kernel/printk/printk.c
>> @@ -1289,15 +1289,13 @@ static int __init boot_delay_setup(char *str)
>> }
>> early_param("boot_delay", boot_delay_setup);
>>
>> -static void boot_delay_msec(int level)
>> +static void boot_delay_msec(void)
>> {
>> unsigned long long k;
>> unsigned long timeout;
>>
>> if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
>
> ^----- you will need to remove this paren as well

That'll teach me to compile test without remembering it also needs
CONFIG_BOOT_PRINTK_DELAY=y :-) Thanks.

>
>> - || suppress_message_printing(level)) {
>> return;
>> - }
>
>John Ogness

2023-04-20 22:05:03

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console

Hi Chris,

kernel test robot noticed the following build errors:

[auto build test ERROR on cb0856346a60fe3eb837ba5e73588a41f81ac05f]

url: https://github.com/intel-lab-lkp/linux/commits/Chris-Down/printk-Do-not-delay-messages-which-aren-t-solicited-by-any-console/20230420-204202
base: cb0856346a60fe3eb837ba5e73588a41f81ac05f
patch link: https://lore.kernel.org/r/43d7f8d6e4b45a1a76fceef2d117bbc3954bc0bf.1681994221.git.chris%40chrisdown.name
patch subject: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console
config: arc-randconfig-r043-20230416 (https://download.01.org/0day-ci/archive/20230421/[email protected]/config)
compiler: arc-elf-gcc (GCC) 12.1.0
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/db9fb81bc5f175ef48cb317c24da85d0f6d4391d
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Chris-Down/printk-Do-not-delay-messages-which-aren-t-solicited-by-any-console/20230420-204202
git checkout db9fb81bc5f175ef48cb317c24da85d0f6d4391d
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=arc olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=gcc-12.1.0 make.cross W=1 O=build_dir ARCH=arc SHELL=/bin/bash fs// kernel/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

kernel/printk/printk.c: In function 'boot_delay_msec':
>> kernel/printk/printk.c:1297:64: error: expected ')' before 'return'
1297 | if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
| ~ ^
| )
1298 | return;
| ~~~~~~
>> kernel/printk/printk.c:1315:1: error: expected expression before '}' token
1315 | }
| ^
kernel/printk/printk.c:1295:23: warning: unused variable 'timeout' [-Wunused-variable]
1295 | unsigned long timeout;
| ^~~~~~~
kernel/printk/printk.c:1294:28: warning: unused variable 'k' [-Wunused-variable]
1294 | unsigned long long k;
| ^


vim +1297 kernel/printk/printk.c

bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1291
db9fb81bc5f175 kernel/printk/printk.c Chris Down 2023-04-20 1292 static void boot_delay_msec(void)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1293 {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1294 unsigned long long k;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1295 unsigned long timeout;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1296
ff48cd26fc4889 kernel/printk/printk.c Thomas Gleixner 2017-05-16 @1297 if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1298 return;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1299
3a3b6ed2235f2f kernel/printk.c Dave Young 2009-09-22 1300 k = (unsigned long long)loops_per_msec * boot_delay;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1301
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1302 timeout = jiffies + msecs_to_jiffies(boot_delay);
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1303 while (k) {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1304 k--;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1305 cpu_relax();
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1306 /*
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1307 * use (volatile) jiffies to prevent
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1308 * compiler reduction; loop termination via jiffies
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1309 * is secondary and may or may not happen.
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1310 */
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1311 if (time_after(jiffies, timeout))
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1312 break;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1313 touch_nmi_watchdog();
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1314 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 @1315 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1316 #else
db9fb81bc5f175 kernel/printk/printk.c Chris Down 2023-04-20 1317 static inline void boot_delay_msec(void)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1318 {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1319 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1320 #endif
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1321

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests

2023-04-21 00:28:01

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console

Hi Chris,

kernel test robot noticed the following build errors:

[auto build test ERROR on cb0856346a60fe3eb837ba5e73588a41f81ac05f]

url: https://github.com/intel-lab-lkp/linux/commits/Chris-Down/printk-Do-not-delay-messages-which-aren-t-solicited-by-any-console/20230420-204202
base: cb0856346a60fe3eb837ba5e73588a41f81ac05f
patch link: https://lore.kernel.org/r/43d7f8d6e4b45a1a76fceef2d117bbc3954bc0bf.1681994221.git.chris%40chrisdown.name
patch subject: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console
config: x86_64-randconfig-a013-20230417 (https://download.01.org/0day-ci/archive/20230421/[email protected]/config)
compiler: clang version 14.0.6 (https://github.com/llvm/llvm-project f28c006a5895fc0e329fe15fead81e37457cb1d1)
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/db9fb81bc5f175ef48cb317c24da85d0f6d4391d
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Chris-Down/printk-Do-not-delay-messages-which-aren-t-solicited-by-any-console/20230420-204202
git checkout db9fb81bc5f175ef48cb317c24da85d0f6d4391d
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=x86_64 olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=x86_64 SHELL=/bin/bash fs// kernel/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

>> kernel/printk/printk.c:1298:3: error: expected ')'
return;
^
kernel/printk/printk.c:1297:5: note: to match this '('
if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
^
1 error generated.


vim +1298 kernel/printk/printk.c

bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1291
db9fb81bc5f175 kernel/printk/printk.c Chris Down 2023-04-20 1292 static void boot_delay_msec(void)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1293 {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1294 unsigned long long k;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1295 unsigned long timeout;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1296
ff48cd26fc4889 kernel/printk/printk.c Thomas Gleixner 2017-05-16 1297 if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 @1298 return;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1299
3a3b6ed2235f2f kernel/printk.c Dave Young 2009-09-22 1300 k = (unsigned long long)loops_per_msec * boot_delay;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1301
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1302 timeout = jiffies + msecs_to_jiffies(boot_delay);
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1303 while (k) {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1304 k--;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1305 cpu_relax();
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1306 /*
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1307 * use (volatile) jiffies to prevent
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1308 * compiler reduction; loop termination via jiffies
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1309 * is secondary and may or may not happen.
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1310 */
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1311 if (time_after(jiffies, timeout))
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1312 break;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1313 touch_nmi_watchdog();
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1314 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1315 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1316 #else
db9fb81bc5f175 kernel/printk/printk.c Chris Down 2023-04-20 1317 static inline void boot_delay_msec(void)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1318 {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1319 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1320 #endif
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1321

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests

2023-04-21 09:05:57

by kernel test robot

[permalink] [raw]
Subject: Re: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console

Hi Chris,

kernel test robot noticed the following build errors:

[auto build test ERROR on cb0856346a60fe3eb837ba5e73588a41f81ac05f]

url: https://github.com/intel-lab-lkp/linux/commits/Chris-Down/printk-Do-not-delay-messages-which-aren-t-solicited-by-any-console/20230420-204202
base: cb0856346a60fe3eb837ba5e73588a41f81ac05f
patch link: https://lore.kernel.org/r/43d7f8d6e4b45a1a76fceef2d117bbc3954bc0bf.1681994221.git.chris%40chrisdown.name
patch subject: [PATCH v4 1/2] printk: Do not delay messages which aren't solicited by any console
config: i386-randconfig-a011 (https://download.01.org/0day-ci/archive/20230421/[email protected]/config)
compiler: clang version 14.0.6 (https://github.com/llvm/llvm-project f28c006a5895fc0e329fe15fead81e37457cb1d1)
reproduce (this is a W=1 build):
wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross
chmod +x ~/bin/make.cross
# https://github.com/intel-lab-lkp/linux/commit/db9fb81bc5f175ef48cb317c24da85d0f6d4391d
git remote add linux-review https://github.com/intel-lab-lkp/linux
git fetch --no-tags linux-review Chris-Down/printk-Do-not-delay-messages-which-aren-t-solicited-by-any-console/20230420-204202
git checkout db9fb81bc5f175ef48cb317c24da85d0f6d4391d
# save the config file
mkdir build_dir && cp config build_dir/.config
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=i386 olddefconfig
COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=i386 SHELL=/bin/bash kernel/

If you fix the issue, kindly add following tag where applicable
| Reported-by: kernel test robot <[email protected]>
| Link: https://lore.kernel.org/oe-kbuild-all/[email protected]/

All errors (new ones prefixed by >>):

>> kernel/printk/printk.c:1297:2: error: unterminated function-like macro invocation
if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
^
include/linux/compiler.h:56:9: note: macro 'if' defined here
#define if(cond, ...) if ( __trace_if_var( !!(cond , ## __VA_ARGS__) ) )
^
>> kernel/printk/printk.c:4272:24: error: expected '}'
#endif /* CONFIG_SMP */
^
kernel/printk/printk.c:1293:1: note: to match this '{'
{
^
2 errors generated.


vim +1297 kernel/printk/printk.c

bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1291
db9fb81bc5f175 kernel/printk/printk.c Chris Down 2023-04-20 1292 static void boot_delay_msec(void)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1293 {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1294 unsigned long long k;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1295 unsigned long timeout;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1296
ff48cd26fc4889 kernel/printk/printk.c Thomas Gleixner 2017-05-16 @1297 if ((boot_delay == 0 || system_state >= SYSTEM_RUNNING)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1298 return;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1299
3a3b6ed2235f2f kernel/printk.c Dave Young 2009-09-22 1300 k = (unsigned long long)loops_per_msec * boot_delay;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1301
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1302 timeout = jiffies + msecs_to_jiffies(boot_delay);
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1303 while (k) {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1304 k--;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1305 cpu_relax();
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1306 /*
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1307 * use (volatile) jiffies to prevent
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1308 * compiler reduction; loop termination via jiffies
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1309 * is secondary and may or may not happen.
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1310 */
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1311 if (time_after(jiffies, timeout))
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1312 break;
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1313 touch_nmi_watchdog();
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1314 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1315 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1316 #else
db9fb81bc5f175 kernel/printk/printk.c Chris Down 2023-04-20 1317 static inline void boot_delay_msec(void)
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1318 {
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1319 }
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1320 #endif
bfe8df3d314bdd kernel/printk.c Randy Dunlap 2007-10-16 1321

--
0-DAY CI Kernel Test Service
https://github.com/intel/lkp-tests

2023-04-25 15:07:30

by Petr Mladek

[permalink] [raw]
Subject: Re: [PATCH v4 2/2] printk: console: Support console-specific loglevels

On Thu 2023-04-20 13:39:32, Chris Down wrote:
> 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 <[email protected]>
> ---
> Documentation/ABI/testing/sysfs-class-console | 47 ++
> Documentation/admin-guide/index.rst | 1 +
> .../admin-guide/kernel-parameters.txt | 28 +-
> .../admin-guide/per-console-loglevel.rst | 92 ++++
> Documentation/admin-guide/serial-console.rst | 17 +-
> Documentation/core-api/printk-basics.rst | 35 +-
> Documentation/networking/netconsole.rst | 17 +
> MAINTAINERS | 3 +
> drivers/tty/sysrq.c | 15 +
> include/linux/console.h | 3 +
> include/linux/printk.h | 9 +
> kernel/printk/console_cmdline.h | 2 +
> kernel/printk/printk.c | 410 +++++++++++++++++-
> kernel/printk/sysctl.c | 57 ++-
> 14 files changed, 688 insertions(+), 48 deletions(-)
> create mode 100644 Documentation/ABI/testing/sysfs-class-console
> create mode 100644 Documentation/admin-guide/per-console-loglevel.rst
>
> diff --git a/Documentation/ABI/testing/sysfs-class-console b/Documentation/ABI/testing/sysfs-class-console
> new file mode 100644
> index 000000000000..35a117d521cf
> --- /dev/null
> +++ b/Documentation/ABI/testing/sysfs-class-console
> @@ -0,0 +1,47 @@
> +What: /sys/class/console/
> +Date: April 2023
> +Contact: Chris Down <[email protected]>
> +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: April 2023
> +Contact: Chris Down <[email protected]>
> +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: April 2023
> +Contact: Chris Down <[email protected]>
> +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: April 2023
> +Contact: Chris Down <[email protected]>
> +Description: Read only. "1" if the console is enabled, "0" otherwise.
> +
> +What: /sys/class/console/<C>/loglevel
> +Date: April 2023
> +Contact: Chris Down <[email protected]>
> +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 "-1" to indicate that no per-console
> + loglevel is set, and we should defer to the global controls.
> diff --git a/Documentation/admin-guide/index.rst b/Documentation/admin-guide/index.rst
> index 09a563bbe3e7..875845b02556 100644
> --- a/Documentation/admin-guide/index.rst
> +++ b/Documentation/admin-guide/index.rst
> @@ -116,6 +116,7 @@ configure specific aspects of kernel behavior to your liking.
> namespaces/index
> numastat
> parport
> + per-console-loglevel
> perf-security
> pm/index
> pnp
> diff --git a/Documentation/admin-guide/kernel-parameters.txt b/Documentation/admin-guide/kernel-parameters.txt
> index 7016cb12dc4e..902f018a5c27 100644
> --- a/Documentation/admin-guide/kernel-parameters.txt
> +++ b/Documentation/admin-guide/kernel-parameters.txt
> @@ -722,13 +722,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.
>
> @@ -2880,10 +2885,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
> diff --git a/Documentation/admin-guide/per-console-loglevel.rst b/Documentation/admin-guide/per-console-loglevel.rst
> new file mode 100644
> index 000000000000..4a7ecce7ba8a
> --- /dev/null
> +++ b/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/ttyS0/``. 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.

I would rather avoid this example. It confused me ;-)

I thought that it was talking about the "global console loglevel"
instead of "global minimum console loglevel". I was surprised
that the global value was used when a console-specific one
was defined.

</more details about my confusion>
The console loglevel defines a maximal message loglevel.
The important thing is that it defines a "limit". Most people,
including me, do not remember if the numbers go up or down.
So I ignored the word "minimum". I though that it described
the effect of the value and not a limit for the value.

Also the global minimum loglevel is a really weird setting.
I do not see much practical use for it. I guess that most
people are not aware of this limit. I think that it creates
more harm than good.
</more details about my confusion>

My proposal. I would replace the "confusing" paragraph with
something like:

* ``effective_loglevel`` (r): The effective loglevel after considering all
loglevel authorities. For example, it would show the value of
the console-specific loglevel when it is defined or the global
console loglevel value when the console-specific one is not defined.

> +* ``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 <[email protected]>, 27-April-2023
> diff --git a/Documentation/admin-guide/serial-console.rst b/Documentation/admin-guide/serial-console.rst
> index 58b32832e50a..4e204115fe4a 100644
> --- a/Documentation/admin-guide/serial-console.rst
> +++ b/Documentation/admin-guide/serial-console.rst
> @@ -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).
>
> @@ -113,3 +123,4 @@ Replace the sample values as needed.
> the integration of these patches into m68k, ppc and alpha.
>
> Miquel van Smoorenburg <[email protected]>, 11-Jun-2000
> +Chris Down <[email protected]>, 27-April-2023
> diff --git a/Documentation/core-api/printk-basics.rst b/Documentation/core-api/printk-basics.rst
> index 2dde24ca7d9f..bfad359505bb 100644
> --- a/Documentation/core-api/printk-basics.rst
> +++ b/Documentation/core-api/printk-basics.rst
> @@ -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/ttyS0/effective_loglevel
> + 6
> + $ cat /sys/class/console/ttyS0/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
> diff --git a/Documentation/networking/netconsole.rst b/Documentation/networking/netconsole.rst
> index dd0518e002f6..e2c094a96f16 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,loglevel:2
> +
> +This can also be changed at runtime::
> +
> + $ ls -l /sys/class/console/netcon0/
> + 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/MAINTAINERS b/MAINTAINERS
> index 0e64787aace8..74a9a31264d2 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -16808,6 +16808,9 @@ R: Steven Rostedt <[email protected]>
> R: John Ogness <[email protected]>
> 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/
>
> diff --git a/drivers/tty/sysrq.c b/drivers/tty/sysrq.c
> index b6e70c5cfa17..f62beafd5d49 100644
> --- a/drivers/tty/sysrq.c
> +++ b/drivers/tty/sysrq.c
> @@ -51,6 +51,7 @@
> #include <linux/syscalls.h>
> #include <linux/of.h>
> #include <linux/rcupdate.h>
> +#include <linux/console.h>
>
> #include <asm/ptrace.h>
> #include <asm/irq_regs.h>
> @@ -101,12 +102,26 @@ __setup("sysrq_always_enabled", sysrq_always_enabled_setup);
> static void sysrq_handle_loglevel(int key)
> {
> int i;
> + int cookie;
> + int warned = 0;
> + struct console *con;
>
> i = key - '0';
> console_loglevel = CONSOLE_LOGLEVEL_DEFAULT;
> pr_info("Loglevel set to %d\n", i);
> console_loglevel = i;
> +
> + cookie = console_srcu_read_lock();
> + for_each_console_srcu(con) {
> + if (!warned && per_console_loglevel_is_set(con)) {
> + warned = 1;
> + pr_warn("Overriding per-console loglevel from sysrq\n");
> + }
> + con->level = -1;
> + }
> + console_srcu_read_unlock(cookie);
> }
> +
> static const struct sysrq_key_op sysrq_loglevel_op = {
> .handler = sysrq_handle_loglevel,
> .help_msg = "loglevel(0-9)",
> diff --git a/include/linux/console.h b/include/linux/console.h
> index d3195664baa5..77052f863571 100644
> --- a/include/linux/console.h
> +++ b/include/linux/console.h
> @@ -17,6 +17,7 @@
> #include <linux/atomic.h>
> #include <linux/bits.h>
> #include <linux/rculist.h>
> +#include <linux/device.h>
> #include <linux/types.h>
>
> struct vc_data;
> @@ -203,6 +204,8 @@ struct console {
> unsigned long dropped;
> void *data;
> struct hlist_node node;
> + int level;
> + struct device *classdev;
> };
>
> #ifdef CONFIG_LOCKDEP
> diff --git a/include/linux/printk.h b/include/linux/printk.h
> index 8ef499ab3c1e..25ba0873dee8 100644
> --- a/include/linux/printk.h
> +++ b/include/linux/printk.h
> @@ -69,6 +69,8 @@ extern int console_printk[];
>
> extern void console_verbose(void);
>
> +int clamp_loglevel(int level);
> +
> /* strlen("ratelimit") + 1 */
> #define DEVKMSG_STR_MAX_SIZE 10
> extern char devkmsg_log_str[];
> @@ -139,6 +141,7 @@ void early_printk(const char *s, ...) { }
> #endif
>
> struct dev_printk_info;
> +struct console;
>
> #ifdef CONFIG_PRINTK
> asmlinkage __printf(4, 0)
> @@ -192,6 +195,8 @@ void show_regs_print_info(const char *log_lvl);
> extern asmlinkage void dump_stack_lvl(const char *log_lvl) __cold;
> extern asmlinkage void dump_stack(void) __cold;
> void printk_trigger_flush(void);
> +
> +bool per_console_loglevel_is_set(const struct console *con);
> #else
> static inline __printf(1, 0)
> int vprintk(const char *s, va_list args)
> @@ -271,6 +276,10 @@ static inline void dump_stack(void)
> static inline void printk_trigger_flush(void)
> {
> }
> +static inline bool per_console_loglevel_is_set(const struct console *con)
> +{
> + return false;
> +}
> #endif
>
> #ifdef CONFIG_SMP
> 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 06f16a5f1516..dbc686dfe15e 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>
> @@ -199,6 +200,12 @@ static int __init control_devkmsg(char *str)
> }
> __setup("printk.devkmsg=", control_devkmsg);
>
> +int clamp_loglevel(int level)
> +{
> + return clamp(level, minimum_console_loglevel,
> + CONSOLE_LOGLEVEL_MOTORMOUTH);
> +}
> +
> char devkmsg_log_str[DEVKMSG_STR_MAX_SIZE] = "ratelimit";
> #if defined(CONFIG_PRINTK) && defined(CONFIG_SYSCTL)
> int devkmsg_sysctl_set_loglvl(struct ctl_table *table, int write,
> @@ -466,6 +473,14 @@ static struct latched_seq clear_seq = {
> .val[1] = 0,
> };
>
> +static struct class *console_class;
> +
> +enum loglevel_source {
> + LLS_GLOBAL,
> + LLS_LOCAL,
> + LLS_IGNORE_LOGLEVEL,
> +};
> +
> #define LOG_LEVEL(v) ((v) & 0x07)
> #define LOG_FACILITY(v) ((v) >> 3 & 0xff)
>
> @@ -696,7 +711,8 @@ static ssize_t msg_print_ext_body(char *buf, size_t size,
> return len;
> }
>
> -static bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
> +static bool printk_get_next_message(struct printk_message *pmsg,
> + struct console *con, u64 seq,
> bool is_extended, bool may_supress);
>
> /* /dev/kmsg - userspace message inject/listen interface */
> @@ -799,7 +815,8 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
> if (ret)
> return ret;
>
> - if (!printk_get_next_message(&pmsg, atomic64_read(&user->seq), true, false)) {
> + if (!printk_get_next_message(&pmsg, NULL, atomic64_read(&user->seq),
> + true, false)) {
> if (file->f_flags & O_NONBLOCK) {
> ret = -EAGAIN;
> goto out;
> @@ -816,8 +833,8 @@ static ssize_t devkmsg_read(struct file *file, char __user *buf,
> * This pairs with __wake_up_klogd:A.
> */
> ret = wait_event_interruptible(log_wait,
> - printk_get_next_message(&pmsg, atomic64_read(&user->seq), true,
> - false)); /* LMM(devkmsg_read:A) */
> + printk_get_next_message(&pmsg, NULL, atomic64_read(&user->seq),
> + true, false)); /* LMM(devkmsg_read:A) */
> if (ret)
> goto out;
> }
> @@ -1261,9 +1278,119 @@ 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)
> +static bool __read_mostly ignore_per_console_loglevel;
> +
> +static int __init ignore_per_console_loglevel_setup(char *str)
> +{
> + ignore_per_console_loglevel = true;
> + return 0;
> +}
> +
> +early_param("ignore_per_console_loglevel", ignore_per_console_loglevel_setup);
> +module_param(ignore_per_console_loglevel, bool, 0644);
> +MODULE_PARM_DESC(
> + ignore_per_console_loglevel,
> + "ignore per-console loglevel setting (only respect global console loglevel)");
> +
> +bool per_console_loglevel_is_set(const struct console *con)
> +{
> + return !ignore_per_console_loglevel && con && (con->level > 0);
> +}
> +
> +/*
> + * Hierarchy of loglevel authority:
> + *
> + * 1. con->level. The locally set, console-specific loglevel. Optional, only
> + * valid if >0.
> + * 2. console_loglevel. The default global console loglevel, always present.
> + *
> + * The behaviour can be further changed by the following printk module
> + * parameters:
> + *
> + * 1. ignore_loglevel. Can be set at boot or at runtime with
> + * /sys/module/printk/parameters/ignore_loglevel. Overrides absolutely
> + * everything since it's used to debug.
> + * 2. ignore_per_console_loglevel. Existing per-console loglevel values are left
> + * intact, but are ignored in favour of console_loglevel as long as this is
> + * true. Also manipulated through syslog(SYSLOG_ACTION_CONSOLE_{ON,OFF}).
> + */
> +static enum loglevel_source
> +console_effective_loglevel_source(const struct console *con)
> +{
> + if (WARN_ON_ONCE(!con))
> + return LLS_GLOBAL;
> +
> + if (ignore_loglevel)
> + return LLS_IGNORE_LOGLEVEL;
> +
> + if (per_console_loglevel_is_set(con))
> + return LLS_LOCAL;
> +
> + return LLS_GLOBAL;
> +}
> +
> +static int console_effective_loglevel(const struct console *con)
> {
> - return (level >= console_loglevel && !ignore_loglevel);
> + enum loglevel_source source;
> + int level;
> +
> + if (WARN_ON_ONCE(!con))
> + return default_console_loglevel;
> +
> + source = console_effective_loglevel_source(con);
> +
> + switch (source) {
> + case LLS_IGNORE_LOGLEVEL:
> + level = CONSOLE_LOGLEVEL_MOTORMOUTH;
> + break;
> + case LLS_LOCAL:
> + level = con->level;
> + break;
> + case LLS_GLOBAL:
> + level = console_loglevel;
> + break;
> + default:
> + pr_warn("Unhandled console loglevel source: %d", source);
> + level = default_console_loglevel;
> + break;
> + }
> +
> + return level;
> +}
> +
> +static const char *
> +console_effective_loglevel_source_str(const struct console *con)
> +{
> + enum loglevel_source source;
> + const char *str;
> +
> + if (WARN_ON_ONCE(!con))
> + return "unknown";
> +
> + source = console_effective_loglevel_source(con);
> +
> + switch (source) {
> + case LLS_IGNORE_LOGLEVEL:
> + str = "ignore_loglevel";
> + break;
> + case LLS_LOCAL:
> + str = "local";
> + break;
> + case LLS_GLOBAL:
> + str = "global";
> + break;
> + default:
> + pr_warn("Unhandled console loglevel source: %d", source);
> + str = "unknown";
> + break;
> + }
> +
> + return str;
> +}
> +
> +static bool suppress_message_printing(int level, struct console *con)
> +{
> + return level >= console_effective_loglevel(con);
> }
>
> #ifdef CONFIG_BOOT_PRINTK_DELAY
> @@ -1718,6 +1845,7 @@ int do_syslog(int type, char __user *buf, int len, int source)
> struct printk_info info;
> bool clear = false;
> static int saved_console_loglevel = LOGLEVEL_DEFAULT;
> + static int saved_ignore_per_console_loglevel;
> int error;
>
> error = check_syslog_permissions(type, source);
> @@ -1758,19 +1886,28 @@ int do_syslog(int type, char __user *buf, int len, int source)
> break;
> /* Disable logging to console */
> case SYSLOG_ACTION_CONSOLE_OFF:
> - if (saved_console_loglevel == LOGLEVEL_DEFAULT)
> + if (saved_console_loglevel == LOGLEVEL_DEFAULT) {
> saved_console_loglevel = console_loglevel;
> + saved_ignore_per_console_loglevel =
> + ignore_per_console_loglevel;
> + }
> console_loglevel = minimum_console_loglevel;
> + ignore_per_console_loglevel = true;
> break;
> /* Enable logging to console */
> case SYSLOG_ACTION_CONSOLE_ON:
> if (saved_console_loglevel != LOGLEVEL_DEFAULT) {
> console_loglevel = saved_console_loglevel;
> + ignore_per_console_loglevel =
> + saved_ignore_per_console_loglevel;
> saved_console_loglevel = LOGLEVEL_DEFAULT;
> }
> break;
> /* Set level of messages printed to console */
> case SYSLOG_ACTION_CONSOLE_LEVEL:
> + if (!ignore_per_console_loglevel)
> + pr_warn_once(
> + "SYSLOG_ACTION_CONSOLE_LEVEL is ignored by consoles with an explicitly set per-console loglevel, see Documentation/admin-guide/per-console-loglevel\n");
> if (len < 1 || len > 8)
> return -EINVAL;
> if (len < minimum_console_loglevel)
> @@ -2060,11 +2197,8 @@ static u8 *__printk_recursion_counter(void)
>
> int printk_delay_msec __read_mostly;
>
> -static inline void printk_delay(int level)
> +static inline void printk_delay(void)
> {
> - if (suppress_message_printing(level))
> - return;
> -
> boot_delay_msec();
>
> if (unlikely(printk_delay_msec)) {
> @@ -2278,7 +2412,9 @@ asmlinkage int vprintk_emit(int facility, int level,
> const char *fmt, va_list args)
> {
> int printed_len;
> + int cookie;
> bool in_sched = false;
> + struct console *con;
>
> /* Suppress unimportant messages after panic happens */
> if (unlikely(suppress_printk))
> @@ -2293,7 +2429,14 @@ asmlinkage int vprintk_emit(int facility, int level,
> in_sched = true;
> }
>
> - printk_delay(level);
> + cookie = console_srcu_read_lock();
> + for_each_console_srcu(con) {
> + if (!suppress_message_printing(level, con)) {
> + printk_delay();
> + break;
> + }
> + }
> + console_srcu_read_unlock(cookie);
>
> printed_len = vprintk_store(facility, level, dev_info, fmt, args);
>
> @@ -2370,7 +2513,10 @@ static ssize_t msg_print_ext_body(char *buf, size_t size,
> struct dev_printk_info *dev_info) { return 0; }
> static void console_lock_spinning_enable(void) { }
> static int console_lock_spinning_disable_and_check(int cookie) { return 0; }
> -static bool suppress_message_printing(int level) { return false; }
> +static bool suppress_message_printing(int level, struct console *con)
> +{
> + return false;
> +}
> static bool pr_flush(int timeout_ms, bool reset_on_progress) { return true; }
> static bool __pr_flush(struct console *con, int timeout_ms, bool reset_on_progress) { return true; }
>
> @@ -2410,11 +2556,78 @@ static void set_user_specified(struct console_cmdline *c, bool user_specified)
> console_set_on_cmdline = 1;
> }
>
> +static bool find_and_remove_console_option(char *options, const char *key,
> + char *val_buf, size_t val_buf_size)
> +{
> + bool found = false, first = true;
> + char *option, *next = options;
> +
> + while ((option = strsep(&next, ","))) {
> + char *value;
> +
> + value = strchr(option, ':');
> + if (value)
> + *(value++) = '\0';
> +
> + if (strcmp(option, key) == 0) {
> + found = true;
> + if (value) {
> + if (strlen(value) > val_buf_size - 1) {
> + pr_warn("Can't copy console option value for %s:%s: not enough space (%zu)\n",
> + option, value, val_buf_size);
> + found = false;
> + } else {
> + strscpy(val_buf, value, val_buf_size);
> + }
> + } else
> + *val_buf = '\0';
> + }
> +
> + if (found)
> + break;
> +
> + if (next)
> + *(next - 1) = ',';
> + if (value)
> + *(value - 1) = ':';
> +
> + first = false;
> + }
> +
> + if (found) {
> + if (next)
> + memmove(option, next, strlen(next) + 1);
> + else if (first)
> + *option = '\0';
> + else
> + *--option = '\0';
> + }
> +
> + return found;
> +}
> +
> +static int find_and_remove_loglevel_option(char *options)
> +{
> + char val[3];
> + int loglevel;
> +
> + if (!find_and_remove_console_option(options, "loglevel", val,
> + sizeof(val)))
> + return -ENOENT;
> +
> + if (kstrtoint(val, 10, &loglevel)) {
> + pr_warn("Invalid console loglevel, ignoring: %s\n", val);
> + return -EINVAL;
> + }
> +
> + return clamp_loglevel(loglevel);
> +}
> +
> static int __add_preferred_console(char *name, int idx, char *options,
> char *brl_options, bool user_specified)
> {
> struct console_cmdline *c;
> - int i;
> + int i, ret;
>
> /*
> * See if this tty is not yet registered, and
> @@ -2435,6 +2648,11 @@ static int __add_preferred_console(char *name, int idx, char *options,
> if (!brl_options)
> preferred_console = i;
> strscpy(c->name, name, sizeof(c->name));
> +
> + ret = find_and_remove_loglevel_option(options);
> + if (ret >= 0)
> + c->level = ret;
> +
> c->options = options;
> set_user_specified(c, user_specified);
> braille_set_options(c, brl_options);
> @@ -2764,7 +2982,8 @@ static void console_prepend_dropped(struct printk_message *pmsg, unsigned long d
> * of @pmsg are valid. (See the documentation of struct printk_message
> * for information about the @pmsg fields.)
> */
> -static bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
> +static bool printk_get_next_message(struct printk_message *pmsg,
> + struct console *con, u64 seq,
> bool is_extended, bool may_suppress)
> {
> static int panic_console_dropped;
> @@ -2808,7 +3027,7 @@ static bool printk_get_next_message(struct printk_message *pmsg, u64 seq,
> }
>
> /* Skip record that has level above the console loglevel. */
> - if (may_suppress && suppress_message_printing(r.info->level))
> + if (may_suppress && suppress_message_printing(r.info->level, con))
> goto out;
>
> if (is_extended) {
> @@ -2851,7 +3070,7 @@ static bool console_emit_next_record(struct console *con, bool *handover, int co
>
> *handover = false;
>
> - if (!printk_get_next_message(&pmsg, con->seq, is_extended, true))
> + if (!printk_get_next_message(&pmsg, con, con->seq, is_extended, true))
> return false;
>
> con->dropped += pmsg.dropped;
> @@ -3199,6 +3418,144 @@ static int __init keep_bootcon_setup(char *str)
>
> early_param("keep_bootcon", keep_bootcon_setup);
>
> +#ifdef CONFIG_PRINTK
> +static ssize_t loglevel_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct console *con = dev_get_drvdata(dev);
> +
> + return sysfs_emit(buf, "%d\n", con->level);
> +}
> +
> +static ssize_t loglevel_store(struct device *dev, struct device_attribute *attr,
> + const char *buf, size_t size)
> +{
> + struct console *con = dev_get_drvdata(dev);
> + ssize_t ret;
> + int level;
> +
> + ret = kstrtoint(buf, 10, &level);
> + if (ret < 0)
> + return ret;
> +
> + if (level == -1) {
> + con->level = level;
> + return size;
> + }
> +
> + if (clamp_loglevel(level) != level)
> + return -ERANGE;
> +
> + con->level = level;
> +
> + 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 = dev_get_drvdata(dev);
> +
> + return sysfs_emit(buf, "%s\n",
> + console_effective_loglevel_source_str(con));
> +}
> +
> +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 = dev_get_drvdata(dev);
> +
> + return sysfs_emit(buf, "%d\n", console_effective_loglevel(con));
> +}
> +
> +static DEVICE_ATTR_RO(effective_loglevel);
> +
> +static ssize_t enabled_show(struct device *dev, struct device_attribute *attr,
> + char *buf)
> +{
> + struct console *con = dev_get_drvdata(dev);
> +
> + return sysfs_emit(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)
> +{
> + kfree(dev);
> +}
> +
> +static void console_register_device(struct console *con)
> +{
> + /*
> + * 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;
> +
> + if (WARN_ON(con->classdev))
> + return;
> +
> + con->classdev = kzalloc(sizeof(struct device), GFP_KERNEL);
> + if (!con->classdev)
> + return;
> +
> + device_initialize(con->classdev);
> + dev_set_name(con->classdev, "%s%d", con->name, con->index);
> + dev_set_drvdata(con->classdev, con);
> + con->classdev->release = console_classdev_release;
> + con->classdev->class = console_class;
> + if (device_add(con->classdev))
> + put_device(con->classdev);
> +}
> +
> +static void console_setup_class(void)
> +{
> + struct console *con;
> + int cookie;
> +
> + /*
> + * printk exists for the lifetime of the kernel, it cannot be unloaded,
> + * so we should never end up back in here.
> + */
> + if (WARN_ON(console_class))
> + return;
> +
> + console_class = class_create(THIS_MODULE, "console");
> + if (!IS_ERR(console_class))
> + console_class->dev_groups = console_sysfs_groups;
> +
> + cookie = console_srcu_read_lock();
> + for_each_console_srcu(con)
> + console_register_device(con);
> + console_srcu_read_unlock(cookie);
> +}
> +#else /* CONFIG_PRINTK */
> +static void console_register_device(struct console *new)
> +{
> +}
> +static void console_setup_class(void)
> +{
> +}
> +#endif
> +
> /*
> * This is called by register_console() to try to match
> * the newly registered console with any of the ones selected
> @@ -3231,6 +3588,14 @@ static int try_enable_preferred_console(struct console *newcon,
> if (newcon->index < 0)
> newcon->index = c->index;
>
> + if (c->level > 0)
> + newcon->level = c->level;
> + else
> + newcon->level = -1;
> +
> + newcon->flags |= c->flags;
> + newcon->classdev = NULL;
> +
> if (_braille_register_console(newcon, c))
> return 0;
>
> @@ -3456,6 +3821,7 @@ void register_console(struct console *newcon)
> * register_console() completes.
> */
>
> + console_register_device(newcon);
> console_sysfs_notify();
>
> /*
> @@ -3523,6 +3889,9 @@ static int unregister_console_locked(struct console *console)
> */
> synchronize_srcu(&console_srcu);
>
> + if (console->classdev)
> + device_unregister(console->classdev);
> +
> console_sysfs_notify();
>
> if (console->exit)
> @@ -3627,6 +3996,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)
> {
> @@ -3664,6 +4037,9 @@ static int __init printk_late_init(void)
> console_cpu_notify, NULL);
> WARN_ON(ret < 0);
> printk_sysctl_init();
> +
> + console_setup_class();
> +
> return 0;
> }
> late_initcall(printk_late_init);
> diff --git a/kernel/printk/sysctl.c b/kernel/printk/sysctl.c
> index c228343eeb97..688e1ebb7066 100644
> --- a/kernel/printk/sysctl.c
> +++ b/kernel/printk/sysctl.c
> @@ -7,10 +7,14 @@
> #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 int min_loglevel = LOGLEVEL_EMERG;
> +static int max_loglevel = CONSOLE_LOGLEVEL_MOTORMOUTH;
> +
> static int proc_dointvec_minmax_sysadmin(struct ctl_table *table, int write,
> void *buffer, size_t *lenp, loff_t *ppos)
> {
> @@ -20,13 +24,48 @@ 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 *buffer, size_t *lenp, loff_t *ppos)
> +{
> + int res = proc_dointvec(table, write, buffer, lenp, ppos);
> +
> + if (write)
> + pr_warn_once(
> + "printk: The kernel.printk sysctl is deprecated. Consider using kernel.console_loglevel or kernel.default_message_loglevel instead.\n");
> +
> + return res;
> +}
> +
> +static int printk_console_loglevel(struct ctl_table *table, int write,
> + void *buffer, size_t *lenp, loff_t *ppos)
> +{
> + struct ctl_table ltable = *table;
> + int ret, level;
> +
> + if (!write)
> + return proc_dointvec(table, write, buffer, lenp, ppos);
> +
> + ltable.data = &level;
> +
> + ret = proc_dointvec(&ltable, write, buffer, lenp, ppos);
> + if (ret)
> + return ret;
> +
> + if (level != -1 && level != clamp_loglevel(level))
> + return -ERANGE;
> +
> + console_loglevel = level;
> +
> + 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 +115,22 @@ static struct ctl_table printk_sysctls[] = {
> .extra1 = SYSCTL_ZERO,
> .extra2 = SYSCTL_TWO,
> },
> + {
> + .procname = "console_loglevel",
> + .data = &console_loglevel,
> + .maxlen = sizeof(int),
> + .mode = 0644,
> + .proc_handler = printk_console_loglevel,
> + },
> + {
> + .procname = "default_message_loglevel",
> + .data = &default_message_loglevel,
> + .maxlen = sizeof(int),
> + .mode = 0644,
> + .proc_handler = proc_dointvec_minmax,
> + .extra1 = &min_loglevel,
> + .extra2 = &max_loglevel,
> + },
> {}
> };
>
> --
> 2.40.0

2023-04-27 14:08:12

by Petr Mladek

[permalink] [raw]
Subject: Re: [PATCH v4 2/2] printk: console: Support console-specific loglevels

On Tue 2023-04-25 16:57:49, Petr Mladek wrote:
> On Thu 2023-04-20 13:39:32, Chris Down wrote:
> > 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.
> >
> > --- /dev/null
> > +++ b/Documentation/admin-guide/per-console-loglevel.rst
> > +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/ttyS0/``. 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.
>
> I would rather avoid this example. It confused me ;-)
>
> I thought that it was talking about the "global console loglevel"
> instead of "global minimum console loglevel". I was surprised
> that the global value was used when a console-specific one
> was defined.
>
> </more details about my confusion>
> The console loglevel defines a maximal message loglevel.
> The important thing is that it defines a "limit". Most people,
> including me, do not remember if the numbers go up or down.
> So I ignored the word "minimum". I though that it described
> the effect of the value and not a limit for the value.
>
> Also the global minimum loglevel is a really weird setting.
> I do not see much practical use for it. I guess that most
> people are not aware of this limit. I think that it creates
> more harm than good.
> </more details about my confusion>
>
> My proposal. I would replace the "confusing" paragraph with
> something like:
>
> * ``effective_loglevel`` (r): The effective loglevel after considering all
> loglevel authorities. For example, it would show the value of
> the console-specific loglevel when it is defined or the global
> console loglevel value when the console-specific one is not defined.
>
> > +* ``effective_loglevel_source`` (r): The loglevel authority which resulted in
> > + the effective loglevel being set. The following values can be present:
> > +

Please, ignore this mail. It was a draft that I sent by
mistake. Let's follow the discussion in my proper review against v5,
see https://lore.kernel.org/r/ZEp9dXwHCYNPidjC@alley

Best Regards,
Petr