watchdog: diag288_wdt: Implement module autoload
authorHeiko Carstens <hca@linux.ibm.com>
Thu, 10 Apr 2025 09:50:36 +0000 (11:50 +0200)
committerHeiko Carstens <hca@linux.ibm.com>
Wed, 16 Apr 2025 07:26:49 +0000 (09:26 +0200)
The s390 specific diag288_wdt watchdog driver makes use of the virtual
watchdog timer, which is available in most machine configurations.
If executing the diagnose instruction with subcode 0x288 results in an
exception the watchdog timer is not available, otherwise it is available.

In order to allow module autoload of the diag288_wdt module, move the
detection of the virtual watchdog timer to early boot code, and provide
its availability as a cpu feature.

This allows to make use of module_cpu_feature_match() to automatically load
the module iff the virtual watchdog timer is available.

Suggested-by: Marc Hartmayer <mhartmay@linux.ibm.com>
Tested-by: Mete Durlu <meted@linux.ibm.com>
Acked-by: Guenter Roeck <linux@roeck-us.net>
Link: https://lore.kernel.org/r/20250410095036.1525057-1-hca@linux.ibm.com
Signed-off-by: Heiko Carstens <hca@linux.ibm.com>
arch/s390/boot/startup.c
arch/s390/include/asm/cpufeature.h
arch/s390/include/asm/diag288.h [new file with mode: 0644]
arch/s390/include/asm/machine.h
arch/s390/kernel/cpufeature.c
drivers/watchdog/diag288_wdt.c

index 06316fb8e0fad8c55b02eb91349c171d9a1c4e70..da8337e63a3e234108ac00e4eea639d28ba3f4fd 100644 (file)
@@ -6,6 +6,7 @@
 #include <asm/boot_data.h>
 #include <asm/extmem.h>
 #include <asm/sections.h>
+#include <asm/diag288.h>
 #include <asm/maccess.h>
 #include <asm/machine.h>
 #include <asm/sysinfo.h>
@@ -71,6 +72,20 @@ static void detect_machine_type(void)
                set_machine_feature(MFEATURE_VM);
 }
 
