Commit | Line | Data |
---|---|---|
a16b4313 MW |
1 | /* |
2 | * Copyright © 2016-2017 Intel Corporation | |
3 | * | |
4 | * Permission is hereby granted, free of charge, to any person obtaining a | |
5 | * copy of this software and associated documentation files (the "Software"), | |
6 | * to deal in the Software without restriction, including without limitation | |
7 | * the rights to use, copy, modify, merge, publish, distribute, sublicense, | |
8 | * and/or sell copies of the Software, and to permit persons to whom the | |
9 | * Software is furnished to do so, subject to the following conditions: | |
10 | * | |
11 | * The above copyright notice and this permission notice (including the next | |
12 | * paragraph) shall be included in all copies or substantial portions of the | |
13 | * Software. | |
14 | * | |
15 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
16 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
17 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL | |
18 | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
19 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING | |
20 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS | |
21 | * IN THE SOFTWARE. | |
22 | * | |
23 | */ | |
24 | ||
ffd5ce22 | 25 | #include <linux/bitfield.h> |
a16b4313 | 26 | #include <linux/firmware.h> |
56ffc742 | 27 | #include <drm/drm_print.h> |
a16b4313 MW |
28 | |
29 | #include "intel_uc_fw.h" | |
30 | #include "i915_drv.h" | |
31 | ||
32 | /** | |
33 | * intel_uc_fw_fetch - fetch uC firmware | |
34 | * | |
35 | * @dev_priv: device private | |
36 | * @uc_fw: uC firmware | |
37 | * | |
38 | * Fetch uC firmware into GEM obj. | |
39 | */ | |
40 | void intel_uc_fw_fetch(struct drm_i915_private *dev_priv, | |
41 | struct intel_uc_fw *uc_fw) | |
42 | { | |
43 | struct pci_dev *pdev = dev_priv->drm.pdev; | |
44 | struct drm_i915_gem_object *obj; | |
45 | const struct firmware *fw = NULL; | |
46 | struct uc_css_header *css; | |
47 | size_t size; | |
48 | int err; | |
49 | ||
f2bb09b6 CW |
50 | if (!uc_fw->path) { |
51 | dev_info(dev_priv->drm.dev, | |
52 | "%s: No firmware was defined for %s!\n", | |
53 | intel_uc_fw_type_repr(uc_fw->type), | |
54 | intel_platform_name(INTEL_INFO(dev_priv)->platform)); | |
55 | return; | |
56 | } | |
57 | ||
5f99afdb MW |
58 | DRM_DEBUG_DRIVER("%s fw fetch %s\n", |
59 | intel_uc_fw_type_repr(uc_fw->type), uc_fw->path); | |
60 | ||
a16b4313 | 61 | uc_fw->fetch_status = INTEL_UC_FIRMWARE_PENDING; |
5f99afdb MW |
62 | DRM_DEBUG_DRIVER("%s fw fetch %s\n", |
63 | intel_uc_fw_type_repr(uc_fw->type), | |
a16b4313 MW |
64 | intel_uc_fw_status_repr(uc_fw->fetch_status)); |
65 | ||
66 | err = request_firmware(&fw, uc_fw->path, &pdev->dev); | |
5f99afdb MW |
67 | if (err) { |
68 | DRM_DEBUG_DRIVER("%s fw request_firmware err=%d\n", | |
69 | intel_uc_fw_type_repr(uc_fw->type), err); | |
a16b4313 | 70 | goto fail; |
5f99afdb | 71 | } |
a16b4313 | 72 | |
5f99afdb MW |
73 | DRM_DEBUG_DRIVER("%s fw size %zu ptr %p\n", |
74 | intel_uc_fw_type_repr(uc_fw->type), fw->size, fw); | |
a16b4313 MW |
75 | |
76 | /* Check the size of the blob before examining buffer contents */ | |
77 | if (fw->size < sizeof(struct uc_css_header)) { | |
5f99afdb MW |
78 | DRM_WARN("%s: Unexpected firmware size (%zu, min %zu)\n", |
79 | intel_uc_fw_type_repr(uc_fw->type), | |
80 | fw->size, sizeof(struct uc_css_header)); | |
81 | err = -ENODATA; | |
a16b4313 MW |
82 | goto fail; |
83 | } | |
84 | ||
85 | css = (struct uc_css_header *)fw->data; | |
86 | ||
87 | /* Firmware bits always start from header */ | |
88 | uc_fw->header_offset = 0; | |
89 | uc_fw->header_size = (css->header_size_dw - css->modulus_size_dw - | |
90 | css->key_size_dw - css->exponent_size_dw) * | |
91 | sizeof(u32); | |
92 | ||
93 | if (uc_fw->header_size != sizeof(struct uc_css_header)) { | |
5f99afdb MW |
94 | DRM_WARN("%s: Mismatched firmware header definition\n", |
95 | intel_uc_fw_type_repr(uc_fw->type)); | |
96 | err = -ENOEXEC; | |
a16b4313 MW |
97 | goto fail; |
98 | } | |
99 | ||
100 | /* then, uCode */ | |
101 | uc_fw->ucode_offset = uc_fw->header_offset + uc_fw->header_size; | |
102 | uc_fw->ucode_size = (css->size_dw - css->header_size_dw) * sizeof(u32); | |
103 | ||
104 | /* now RSA */ | |
53fa54a6 | 105 | if (css->key_size_dw != UOS_RSA_SCRATCH_COUNT) { |
5f99afdb MW |
106 | DRM_WARN("%s: Mismatched firmware RSA key size (%u)\n", |
107 | intel_uc_fw_type_repr(uc_fw->type), css->key_size_dw); | |
108 | err = -ENOEXEC; | |
a16b4313 MW |
109 | goto fail; |
110 | } | |
111 | uc_fw->rsa_offset = uc_fw->ucode_offset + uc_fw->ucode_size; | |
112 | uc_fw->rsa_size = css->key_size_dw * sizeof(u32); | |
113 | ||
114 | /* At least, it should have header, uCode and RSA. Size of all three. */ | |
115 | size = uc_fw->header_size + uc_fw->ucode_size + uc_fw->rsa_size; | |
116 | if (fw->size < size) { | |
5f99afdb MW |
117 | DRM_WARN("%s: Truncated firmware (%zu, expected %zu)\n", |
118 | intel_uc_fw_type_repr(uc_fw->type), fw->size, size); | |
119 | err = -ENOEXEC; | |
a16b4313 MW |
120 | goto fail; |
121 | } | |
122 | ||
ffd5ce22 | 123 | /* Get version numbers from the CSS header */ |
a16b4313 MW |
124 | switch (uc_fw->type) { |
125 | case INTEL_UC_FW_TYPE_GUC: | |
ffd5ce22 MW |
126 | uc_fw->major_ver_found = FIELD_GET(CSS_SW_VERSION_GUC_MAJOR, |
127 | css->sw_version); | |
128 | uc_fw->minor_ver_found = FIELD_GET(CSS_SW_VERSION_GUC_MINOR, | |
129 | css->sw_version); | |
a16b4313 MW |
130 | break; |
131 | ||
132 | case INTEL_UC_FW_TYPE_HUC: | |
ffd5ce22 MW |
133 | uc_fw->major_ver_found = FIELD_GET(CSS_SW_VERSION_HUC_MAJOR, |
134 | css->sw_version); | |
135 | uc_fw->minor_ver_found = FIELD_GET(CSS_SW_VERSION_HUC_MINOR, | |
136 | css->sw_version); | |
a16b4313 MW |
137 | break; |
138 | ||
139 | default: | |
5f99afdb MW |
140 | MISSING_CASE(uc_fw->type); |
141 | break; | |
a16b4313 MW |
142 | } |
143 | ||
5f99afdb MW |
144 | DRM_DEBUG_DRIVER("%s fw version %u.%u (wanted %u.%u)\n", |
145 | intel_uc_fw_type_repr(uc_fw->type), | |
146 | uc_fw->major_ver_found, uc_fw->minor_ver_found, | |
147 | uc_fw->major_ver_wanted, uc_fw->minor_ver_wanted); | |
148 | ||
a16b4313 | 149 | if (uc_fw->major_ver_wanted == 0 && uc_fw->minor_ver_wanted == 0) { |
5f99afdb | 150 | DRM_NOTE("%s: Skipping firmware version check\n", |
a16b4313 MW |
151 | intel_uc_fw_type_repr(uc_fw->type)); |
152 | } else if (uc_fw->major_ver_found != uc_fw->major_ver_wanted || | |
153 | uc_fw->minor_ver_found < uc_fw->minor_ver_wanted) { | |
5f99afdb | 154 | DRM_NOTE("%s: Wrong firmware version (%u.%u, required %u.%u)\n", |
a16b4313 MW |
155 | intel_uc_fw_type_repr(uc_fw->type), |
156 | uc_fw->major_ver_found, uc_fw->minor_ver_found, | |
157 | uc_fw->major_ver_wanted, uc_fw->minor_ver_wanted); | |
158 | err = -ENOEXEC; | |
159 | goto fail; | |
160 | } | |
161 | ||
8475355f CW |
162 | obj = i915_gem_object_create_shmem_from_data(dev_priv, |
163 | fw->data, fw->size); | |
a16b4313 MW |
164 | if (IS_ERR(obj)) { |
165 | err = PTR_ERR(obj); | |
5f99afdb MW |
166 | DRM_DEBUG_DRIVER("%s fw object_create err=%d\n", |
167 | intel_uc_fw_type_repr(uc_fw->type), err); | |
a16b4313 MW |
168 | goto fail; |
169 | } | |
170 | ||
171 | uc_fw->obj = obj; | |
172 | uc_fw->size = fw->size; | |
5f99afdb MW |
173 | uc_fw->fetch_status = INTEL_UC_FIRMWARE_SUCCESS; |
174 | DRM_DEBUG_DRIVER("%s fw fetch %s\n", | |
175 | intel_uc_fw_type_repr(uc_fw->type), | |
176 | intel_uc_fw_status_repr(uc_fw->fetch_status)); | |
a16b4313 MW |
177 | |
178 | release_firmware(fw); | |
a16b4313 MW |
179 | return; |
180 | ||
181 | fail: | |
5f99afdb MW |
182 | uc_fw->fetch_status = INTEL_UC_FIRMWARE_FAIL; |
183 | DRM_DEBUG_DRIVER("%s fw fetch %s\n", | |
184 | intel_uc_fw_type_repr(uc_fw->type), | |
185 | intel_uc_fw_status_repr(uc_fw->fetch_status)); | |
186 | ||
187 | DRM_WARN("%s: Failed to fetch firmware %s (error %d)\n", | |
188 | intel_uc_fw_type_repr(uc_fw->type), uc_fw->path, err); | |
1e913d27 MW |
189 | DRM_INFO("%s: Firmware can be downloaded from %s\n", |
190 | intel_uc_fw_type_repr(uc_fw->type), INTEL_UC_FIRMWARE_URL); | |
a16b4313 MW |
191 | |
192 | release_firmware(fw); /* OK even if fw is NULL */ | |
a16b4313 MW |
193 | } |
194 | ||
fc488b59 FP |
195 | static void intel_uc_fw_ggtt_bind(struct intel_uc_fw *uc_fw) |
196 | { | |
197 | struct drm_i915_gem_object *obj = uc_fw->obj; | |
198 | struct i915_ggtt *ggtt = &to_i915(obj->base.dev)->ggtt; | |
199 | struct i915_vma dummy = { | |
200 | .node.start = intel_uc_fw_ggtt_offset(uc_fw), | |
201 | .node.size = obj->base.size, | |
202 | .pages = obj->mm.pages, | |
203 | .vm = &ggtt->vm, | |
204 | }; | |
205 | ||
206 | GEM_BUG_ON(!i915_gem_object_has_pinned_pages(obj)); | |
207 | GEM_BUG_ON(dummy.node.size > ggtt->uc_fw.size); | |
208 | ||
209 | /* uc_fw->obj cache domains were not controlled across suspend */ | |
210 | drm_clflush_sg(dummy.pages); | |
211 | ||
212 | ggtt->vm.insert_entries(&ggtt->vm, &dummy, I915_CACHE_NONE, 0); | |
213 | } | |
214 | ||
215 | static void intel_uc_fw_ggtt_unbind(struct intel_uc_fw *uc_fw) | |
216 | { | |
217 | struct drm_i915_gem_object *obj = uc_fw->obj; | |
218 | struct i915_ggtt *ggtt = &to_i915(obj->base.dev)->ggtt; | |
219 | u64 start = intel_uc_fw_ggtt_offset(uc_fw); | |
220 | ||
221 | ggtt->vm.clear_range(&ggtt->vm, start, obj->base.size); | |
222 | } | |
223 | ||
4502e9ec MW |
224 | /** |
225 | * intel_uc_fw_upload - load uC firmware using custom loader | |
4502e9ec | 226 | * @uc_fw: uC firmware |
b8aad15e | 227 | * @xfer: custom uC firmware loader function |
4502e9ec MW |
228 | * |
229 | * Loads uC firmware using custom loader and updates internal flags. | |
b8aad15e CW |
230 | * |
231 | * Return: 0 on success, non-zero on failure. | |
4502e9ec MW |
232 | */ |
233 | int intel_uc_fw_upload(struct intel_uc_fw *uc_fw, | |
fc488b59 | 234 | int (*xfer)(struct intel_uc_fw *uc_fw)) |
4502e9ec | 235 | { |
4502e9ec MW |
236 | int err; |
237 | ||
238 | DRM_DEBUG_DRIVER("%s fw load %s\n", | |
239 | intel_uc_fw_type_repr(uc_fw->type), uc_fw->path); | |
240 | ||
241 | if (uc_fw->fetch_status != INTEL_UC_FIRMWARE_SUCCESS) | |
8620eb1d | 242 | return -ENOEXEC; |
4502e9ec MW |
243 | |
244 | uc_fw->load_status = INTEL_UC_FIRMWARE_PENDING; | |
245 | DRM_DEBUG_DRIVER("%s fw load %s\n", | |
246 | intel_uc_fw_type_repr(uc_fw->type), | |
247 | intel_uc_fw_status_repr(uc_fw->load_status)); | |
248 | ||
4502e9ec | 249 | /* Call custom loader */ |
6951e589 | 250 | intel_uc_fw_ggtt_bind(uc_fw); |
fc488b59 | 251 | err = xfer(uc_fw); |
6951e589 | 252 | intel_uc_fw_ggtt_unbind(uc_fw); |
4502e9ec MW |
253 | if (err) |
254 | goto fail; | |
255 | ||
256 | uc_fw->load_status = INTEL_UC_FIRMWARE_SUCCESS; | |
257 | DRM_DEBUG_DRIVER("%s fw load %s\n", | |
258 | intel_uc_fw_type_repr(uc_fw->type), | |
259 | intel_uc_fw_status_repr(uc_fw->load_status)); | |
260 | ||
261 | DRM_INFO("%s: Loaded firmware %s (version %u.%u)\n", | |
262 | intel_uc_fw_type_repr(uc_fw->type), | |
263 | uc_fw->path, | |
264 | uc_fw->major_ver_found, uc_fw->minor_ver_found); | |
265 | ||
266 | return 0; | |
267 | ||
268 | fail: | |
269 | uc_fw->load_status = INTEL_UC_FIRMWARE_FAIL; | |
270 | DRM_DEBUG_DRIVER("%s fw load %s\n", | |
271 | intel_uc_fw_type_repr(uc_fw->type), | |
272 | intel_uc_fw_status_repr(uc_fw->load_status)); | |
273 | ||
274 | DRM_WARN("%s: Failed to load firmware %s (error %d)\n", | |
275 | intel_uc_fw_type_repr(uc_fw->type), uc_fw->path, err); | |
276 | ||
277 | return err; | |
278 | } | |
279 | ||
fc488b59 FP |
280 | int intel_uc_fw_init(struct intel_uc_fw *uc_fw) |
281 | { | |
282 | int err; | |
283 | ||
284 | if (uc_fw->fetch_status != INTEL_UC_FIRMWARE_SUCCESS) | |
285 | return -ENOEXEC; | |
286 | ||
287 | err = i915_gem_object_pin_pages(uc_fw->obj); | |
288 | if (err) | |
289 | DRM_DEBUG_DRIVER("%s fw pin-pages err=%d\n", | |
290 | intel_uc_fw_type_repr(uc_fw->type), err); | |
291 | ||
292 | return err; | |
293 | } | |
294 | ||
295 | void intel_uc_fw_fini(struct intel_uc_fw *uc_fw) | |
296 | { | |
297 | if (uc_fw->fetch_status != INTEL_UC_FIRMWARE_SUCCESS) | |
298 | return; | |
299 | ||
300 | i915_gem_object_unpin_pages(uc_fw->obj); | |
301 | } | |
302 | ||
303 | u32 intel_uc_fw_ggtt_offset(struct intel_uc_fw *uc_fw) | |
304 | { | |
305 | struct drm_i915_private *i915 = to_i915(uc_fw->obj->base.dev); | |
306 | struct i915_ggtt *ggtt = &i915->ggtt; | |
307 | struct drm_mm_node *node = &ggtt->uc_fw; | |
308 | ||
309 | GEM_BUG_ON(!node->allocated); | |
310 | GEM_BUG_ON(upper_32_bits(node->start)); | |
311 | GEM_BUG_ON(upper_32_bits(node->start + node->size - 1)); | |
312 | ||
313 | return lower_32_bits(node->start); | |
314 | } | |
315 | ||
a16b4313 | 316 | /** |
95ebcda3 | 317 | * intel_uc_fw_cleanup_fetch - cleanup uC firmware |
a16b4313 MW |
318 | * |
319 | * @uc_fw: uC firmware | |
320 | * | |
321 | * Cleans up uC firmware by releasing the firmware GEM obj. | |
322 | */ | |
95ebcda3 | 323 | void intel_uc_fw_cleanup_fetch(struct intel_uc_fw *uc_fw) |
a16b4313 MW |
324 | { |
325 | struct drm_i915_gem_object *obj; | |
326 | ||
327 | obj = fetch_and_zero(&uc_fw->obj); | |
328 | if (obj) | |
329 | i915_gem_object_put(obj); | |
330 | ||
331 | uc_fw->fetch_status = INTEL_UC_FIRMWARE_NONE; | |
332 | } | |
56ffc742 MW |
333 | |
334 | /** | |
335 | * intel_uc_fw_dump - dump information about uC firmware | |
336 | * @uc_fw: uC firmware | |
337 | * @p: the &drm_printer | |
338 | * | |
339 | * Pretty printer for uC firmware. | |
340 | */ | |
7d41ef34 | 341 | void intel_uc_fw_dump(const struct intel_uc_fw *uc_fw, struct drm_printer *p) |
56ffc742 MW |
342 | { |
343 | drm_printf(p, "%s firmware: %s\n", | |
344 | intel_uc_fw_type_repr(uc_fw->type), uc_fw->path); | |
345 | drm_printf(p, "\tstatus: fetch %s, load %s\n", | |
346 | intel_uc_fw_status_repr(uc_fw->fetch_status), | |
347 | intel_uc_fw_status_repr(uc_fw->load_status)); | |
348 | drm_printf(p, "\tversion: wanted %u.%u, found %u.%u\n", | |
349 | uc_fw->major_ver_wanted, uc_fw->minor_ver_wanted, | |
350 | uc_fw->major_ver_found, uc_fw->minor_ver_found); | |
351 | drm_printf(p, "\theader: offset %u, size %u\n", | |
352 | uc_fw->header_offset, uc_fw->header_size); | |
353 | drm_printf(p, "\tuCode: offset %u, size %u\n", | |
354 | uc_fw->ucode_offset, uc_fw->ucode_size); | |
355 | drm_printf(p, "\tRSA: offset %u, size %u\n", | |
356 | uc_fw->rsa_offset, uc_fw->rsa_size); | |
357 | } |