From nobody Mon Dec 1 22:03:45 2025 Received: from mail-pl1-f180.google.com (mail-pl1-f180.google.com [209.85.214.180]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9B36D30B53C for ; Thu, 27 Nov 2025 19:44:09 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.180 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764272651; cv=none; b=PI+5H3FF8DuRgCxrirGNj06pMyGGgFKb4BP0lQwh3L+e3dFF6ibaD/a8GqjkFDkdZZ0bIVj8HFS9EXbMD7B7OvUq9YXEQWLFUKmac7BI0kO1yi9q/Q5L4qO4iQsmLtZLRf3jwOWcNQfo9MwSLnrA1aEygUMmnRnEkBB/Ddrfx6c= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1764272651; c=relaxed/simple; bh=tnViH2dzBqWyBnom7yG1amwDdBtY1T5Ml7kUmk5V3p0=; h=Date:From:To:Cc:Subject:Message-ID:References:MIME-Version: Content-Type:Content-Disposition:In-Reply-To; b=ZMEGZ8bz72JTOOz4Flmlrnk/Sro4MdrSIMNcpc8nk7dRbMTCFfPxU60v6xuyfF/6GeRxkfLSYWLggbCWTZqQ3qrBYOx+X+x9yGf+pk7faQNGTbrZRuveIXFskxNTuDS9PaAPXmNLSXzsbbFQnieNXszuFpGeK09CYugIAPxgqWs= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=chrisdown.name; spf=pass smtp.mailfrom=chrisdown.name; dkim=pass (1024-bit key) header.d=chrisdown.name header.i=@chrisdown.name header.b=M39zbYWh; arc=none smtp.client-ip=209.85.214.180 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=chrisdown.name Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=chrisdown.name Authentication-Results: smtp.subspace.kernel.org; dkim=pass (1024-bit key) header.d=chrisdown.name header.i=@chrisdown.name header.b="M39zbYWh" Received: by mail-pl1-f180.google.com with SMTP id d9443c01a7336-298039e00c2so16171835ad.3 for ; Thu, 27 Nov 2025 11:44:09 -0800 (PST) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=chrisdown.name; s=google; t=1764272649; x=1764877449; darn=vger.kernel.org; h=user-agent:in-reply-to:content-disposition:mime-version:references :message-id:subject:cc:to:from:date:from:to:cc:subject:date :message-id:reply-to; bh=1Xbzn/TCryABbXfwDYMxAd2A2AjmWx+28BBZyX0HR+o=; b=M39zbYWh+11wUwMN3H9meSb8oGuRCqvKIVATc6JsznC+WG1zc8QuX/3G0SK2dTFjoc AyBmqQFoZkDVJOljacfz/YOWsnxpZn70ovdvV7XRFBz1IIg50y3BQajKEi3Oc0LLX+0s VNcgJ913GUXXbv2XPA8fMa/QnB4YctmK13OLA= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1764272649; x=1764877449; h=user-agent:in-reply-to:content-disposition:mime-version:references :message-id:subject:cc:to:from:date:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=1Xbzn/TCryABbXfwDYMxAd2A2AjmWx+28BBZyX0HR+o=; b=eEFZ6QlsIIdokENwc0xL0opZtl8dIfEmiT+iVf9Zb3h7pCM2kV6VuXZKKw56mI6Y7i SKS16BDY0w7zHnbY3lOgY5v3d6TDQNv//gtq0M+eBCOeTGdqmnkImk8VVTsJyONC3Ak+ hrrxU+7GpJhqhaL4WY/FqWYTTEGrS+BA9JSOdCGDW6fXnxFHXjZcA+RKOkqZZ2p3Oqvn /BxmQNo0l57cRnlMvY6svT5xVFLq2h5IW/UV0mx3StMtvWJTeMdcXwQszxWXgzja3e2a KBFGIeP4YB2b/zxycn8cctGYqd54NlQC53ae0cDvtoIALlAV4czv0KFAYwsxLdvDtK+r Q93g== X-Gm-Message-State: AOJu0YyrZsWjT7wXcdJoz/gH9ZAf+tPFPdAqmBFBL4gDIlE6FUZ1oLlL Df5UMzkhaq5Owq7816Eeop9c83xEMh2ods+/SNTixDzNh4OgUlmSjt5IoFNIX9rFj0A= X-Gm-Gg: ASbGncsrQe+dBOL/+/VckFIsQ3XHLJrsfYrkHKgovBGJK50XlC6z4TL5qJOXBI7XBbM cY2C94WbK3PsZm3tDfJSkE5SJ5J6iEl/GhwCRwItQQyKOQ/XS/51QFeewiAPqGPUw9f+OxBuy1a IWp1d+z+L2Yuk/bDeF8eYe2sVrJ3u5piEhZ3DnzSc5c1PxXQR8HuCC9dEUH1zT9yqhv2ayNshBO WOTviDGOzFBIg3GXNibnVWhjEmGYlM/FxvPjCWC8r/0DJvD2/r5pLMvBQkFeGgfvsnbXkeMLMsy XamO9UHWInyol3a9F4yIv6S+r6X8rpY/DhNNyUaLzwQ5Rurd7O8YS5snYxuVTkL6/WJsjhuWogw KAw8KNDtV/zDHGT5KpzDriVw2k5SaHO4eyBJ8bUn7ybX3C3MH6h5RrnicBS9VZJlTwT/SJW3iCT Vapik79qf3 X-Google-Smtp-Source: AGHT+IG24r6Vt/CfEgbSfcwPtZvdjreuM/i+/ClXQNNHI0dmyNPh8jzrVnCn7KTjgz3qrQ9i3SZv8w== X-Received: by 2002:a17:903:3845:b0:295:2cb6:f4a8 with SMTP id d9443c01a7336-29b6bf7f2cdmr230193015ad.51.1764272648689; Thu, 27 Nov 2025 11:44:08 -0800 (PST) Received: from localhost ([116.86.198.140]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-29bce4142f4sm25332275ad.10.2025.11.27.11.44.08 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Thu, 27 Nov 2025 11:44:08 -0800 (PST) Date: Fri, 28 Nov 2025 03:44:06 +0800 From: Chris Down To: Petr Mladek Cc: linux-kernel@vger.kernel.org, Greg Kroah-Hartman , Sergey Senozhatsky , Steven Rostedt , John Ogness , Geert Uytterhoeven , Tony Lindgren , kernel-team@fb.com Subject: [PATCH v8 14/21] printk: console: Introduce sysfs interface for per-console loglevels Message-ID: <84b8d91693e56f6026271bc40553bcb6657ab24d.1764272407.git.chris@chrisdown.name> References: Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: User-Agent: Mutt/2.2.15 (2b349c5e) (2025-10-02) Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" A sysfs interface under /sys/class/console/ is created that permits viewing and configuring per-console attributes. This is the main interface with which we expect users to interact with and configure per-console loglevels. Each console device now has its own directory (for example, /sys/class/console/ttyS0/) containing the following attributes: - effective_loglevel (ro): The effective loglevel for the console after considering all loglevel authorities (e.g., global loglevel, per-console loglevel). - effective_loglevel_source (ro): The source of the effective loglevel (e.g., local, global, ignore_loglevel). - loglevel (rw): The per-console loglevel. Writing a value between 0 (KERN_EMERG) and 8 (KERN_DEBUG + 1) sets the per-console loglevel. Writing -1 disables the per-console loglevel. In terms of technical implementation, we embed a device pointer in the console struct, and register each console using it so we can expose attributes in sysfs. We currently expose the following attributes: % ls -l /sys/class/console/ttyS0/ total 0 lrwxrwxrwx 1 root root 0 Oct 23 13:17 subsystem -> ../../../../class= /console/ -r--r--r-- 1 root root 4096 Oct 23 13:18 effective_loglevel -r--r--r-- 1 root root 4096 Oct 23 13:18 effective_loglevel_source -rw-r--r-- 1 root root 4096 Oct 23 13:18 loglevel -rw-r--r-- 1 root root 4096 Oct 23 13:17 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) # kref_init: refcount =3D 1 device_add(con->classdev) get_device(con->classdev) # temporary: refcount++ (to 2) ... put_device(con->classdev) # drop temporary refcount-- (to 1) At steady state the class device holds a single persistent reference. Unregistration: unregister_console_locked(con) struct device *dev =3D console->classdev; console->classdev =3D NULL; device_unregister(dev) device_del(dev) device_remove_class_symlinks(dev) sysfs_delete_link() kernfs_remove_by_name_ns() __kernfs_remove() kernfs_drain() kernfs_drain_open_files() # waits for open file handles ... kobject_del(&dev->kobj) # removes from sysfs put_device(dev) # final kref_put: refcount-- (1 -> 0) kobject_release() kobject_cleanup() device_release() console_classdev_release(dev) kfree(dev) Signed-off-by: Chris Down --- Documentation/ABI/testing/sysfs-class-console | 58 ++++ Documentation/core-api/printk-basics.rst | 35 ++- Documentation/networking/netconsole.rst | 12 + MAINTAINERS | 1 + include/linux/console.h | 4 + kernel/printk/Makefile | 2 +- kernel/printk/internal.h | 8 + kernel/printk/printk.c | 32 ++ kernel/printk/sysfs.c | 290 ++++++++++++++++++ 9 files changed, 424 insertions(+), 18 deletions(-) create mode 100644 Documentation/ABI/testing/sysfs-class-console create mode 100644 kernel/printk/sysfs.c diff --git a/Documentation/ABI/testing/sysfs-class-console b/Documentation/= ABI/testing/sysfs-class-console new file mode 100644 index 000000000000..8c0f0cf3f6c5 --- /dev/null +++ b/Documentation/ABI/testing/sysfs-class-console @@ -0,0 +1,58 @@ +What: /sys/class/console/ +Date: November 2025 +Contact: Chris Down +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//effective_loglevel +Date: November 2025 +Contact: Chris Down +Permissions: 0444 (world readable) +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//effective_loglevel_source +Date: November 2025 +Contact: Chris Down +Permissions: 0444 (world readable) +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: + =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + local The loglevel comes from the console's + per-console loglevel setting. + global The loglevel comes from the global + console_loglevel. + ignore_loglevel Both the per-console loglevel and global + loglevel are ignored as ignore_loglevel is + present on the kernel command line. + =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D =3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D + +What: /sys/class/console//loglevel +Date: November 2025 +Contact: Chris Down +Permissions: 0644 (root read/write, user read) +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). + + Valid values: + 1-8: LOGLEVEL_ALERT (1) to LOGLEVEL_DEBUG + 1 (8) + -1: Use global console_loglevel (default) + 0: Explicitly rejected (KERN_EMERG not allowed) + + Error codes: + EINVAL: Non-numeric input + ERANGE: Value out of valid range (< 1 or > 8, excluding -1) + ERANGE: Value is 0 (KERN_EMERG not allowed for per-console) + ERANGE: Value below system minimum_console_loglevel + + The special value -1 disables the per-console loglevel, making + the console use the global loglevel instead. + 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: =20 The log level specifies the importance of a message. The kernel decides wh= ether to show the message immediately (printing it to the current console) depen= ding -on its log level and the current *console_loglevel* (a kernel variable). I= f the -message priority is higher (lower log level value) than the *console_logle= vel* -the message will be printed to the console. +on its log level and the current global *console_loglevel* or local per-co= nsole +loglevel (kernel variables). If the message priority is higher (lower log = level +value) than the effective loglevel the message will be printed to the cons= ole. =20 If the log level is omitted, the message is printed with ``KERN_DEFAULT`` level. =20 -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: =20 - $ 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 =20 -The result shows the *current*, *default*, *minimum* and *boot-time-defaul= t* log -levels. +To change the default loglevel for all consoles, simply write the desired = level +to ``/proc/sys/kernel/console_loglevel``. For example:: =20 -To change the current console_loglevel simply write the desired level to -``/proc/sys/kernel/printk``. For example, to print all messages to the con= sole:: + # echo 5 > /proc/sys/kernel/console_loglevel =20 - # 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 o= r at +``/sys/module/printk/parameters/ignore_per_console_loglevel``. =20 -Another way, using ``dmesg``:: - - # dmesg -n 5 - -sets the console_loglevel to print KERN_WARNING (4) or more severe message= s to -console. See ``dmesg(1)`` for more information. +For more information on per-console loglevels, see +Documentation/admin-guide/per-console-loglevel.rst. =20 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/networ= king/netconsole.rst index 59cb9982afe6..5ff12e88e5b8 100644 --- a/Documentation/networking/netconsole.rst +++ b/Documentation/networking/netconsole.rst @@ -78,6 +78,18 @@ Built-in netconsole starts immediately after the TCP sta= ck is initialized and attempts to bring up the supplied dev at the supplied address. =20 +You can also set a loglevel 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 + -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 informatio= n. + The remote host has several options to receive the kernel messages, for example: =20 diff --git a/MAINTAINERS b/MAINTAINERS index e56494c7a956..2e6faa647b43 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -20505,6 +20505,7 @@ R: John Ogness R: Sergey Senozhatsky 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/core-api/printk-basics.rst F: include/linux/printk.h F: kernel/printk/ diff --git a/include/linux/console.h b/include/linux/console.h index a670c40623ad..a97235550668 100644 --- a/include/linux/console.h +++ b/include/linux/console.h @@ -16,6 +16,7 @@ =20 #include #include +#include #include #include #include @@ -323,6 +324,8 @@ struct nbcon_write_context { * @dropped: Number of unreported dropped ringbuffer records * @data: Driver private data * @node: hlist node for the console list + * @classdev: sysfs class device for this console, used to expose + * per-console controls in /sys/class/console// * * @nbcon_state: State for nbcon consoles * @nbcon_seq: Sequence number of the next record for nbcon to print @@ -352,6 +355,7 @@ struct console { unsigned long dropped; void *data; struct hlist_node node; + struct device *classdev; =20 /* nbcon console specific members */ =20 diff --git a/kernel/printk/Makefile b/kernel/printk/Makefile index f8004ac3983d..19e4919a13a7 100644 --- a/kernel/printk/Makefile +++ b/kernel/printk/Makefile @@ -1,6 +1,6 @@ # SPDX-License-Identifier: GPL-2.0-only obj-y =3D printk.o -obj-$(CONFIG_PRINTK) +=3D printk_safe.o nbcon.o +obj-$(CONFIG_PRINTK) +=3D sysfs.o printk_safe.o nbcon.o obj-$(CONFIG_A11Y_BRAILLE_CONSOLE) +=3D braille.o obj-$(CONFIG_PRINTK_INDEX) +=3D index.o =20 diff --git a/kernel/printk/internal.h b/kernel/printk/internal.h index f2ebaa2a6aa2..3b3a3c982412 100644 --- a/kernel/printk/internal.h +++ b/kernel/printk/internal.h @@ -21,6 +21,8 @@ enum loglevel_source { LLS_IGNORE_LOGLEVEL, }; =20 +int console_clamp_loglevel(int level); + enum loglevel_source console_effective_loglevel_source(int con_level); int console_effective_loglevel(int con_level); @@ -46,6 +48,9 @@ int console_effective_loglevel(int con_level); =20 #ifdef CONFIG_PRINTK =20 +void console_register_device(struct console *new); +void console_setup_class(void); + #ifdef CONFIG_PRINTK_CALLER #define PRINTK_PREFIX_MAX 48 #else @@ -217,6 +222,9 @@ static inline void nbcon_kthreads_wake(void) { } static inline bool console_is_usable(struct console *con, short flags, bool use_atomic) { return false; } =20 +static inline void console_register_device(struct console *new) { } +static inline void console_setup_class(void) { } + #endif /* CONFIG_PRINTK */ =20 extern bool have_boot_console; diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c index 1d28887e7218..605e0811cfc6 100644 --- a/kernel/printk/printk.c +++ b/kernel/printk/printk.c @@ -200,6 +200,24 @@ static int __init control_devkmsg(char *str) } __setup("printk.devkmsg=3D", control_devkmsg); =20 +/** + * console_clamp_loglevel - Clamp a loglevel to valid console loglevel ran= ge + * + * @level: The loglevel to clamp + * + * Console loglevels must be within the range [LOGLEVEL_ALERT, LOGLEVEL_DE= BUG + 1]. + * This function clamps a given level to this valid range. + * + * Note: This does not allow LOGLEVEL_EMERG (0) for per-console loglevels,= as + * level 0 is reserved for emergency messages that should always go to all= consoles. + * + * Return: The clamped loglevel value + */ +int console_clamp_loglevel(int level) +{ + return clamp(level, LOGLEVEL_ALERT, LOGLEVEL_DEBUG + 1); +} + char devkmsg_log_str[DEVKMSG_STR_MAX_SIZE] =3D "ratelimit"; #if defined(CONFIG_PRINTK) && defined(CONFIG_SYSCTL) int devkmsg_sysctl_set_loglvl(const struct ctl_table *table, int write, @@ -4185,6 +4203,9 @@ void register_console(struct console *newcon) u64 init_seq; int err; =20 + if (newcon->level =3D=3D 0) + newcon->level =3D LOGLEVEL_DEFAULT; + console_list_lock(); =20 for_each_console(con) { @@ -4314,6 +4335,7 @@ void register_console(struct console *newcon) if (use_device_lock) newcon->device_unlock(newcon, flags); =20 + console_register_device(newcon); console_sysfs_notify(); =20 /* @@ -4429,6 +4451,13 @@ static int unregister_console_locked(struct console = *console) if (console->flags & CON_NBCON) nbcon_free(console); =20 + if (console->classdev) { + struct device *dev =3D console->classdev; + + console->classdev =3D NULL; + device_unregister(dev); + } + console_sysfs_notify(); =20 if (console->exit) @@ -4578,6 +4607,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/sysfs.c b/kernel/printk/sysfs.c new file mode 100644 index 000000000000..38d03046c45d --- /dev/null +++ b/kernel/printk/sysfs.c @@ -0,0 +1,290 @@ +// SPDX-License-Identifier: GPL-2.0 +#include +#include +#include +#include +#include +#include "internal.h" + +/** + * console_sysfs_read_loglevel - Locklessly read the console specific logl= evel + * when accessing the related sysfs interface + * @con: struct console pointer of console to read loglevel from + * + * Locklessly reading @con->level provides a consistent read value because + * there is at most one CPU modifying @con->level and that CPU is using on= ly + * read-modify-write operations to do so. + * + * Only use this function to read the loglevel via the related sysfs inter= face. + * The sysfs API makes sure that the structure cannot disappear while the + * interface is used. + * + * Context: Sysfs interface for the given console. + * Return: The current value of the @con->level field. + */ +static inline int console_sysfs_read_loglevel(const struct console *con) +{ + /* + * The READ_ONCE() matches the WRITE_ONCE() when @level is modified + * for registered consoles. + */ + return data_race(READ_ONCE(con->level)); +} + +/** + * console_sysfs_write_loglevel - Write the console specific loglevel via + * sysfs interface. + * @con: struct console pointer of console to write loglevel to + * @con_level: new loglevel value to write + * + * Only use this function to write the loglevel via the related sysfs inte= rface. + * The sysfs API makes sure that the structure cannot disappear while the + * interface is used. + * + * Context: Any context. + */ +static inline void console_sysfs_write_loglevel(struct console *con, int c= on_level) +{ + /* This matches the READ_ONCE() in console_sysfs_read_loglevel(). */ + WRITE_ONCE(con->level, con_level); +} + +/** + * console_effective_loglevel_source_str - Get string name of loglevel sou= rce + * + * @con: The console to query + * + * Returns a human-readable string describing the source of the console's + * effective loglevel (e.g., "local", "global", "ignore_loglevel"). + * + * Return: String name of the loglevel source + */ +static const char * +console_effective_loglevel_source_str(const struct console *con) +{ + enum loglevel_source source; + const char *str; + int con_level; + + con_level =3D console_sysfs_read_loglevel(con); + source =3D console_effective_loglevel_source(con_level); + + switch (source) { + case LLS_IGNORE_LOGLEVEL: + str =3D "ignore_loglevel"; + break; + case LLS_LOCAL: + str =3D "local"; + break; + case LLS_GLOBAL: + str =3D "global"; + break; + default: + str =3D "unknown"; + break; + } + + return str; +} + +static ssize_t effective_loglevel_source_show(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + struct console *con =3D dev_get_drvdata(dev); + const char *str; + + str =3D console_effective_loglevel_source_str(con); + return sysfs_emit(buf, "%s\n", str); +} + +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 =3D dev_get_drvdata(dev); + int con_level; + + con_level =3D console_sysfs_read_loglevel(con); + return sysfs_emit(buf, "%d\n", console_effective_loglevel(con_level)); +} + +static DEVICE_ATTR_RO(effective_loglevel); + +static ssize_t loglevel_show(struct device *dev, struct device_attribute *= attr, + char *buf) +{ + struct console *con =3D dev_get_drvdata(dev); + int con_level; + + con_level =3D console_sysfs_read_loglevel(con); + 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 =3D dev_get_drvdata(dev); + ssize_t ret; + int level; + + ret =3D kstrtoint(buf, 10, &level); + if (ret < 0) + return ret; + + /* -1 means "use global loglevel" */ + if (level =3D=3D -1) + goto out; + + /* + * Reject level 0 (KERN_EMERG) - per-console loglevel must be > 0. + * Emergency messages should go to all consoles, so they cannot be + * filtered per-console. + */ + if (level =3D=3D 0) + return -ERANGE; + + if (console_clamp_loglevel(level) !=3D level) + return -ERANGE; + + /* + * If the system has a minimum console loglevel set (via sysctl or + * kernel parameter), enforce it. This prevents setting per-console + * loglevels below the system minimum. + */ + if (minimum_console_loglevel > CONSOLE_LOGLEVEL_MIN && + level < minimum_console_loglevel) + return -ERANGE; + +out: + console_sysfs_write_loglevel(con, level); + return size; +} + +static DEVICE_ATTR_RW(loglevel); + +static struct attribute *console_sysfs_attrs[] =3D { + &dev_attr_loglevel.attr, + &dev_attr_effective_loglevel_source.attr, + &dev_attr_effective_loglevel.attr, + NULL, +}; + +ATTRIBUTE_GROUPS(console_sysfs); + +static const struct class console_class =3D { + .name =3D "console", + .dev_groups =3D console_sysfs_groups, +}; +static bool console_class_registered; + +/** + * console_classdev_release - Release callback for console class devices + * + * @dev: The device being released + * + * Called when the last reference to a console class device is dropped. + * Frees the memory allocated for the device structure. + */ +static void console_classdev_release(struct device *dev) +{ + kfree(dev); +} + +/** + * console_register_device - Register a console's sysfs class device + * + * @con: The console to register + * + * Creates a sysfs class device for the given console under /sys/class/con= sole/. + * This enables userspace access to per-console attributes like loglevel. + * + * If called before the console class is registered (during early boot), + * this function returns early and the device will be registered later + * by console_setup_class(). + */ +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 (!console_class_registered) + return; + + if (WARN_ON(con->classdev)) + return; + + con->classdev =3D kzalloc(sizeof(struct device), GFP_KERNEL); + if (!con->classdev) + return; + + device_initialize(con->classdev); + con->classdev->release =3D console_classdev_release; + con->classdev->class =3D &console_class; + dev_set_drvdata(con->classdev, con); + if (dev_set_name(con->classdev, "%s%d", con->name, con->index)) { + put_device(con->classdev); + con->classdev =3D NULL; + return; + } + + /* + * This class device exists solely to expose attributes (like loglevel) + * and does not control physical power states. Power is managed by the + * underlying hardware device. Disable PM entirely to prevent the + * creation of confusing and unused power sysfs attributes. + */ + device_set_pm_not_required(con->classdev); + + if (device_add(con->classdev)) { + put_device(con->classdev); + con->classdev =3D NULL; + } +} + +/** + * console_setup_class - Initialize the console sysfs class + * + * Registers the console class with sysfs and creates class devices for all + * currently registered consoles. Called during late init after sysfs is + * available. + * + * Consoles registered before this function is called will have their class + * devices created here. Consoles registered afterwards will have their + * devices created by console_register_device() during register_console(). + */ +void console_setup_class(void) +{ + struct console *con; + int cookie; + int err; + + /* + * 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_registered)) + return; + + err =3D class_register(&console_class); + if (err) { + pr_err("console: failed to register class: %pe\n", ERR_PTR(err)); + return; + } + + /* + * Take console_list_lock() before exposing the class globally. + * This ensures register_console() (which holds the lock) cannot + * see the class until it's fully initialised with dev_groups. + */ + console_list_lock(); + console_class_registered =3D true; + cookie =3D console_srcu_read_lock(); + for_each_console_srcu(con) + console_register_device(con); + console_srcu_read_unlock(cookie); + console_list_unlock(); +} --=20 2.51.2