]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
s390/debug: Add s390dbf kernel parameter
authorPeter Oberparleiter <oberpar@linux.ibm.com>
Wed, 6 May 2026 14:53:27 +0000 (16:53 +0200)
committerAlexander Gordeev <agordeev@linux.ibm.com>
Mon, 11 May 2026 14:42:31 +0000 (16:42 +0200)
Problem determination using s390dbf logging sometimes requires changing
the default logging level or log area size. While this is possible
using sysfs interfaces, there is no easy way to adjust these parameters
for early boot code that emits logs before userspace is available.

Add an s390dbf kernel parameter to address this shortcoming. The
parameter can be used to specify log level and area size (in units of
pages). A level of '-' turns logging off for an area. Logs can be
identified by name or a shell-style pattern.

Parameter format:

  s390dbf=<name|pattern>:[<level>|-]:[<pages>][,...]

Example:

  s390dbf=cio*:6:128,sclp_err::2

Specified parameters are applied immediately during debug area
registration for regular log areas. For early, static debug areas,
log levels are changed during early_param() parsing, while size
changes are applied at arch_initcall-time.

Signed-off-by: Peter Oberparleiter <oberpar@linux.ibm.com>
Tested-by: Vineeth Vijayan <vneethv@linux.ibm.com>
Reviewed-by: Heiko Carstens <hca@linux.ibm.com>
Signed-off-by: Alexander Gordeev <agordeev@linux.ibm.com>
Documentation/arch/s390/s390dbf.rst
arch/s390/Kconfig
arch/s390/include/asm/debug.h
arch/s390/kernel/debug.c
arch/s390/kernel/vmlinux.lds.S

index aad6d88974fe586677855405a2926eaac525dbbc..034374c88dbaf4060e0ed403d5592d93ca2bb5c7 100644 (file)
@@ -106,6 +106,36 @@ the ``debug_stoppable`` sysctl. If you set ``debug_stoppable`` to 0 the debug
 feature cannot be stopped. If the debug feature is already stopped, it
 will stay deactivated.
 
+Kernel parameters
+-----------------
+The size and log levels of debug logs can be configured early during boot using
+the ``s390dbf`` kernel parameter. The parameter accepts a debug log name, a log
+level, and a log size, separated by colon characters (``:``). To configure only
+a single attribute, either the log level or the log size may be omitted.
+
+To configure multiple debug logs, the parameter may be specified multiple times,
+or multiple parameter sets may be provided in a single instance, separated by
+commas.
+
+Parameter format::
+
+       s390dbf=<name|pattern>:[<level>|-]:[<pages>][,...]
+
+where:
+
+- ``name`` specifies either an exact debug log name or a shell-style wildcard
+  pattern
+- ``level`` specifies the log level, or ``-`` to completely deactivate the log
+- ``pages`` specifies the debug area size in pages
+
+Example::
+
+       s390dbf=cio*:6:128,sclp_err::2
+
+This example sets the log level to 6 and the log size to 128 pages for all debug
+logs whose names start with ``cio``. It also sets the log level of the
+``sclp_err`` debug log to 2.
+
 Kernel Interfaces:
 ------------------
 
index ecbcbb781e40aa8cf3b0ef160b447bcb51eafde2..301cdc9f73aff797f784ef3f3edca4bd33cbaca6 100644 (file)
@@ -179,6 +179,7 @@ config S390
        select GENERIC_SMP_IDLE_THREAD
        select GENERIC_TIME_VSYSCALL
        select GENERIC_IOREMAP if PCI
+       select GLOB
        select HAVE_ALIGNED_STRUCT_PAGE
        select HAVE_ARCH_AUDITSYSCALL
        select HAVE_ARCH_GET_SECUREBOOT
index c5440b3ee53d737ec76fea58f192d2eb6e3a3d8f..39d484c597748c31082517cceacd93bf031021c9 100644 (file)
@@ -490,6 +490,7 @@ arch_initcall(VNAME(var, reg))
 __DEFINE_STATIC_AREA(var);                                             \
 static debug_info_t __refdata var =                                    \
        __DEBUG_INFO_INIT(var, (name), (buf_size));                     \
