blob: 949fdbf0e8b659ead49c57b8e89f344802b214f5 [file] [log] [blame] [edit]
// 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);