1 | // SPDX-License-Identifier: MIT |
2 | |
3 | /* |
4 | * Copyright © 2020 Intel Corporation |
5 | */ |
6 | |
7 | #include <linux/bitmap.h> |
8 | #include <linux/string_helpers.h> |
9 | |
10 | #include "i915_drv.h" |
11 | #include "intel_gt_debugfs.h" |
12 | #include "intel_gt_regs.h" |
13 | #include "intel_sseu_debugfs.h" |
14 | |
15 | static void cherryview_sseu_device_status(struct intel_gt *gt, |
16 | struct sseu_dev_info *sseu) |
17 | { |
18 | #define SS_MAX 2 |
19 | struct intel_uncore *uncore = gt->uncore; |
20 | const int ss_max = SS_MAX; |
21 | u32 sig1[SS_MAX], sig2[SS_MAX]; |
22 | int ss; |
23 | |
24 | sig1[0] = intel_uncore_read(uncore, CHV_POWER_SS0_SIG1); |
25 | sig1[1] = intel_uncore_read(uncore, CHV_POWER_SS1_SIG1); |
26 | sig2[0] = intel_uncore_read(uncore, CHV_POWER_SS0_SIG2); |
27 | sig2[1] = intel_uncore_read(uncore, CHV_POWER_SS1_SIG2); |
28 | |
29 | for (ss = 0; ss < ss_max; ss++) { |
30 | unsigned int eu_cnt; |
31 | |
32 | if (sig1[ss] & CHV_SS_PG_ENABLE) |
33 | /* skip disabled subslice */ |
34 | continue; |
35 | |
36 | sseu->slice_mask = BIT(0); |
37 | sseu->subslice_mask.hsw[0] |= BIT(ss); |
38 | eu_cnt = ((sig1[ss] & CHV_EU08_PG_ENABLE) ? 0 : 2) + |
39 | ((sig1[ss] & CHV_EU19_PG_ENABLE) ? 0 : 2) + |
40 | ((sig1[ss] & CHV_EU210_PG_ENABLE) ? 0 : 2) + |
41 | ((sig2[ss] & CHV_EU311_PG_ENABLE) ? 0 : 2); |
42 | sseu->eu_total += eu_cnt; |
43 | sseu->eu_per_subslice = max_t(unsigned int, |
44 | sseu->eu_per_subslice, eu_cnt); |
45 | } |
46 | #undef SS_MAX |
47 | } |
48 | |
49 | static void gen11_sseu_device_status(struct intel_gt *gt, |
50 | struct sseu_dev_info *sseu) |
51 | { |
52 | #define SS_MAX 8 |
53 | struct intel_uncore *uncore = gt->uncore; |
54 | const struct intel_gt_info *info = >->info; |
55 | u32 s_reg[SS_MAX], eu_reg[2 * SS_MAX], eu_mask[2]; |
56 | int s, ss; |
57 | |
58 | for (s = 0; s < info->sseu.max_slices; s++) { |
59 | /* |
60 | * FIXME: Valid SS Mask respects the spec and read |
61 | * only valid bits for those registers, excluding reserved |
62 | * although this seems wrong because it would leave many |
63 | * subslices without ACK. |
64 | */ |
65 | s_reg[s] = intel_uncore_read(uncore, GEN10_SLICE_PGCTL_ACK(s)) & |
66 | GEN10_PGCTL_VALID_SS_MASK(s); |
67 | eu_reg[2 * s] = intel_uncore_read(uncore, |
68 | GEN10_SS01_EU_PGCTL_ACK(s)); |
69 | eu_reg[2 * s + 1] = intel_uncore_read(uncore, |
70 | GEN10_SS23_EU_PGCTL_ACK(s)); |
71 | } |
72 | |
73 | eu_mask[0] = GEN9_PGCTL_SSA_EU08_ACK | |
74 | GEN9_PGCTL_SSA_EU19_ACK | |
75 | GEN9_PGCTL_SSA_EU210_ACK | |
76 | GEN9_PGCTL_SSA_EU311_ACK; |
77 | eu_mask[1] = GEN9_PGCTL_SSB_EU08_ACK | |
78 | GEN9_PGCTL_SSB_EU19_ACK | |
79 | GEN9_PGCTL_SSB_EU210_ACK | |
80 | GEN9_PGCTL_SSB_EU311_ACK; |
81 | |
82 | for (s = 0; s < info->sseu.max_slices; s++) { |
83 | if ((s_reg[s] & GEN9_PGCTL_SLICE_ACK) == 0) |
84 | /* skip disabled slice */ |
85 | continue; |
86 | |
87 | sseu->slice_mask |= BIT(s); |
88 | sseu->subslice_mask.hsw[s] = info->sseu.subslice_mask.hsw[s]; |
89 | |
90 | for (ss = 0; ss < info->sseu.max_subslices; ss++) { |
91 | unsigned int eu_cnt; |
92 | |
93 | if (info->sseu.has_subslice_pg && |
94 | !(s_reg[s] & (GEN9_PGCTL_SS_ACK(ss)))) |
95 | /* skip disabled subslice */ |
96 | continue; |
97 | |
98 | eu_cnt = 2 * hweight32(eu_reg[2 * s + ss / 2] & |
99 | eu_mask[ss % 2]); |
100 | sseu->eu_total += eu_cnt; |
101 | sseu->eu_per_subslice = max_t(unsigned int, |
102 | sseu->eu_per_subslice, |
103 | eu_cnt); |
104 | } |
105 | } |
106 | #undef SS_MAX |
107 | } |
108 | |
109 | static void gen9_sseu_device_status(struct intel_gt *gt, |
110 | struct sseu_dev_info *sseu) |
111 | { |
112 | #define SS_MAX 3 |
113 | struct intel_uncore *uncore = gt->uncore; |
114 | const struct intel_gt_info *info = >->info; |
115 | u32 s_reg[SS_MAX], eu_reg[2 * SS_MAX], eu_mask[2]; |
116 | int s, ss; |
117 | |
118 | for (s = 0; s < info->sseu.max_slices; s++) { |
119 | s_reg[s] = intel_uncore_read(uncore, GEN9_SLICE_PGCTL_ACK(s)); |
120 | eu_reg[2 * s] = |
121 | intel_uncore_read(uncore, GEN9_SS01_EU_PGCTL_ACK(s)); |
122 | eu_reg[2 * s + 1] = |
123 | intel_uncore_read(uncore, GEN9_SS23_EU_PGCTL_ACK(s)); |
124 | } |
125 | |
126 | eu_mask[0] = GEN9_PGCTL_SSA_EU08_ACK | |
127 | GEN9_PGCTL_SSA_EU19_ACK | |
128 | GEN9_PGCTL_SSA_EU210_ACK | |
129 | GEN9_PGCTL_SSA_EU311_ACK; |
130 | eu_mask[1] = GEN9_PGCTL_SSB_EU08_ACK | |
131 | GEN9_PGCTL_SSB_EU19_ACK | |
132 | GEN9_PGCTL_SSB_EU210_ACK | |
133 | GEN9_PGCTL_SSB_EU311_ACK; |
134 | |
135 | for (s = 0; s < info->sseu.max_slices; s++) { |
136 | if ((s_reg[s] & GEN9_PGCTL_SLICE_ACK) == 0) |
137 | /* skip disabled slice */ |
138 | continue; |
139 | |
140 | sseu->slice_mask |= BIT(s); |
141 | |
142 | if (IS_GEN9_BC(gt->i915)) |
143 | sseu->subslice_mask.hsw[s] = info->sseu.subslice_mask.hsw[s]; |
144 | |
145 | for (ss = 0; ss < info->sseu.max_subslices; ss++) { |
146 | unsigned int eu_cnt; |
147 | |
148 | if (IS_GEN9_LP(gt->i915)) { |
149 | if (!(s_reg[s] & (GEN9_PGCTL_SS_ACK(ss)))) |
150 | /* skip disabled subslice */ |
151 | continue; |
152 | |
153 | sseu->subslice_mask.hsw[s] |= BIT(ss); |
154 | } |
155 | |
156 | eu_cnt = eu_reg[2 * s + ss / 2] & eu_mask[ss % 2]; |
157 | eu_cnt = 2 * hweight32(eu_cnt); |
158 | |
159 | sseu->eu_total += eu_cnt; |
160 | sseu->eu_per_subslice = max_t(unsigned int, |
161 | sseu->eu_per_subslice, |
162 | eu_cnt); |
163 | } |
164 | } |
165 | #undef SS_MAX |
166 | } |
167 | |
168 | static void bdw_sseu_device_status(struct intel_gt *gt, |
169 | struct sseu_dev_info *sseu) |
170 | { |
171 | const struct intel_gt_info *info = >->info; |
172 | u32 slice_info = intel_uncore_read(uncore: gt->uncore, GEN8_GT_SLICE_INFO); |
173 | int s; |
174 | |
175 | sseu->slice_mask = slice_info & GEN8_LSLICESTAT_MASK; |
176 | |
177 | if (sseu->slice_mask) { |
178 | sseu->eu_per_subslice = info->sseu.eu_per_subslice; |
179 | for (s = 0; s < fls(x: sseu->slice_mask); s++) |
180 | sseu->subslice_mask.hsw[s] = info->sseu.subslice_mask.hsw[s]; |
181 | sseu->eu_total = sseu->eu_per_subslice * |
182 | intel_sseu_subslice_total(sseu); |
183 | |
184 | /* subtract fused off EU(s) from enabled slice(s) */ |
185 | for (s = 0; s < fls(x: sseu->slice_mask); s++) { |
186 | u8 subslice_7eu = info->sseu.subslice_7eu[s]; |
187 | |
188 | sseu->eu_total -= hweight8(subslice_7eu); |
189 | } |
190 | } |
191 | } |
192 | |
193 | static void i915_print_sseu_info(struct seq_file *m, |
194 | bool is_available_info, |
195 | bool has_pooled_eu, |
196 | const struct sseu_dev_info *sseu) |
197 | { |
198 | const char *type = is_available_info ? "Available" : "Enabled" ; |
199 | |
200 | seq_printf(m, fmt: " %s Slice Mask: %04x\n" , type, |
201 | sseu->slice_mask); |
202 | seq_printf(m, fmt: " %s Slice Total: %u\n" , type, |
203 | hweight8(sseu->slice_mask)); |
204 | seq_printf(m, fmt: " %s Subslice Total: %u\n" , type, |
205 | intel_sseu_subslice_total(sseu)); |
206 | intel_sseu_print_ss_info(type, sseu, m); |
207 | seq_printf(m, fmt: " %s EU Total: %u\n" , type, |
208 | sseu->eu_total); |
209 | seq_printf(m, fmt: " %s EU Per Subslice: %u\n" , type, |
210 | sseu->eu_per_subslice); |
211 | |
212 | if (!is_available_info) |
213 | return; |
214 | |
215 | seq_printf(m, fmt: " Has Pooled EU: %s\n" , str_yes_no(v: has_pooled_eu)); |
216 | if (has_pooled_eu) |
217 | seq_printf(m, fmt: " Min EU in pool: %u\n" , sseu->min_eu_in_pool); |
218 | |
219 | seq_printf(m, fmt: " Has Slice Power Gating: %s\n" , |
220 | str_yes_no(v: sseu->has_slice_pg)); |
221 | seq_printf(m, fmt: " Has Subslice Power Gating: %s\n" , |
222 | str_yes_no(v: sseu->has_subslice_pg)); |
223 | seq_printf(m, fmt: " Has EU Power Gating: %s\n" , |
224 | str_yes_no(v: sseu->has_eu_pg)); |
225 | } |
226 | |
227 | /* |
228 | * this is called from top-level debugfs as well, so we can't get the gt from |
229 | * the seq_file. |
230 | */ |
231 | int intel_sseu_status(struct seq_file *m, struct intel_gt *gt) |
232 | { |
233 | struct drm_i915_private *i915 = gt->i915; |
234 | const struct intel_gt_info *info = >->info; |
235 | struct sseu_dev_info *sseu; |
236 | intel_wakeref_t wakeref; |
237 | |
238 | if (GRAPHICS_VER(i915) < 8) |
239 | return -ENODEV; |
240 | |
241 | seq_puts(m, s: "SSEU Device Info\n" ); |
242 | i915_print_sseu_info(m, is_available_info: true, HAS_POOLED_EU(i915), sseu: &info->sseu); |
243 | |
244 | seq_puts(m, s: "SSEU Device Status\n" ); |
245 | |
246 | sseu = kzalloc(size: sizeof(*sseu), GFP_KERNEL); |
247 | if (!sseu) |
248 | return -ENOMEM; |
249 | |
250 | intel_sseu_set_info(sseu, max_slices: info->sseu.max_slices, |
251 | max_subslices: info->sseu.max_subslices, |
252 | max_eus_per_subslice: info->sseu.max_eus_per_subslice); |
253 | |
254 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
255 | if (IS_CHERRYVIEW(i915)) |
256 | cherryview_sseu_device_status(gt, sseu); |
257 | else if (IS_BROADWELL(i915)) |
258 | bdw_sseu_device_status(gt, sseu); |
259 | else if (GRAPHICS_VER(i915) == 9) |
260 | gen9_sseu_device_status(gt, sseu); |
261 | else if (GRAPHICS_VER(i915) >= 11) |
262 | gen11_sseu_device_status(gt, sseu); |
263 | } |
264 | |
265 | i915_print_sseu_info(m, is_available_info: false, HAS_POOLED_EU(i915), sseu); |
266 | |
267 | kfree(objp: sseu); |
268 | |
269 | return 0; |
270 | } |
271 | |
272 | static int sseu_status_show(struct seq_file *m, void *unused) |
273 | { |
274 | struct intel_gt *gt = m->private; |
275 | |
276 | return intel_sseu_status(m, gt); |
277 | } |
278 | DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(sseu_status); |
279 | |
280 | static int sseu_topology_show(struct seq_file *m, void *unused) |
281 | { |
282 | struct intel_gt *gt = m->private; |
283 | struct drm_printer p = drm_seq_file_printer(f: m); |
284 | |
285 | intel_sseu_print_topology(i915: gt->i915, sseu: >->info.sseu, p: &p); |
286 | |
287 | return 0; |
288 | } |
289 | DEFINE_INTEL_GT_DEBUGFS_ATTRIBUTE(sseu_topology); |
290 | |
291 | void intel_sseu_debugfs_register(struct intel_gt *gt, struct dentry *root) |
292 | { |
293 | static const struct intel_gt_debugfs_file files[] = { |
294 | { "sseu_status" , &sseu_status_fops, NULL }, |
295 | { "sseu_topology" , &sseu_topology_fops, NULL }, |
296 | }; |
297 | |
298 | intel_gt_debugfs_register_files(root, files, ARRAY_SIZE(files), data: gt); |
299 | } |
300 | |