+static debug_info_t __used __section(".s390dbf_info") *VNAME(var, info) = &var; \
 __REGISTER_STATIC_DEBUG_INFO(var, name, pages, nr_areas, view)
 
 void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas);
index 7650f2adb5cf8667f464c734ee4f15245b5f1bd9..7bc403d081fac6c40b876fd711e3c2d12b1c5b17 100644 (file)
@@ -26,6 +26,8 @@
 #include <linux/math.h>
 #include <linux/minmax.h>
 #include <linux/debugfs.h>
+#include <linux/glob.h>
+#include <linux/stringify.h>
 
 #include <asm/debug.h>
 
@@ -154,6 +156,7 @@ static unsigned int __used debug_feature_version = __DEBUG_FEATURE_VERSION;
 static debug_info_t *debug_area_first;
 static debug_info_t *debug_area_last;
 static DEFINE_MUTEX(debug_mutex);
+extern debug_info_t *__s390dbf_info[], *__s390dbf_info_end[];
 
 static int initialized;
 static int debug_critical;
@@ -168,7 +171,91 @@ static const struct file_operations debug_file_ops = {
 
 static struct dentry *debug_debugfs_root_entry;
 
+/* List of debug area parameters to override */
+#define PARAM_UNSET    -2
+#define PARAM_NUM      16
+static struct debug_param_t {
+       char name[DEBUG_MAX_NAME_LEN + 1];
+       int level;
+       int pages;
+} debug_param[PARAM_NUM];
+static int debug_param_num;
+
 /* functions */
+static void debug_get_param(const char *name, int *level, int *pages)
+{
+       struct debug_param_t *p;
+       int i;
+
+       for (i = 0; i < debug_param_num; i++) {
+               p = &debug_param[i];
+               if (!glob_match(p->name, name))
+                       continue;
+               if (level && p->level != PARAM_UNSET) {
+                       pr_info("%s: override level to %d\n", name, p->level);
+                       *level = p->level;
+               }
+               if (pages && p->pages != PARAM_UNSET) {
+                       pr_info("%s: override pages to %d\n", name, p->pages);
+                       *pages = p->pages;
+               }
+       }
+}
+
+#define LVL_LEN                10
+#define LVL_FMT                "%" __stringify(LVL_LEN) "[^:]"
+#define NAME_FMT       "%" __stringify(DEBUG_MAX_NAME_LEN) "[^:]"
+
+static bool __init s390dbf_parse_one(const char *arg, struct debug_param_t *param)
+{
+       struct debug_param_t p = { { 0 }, PARAM_UNSET, PARAM_UNSET };
+       char level[LVL_LEN + 1] = { 0 };
+
+       /* arg: <name|pattern>:[<level>|-]:[<pages>] */
+       if (sscanf(arg, NAME_FMT ":" LVL_FMT ":%d", p.name, level, &p.pages) > 1) {
+               if (strcmp(level, "-") == 0)
+                       p.level = DEBUG_OFF_LEVEL;
+               else if (kstrtoint(level, 0, &p.level) != 0)
+                       return false;
+       } else if (sscanf(arg, NAME_FMT "::%d", p.name, &p.pages) != 2) {
+               return false;
+       }
+
+       if (p.level != PARAM_UNSET && p.level != DEBUG_OFF_LEVEL &&
+           (p.level < 0 || p.level > DEBUG_MAX_LEVEL))
+               return false;
+       if (p.pages != PARAM_UNSET && p.pages < 0)
+               return false;
+       *param = p;
+
+       return true;
+}
+
+static int __init s390dbf_parse(char *arg)
+{
+       debug_info_t **id;
+       int i, rc = 0;
+
+       while (arg && debug_param_num < PARAM_NUM) {
+               if (s390dbf_parse_one(arg, &debug_param[debug_param_num]))
+                       debug_param_num++;
+               else
+                       rc = -EINVAL;
+               arg = strchr(arg, ',');
+               if (arg)
+                       arg++;
+       }
+
+       /*
+        * Apply level to static debug areas, delay buffer size changes until
+        * regular memory allocations are possible.
+        */
+       for (i = 0, id = __s390dbf_info; &id[i] < __s390dbf_info_end; i++)
+               debug_get_param(id[i]->name, &id[i]->level, NULL);
+
+       return rc;
+}
+early_param("s390dbf", s390dbf_parse);
 
 /*
  * debug_areas_alloc
@@ -305,10 +392,11 @@ static void debug_info_free(debug_info_t *db_info)
 static debug_info_t *debug_info_create(const char *name, int pages_per_area,
                                       int nr_areas, int buf_size, umode_t mode)
 {
+       int level = DEBUG_DEFAULT_LEVEL;
        debug_info_t *rc;
 
-       rc = debug_info_alloc(name, pages_per_area, nr_areas, buf_size,
-                             DEBUG_DEFAULT_LEVEL, ALL_AREAS);
+       debug_get_param(name, &level, &pages_per_area);
+       rc = debug_info_alloc(name, pages_per_area, nr_areas, buf_size, level, ALL_AREAS);
        if (!rc)
                goto out;
 
@@ -872,6 +960,7 @@ void debug_register_static(debug_info_t *id, int pages_per_area, int nr_areas)
                return;
        }
 
+       debug_get_param(id->name, &id->level, &pages_per_area);
        copy = debug_info_alloc("", pages_per_area, nr_areas, id->buf_size,
                                id->level, ALL_AREAS);
        if (!copy) {
@@ -975,16 +1064,7 @@ static int debug_set_size(debug_info_t *id, int nr_areas, int pages_per_area)
        return 0;
 }
 
-/**
- * debug_set_level() - Sets new actual debug level if new_level is valid.
- *
- * @id:                handle for debug log
- * @new_level: new debug level
- *
- * Return:
- *    none
- */
-void debug_set_level(debug_info_t *id, int new_level)
+static void _debug_set_level(debug_info_t *id, int new_level)
 {
        unsigned long flags;
 
@@ -1003,6 +1083,23 @@ void debug_set_level(debug_info_t *id, int new_level)
        id->level = new_level;
        raw_spin_unlock_irqrestore(&id->lock, flags);
 }
+
+/**
+ * debug_set_level() - Sets new actual debug level if new_level is valid.
+ *
+ * @id:                handle for debug log
+ * @new_level: new debug level
+ *
+ * Return:
+ *    none
+ */
+void debug_set_level(debug_info_t *id, int new_level)
+{
+       /* Level specified via kernel parameter takes precedence */
+       debug_get_param(id->name, &new_level, NULL);
+
+       _debug_set_level(id, new_level);
+}
 EXPORT_SYMBOL(debug_set_level);
 
 /*
@@ -1529,7 +1626,7 @@ static int debug_input_level_fn(debug_info_t *id, struct debug_view *view,
                goto out;
        }
        if (str[0] == '-') {
-               debug_set_level(id, DEBUG_OFF_LEVEL);
+               _debug_set_level(id, DEBUG_OFF_LEVEL);
                rc = user_len;
                goto free_str;
        } else {
@@ -1539,7 +1636,7 @@ static int debug_input_level_fn(debug_info_t *id, struct debug_view *view,
                pr_warn("%s is not a valid level for a debug feature\n", str);
                rc = -EINVAL;
        } else {
-               debug_set_level(id, new_level);
+               _debug_set_level(id, new_level);
                rc = user_len;
        }
 free_str:
index 2b62395e35bfb1279679c593d59de7208cb65f09..1f0e71c58eb9cca9b0aa6c9925740f689116ad2e 100644 (file)
@@ -159,6 +159,13 @@ SECTIONS
        }
 #endif
 
+       . = ALIGN(8);
+       .s390dbf_info : {
+               __s390dbf_info = .;
+               *(.s390dbf_info)
+               __s390dbf_info_end = .;
+       }
+
        /*
         * Table with the patch locations to undo expolines
        */