1 // SPDX-License-Identifier: GPL-2.0
3 * Support for warning track interruption
5 * Copyright IBM Corp. 2023
9 #include <linux/debugfs.h>
10 #include <linux/kallsyms.h>
11 #include <linux/smpboot.h>
12 #include <linux/irq.h>
13 #include <uapi/linux/sched/types.h>
14 #include <asm/debug.h>
18 #define WTI_DBF_LEN 64
27 /* debug data for s390dbf */
30 * Represents the real-time thread responsible to
31 * acknowledge the warning-track interrupt and trigger
32 * preliminary and postliminary precautions.
34 struct task_struct *thread;
36 * If pending is true, the real-time thread must be scheduled.
37 * If not, a wake up of that thread will remain a noop.
42 static DEFINE_PER_CPU(struct wti_state, wti_state);
44 static debug_info_t *wti_dbg;
47 * During a warning-track grace period, interrupts are disabled
48 * to prevent delays of the warning-track acknowledgment.
50 * Once the CPU is physically dispatched again, interrupts are
54 static void wti_irq_disable(void)
59 local_irq_save(flags);
60 local_ctl_store(6, &cr6);
61 /* disable all I/O interrupts */
62 cr6.val &= ~0xff000000UL;
63 local_ctl_load(6, &cr6);
64 local_irq_restore(flags);
67 static void wti_irq_enable(void)
72 local_irq_save(flags);
73 local_ctl_store(6, &cr6);
74 /* enable all I/O interrupts */
75 cr6.val |= 0xff000000UL;
76 local_ctl_load(6, &cr6);
77 local_irq_restore(flags);
80 static void store_debug_data(struct wti_state *st)
82 struct pt_regs *regs = get_irq_regs();
84 st->dbg.pid = current->pid;
87 st->dbg.addr = regs->psw.addr;
90 static void wti_interrupt(struct ext_code ext_code,
91 unsigned int param32, unsigned long param64)
93 struct wti_state *st = this_cpu_ptr(&wti_state);
95 inc_irq_stat(IRQEXT_WTI);
99 wake_up_process(st->thread);
102 static int wti_pending(unsigned int cpu)
104 struct wti_state *st = per_cpu_ptr(&wti_state, cpu);
109 static void wti_dbf_grace_period(struct wti_state *st)
111 struct wti_debug *wdi = &st->dbg;
112 char buf[WTI_DBF_LEN];
115 snprintf(buf, sizeof(buf), "%d %pS", wdi->pid, (void *)wdi->addr);
117 snprintf(buf, sizeof(buf), "%d <user>", wdi->pid);
118 debug_text_event(wti_dbg, 2, buf);
122 static int wti_show(struct seq_file *seq, void *v)
124 struct wti_state *st;
129 for_each_online_cpu(cpu)
130 seq_printf(seq, "CPU%-8d", cpu);
132 for_each_online_cpu(cpu) {
133 st = per_cpu_ptr(&wti_state, cpu);
134 seq_printf(seq, " %10lu", st->dbg.missed);
140 DEFINE_SHOW_ATTRIBUTE(wti);
142 static void wti_thread_fn(unsigned int cpu)
144 struct wti_state *st = per_cpu_ptr(&wti_state, cpu);
148 * Yield CPU voluntarily to the hypervisor. Control
149 * resumes when hypervisor decides to dispatch CPU
150 * to this LPAR again.
152 if (diag49c(DIAG49C_SUBC_ACK))
153 wti_dbf_grace_period(st);
157 static struct smp_hotplug_thread wti_threads = {
158 .store = &wti_state.thread,
159 .thread_should_run = wti_pending,
160 .thread_fn = wti_thread_fn,
161 .thread_comm = "cpuwti/%u",
162 .selfparking = false,
165 static int __init wti_init(void)
167 struct sched_param wti_sched_param = { .sched_priority = MAX_RT_PRIO - 1 };
168 struct dentry *wti_dir;
169 struct wti_state *st;
175 rc = smpboot_register_percpu_thread(&wti_threads);
178 for_each_online_cpu(cpu) {
179 st = per_cpu_ptr(&wti_state, cpu);
180 sched_setscheduler(st->thread, SCHED_FIFO, &wti_sched_param);
182 rc = register_external_irq(EXT_IRQ_WARNING_TRACK, wti_interrupt);
184 pr_warn("Couldn't request external interrupt 0x1007\n");
187 irq_subclass_register(IRQ_SUBCLASS_WARNING_TRACK);
188 rc = diag49c(DIAG49C_SUBC_REG);
190 pr_warn("Failed to register warning track interrupt through DIAG 49C\n");
194 wti_dir = debugfs_create_dir("wti", arch_debugfs_dir);
195 debugfs_create_file("stat", 0400, wti_dir, NULL, &wti_fops);
196 wti_dbg = debug_register("wti", 1, 1, WTI_DBF_LEN);
199 goto out_debug_register;
201 rc = debug_register_view(wti_dbg, &debug_hex_ascii_view);
203 goto out_debug_register;
206 debug_unregister(wti_dbg);
208 irq_subclass_unregister(IRQ_SUBCLASS_WARNING_TRACK);
209 unregister_external_irq(EXT_IRQ_WARNING_TRACK, wti_interrupt);
211 smpboot_unregister_percpu_thread(&wti_threads);
215 late_initcall(wti_init);