1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * Copyright © 2019 Intel Corporation |
4 | */ |
5 | |
6 | #include "intel_context.h" |
7 | #include "intel_engine_pm.h" |
8 | #include "intel_gpu_commands.h" |
9 | #include "intel_gt_requests.h" |
10 | #include "intel_ring.h" |
11 | #include "selftest_rc6.h" |
12 | |
13 | #include "selftests/i915_random.h" |
14 | #include "selftests/librapl.h" |
15 | |
16 | static u64 rc6_residency(struct intel_rc6 *rc6) |
17 | { |
18 | u64 result; |
19 | |
20 | /* XXX VLV_GT_MEDIA_RC6? */ |
21 | |
22 | result = intel_rc6_residency_ns(rc6, id: INTEL_RC6_RES_RC6); |
23 | if (HAS_RC6p(rc6_to_i915(rc6))) |
24 | result += intel_rc6_residency_ns(rc6, id: INTEL_RC6_RES_RC6p); |
25 | if (HAS_RC6pp(rc6_to_i915(rc6))) |
26 | result += intel_rc6_residency_ns(rc6, id: INTEL_RC6_RES_RC6pp); |
27 | |
28 | return result; |
29 | } |
30 | |
31 | int live_rc6_manual(void *arg) |
32 | { |
33 | struct intel_gt *gt = arg; |
34 | struct intel_rc6 *rc6 = >->rc6; |
35 | u64 rc0_power, rc6_power; |
36 | intel_wakeref_t wakeref; |
37 | bool has_power; |
38 | ktime_t dt; |
39 | u64 res[2]; |
40 | int err = 0; |
41 | |
42 | /* |
43 | * Our claim is that we can "encourage" the GPU to enter rc6 at will. |
44 | * Let's try it! |
45 | */ |
46 | |
47 | if (!rc6->enabled) |
48 | return 0; |
49 | |
50 | /* bsw/byt use a PCU and decouple RC6 from our manual control */ |
51 | if (IS_VALLEYVIEW(gt->i915) || IS_CHERRYVIEW(gt->i915)) |
52 | return 0; |
53 | |
54 | has_power = librapl_supported(i915: gt->i915); |
55 | wakeref = intel_runtime_pm_get(rpm: gt->uncore->rpm); |
56 | |
57 | /* Force RC6 off for starters */ |
58 | __intel_rc6_disable(rc6); |
59 | msleep(msecs: 1); /* wakeup is not immediate, takes about 100us on icl */ |
60 | |
61 | res[0] = rc6_residency(rc6); |
62 | |
63 | dt = ktime_get(); |
64 | rc0_power = librapl_energy_uJ(); |
65 | msleep(msecs: 1000); |
66 | rc0_power = librapl_energy_uJ() - rc0_power; |
67 | dt = ktime_sub(ktime_get(), dt); |
68 | res[1] = rc6_residency(rc6); |
69 | if ((res[1] - res[0]) >> 10) { |
70 | pr_err("RC6 residency increased by %lldus while disabled for 1000ms!\n" , |
71 | (res[1] - res[0]) >> 10); |
72 | err = -EINVAL; |
73 | goto out_unlock; |
74 | } |
75 | |
76 | if (has_power) { |
77 | rc0_power = div64_u64(NSEC_PER_SEC * rc0_power, |
78 | divisor: ktime_to_ns(kt: dt)); |
79 | if (!rc0_power) { |
80 | pr_err("No power measured while in RC0\n" ); |
81 | err = -EINVAL; |
82 | goto out_unlock; |
83 | } |
84 | } |
85 | |
86 | /* Manually enter RC6 */ |
87 | intel_rc6_park(rc6); |
88 | |
89 | res[0] = rc6_residency(rc6); |
90 | intel_uncore_forcewake_flush(uncore: rc6_to_uncore(rc: rc6), fw_domains: FORCEWAKE_ALL); |
91 | dt = ktime_get(); |
92 | rc6_power = librapl_energy_uJ(); |
93 | msleep(msecs: 100); |
94 | rc6_power = librapl_energy_uJ() - rc6_power; |
95 | dt = ktime_sub(ktime_get(), dt); |
96 | res[1] = rc6_residency(rc6); |
97 | if (res[1] == res[0]) { |
98 | pr_err("Did not enter RC6! RC6_STATE=%08x, RC6_CONTROL=%08x, residency=%lld\n" , |
99 | intel_uncore_read_fw(gt->uncore, GEN6_RC_STATE), |
100 | intel_uncore_read_fw(gt->uncore, GEN6_RC_CONTROL), |
101 | res[0]); |
102 | err = -EINVAL; |
103 | } |
104 | |
105 | if (has_power) { |
106 | rc6_power = div64_u64(NSEC_PER_SEC * rc6_power, |
107 | divisor: ktime_to_ns(kt: dt)); |
108 | pr_info("GPU consumed %llduW in RC0 and %llduW in RC6\n" , |
109 | rc0_power, rc6_power); |
110 | if (2 * rc6_power > rc0_power) { |
111 | pr_err("GPU leaked energy while in RC6!\n" ); |
112 | err = -EINVAL; |
113 | goto out_unlock; |
114 | } |
115 | } |
116 | |
117 | /* Restore what should have been the original state! */ |
118 | intel_rc6_unpark(rc6); |
119 | |
120 | out_unlock: |
121 | intel_runtime_pm_put(rpm: gt->uncore->rpm, wref: wakeref); |
122 | return err; |
123 | } |
124 | |
125 | static const u32 *__live_rc6_ctx(struct intel_context *ce) |
126 | { |
127 | struct i915_request *rq; |
128 | const u32 *result; |
129 | u32 cmd; |
130 | u32 *cs; |
131 | |
132 | rq = intel_context_create_request(ce); |
133 | if (IS_ERR(ptr: rq)) |
134 | return ERR_CAST(ptr: rq); |
135 | |
136 | cs = intel_ring_begin(rq, num_dwords: 4); |
137 | if (IS_ERR(ptr: cs)) { |
138 | i915_request_add(rq); |
139 | return cs; |
140 | } |
141 | |
142 | cmd = MI_STORE_REGISTER_MEM | MI_USE_GGTT; |
143 | if (GRAPHICS_VER(rq->i915) >= 8) |
144 | cmd++; |
145 | |
146 | *cs++ = cmd; |
147 | *cs++ = i915_mmio_reg_offset(GEN8_RC6_CTX_INFO); |
148 | *cs++ = ce->timeline->hwsp_offset + 8; |
149 | *cs++ = 0; |
150 | intel_ring_advance(rq, cs); |
151 | |
152 | result = rq->hwsp_seqno + 2; |
153 | i915_request_add(rq); |
154 | |
155 | return result; |
156 | } |
157 | |
158 | static struct intel_engine_cs ** |
159 | randomised_engines(struct intel_gt *gt, |
160 | struct rnd_state *prng, |
161 | unsigned int *count) |
162 | { |
163 | struct intel_engine_cs *engine, **engines; |
164 | enum intel_engine_id id; |
165 | int n; |
166 | |
167 | n = 0; |
168 | for_each_engine(engine, gt, id) |
169 | n++; |
170 | if (!n) |
171 | return NULL; |
172 | |
173 | engines = kmalloc_array(n, size: sizeof(*engines), GFP_KERNEL); |
174 | if (!engines) |
175 | return NULL; |
176 | |
177 | n = 0; |
178 | for_each_engine(engine, gt, id) |
179 | engines[n++] = engine; |
180 | |
181 | i915_prandom_shuffle(arr: engines, elsz: sizeof(*engines), count: n, state: prng); |
182 | |
183 | *count = n; |
184 | return engines; |
185 | } |
186 | |
187 | int live_rc6_ctx_wa(void *arg) |
188 | { |
189 | struct intel_gt *gt = arg; |
190 | struct intel_engine_cs **engines; |
191 | unsigned int n, count; |
192 | I915_RND_STATE(prng); |
193 | int err = 0; |
194 | |
195 | /* A read of CTX_INFO upsets rc6. Poke the bear! */ |
196 | if (GRAPHICS_VER(gt->i915) < 8) |
197 | return 0; |
198 | |
199 | engines = randomised_engines(gt, prng: &prng, count: &count); |
200 | if (!engines) |
201 | return 0; |
202 | |
203 | for (n = 0; n < count; n++) { |
204 | struct intel_engine_cs *engine = engines[n]; |
205 | int pass; |
206 | |
207 | for (pass = 0; pass < 2; pass++) { |
208 | struct i915_gpu_error *error = >->i915->gpu_error; |
209 | struct intel_context *ce; |
210 | unsigned int resets = |
211 | i915_reset_engine_count(error, engine); |
212 | const u32 *res; |
213 | |
214 | /* Use a sacrifical context */ |
215 | ce = intel_context_create(engine); |
216 | if (IS_ERR(ptr: ce)) { |
217 | err = PTR_ERR(ptr: ce); |
218 | goto out; |
219 | } |
220 | |
221 | intel_engine_pm_get(engine); |
222 | res = __live_rc6_ctx(ce); |
223 | intel_engine_pm_put(engine); |
224 | intel_context_put(ce); |
225 | if (IS_ERR(ptr: res)) { |
226 | err = PTR_ERR(ptr: res); |
227 | goto out; |
228 | } |
229 | |
230 | if (intel_gt_wait_for_idle(gt, HZ / 5) == -ETIME) { |
231 | intel_gt_set_wedged(gt); |
232 | err = -ETIME; |
233 | goto out; |
234 | } |
235 | |
236 | intel_gt_pm_wait_for_idle(gt); |
237 | pr_debug("%s: CTX_INFO=%0x\n" , |
238 | engine->name, READ_ONCE(*res)); |
239 | |
240 | if (resets != |
241 | i915_reset_engine_count(error, engine)) { |
242 | pr_err("%s: GPU reset required\n" , |
243 | engine->name); |
244 | add_taint_for_CI(i915: gt->i915, TAINT_WARN); |
245 | err = -EIO; |
246 | goto out; |
247 | } |
248 | } |
249 | } |
250 | |
251 | out: |
252 | kfree(objp: engines); |
253 | return err; |
254 | } |
255 | |