mirror of
https://github.com/torvalds/linux.git
synced 2024-12-01 08:31:37 +00:00
307b675cf0
Introduce a new debug file which allows to determine how many warning track grace periods were missed on each CPU. The new file can be found as /sys/kernel/debug/s390/wti It is formatted as: CPU0 CPU1 [...] CPUx xyz xyz [...] xyz Acked-by: Heiko Carstens <hca@linux.ibm.com> Reviewed-by: Mete Durlu <meted@linux.ibm.com> Signed-off-by: Tobias Huschle <huschle@linux.ibm.com> Signed-off-by: Vasily Gorbik <gor@linux.ibm.com>
216 lines
4.9 KiB
C
216 lines
4.9 KiB
C
// 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);
|