]>
Commit | Line | Data |
---|---|---|
7f5ac1a0 HB |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
3 | * CPU-Measurement Counter Facility Support - Common Layer | |
4 | * | |
5 | * Copyright IBM Corp. 2019 | |
6 | * Author(s): Hendrik Brueckner <brueckner@linux.ibm.com> | |
7 | */ | |
8 | #define KMSG_COMPONENT "cpum_cf_common" | |
9 | #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | |
10 | ||
11 | #include <linux/kernel.h> | |
12 | #include <linux/kernel_stat.h> | |
13 | #include <linux/percpu.h> | |
14 | #include <linux/notifier.h> | |
15 | #include <linux/init.h> | |
16 | #include <linux/export.h> | |
17 | #include <asm/ctl_reg.h> | |
18 | #include <asm/irq.h> | |
19 | #include <asm/cpu_mcf.h> | |
20 | ||
21 | /* Per-CPU event structure for the counter facility */ | |
22 | DEFINE_PER_CPU(struct cpu_cf_events, cpu_cf_events) = { | |
23 | .ctr_set = { | |
24 | [CPUMF_CTR_SET_BASIC] = ATOMIC_INIT(0), | |
25 | [CPUMF_CTR_SET_USER] = ATOMIC_INIT(0), | |
26 | [CPUMF_CTR_SET_CRYPTO] = ATOMIC_INIT(0), | |
27 | [CPUMF_CTR_SET_EXT] = ATOMIC_INIT(0), | |
28 | [CPUMF_CTR_SET_MT_DIAG] = ATOMIC_INIT(0), | |
29 | }, | |
30 | .alert = ATOMIC64_INIT(0), | |
31 | .state = 0, | |
32 | .flags = 0, | |
33 | .txn_flags = 0, | |
34 | }; | |
35 | /* Indicator whether the CPU-Measurement Counter Facility Support is ready */ | |
36 | static bool cpum_cf_initalized; | |
37 | ||
38 | /* CPU-measurement alerts for the counter facility */ | |
39 | static void cpumf_measurement_alert(struct ext_code ext_code, | |
40 | unsigned int alert, unsigned long unused) | |
41 | { | |
42 | struct cpu_cf_events *cpuhw; | |
43 | ||
44 | if (!(alert & CPU_MF_INT_CF_MASK)) | |
45 | return; | |
46 | ||
47 | inc_irq_stat(IRQEXT_CMC); | |
48 | cpuhw = this_cpu_ptr(&cpu_cf_events); | |
49 | ||
50 | /* Measurement alerts are shared and might happen when the PMU | |
51 | * is not reserved. Ignore these alerts in this case. */ | |
52 | if (!(cpuhw->flags & PMU_F_RESERVED)) | |
53 | return; | |
54 | ||
55 | /* counter authorization change alert */ | |
56 | if (alert & CPU_MF_INT_CF_CACA) | |
57 | qctri(&cpuhw->info); | |
58 | ||
59 | /* loss of counter data alert */ | |
60 | if (alert & CPU_MF_INT_CF_LCDA) | |
61 | pr_err("CPU[%i] Counter data was lost\n", smp_processor_id()); | |
62 | ||
63 | /* loss of MT counter data alert */ | |
64 | if (alert & CPU_MF_INT_CF_MTDA) | |
65 | pr_warn("CPU[%i] MT counter data was lost\n", | |
66 | smp_processor_id()); | |
67 | ||
68 | /* store alert for special handling by in-kernel users */ | |
69 | atomic64_or(alert, &cpuhw->alert); | |
70 | } | |
71 | ||
72 | #define PMC_INIT 0 | |
73 | #define PMC_RELEASE 1 | |
74 | static void cpum_cf_setup_cpu(void *flags) | |
75 | { | |
76 | struct cpu_cf_events *cpuhw = this_cpu_ptr(&cpu_cf_events); | |
77 | ||
78 | switch (*((int *) flags)) { | |
79 | case PMC_INIT: | |
80 | memset(&cpuhw->info, 0, sizeof(cpuhw->info)); | |
81 | qctri(&cpuhw->info); | |
82 | cpuhw->flags |= PMU_F_RESERVED; | |
83 | break; | |
84 | ||
85 | case PMC_RELEASE: | |
86 | cpuhw->flags &= ~PMU_F_RESERVED; | |
87 | break; | |
88 | } | |
89 | ||
90 | /* Disable CPU counter sets */ | |
91 | lcctl(0); | |
92 | } | |
93 | ||
94 | bool kernel_cpumcf_avail(void) | |
95 | { | |
96 | return cpum_cf_initalized; | |
97 | } | |
98 | EXPORT_SYMBOL(kernel_cpumcf_avail); | |
99 | ||
100 | ||
101 | /* Reserve/release functions for sharing perf hardware */ | |
102 | static DEFINE_SPINLOCK(cpumcf_owner_lock); | |
103 | static void *cpumcf_owner; | |
104 | ||
105 | /* Initialize the CPU-measurement counter facility */ | |
106 | int __kernel_cpumcf_begin(void) | |
107 | { | |
108 | int flags = PMC_INIT; | |
109 | int err = 0; | |
110 | ||
111 | spin_lock(&cpumcf_owner_lock); | |
112 | if (cpumcf_owner) | |
113 | err = -EBUSY; | |
114 | else | |
115 | cpumcf_owner = __builtin_return_address(0); | |
116 | spin_unlock(&cpumcf_owner_lock); | |
117 | if (err) | |
118 | return err; | |
119 | ||
120 | on_each_cpu(cpum_cf_setup_cpu, &flags, 1); | |
121 | irq_subclass_register(IRQ_SUBCLASS_MEASUREMENT_ALERT); | |
122 | ||
123 | return 0; | |
124 | } | |
125 | EXPORT_SYMBOL(__kernel_cpumcf_begin); | |
126 | ||
127 | /* Obtain the CPU-measurement alerts for the counter facility */ | |
128 | unsigned long kernel_cpumcf_alert(int clear) | |
129 | { | |
130 | struct cpu_cf_events *cpuhw = this_cpu_ptr(&cpu_cf_events); | |
131 | unsigned long alert; | |
132 | ||
133 | alert = atomic64_read(&cpuhw->alert); | |
134 | if (clear) | |
135 | atomic64_set(&cpuhw->alert, 0); | |
136 | ||
137 | return alert; | |
138 | } | |
139 | EXPORT_SYMBOL(kernel_cpumcf_alert); | |
140 | ||
141 | /* Release the CPU-measurement counter facility */ | |
142 | void __kernel_cpumcf_end(void) | |
143 | { | |
144 | int flags = PMC_RELEASE; | |
145 | ||
146 | on_each_cpu(cpum_cf_setup_cpu, &flags, 1); | |
147 | irq_subclass_unregister(IRQ_SUBCLASS_MEASUREMENT_ALERT); | |
148 | ||
149 | spin_lock(&cpumcf_owner_lock); | |
150 | cpumcf_owner = NULL; | |
151 | spin_unlock(&cpumcf_owner_lock); | |
152 | } | |
153 | EXPORT_SYMBOL(__kernel_cpumcf_end); | |
154 | ||
155 | static int cpum_cf_setup(unsigned int cpu, int flags) | |
156 | { | |
157 | local_irq_disable(); | |
158 | cpum_cf_setup_cpu(&flags); | |
159 | local_irq_enable(); | |
160 | return 0; | |
161 | } | |
162 | ||
163 | static int cpum_cf_online_cpu(unsigned int cpu) | |
164 | { | |
165 | return cpum_cf_setup(cpu, PMC_INIT); | |
166 | } | |
167 | ||
168 | static int cpum_cf_offline_cpu(unsigned int cpu) | |
169 | { | |
170 | return cpum_cf_setup(cpu, PMC_RELEASE); | |
171 | } | |
172 | ||
173 | static int __init cpum_cf_init(void) | |
174 | { | |
175 | int rc; | |
176 | ||
177 | if (!cpum_cf_avail()) | |
178 | return -ENODEV; | |
179 | ||
180 | /* clear bit 15 of cr0 to unauthorize problem-state to | |
181 | * extract measurement counters */ | |
182 | ctl_clear_bit(0, 48); | |
183 | ||
184 | /* register handler for measurement-alert interruptions */ | |
185 | rc = register_external_irq(EXT_IRQ_MEASURE_ALERT, | |
186 | cpumf_measurement_alert); | |
187 | if (rc) { | |
188 | pr_err("Registering for CPU-measurement alerts " | |
189 | "failed with rc=%i\n", rc); | |
190 | return rc; | |
191 | } | |
192 | ||
193 | rc = cpuhp_setup_state(CPUHP_AP_PERF_S390_CF_ONLINE, | |
194 | "perf/s390/cf:online", | |
195 | cpum_cf_online_cpu, cpum_cf_offline_cpu); | |
196 | if (!rc) | |
197 | cpum_cf_initalized = true; | |
198 | ||
199 | return rc; | |
200 | } | |
201 | early_initcall(cpum_cf_init); |