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 20 struct wti_debug { 21 unsigned long missed; 22 unsigned long addr; 23 pid_t pid; 24 }; 25 26 struct 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 42 static DEFINE_PER_CPU(struct wti_state, wti_state); 43 44 static 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 54 static 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 67 static 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 80 static 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 90 static 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 102 static 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 109 static 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 122 static 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 } 140 DEFINE_SHOW_ATTRIBUTE(wti); 141 142 static 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 157 static 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 165 static 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; 205 out_debug_register: 206 debug_unregister(wti_dbg); 207 out_subclass: 208 irq_subclass_unregister(IRQ_SUBCLASS_WARNING_TRACK); 209 unregister_external_irq(EXT_IRQ_WARNING_TRACK, wti_interrupt); 210 out_thread: 211 smpboot_unregister_percpu_thread(&wti_threads); 212 out: 213 return rc; 214 } 215 late_initcall(wti_init); 216
Linux® is a registered trademark of Linus Torvalds in the United States and other countries.
TOMOYO® is a registered trademark of NTT DATA CORPORATION.