xref: /linux/arch/s390/kernel/wti.c (revision b8fc42dc065742bc68df6a61a2aff8cbe364fa17)
1 // SPDX-License-Identifier: GPL-2.0
2 /*
3  * Support for warning track interruption
4  *
5  * Copyright IBM Corp. 2023
6  */
7 
8 #include <linux/cpu.h>
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>
15 #include <asm/diag.h>
16 #include <asm/sclp.h>
17 
18 #define WTI_DBF_LEN 64
19 
20 struct wti_debug {
21 	unsigned long	missed;
22 	unsigned long	addr;
23 	pid_t		pid;
24 };
25 
26 struct wti_state {
27 	/* debug data for s390dbf */
28 	struct wti_debug	dbg;
29 	/*
30 	 * Represents the real-time thread responsible to
31 	 * acknowledge the warning-track interrupt and trigger
32 	 * preliminary and postliminary precautions.
33 	 */
34 	struct task_struct	*thread;
35 	/*
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.
38 	 */
39 	bool			pending;
40 };
41 
42 static DEFINE_PER_CPU(struct wti_state, wti_state);
43 
44 static debug_info_t *wti_dbg;
45 
46 /*
47  * During a warning-track grace period, interrupts are disabled
48  * to prevent delays of the warning-track acknowledgment.
49  *
50  * Once the CPU is physically dispatched again, interrupts are
51  * re-enabled.
52  */
53 
54 static void wti_irq_disable(void)
55 {
56 	unsigned long flags;
57 	struct ctlreg cr6;
58 
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);
65 }
66 
67 static void wti_irq_enable(void)
68 {
69 	unsigned long flags;
70 	struct ctlreg cr6;
71 
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);
78 }
79 
80 static void store_debug_data(struct wti_state *st)
81 {
82 	struct pt_regs *regs = get_irq_regs();
83 
84 	st->dbg.pid = current->pid;
85 	st->dbg.addr = 0;
86 	if (!user_mode(regs))
87 		st->dbg.addr = regs->psw.addr;
88 }
89 
90 static void wti_interrupt(struct ext_code ext_code,
91 			  unsigned int param32, unsigned long param64)
92 {
93 	struct wti_state *st = this_cpu_ptr(&wti_state);
94 
95 	inc_irq_stat(IRQEXT_WTI);
96 	wti_irq_disable();
97 	store_debug_data(st);
98 	st->pending = true;
99 	wake_up_process(st->thread);
100 }
101 
102 static int wti_pending(unsigned int cpu)
103 {
104 	struct wti_state *st = per_cpu_ptr(&wti_state, cpu);
105 
106 	return st->pending;
107 }
108 
109 static void wti_dbf_grace_period(struct wti_state *st)
110 {
111 	struct wti_debug *wdi = &st->dbg;
112 	char buf[WTI_DBF_LEN];
113 
114 	if (wdi->addr)
115 		snprintf(buf, sizeof(buf), "%d %pS", wdi->pid, (void *)wdi->addr);
116 	else
117 		snprintf(buf, sizeof(buf), "%d <user>", wdi->pid);
118 	debug_text_event(wti_dbg, 2, buf);
119 	wdi->missed++;
120 }
121 
122 static int wti_show(struct seq_file *seq, void *v)
123 {
124 	struct wti_state *st;
125 	int cpu;
126 
127 	cpus_read_lock();
128 	seq_puts(seq, "       ");
129 	for_each_online_cpu(cpu)
130 		seq_printf(seq, "CPU%-8d", cpu);
131 	seq_putc(seq, '\n');
132 	for_each_online_cpu(cpu) {
133 		st = per_cpu_ptr(&wti_state, cpu);
134 		seq_printf(seq, " %10lu", st->dbg.missed);
135 	}
136 	seq_putc(seq, '\n');
137 	cpus_read_unlock();
138 	return 0;
139 }
140 DEFINE_SHOW_ATTRIBUTE(wti);
141 
142 static void wti_thread_fn(unsigned int cpu)
143 {
144 	struct wti_state *st = per_cpu_ptr(&wti_state, cpu);
145 
146 	st->pending = false;
147 	/*
148 	 * Yield CPU voluntarily to the hypervisor. Control
149 	 * resumes when hypervisor decides to dispatch CPU
150 	 * to this LPAR again.
151 	 */
152 	if (diag49c(DIAG49C_SUBC_ACK))
153 		wti_dbf_grace_period(st);
154 	wti_irq_enable();
155 }
156 
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,
163 };
164 
165 static int __init wti_init(void)
166 {
167 	struct sched_param wti_sched_param = { .sched_priority = MAX_RT_PRIO - 1 };
168 	struct dentry *wti_dir;
169 	struct wti_state *st;
170 	int cpu, rc;
171 
172 	rc = -EOPNOTSUPP;
173 	if (!sclp.has_wti)
174 		goto out;
175 	rc = smpboot_register_percpu_thread(&wti_threads);
176 	if (WARN_ON(rc))
177 		goto out;
178 	for_each_online_cpu(cpu) {
179 		st = per_cpu_ptr(&wti_state, cpu);
180 		sched_setscheduler(st->thread, SCHED_FIFO, &wti_sched_param);
181 	}
182 	rc = register_external_irq(EXT_IRQ_WARNING_TRACK, wti_interrupt);
183 	if (rc) {
184 		pr_warn("Couldn't request external interrupt 0x1007\n");
185 		goto out_thread;
186 	}
187 	irq_subclass_register(IRQ_SUBCLASS_WARNING_TRACK);
188 	rc = diag49c(DIAG49C_SUBC_REG);
189 	if (rc) {
190 		pr_warn("Failed to register warning track interrupt through DIAG 49C\n");
191 		rc = -EOPNOTSUPP;
192 		goto out_subclass;
193 	}
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);
197 	if (!wti_dbg) {
198 		rc = -ENOMEM;
199 		goto out_debug_register;
200 	}
201 	rc = debug_register_view(wti_dbg, &debug_hex_ascii_view);
202 	if (rc)
203 		goto out_debug_register;
204 	goto out;
205 out_debug_register:
206 	debug_unregister(wti_dbg);
207 out_subclass:
208 	irq_subclass_unregister(IRQ_SUBCLASS_WARNING_TRACK);
209 	unregister_external_irq(EXT_IRQ_WARNING_TRACK, wti_interrupt);
210 out_thread:
211 	smpboot_unregister_percpu_thread(&wti_threads);
212 out:
213 	return rc;
214 }
215 late_initcall(wti_init);
216