root/arch/s390/kernel/wti.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);