1 | // SPDX-License-Identifier: MIT |
---|---|
2 | /* |
3 | * Copyright © 2023 Intel Corporation |
4 | */ |
5 | |
6 | #include <drm/drm_managed.h> |
7 | |
8 | #include "xe_force_wake.h" |
9 | #include "xe_device.h" |
10 | #include "xe_gt.h" |
11 | #include "xe_gt_idle.h" |
12 | #include "xe_gt_sysfs.h" |
13 | #include "xe_guc_pc.h" |
14 | #include "regs/xe_gt_regs.h" |
15 | #include "xe_macros.h" |
16 | #include "xe_mmio.h" |
17 | #include "xe_pm.h" |
18 | #include "xe_sriov.h" |
19 | |
20 | /** |
21 | * DOC: Xe GT Idle |
22 | * |
23 | * Contains functions that init GT idle features like C6 |
24 | * |
25 | * device/gt#/gtidle/name - name of the state |
26 | * device/gt#/gtidle/idle_residency_ms - Provides residency of the idle state in ms |
27 | * device/gt#/gtidle/idle_status - Provides current idle state |
28 | */ |
29 | |
30 | static struct xe_gt_idle *dev_to_gtidle(struct device *dev) |
31 | { |
32 | struct kobject *kobj = &dev->kobj; |
33 | |
34 | return &kobj_to_gt(kobj: kobj->parent)->gtidle; |
35 | } |
36 | |
37 | static struct xe_gt *gtidle_to_gt(struct xe_gt_idle *gtidle) |
38 | { |
39 | return container_of(gtidle, struct xe_gt, gtidle); |
40 | } |
41 | |
42 | static struct xe_guc_pc *gtidle_to_pc(struct xe_gt_idle *gtidle) |
43 | { |
44 | return >idle_to_gt(gtidle)->uc.guc.pc; |
45 | } |
46 | |
47 | static struct xe_device * |
48 | pc_to_xe(struct xe_guc_pc *pc) |
49 | { |
50 | struct xe_guc *guc = container_of(pc, struct xe_guc, pc); |
51 | struct xe_gt *gt = container_of(guc, struct xe_gt, uc.guc); |
52 | |
53 | return gt_to_xe(gt); |
54 | } |
55 | |
56 | static const char *gt_idle_state_to_string(enum xe_gt_idle_state state) |
57 | { |
58 | switch (state) { |
59 | case GT_IDLE_C0: |
60 | return "gt-c0"; |
61 | case GT_IDLE_C6: |
62 | return "gt-c6"; |
63 | default: |
64 | return "unknown"; |
65 | } |
66 | } |
67 | |
68 | static u64 get_residency_ms(struct xe_gt_idle *gtidle, u64 cur_residency) |
69 | { |
70 | u64 delta, overflow_residency, prev_residency; |
71 | |
72 | lockdep_assert_held(>idle->lock); |
73 | |
74 | overflow_residency = BIT_ULL(32); |
75 | |
76 | /* |
77 | * Counter wrap handling |
78 | * Store previous hw counter values for counter wrap-around handling |
79 | * Relying on sufficient frequency of queries otherwise counters can still wrap. |
80 | */ |
81 | prev_residency = gtidle->prev_residency; |
82 | gtidle->prev_residency = cur_residency; |
83 | |
84 | /* delta */ |
85 | if (cur_residency >= prev_residency) |
86 | delta = cur_residency - prev_residency; |
87 | else |
88 | delta = cur_residency + (overflow_residency - prev_residency); |
89 | |
90 | /* Add delta to extended raw driver copy of idle residency */ |
91 | cur_residency = gtidle->cur_residency + delta; |
92 | gtidle->cur_residency = cur_residency; |
93 | |
94 | /* residency multiplier in ns, convert to ms */ |
95 | cur_residency = mul_u64_u32_div(a: cur_residency, mul: gtidle->residency_multiplier, div: 1e6); |
96 | |
97 | return cur_residency; |
98 | } |
99 | |
100 | void xe_gt_idle_enable_pg(struct xe_gt *gt) |
101 | { |
102 | struct xe_device *xe = gt_to_xe(gt); |
103 | struct xe_gt_idle *gtidle = >->gtidle; |
104 | struct xe_mmio *mmio = >->mmio; |
105 | u32 vcs_mask, vecs_mask; |
106 | unsigned int fw_ref; |
107 | int i, j; |
108 | |
109 | if (IS_SRIOV_VF(xe)) |
110 | return; |
111 | |
112 | /* Disable CPG for PVC */ |
113 | if (xe->info.platform == XE_PVC) |
114 | return; |
115 | |
116 | xe_device_assert_mem_access(gt_to_xe(gt)); |
117 | |
118 | vcs_mask = xe_hw_engine_mask_per_class(gt, engine_class: XE_ENGINE_CLASS_VIDEO_DECODE); |
119 | vecs_mask = xe_hw_engine_mask_per_class(gt, engine_class: XE_ENGINE_CLASS_VIDEO_ENHANCE); |
120 | |
121 | if (vcs_mask || vecs_mask) |
122 | gtidle->powergate_enable = MEDIA_POWERGATE_ENABLE; |
123 | |
124 | if (!xe_gt_is_media_type(gt)) |
125 | gtidle->powergate_enable |= RENDER_POWERGATE_ENABLE; |
126 | |
127 | if (xe->info.platform != XE_DG1) { |
128 | for (i = XE_HW_ENGINE_VCS0, j = 0; i <= XE_HW_ENGINE_VCS7; ++i, ++j) { |
129 | if ((gt->info.engine_mask & BIT(i))) |
130 | gtidle->powergate_enable |= (VDN_HCP_POWERGATE_ENABLE(j) | |
131 | VDN_MFXVDENC_POWERGATE_ENABLE(j)); |
132 | } |
133 | } |
134 | |
135 | fw_ref = xe_force_wake_get(fw: gt_to_fw(gt), domains: XE_FW_GT); |
136 | if (xe->info.skip_guc_pc) { |
137 | /* |
138 | * GuC sets the hysteresis value when GuC PC is enabled |
139 | * else set it to 25 (25 * 1.28us) |
140 | */ |
141 | xe_mmio_write32(mmio, MEDIA_POWERGATE_IDLE_HYSTERESIS, val: 25); |
142 | xe_mmio_write32(mmio, RENDER_POWERGATE_IDLE_HYSTERESIS, val: 25); |
143 | } |
144 | |
145 | xe_mmio_write32(mmio, POWERGATE_ENABLE, val: gtidle->powergate_enable); |
146 | xe_force_wake_put(fw: gt_to_fw(gt), fw_ref); |
147 | } |
148 | |
149 | void xe_gt_idle_disable_pg(struct xe_gt *gt) |
150 | { |
151 | struct xe_gt_idle *gtidle = >->gtidle; |
152 | unsigned int fw_ref; |
153 | |
154 | if (IS_SRIOV_VF(gt_to_xe(gt))) |
155 | return; |
156 | |
157 | xe_device_assert_mem_access(gt_to_xe(gt)); |
158 | gtidle->powergate_enable = 0; |
159 | |
160 | fw_ref = xe_force_wake_get(fw: gt_to_fw(gt), domains: XE_FW_GT); |
161 | xe_mmio_write32(mmio: >->mmio, POWERGATE_ENABLE, val: gtidle->powergate_enable); |
162 | xe_force_wake_put(fw: gt_to_fw(gt), fw_ref); |
163 | } |
164 | |
165 | /** |
166 | * xe_gt_idle_pg_print - Xe powergating info |
167 | * @gt: GT object |
168 | * @p: drm_printer. |
169 | * |
170 | * This function prints the powergating information |
171 | * |
172 | * Return: 0 on success, negative error code otherwise |
173 | */ |
174 | int xe_gt_idle_pg_print(struct xe_gt *gt, struct drm_printer *p) |
175 | { |
176 | struct xe_gt_idle *gtidle = >->gtidle; |
177 | struct xe_device *xe = gt_to_xe(gt); |
178 | enum xe_gt_idle_state state; |
179 | u32 pg_enabled, pg_status = 0; |
180 | u32 vcs_mask, vecs_mask; |
181 | unsigned int fw_ref; |
182 | int n; |
183 | /* |
184 | * Media Slices |
185 | * |
186 | * Slice 0: VCS0, VCS1, VECS0 |
187 | * Slice 1: VCS2, VCS3, VECS1 |
188 | * Slice 2: VCS4, VCS5, VECS2 |
189 | * Slice 3: VCS6, VCS7, VECS3 |
190 | */ |
191 | static const struct { |
192 | u64 engines; |
193 | u32 status_bit; |
194 | } media_slices[] = { |
195 | {(BIT(XE_HW_ENGINE_VCS0) | BIT(XE_HW_ENGINE_VCS1) | |
196 | BIT(XE_HW_ENGINE_VECS0)), MEDIA_SLICE0_AWAKE_STATUS}, |
197 | |
198 | {(BIT(XE_HW_ENGINE_VCS2) | BIT(XE_HW_ENGINE_VCS3) | |
199 | BIT(XE_HW_ENGINE_VECS1)), MEDIA_SLICE1_AWAKE_STATUS}, |
200 | |
201 | {(BIT(XE_HW_ENGINE_VCS4) | BIT(XE_HW_ENGINE_VCS5) | |
202 | BIT(XE_HW_ENGINE_VECS2)), MEDIA_SLICE2_AWAKE_STATUS}, |
203 | |
204 | {(BIT(XE_HW_ENGINE_VCS6) | BIT(XE_HW_ENGINE_VCS7) | |
205 | BIT(XE_HW_ENGINE_VECS3)), MEDIA_SLICE3_AWAKE_STATUS}, |
206 | }; |
207 | |
208 | if (xe->info.platform == XE_PVC) { |
209 | drm_printf(p, f: "Power Gating not supported\n"); |
210 | return 0; |
211 | } |
212 | |
213 | state = gtidle->idle_status(gtidle_to_pc(gtidle)); |
214 | pg_enabled = gtidle->powergate_enable; |
215 | |
216 | /* Do not wake the GT to read powergating status */ |
217 | if (state != GT_IDLE_C6) { |
218 | fw_ref = xe_force_wake_get(fw: gt_to_fw(gt), domains: XE_FW_GT); |
219 | if (!fw_ref) |
220 | return -ETIMEDOUT; |
221 | |
222 | pg_enabled = xe_mmio_read32(mmio: >->mmio, POWERGATE_ENABLE); |
223 | pg_status = xe_mmio_read32(mmio: >->mmio, POWERGATE_DOMAIN_STATUS); |
224 | |
225 | xe_force_wake_put(fw: gt_to_fw(gt), fw_ref); |
226 | } |
227 | |
228 | if (gt->info.engine_mask & XE_HW_ENGINE_RCS_MASK) { |
229 | drm_printf(p, f: "Render Power Gating Enabled: %s\n", |
230 | str_yes_no(v: pg_enabled & RENDER_POWERGATE_ENABLE)); |
231 | |
232 | drm_printf(p, f: "Render Power Gate Status: %s\n", |
233 | str_up_down(v: pg_status & RENDER_AWAKE_STATUS)); |
234 | } |
235 | |
236 | vcs_mask = xe_hw_engine_mask_per_class(gt, engine_class: XE_ENGINE_CLASS_VIDEO_DECODE); |
237 | vecs_mask = xe_hw_engine_mask_per_class(gt, engine_class: XE_ENGINE_CLASS_VIDEO_ENHANCE); |
238 | |
239 | /* Print media CPG status only if media is present */ |
240 | if (vcs_mask || vecs_mask) { |
241 | drm_printf(p, f: "Media Power Gating Enabled: %s\n", |
242 | str_yes_no(v: pg_enabled & MEDIA_POWERGATE_ENABLE)); |
243 | |
244 | for (n = 0; n < ARRAY_SIZE(media_slices); n++) |
245 | if (gt->info.engine_mask & media_slices[n].engines) |
246 | drm_printf(p, f: "Media Slice%d Power Gate Status: %s\n", n, |
247 | str_up_down(v: pg_status & media_slices[n].status_bit)); |
248 | } |
249 | return 0; |
250 | } |
251 | |
252 | static ssize_t name_show(struct kobject *kobj, |
253 | struct kobj_attribute *attr, char *buff) |
254 | { |
255 | struct device *dev = kobj_to_dev(kobj); |
256 | struct xe_gt_idle *gtidle = dev_to_gtidle(dev); |
257 | struct xe_guc_pc *pc = gtidle_to_pc(gtidle); |
258 | ssize_t ret; |
259 | |
260 | xe_pm_runtime_get(xe: pc_to_xe(pc)); |
261 | ret = sysfs_emit(buf: buff, fmt: "%s\n", gtidle->name); |
262 | xe_pm_runtime_put(xe: pc_to_xe(pc)); |
263 | |
264 | return ret; |
265 | } |
266 | static struct kobj_attribute name_attr = __ATTR_RO(name); |
267 | |
268 | static ssize_t idle_status_show(struct kobject *kobj, |
269 | struct kobj_attribute *attr, char *buff) |
270 | { |
271 | struct device *dev = kobj_to_dev(kobj); |
272 | struct xe_gt_idle *gtidle = dev_to_gtidle(dev); |
273 | struct xe_guc_pc *pc = gtidle_to_pc(gtidle); |
274 | enum xe_gt_idle_state state; |
275 | |
276 | xe_pm_runtime_get(xe: pc_to_xe(pc)); |
277 | state = gtidle->idle_status(pc); |
278 | xe_pm_runtime_put(xe: pc_to_xe(pc)); |
279 | |
280 | return sysfs_emit(buf: buff, fmt: "%s\n", gt_idle_state_to_string(state)); |
281 | } |
282 | static struct kobj_attribute idle_status_attr = __ATTR_RO(idle_status); |
283 | |
284 | u64 xe_gt_idle_residency_msec(struct xe_gt_idle *gtidle) |
285 | { |
286 | struct xe_guc_pc *pc = gtidle_to_pc(gtidle); |
287 | u64 residency; |
288 | unsigned long flags; |
289 | |
290 | raw_spin_lock_irqsave(>idle->lock, flags); |
291 | residency = get_residency_ms(gtidle, cur_residency: gtidle->idle_residency(pc)); |
292 | raw_spin_unlock_irqrestore(>idle->lock, flags); |
293 | |
294 | return residency; |
295 | } |
296 | |
297 | |
298 | static ssize_t idle_residency_ms_show(struct kobject *kobj, |
299 | struct kobj_attribute *attr, char *buff) |
300 | { |
301 | struct device *dev = kobj_to_dev(kobj); |
302 | struct xe_gt_idle *gtidle = dev_to_gtidle(dev); |
303 | struct xe_guc_pc *pc = gtidle_to_pc(gtidle); |
304 | u64 residency; |
305 | |
306 | xe_pm_runtime_get(xe: pc_to_xe(pc)); |
307 | residency = xe_gt_idle_residency_msec(gtidle); |
308 | xe_pm_runtime_put(xe: pc_to_xe(pc)); |
309 | |
310 | return sysfs_emit(buf: buff, fmt: "%llu\n", residency); |
311 | } |
312 | static struct kobj_attribute idle_residency_attr = __ATTR_RO(idle_residency_ms); |
313 | |
314 | static const struct attribute *gt_idle_attrs[] = { |
315 | &name_attr.attr, |
316 | &idle_status_attr.attr, |
317 | &idle_residency_attr.attr, |
318 | NULL, |
319 | }; |
320 | |
321 | static void gt_idle_fini(void *arg) |
322 | { |
323 | struct kobject *kobj = arg; |
324 | struct xe_gt *gt = kobj_to_gt(kobj: kobj->parent); |
325 | unsigned int fw_ref; |
326 | |
327 | xe_gt_idle_disable_pg(gt); |
328 | |
329 | if (gt_to_xe(gt)->info.skip_guc_pc) { |
330 | fw_ref = xe_force_wake_get(fw: gt_to_fw(gt), domains: XE_FW_GT); |
331 | xe_gt_idle_disable_c6(gt); |
332 | xe_force_wake_put(fw: gt_to_fw(gt), fw_ref); |
333 | } |
334 | |
335 | sysfs_remove_files(kobj, attr: gt_idle_attrs); |
336 | kobject_put(kobj); |
337 | } |
338 | |
339 | int xe_gt_idle_init(struct xe_gt_idle *gtidle) |
340 | { |
341 | struct xe_gt *gt = gtidle_to_gt(gtidle); |
342 | struct xe_device *xe = gt_to_xe(gt); |
343 | struct kobject *kobj; |
344 | int err; |
345 | |
346 | if (IS_SRIOV_VF(xe)) |
347 | return 0; |
348 | |
349 | kobj = kobject_create_and_add(name: "gtidle", parent: gt->sysfs); |
350 | if (!kobj) |
351 | return -ENOMEM; |
352 | |
353 | raw_spin_lock_init(>idle->lock); |
354 | |
355 | if (xe_gt_is_media_type(gt)) { |
356 | snprintf(buf: gtidle->name, size: sizeof(gtidle->name), fmt: "gt%d-mc", gt->info.id); |
357 | gtidle->idle_residency = xe_guc_pc_mc6_residency; |
358 | } else { |
359 | snprintf(buf: gtidle->name, size: sizeof(gtidle->name), fmt: "gt%d-rc", gt->info.id); |
360 | gtidle->idle_residency = xe_guc_pc_rc6_residency; |
361 | } |
362 | |
363 | /* Multiplier for Residency counter in units of 1.28us */ |
364 | gtidle->residency_multiplier = 1280; |
365 | gtidle->idle_status = xe_guc_pc_c_status; |
366 | |
367 | err = sysfs_create_files(kobj, attr: gt_idle_attrs); |
368 | if (err) { |
369 | kobject_put(kobj); |
370 | return err; |
371 | } |
372 | |
373 | xe_gt_idle_enable_pg(gt); |
374 | |
375 | return devm_add_action_or_reset(xe->drm.dev, gt_idle_fini, kobj); |
376 | } |
377 | |
378 | void xe_gt_idle_enable_c6(struct xe_gt *gt) |
379 | { |
380 | xe_device_assert_mem_access(gt_to_xe(gt)); |
381 | xe_force_wake_assert_held(fw: gt_to_fw(gt), domain: XE_FW_GT); |
382 | |
383 | if (IS_SRIOV_VF(gt_to_xe(gt))) |
384 | return; |
385 | |
386 | /* Units of 1280 ns for a total of 5s */ |
387 | xe_mmio_write32(mmio: >->mmio, RC_IDLE_HYSTERSIS, val: 0x3B9ACA); |
388 | /* Enable RC6 */ |
389 | xe_mmio_write32(mmio: >->mmio, RC_CONTROL, |
390 | RC_CTL_HW_ENABLE | RC_CTL_TO_MODE | RC_CTL_RC6_ENABLE); |
391 | } |
392 | |
393 | void xe_gt_idle_disable_c6(struct xe_gt *gt) |
394 | { |
395 | xe_device_assert_mem_access(gt_to_xe(gt)); |
396 | xe_force_wake_assert_held(fw: gt_to_fw(gt), domain: XE_FW_GT); |
397 | |
398 | if (IS_SRIOV_VF(gt_to_xe(gt))) |
399 | return; |
400 | |
401 | xe_mmio_write32(mmio: >->mmio, RC_CONTROL, val: 0); |
402 | xe_mmio_write32(mmio: >->mmio, RC_STATE, val: 0); |
403 | } |
404 |
Definitions
- dev_to_gtidle
- gtidle_to_gt
- gtidle_to_pc
- pc_to_xe
- gt_idle_state_to_string
- get_residency_ms
- xe_gt_idle_enable_pg
- xe_gt_idle_disable_pg
- xe_gt_idle_pg_print
- name_show
- name_attr
- idle_status_show
- idle_status_attr
- xe_gt_idle_residency_msec
- idle_residency_ms_show
- idle_residency_attr
- gt_idle_attrs
- gt_idle_fini
- xe_gt_idle_init
- xe_gt_idle_enable_c6
Improve your Profiling and Debugging skills
Find out more