| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Support for warning track interruption | |
| * | |
| * Copyright IBM Corp. 2023 | |
| */ | |
| #include <linux/cpu.h> | |
| #include <linux/debugfs.h> | |
| #include <linux/kallsyms.h> | |
| #include <linux/smpboot.h> | |
| #include <linux/irq.h> | |
| #include <uapi/linux/sched/types.h> | |
| #include <asm/debug.h> | |
| #include <asm/diag.h> | |
| #include <asm/sclp.h> | |
| #define WTI_DBF_LEN 64 | |
| struct wti_debug { | |
| unsigned long missed; | |
| unsigned long addr; | |
| pid_t pid; | |
| }; | |
| struct wti_state { | |
| /* debug data for s390dbf */ | |
| struct wti_debug dbg; | |
| /* | |
| * Represents the real-time thread responsible to | |
| * acknowledge the warning-track interrupt and trigger | |
| * preliminary and postliminary precautions. | |
| */ | |
| struct task_struct *thread; | |
| /* | |
| * If pending is true, the real-time thread must be scheduled. | |
| * If not, a wake up of that thread will remain a noop. | |
| */ | |
| bool pending; | |
| }; | |
| static DEFINE_PER_CPU(struct wti_state, wti_state); | |
| static debug_info_t *wti_dbg; | |
| /* | |
| * During a warning-track grace period, interrupts are disabled | |
| * to prevent delays of the warning-track acknowledgment. | |
| * | |
| * Once the CPU is physically dispatched again, interrupts are | |
| * re-enabled. | |
| */ | |
| static void wti_irq_disable(void) | |
| { | |
| unsigned long flags; | |
| struct ctlreg cr6; | |
| local_irq_save(flags); | |
| local_ctl_store(6, &cr6); | |
| /* disable all I/O interrupts */ | |
| cr6.val &= ~0xff000000UL; | |
| local_ctl_load(6, &cr6); | |
| local_irq_restore(flags); | |
| } | |
| static void wti_irq_enable(void) | |
| { | |
| unsigned long flags; | |
| struct ctlreg cr6; | |
| local_irq_save(flags); | |
| local_ctl_store(6, &cr6); | |
| /* enable all I/O interrupts */ | |
| cr6.val |= 0xff000000UL; | |
| local_ctl_load(6, &cr6); | |
| local_irq_restore(flags); | |
| } | |
| static void store_debug_data(struct wti_state *st) | |
| { | |
| struct pt_regs *regs = get_irq_regs(); | |
| st->dbg.pid = current->pid; | |
| st->dbg.addr = 0; | |
| if (!user_mode(regs)) | |
| st->dbg.addr = regs->psw.addr; | |
| } | |
| static void wti_interrupt(struct ext_code ext_code, | |
| unsigned int param32, unsigned long param64) | |
| { | |
| struct wti_state *st = this_cpu_ptr(&wti_state); | |
| inc_irq_stat(IRQEXT_WTI); | |
| wti_irq_disable(); | |
| store_debug_data(st); | |
| st->pending = true; | |
| wake_up_process(st->thread); | |
| } | |
| static int wti_pending(unsigned int cpu) | |
| { | |
| struct wti_state *st = per_cpu_ptr(&wti_state, cpu); | |
| return st->pending; | |
| } | |
| static void wti_dbf_grace_period(struct wti_state *st) | |
| { | |
| struct wti_debug *wdi = &st->dbg; | |
| char buf[WTI_DBF_LEN]; | |
| if (wdi->addr) | |
| snprintf(buf, sizeof(buf), "%d %pS", wdi->pid, (void *)wdi->addr); | |
| else | |
| snprintf(buf, sizeof(buf), "%d <user>", wdi->pid); | |
| debug_text_event(wti_dbg, 2, buf); | |
| wdi->missed++; | |
| } | |
| static int wti_show(struct seq_file *seq, void *v) | |
| { | |
| struct wti_state *st; | |
| int cpu; | |
| cpus_read_lock(); | |
| seq_puts(seq, " "); | |
| for_each_online_cpu(cpu) | |
| seq_printf(seq, "CPU%-8d", cpu); | |
| seq_putc(seq, '\n'); | |
| for_each_online_cpu(cpu) { | |
| st = per_cpu_ptr(&wti_state, cpu); | |
| seq_printf(seq, " %10lu", st->dbg.missed); | |
| } | |
| seq_putc(seq, '\n'); | |
| cpus_read_unlock(); | |
| return 0; | |
| } | |
| DEFINE_SHOW_ATTRIBUTE(wti); | |
| static void wti_thread_fn(unsigned int cpu) | |
| { | |
| struct wti_state *st = per_cpu_ptr(&wti_state, cpu); | |
| st->pending = false; | |
| /* | |
| * Yield CPU voluntarily to the hypervisor. Control | |
| * resumes when hypervisor decides to dispatch CPU | |
| * to this LPAR again. | |
| */ | |
| if (diag49c(DIAG49C_SUBC_ACK)) | |
| wti_dbf_grace_period(st); | |
| wti_irq_enable(); | |
| } | |
| static struct smp_hotplug_thread wti_threads = { | |
| .store = &wti_state.thread, | |
| .thread_should_run = wti_pending, | |
| .thread_fn = wti_thread_fn, | |
| .thread_comm = "cpuwti/%u", | |
| .selfparking = false, | |
| }; | |
| static int __init wti_init(void) | |
| { | |
| struct sched_param wti_sched_param = { .sched_priority = MAX_RT_PRIO - 1 }; | |
| struct dentry *wti_dir; | |
| struct wti_state *st; | |
| int cpu, rc; | |
| rc = -EOPNOTSUPP; | |
| if (!sclp.has_wti) | |
| goto out; | |
| rc = smpboot_register_percpu_thread(&wti_threads); | |
| if (WARN_ON(rc)) | |
| goto out; | |
| for_each_online_cpu(cpu) { | |
| st = per_cpu_ptr(&wti_state, cpu); | |
| sched_setscheduler(st->thread, SCHED_FIFO, &wti_sched_param); | |
| } | |
| rc = register_external_irq(EXT_IRQ_WARNING_TRACK, wti_interrupt); | |
| if (rc) { | |
| pr_warn("Couldn't request external interrupt 0x1007\n"); | |
| goto out_thread; | |
| } | |
| irq_subclass_register(IRQ_SUBCLASS_WARNING_TRACK); | |
| rc = diag49c(DIAG49C_SUBC_REG); | |
| if (rc) { | |
| pr_warn("Failed to register warning track interrupt through DIAG 49C\n"); | |
| rc = -EOPNOTSUPP; | |
| goto out_subclass; | |
| } | |
| wti_dir = debugfs_create_dir("wti", arch_debugfs_dir); | |
| debugfs_create_file("stat", 0400, wti_dir, NULL, &wti_fops); | |
| wti_dbg = debug_register("wti", 1, 1, WTI_DBF_LEN); | |
| if (!wti_dbg) { | |
| rc = -ENOMEM; | |
| goto out_debug_register; | |
| } | |
| rc = debug_register_view(wti_dbg, &debug_hex_ascii_view); | |
| if (rc) | |
| goto out_debug_register; | |
| goto out; | |
| out_debug_register: | |
| debug_unregister(wti_dbg); | |
| out_subclass: | |
| irq_subclass_unregister(IRQ_SUBCLASS_WARNING_TRACK); | |
| unregister_external_irq(EXT_IRQ_WARNING_TRACK, wti_interrupt); | |
| out_thread: | |
| smpboot_unregister_percpu_thread(&wti_threads); | |
| out: | |
| return rc; | |
| } | |
| late_initcall(wti_init); |