+static void detect_diag288(void)
+{
+       /* "BEGIN" in EBCDIC character set */
+       static const char cmd[] = "\xc2\xc5\xc7\xc9\xd5";
+       unsigned long action, len;
+
+       action = machine_is_vm() ? (unsigned long)cmd : LPARWDT_RESTART;
+       len = machine_is_vm() ? sizeof(cmd) : 0;
+       if (__diag288(WDT_FUNC_INIT, MIN_INTERVAL, action, len))
+               return;
+       __diag288(WDT_FUNC_CANCEL, 0, 0, 0);
+       set_machine_feature(MFEATURE_DIAG288);
+}
+
 static void detect_diag9c(void)
 {
        unsigned int cpu;
@@ -519,6 +534,8 @@ void startup_kernel(void)
        detect_facilities();
        detect_diag9c();
        detect_machine_type();
+       /* detect_diag288() needs machine type */
+       detect_diag288();
        cmma_init();
        sanitize_prot_virt_host();
        max_physmem_end = detect_max_physmem_end();
index e08169bd63a55d1448507a79bcaf469d9ce9366d..6c6a99660e78b838ba63ebb858766b350e34a3d0 100644 (file)
@@ -15,6 +15,7 @@ enum {
        S390_CPU_FEATURE_MSA,
        S390_CPU_FEATURE_VXRS,
        S390_CPU_FEATURE_UV,
+       S390_CPU_FEATURE_D288,
        MAX_CPU_FEATURES
 };
 
diff --git a/arch/s390/include/asm/diag288.h b/arch/s390/include/asm/diag288.h
new file mode 100644 (file)
index 0000000..5e1b43c
--- /dev/null
@@ -0,0 +1,41 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#ifndef _ASM_S390_DIAG288_H
+#define _ASM_S390_DIAG288_H
+
+#include <asm/asm-extable.h>
+#include <asm/types.h>
+
+#define MIN_INTERVAL 15            /* Minimal time supported by diag288 */
+#define MAX_INTERVAL 3600   /* One hour should be enough - pure estimation */
+
+#define WDT_DEFAULT_TIMEOUT 30
+
+/* Function codes - init, change, cancel */
+#define WDT_FUNC_INIT 0
+#define WDT_FUNC_CHANGE 1
+#define WDT_FUNC_CANCEL 2
+#define WDT_FUNC_CONCEAL 0x80000000
+
+/* Action codes for LPAR watchdog */
+#define LPARWDT_RESTART 0
+
+static inline int __diag288(unsigned int func, unsigned int timeout,
+                           unsigned long action, unsigned int len)
+{
+       union register_pair r1 = { .even = func, .odd = timeout, };
+       union register_pair r3 = { .even = action, .odd = len, };
+       int rc = -EINVAL;
+
+       asm volatile(
+               "       diag    %[r1],%[r3],0x288\n"
+               "0:     lhi     %[rc],0\n"
+               "1:"
+               EX_TABLE(0b, 1b)
+               : [rc] "+d" (rc)
+               : [r1] "d" (r1.pair), [r3] "d" (r3.pair)
+               : "cc", "memory");
+       return rc;
+}
+
+#endif /* _ASM_S390_DIAG288_H */
index 54478caa52378b6a04c4baaed330330a37d03691..8abe5afdbfc45e4023e4309920474f49a2b9c7e3 100644 (file)
@@ -18,6 +18,7 @@
 #define MFEATURE_VM            7
 #define MFEATURE_KVM           8
 #define MFEATURE_LPAR          9
+#define MFEATURE_DIAG288       10
 
 #ifndef __ASSEMBLY__
 
index 1b2ae42a0c156e1656cbd145138928b1ebeaafde..76210f001028faef51eb1ceeba97e1bb3dad7933 100644 (file)
@@ -5,11 +5,13 @@
 
 #include <linux/cpufeature.h>
 #include <linux/bug.h>
+#include <asm/machine.h>
 #include <asm/elf.h>
 
 enum {
        TYPE_HWCAP,
        TYPE_FACILITY,
+       TYPE_MACHINE,
 };
 
 struct s390_cpu_feature {
@@ -21,6 +23,7 @@ static struct s390_cpu_feature s390_cpu_features[MAX_CPU_FEATURES] = {
        [S390_CPU_FEATURE_MSA]  = {.type = TYPE_HWCAP, .num = HWCAP_NR_MSA},
        [S390_CPU_FEATURE_VXRS] = {.type = TYPE_HWCAP, .num = HWCAP_NR_VXRS},
        [S390_CPU_FEATURE_UV]   = {.type = TYPE_FACILITY, .num = 158},
+       [S390_CPU_FEATURE_D288] = {.type = TYPE_MACHINE, .num = MFEATURE_DIAG288},
 };
 
 /*
@@ -38,6 +41,8 @@ int cpu_have_feature(unsigned int num)
                return !!(elf_hwcap & BIT(feature->num));
        case TYPE_FACILITY:
                return test_facility(feature->num);
+       case TYPE_MACHINE:
+               return test_machine_feature(feature->num);
        default:
                WARN_ON_ONCE(1);
                return 0;
index 76dffc89c641bfa3a74740dce77579211fcddf0c..887d5a6c155b95949f68bab4724283fec0191b1d 100644 (file)
 #include <linux/watchdog.h>
 #include <asm/machine.h>
 #include <asm/ebcdic.h>
+#include <asm/diag288.h>
 #include <asm/diag.h>
 #include <linux/io.h>
 
 #define MAX_CMDLEN 240
 #define DEFAULT_CMD "SYSTEM RESTART"
 
-#define MIN_INTERVAL 15     /* Minimal time supported by diag88 */
-#define MAX_INTERVAL 3600   /* One hour should be enough - pure estimation */
-
-#define WDT_DEFAULT_TIMEOUT 30
-
-/* Function codes - init, change, cancel */
-#define WDT_FUNC_INIT 0
-#define WDT_FUNC_CHANGE 1
-#define WDT_FUNC_CANCEL 2
-#define WDT_FUNC_CONCEAL 0x80000000
-
-/* Action codes for LPAR watchdog */
-#define LPARWDT_RESTART 0
-
 static char wdt_cmd[MAX_CMDLEN] = DEFAULT_CMD;
 static bool conceal_on;
 static bool nowayout_info = WATCHDOG_NOWAYOUT;
@@ -75,22 +62,8 @@ static char *cmd_buf;
 static int diag288(unsigned int func, unsigned int timeout,
                   unsigned long action, unsigned int len)
 {
-       union register_pair r1 = { .even = func, .odd = timeout, };
-       union register_pair r3 = { .even = action, .odd = len, };
-       int err;
-
        diag_stat_inc(DIAG_STAT_X288);
-
-       err = -EINVAL;
-       asm volatile(
-               "       diag    %[r1],%[r3],0x288\n"
-               "0:     la      %[err],0\n"
-               "1:\n"
-               EX_TABLE(0b, 1b)
-               : [err] "+d" (err)
-               : [r1] "d" (r1.pair), [r3] "d" (r3.pair)
-               : "cc", "memory");
-       return err;
+       return __diag288(func, timeout, action, len);
 }
 
 static int diag288_str(unsigned int func, unsigned int timeout, char *cmd)
@@ -189,8 +162,6 @@ static struct watchdog_device wdt_dev = {
 
 static int __init diag288_init(void)
 {
-       int ret;
-
        watchdog_set_nowayout(&wdt_dev, nowayout_info);
 
        if (machine_is_vm()) {
@@ -199,24 +170,6 @@ static int __init diag288_init(void)
                        pr_err("The watchdog cannot be initialized\n");
                        return -ENOMEM;
                }
-
-               ret = diag288_str(WDT_FUNC_INIT, MIN_INTERVAL, "BEGIN");
-               if (ret != 0) {
-                       pr_err("The watchdog cannot be initialized\n");
-                       kfree(cmd_buf);
-                       return -EINVAL;
-               }
-       } else {
-               if (diag288(WDT_FUNC_INIT, WDT_DEFAULT_TIMEOUT,
-                           LPARWDT_RESTART, 0)) {
-                       pr_err("The watchdog cannot be initialized\n");
-                       return -EINVAL;
-               }
-       }
-
-       if (diag288(WDT_FUNC_CANCEL, 0, 0, 0)) {
-               pr_err("The watchdog cannot be deactivated\n");
-               return -EINVAL;
        }
 
        return watchdog_register_device(&wdt_dev);
@@ -228,5 +181,5 @@ static void __exit diag288_exit(void)
        kfree(cmd_buf);
 }
 
-module_init(diag288_init);
+module_cpu_feature_match(S390_CPU_FEATURE_D288, diag288_init);
 module_exit(diag288_exit);