1 | /* |
2 | * SPDX-License-Identifier: MIT |
3 | * |
4 | * Copyright © 2018 Intel Corporation |
5 | */ |
6 | |
7 | #include <linux/random.h> |
8 | |
9 | #include "gem/i915_gem_internal.h" |
10 | #include "gem/i915_gem_pm.h" |
11 | #include "gem/selftests/igt_gem_utils.h" |
12 | #include "gem/selftests/mock_context.h" |
13 | #include "gt/intel_gt.h" |
14 | #include "gt/intel_gt_pm.h" |
15 | |
16 | #include "i915_selftest.h" |
17 | |
18 | #include "igt_flush_test.h" |
19 | #include "mock_drm.h" |
20 | |
21 | static int switch_to_context(struct i915_gem_context *ctx) |
22 | { |
23 | struct i915_gem_engines_iter it; |
24 | struct intel_context *ce; |
25 | int err = 0; |
26 | |
27 | for_each_gem_engine(ce, i915_gem_context_lock_engines(ctx), it) { |
28 | struct i915_request *rq; |
29 | |
30 | rq = intel_context_create_request(ce); |
31 | if (IS_ERR(ptr: rq)) { |
32 | err = PTR_ERR(ptr: rq); |
33 | break; |
34 | } |
35 | |
36 | i915_request_add(rq); |
37 | } |
38 | i915_gem_context_unlock_engines(ctx); |
39 | |
40 | return err; |
41 | } |
42 | |
43 | static void trash_stolen(struct drm_i915_private *i915) |
44 | { |
45 | struct i915_ggtt *ggtt = to_gt(i915)->ggtt; |
46 | const u64 slot = ggtt->error_capture.start; |
47 | const resource_size_t size = resource_size(res: &i915->dsm.stolen); |
48 | unsigned long page; |
49 | u32 prng = 0x12345678; |
50 | |
51 | /* XXX: fsck. needs some more thought... */ |
52 | if (!i915_ggtt_has_aperture(ggtt)) |
53 | return; |
54 | |
55 | for (page = 0; page < size; page += PAGE_SIZE) { |
56 | const dma_addr_t dma = i915->dsm.stolen.start + page; |
57 | u32 __iomem *s; |
58 | int x; |
59 | |
60 | ggtt->vm.insert_page(&ggtt->vm, dma, slot, |
61 | i915_gem_get_pat_index(i915, |
62 | level: I915_CACHE_NONE), |
63 | 0); |
64 | |
65 | s = io_mapping_map_atomic_wc(mapping: &ggtt->iomap, offset: slot); |
66 | for (x = 0; x < PAGE_SIZE / sizeof(u32); x++) { |
67 | prng = next_pseudo_random32(seed: prng); |
68 | iowrite32(prng, &s[x]); |
69 | } |
70 | io_mapping_unmap_atomic(vaddr: s); |
71 | } |
72 | |
73 | ggtt->vm.clear_range(&ggtt->vm, slot, PAGE_SIZE); |
74 | } |
75 | |
76 | static void simulate_hibernate(struct drm_i915_private *i915) |
77 | { |
78 | intel_wakeref_t wakeref; |
79 | |
80 | wakeref = intel_runtime_pm_get(rpm: &i915->runtime_pm); |
81 | |
82 | /* |
83 | * As a final sting in the tail, invalidate stolen. Under a real S4, |
84 | * stolen is lost and needs to be refilled on resume. However, under |
85 | * CI we merely do S4-device testing (as full S4 is too unreliable |
86 | * for automated testing across a cluster), so to simulate the effect |
87 | * of stolen being trashed across S4, we trash it ourselves. |
88 | */ |
89 | trash_stolen(i915); |
90 | |
91 | intel_runtime_pm_put(rpm: &i915->runtime_pm, wref: wakeref); |
92 | } |
93 | |
94 | static int igt_pm_prepare(struct drm_i915_private *i915) |
95 | { |
96 | i915_gem_suspend(i915); |
97 | |
98 | return 0; |
99 | } |
100 | |
101 | static void igt_pm_suspend(struct drm_i915_private *i915) |
102 | { |
103 | intel_wakeref_t wakeref; |
104 | |
105 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
106 | i915_ggtt_suspend(gtt: to_gt(i915)->ggtt); |
107 | i915_gem_suspend_late(i915); |
108 | } |
109 | } |
110 | |
111 | static void igt_pm_hibernate(struct drm_i915_private *i915) |
112 | { |
113 | intel_wakeref_t wakeref; |
114 | |
115 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
116 | i915_ggtt_suspend(gtt: to_gt(i915)->ggtt); |
117 | |
118 | i915_gem_freeze(i915); |
119 | i915_gem_freeze_late(i915); |
120 | } |
121 | } |
122 | |
123 | static void igt_pm_resume(struct drm_i915_private *i915) |
124 | { |
125 | intel_wakeref_t wakeref; |
126 | |
127 | /* |
128 | * Both suspend and hibernate follow the same wakeup path and assume |
129 | * that runtime-pm just works. |
130 | */ |
131 | with_intel_runtime_pm(&i915->runtime_pm, wakeref) { |
132 | i915_ggtt_resume(ggtt: to_gt(i915)->ggtt); |
133 | if (GRAPHICS_VER(i915) >= 8) |
134 | setup_private_pat(to_gt(i915)); |
135 | i915_gem_resume(i915); |
136 | } |
137 | } |
138 | |
139 | static int igt_gem_suspend(void *arg) |
140 | { |
141 | struct drm_i915_private *i915 = arg; |
142 | struct i915_gem_context *ctx; |
143 | struct file *file; |
144 | int err; |
145 | |
146 | file = mock_file(i915); |
147 | if (IS_ERR(ptr: file)) |
148 | return PTR_ERR(ptr: file); |
149 | |
150 | err = -ENOMEM; |
151 | ctx = live_context(i915, file); |
152 | if (!IS_ERR(ptr: ctx)) |
153 | err = switch_to_context(ctx); |
154 | if (err) |
155 | goto out; |
156 | |
157 | err = igt_pm_prepare(i915); |
158 | if (err) |
159 | goto out; |
160 | |
161 | igt_pm_suspend(i915); |
162 | |
163 | /* Here be dragons! Note that with S3RST any S3 may become S4! */ |
164 | simulate_hibernate(i915); |
165 | |
166 | igt_pm_resume(i915); |
167 | |
168 | err = switch_to_context(ctx); |
169 | out: |
170 | fput(file); |
171 | return err; |
172 | } |
173 | |
174 | static int igt_gem_hibernate(void *arg) |
175 | { |
176 | struct drm_i915_private *i915 = arg; |
177 | struct i915_gem_context *ctx; |
178 | struct file *file; |
179 | int err; |
180 | |
181 | file = mock_file(i915); |
182 | if (IS_ERR(ptr: file)) |
183 | return PTR_ERR(ptr: file); |
184 | |
185 | err = -ENOMEM; |
186 | ctx = live_context(i915, file); |
187 | if (!IS_ERR(ptr: ctx)) |
188 | err = switch_to_context(ctx); |
189 | if (err) |
190 | goto out; |
191 | |
192 | err = igt_pm_prepare(i915); |
193 | if (err) |
194 | goto out; |
195 | |
196 | igt_pm_hibernate(i915); |
197 | |
198 | /* Here be dragons! */ |
199 | simulate_hibernate(i915); |
200 | |
201 | igt_pm_resume(i915); |
202 | |
203 | err = switch_to_context(ctx); |
204 | out: |
205 | fput(file); |
206 | return err; |
207 | } |
208 | |
209 | static int igt_gem_ww_ctx(void *arg) |
210 | { |
211 | struct drm_i915_private *i915 = arg; |
212 | struct drm_i915_gem_object *obj, *obj2; |
213 | struct i915_gem_ww_ctx ww; |
214 | int err = 0; |
215 | |
216 | obj = i915_gem_object_create_internal(i915, PAGE_SIZE); |
217 | if (IS_ERR(ptr: obj)) |
218 | return PTR_ERR(ptr: obj); |
219 | |
220 | obj2 = i915_gem_object_create_internal(i915, PAGE_SIZE); |
221 | if (IS_ERR(ptr: obj2)) { |
222 | err = PTR_ERR(ptr: obj2); |
223 | goto put1; |
224 | } |
225 | |
226 | i915_gem_ww_ctx_init(ctx: &ww, intr: true); |
227 | retry: |
228 | /* Lock the objects, twice for good measure (-EALREADY handling) */ |
229 | err = i915_gem_object_lock(obj, ww: &ww); |
230 | if (!err) |
231 | err = i915_gem_object_lock_interruptible(obj, ww: &ww); |
232 | if (!err) |
233 | err = i915_gem_object_lock_interruptible(obj: obj2, ww: &ww); |
234 | if (!err) |
235 | err = i915_gem_object_lock(obj: obj2, ww: &ww); |
236 | |
237 | if (err == -EDEADLK) { |
238 | err = i915_gem_ww_ctx_backoff(ctx: &ww); |
239 | if (!err) |
240 | goto retry; |
241 | } |
242 | i915_gem_ww_ctx_fini(ctx: &ww); |
243 | i915_gem_object_put(obj: obj2); |
244 | put1: |
245 | i915_gem_object_put(obj); |
246 | return err; |
247 | } |
248 | |
249 | int i915_gem_live_selftests(struct drm_i915_private *i915) |
250 | { |
251 | static const struct i915_subtest tests[] = { |
252 | SUBTEST(igt_gem_suspend), |
253 | SUBTEST(igt_gem_hibernate), |
254 | SUBTEST(igt_gem_ww_ctx), |
255 | }; |
256 | |
257 | if (intel_gt_is_wedged(gt: to_gt(i915))) |
258 | return 0; |
259 | |
260 | return i915_live_subtests(tests, i915); |
261 | } |
262 | |