| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- // 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);
|