Linux Audio

Check our new training course

Linux kernel drivers training

May 6-19, 2025
Register
Loading...
Note: File does not exist in v4.10.11.
  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
 20struct wti_debug {
 21	unsigned long	missed;
 22	unsigned long	addr;
 23	pid_t		pid;
 24};
 25
 26struct 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
 42static DEFINE_PER_CPU(struct wti_state, wti_state);
 43
 44static 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
 54static 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
 67static 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
 80static 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
 90static 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
102static 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
109static 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
122static 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}
140DEFINE_SHOW_ATTRIBUTE(wti);
141
142static 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
157static 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
165static 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;
205out_debug_register:
206	debug_unregister(wti_dbg);
207out_subclass:
208	irq_subclass_unregister(IRQ_SUBCLASS_WARNING_TRACK);
209	unregister_external_irq(EXT_IRQ_WARNING_TRACK, wti_interrupt);
210out_thread:
211	smpboot_unregister_percpu_thread(&wti_threads);
212out:
213	return rc;
214}
215late_initcall(wti_init);