1 | // SPDX-License-Identifier: MIT |
2 | /* |
3 | * Copyright(c) 2023 Intel Corporation. |
4 | */ |
5 | |
6 | #include "gem/i915_gem_internal.h" |
7 | |
8 | #include "gt/intel_context.h" |
9 | #include "gt/intel_gt.h" |
10 | #include "gt/uc/intel_gsc_fw.h" |
11 | #include "gt/uc/intel_gsc_uc_heci_cmd_submit.h" |
12 | |
13 | #include "i915_drv.h" |
14 | #include "intel_pxp.h" |
15 | #include "intel_pxp_cmd_interface_42.h" |
16 | #include "intel_pxp_cmd_interface_43.h" |
17 | #include "intel_pxp_gsccs.h" |
18 | #include "intel_pxp_types.h" |
19 | |
20 | static bool |
21 | is_fw_err_platform_config(struct intel_pxp *pxp, u32 type) |
22 | { |
23 | switch (type) { |
24 | case PXP_STATUS_ERROR_API_VERSION: |
25 | case PXP_STATUS_PLATFCONFIG_KF1_NOVERIF: |
26 | case PXP_STATUS_PLATFCONFIG_KF1_BAD: |
27 | pxp->platform_cfg_is_bad = true; |
28 | return true; |
29 | default: |
30 | break; |
31 | } |
32 | return false; |
33 | } |
34 | |
35 | static const char * |
36 | fw_err_to_string(u32 type) |
37 | { |
38 | switch (type) { |
39 | case PXP_STATUS_ERROR_API_VERSION: |
40 | return "ERR_API_VERSION" ; |
41 | case PXP_STATUS_NOT_READY: |
42 | return "ERR_NOT_READY" ; |
43 | case PXP_STATUS_PLATFCONFIG_KF1_NOVERIF: |
44 | case PXP_STATUS_PLATFCONFIG_KF1_BAD: |
45 | return "ERR_PLATFORM_CONFIG" ; |
46 | default: |
47 | break; |
48 | } |
49 | return NULL; |
50 | } |
51 | |
52 | static int |
53 | gsccs_send_message(struct intel_pxp *pxp, |
54 | void *msg_in, size_t msg_in_size, |
55 | void *msg_out, size_t msg_out_size_max, |
56 | size_t *msg_out_len, |
57 | u64 *gsc_msg_handle_retry) |
58 | { |
59 | struct intel_gt *gt = pxp->ctrl_gt; |
60 | struct drm_i915_private *i915 = gt->i915; |
61 | struct gsccs_session_resources *exec_res = &pxp->gsccs_res; |
62 | struct intel_gsc_mtl_header * = exec_res->pkt_vaddr; |
63 | struct intel_gsc_heci_non_priv_pkt pkt; |
64 | size_t max_msg_size; |
65 | u32 reply_size; |
66 | int ret; |
67 | |
68 | if (!exec_res->ce) |
69 | return -ENODEV; |
70 | |
71 | max_msg_size = PXP43_MAX_HECI_INOUT_SIZE - sizeof(*header); |
72 | |
73 | if (msg_in_size > max_msg_size || msg_out_size_max > max_msg_size) |
74 | return -ENOSPC; |
75 | |
76 | if (!exec_res->pkt_vma || !exec_res->bb_vma) |
77 | return -ENOENT; |
78 | |
79 | GEM_BUG_ON(exec_res->pkt_vma->size < (2 * PXP43_MAX_HECI_INOUT_SIZE)); |
80 | |
81 | mutex_lock(&pxp->tee_mutex); |
82 | |
83 | memset(header, 0, sizeof(*header)); |
84 | intel_gsc_uc_heci_cmd_emit_mtl_header(header, HECI_MEADDRESS_PXP, |
85 | msg_size: msg_in_size + sizeof(*header), |
86 | host_session_id: exec_res->host_session_handle); |
87 | |
88 | /* check if this is a host-session-handle cleanup call (empty packet) */ |
89 | if (!msg_in && !msg_out) |
90 | header->flags |= GSC_INFLAG_MSG_CLEANUP; |
91 | |
92 | /* copy caller provided gsc message handle if this is polling for a prior msg completion */ |
93 | header->gsc_message_handle = *gsc_msg_handle_retry; |
94 | |
95 | /* NOTE: zero size packets are used for session-cleanups */ |
96 | if (msg_in && msg_in_size) |
97 | memcpy(exec_res->pkt_vaddr + sizeof(*header), msg_in, msg_in_size); |
98 | |
99 | pkt.addr_in = i915_vma_offset(vma: exec_res->pkt_vma); |
100 | pkt.size_in = header->message_size; |
101 | pkt.addr_out = pkt.addr_in + PXP43_MAX_HECI_INOUT_SIZE; |
102 | pkt.size_out = msg_out_size_max + sizeof(*header); |
103 | pkt.heci_pkt_vma = exec_res->pkt_vma; |
104 | pkt.bb_vma = exec_res->bb_vma; |
105 | |
106 | /* |
107 | * Before submitting, let's clear-out the validity marker on the reply offset. |
108 | * We use offset PXP43_MAX_HECI_INOUT_SIZE for reply location so point header there. |
109 | */ |
110 | header = exec_res->pkt_vaddr + PXP43_MAX_HECI_INOUT_SIZE; |
111 | header->validity_marker = 0; |
112 | |
113 | ret = intel_gsc_uc_heci_cmd_submit_nonpriv(gsc: >->uc.gsc, |
114 | ce: exec_res->ce, pkt: &pkt, cs: exec_res->bb_vaddr, |
115 | GSC_HECI_REPLY_LATENCY_MS); |
116 | if (ret) { |
117 | drm_err(&i915->drm, "failed to send gsc PXP msg (%d)\n" , ret); |
118 | goto unlock; |
119 | } |
120 | |
121 | /* Response validity marker, status and busyness */ |
122 | if (header->validity_marker != GSC_HECI_VALIDITY_MARKER) { |
123 | drm_err(&i915->drm, "gsc PXP reply with invalid validity marker\n" ); |
124 | ret = -EINVAL; |
125 | goto unlock; |
126 | } |
127 | if (header->status != 0) { |
128 | drm_dbg(&i915->drm, "gsc PXP reply status has error = 0x%08x\n" , |
129 | header->status); |
130 | ret = -EINVAL; |
131 | goto unlock; |
132 | } |
133 | if (header->flags & GSC_OUTFLAG_MSG_PENDING) { |
134 | drm_dbg(&i915->drm, "gsc PXP reply is busy\n" ); |
135 | /* |
136 | * When the GSC firmware replies with pending bit, it means that the requested |
137 | * operation has begun but the completion is pending and the caller needs |
138 | * to re-request with the gsc_message_handle that was returned by the firmware. |
139 | * until the pending bit is turned off. |
140 | */ |
141 | *gsc_msg_handle_retry = header->gsc_message_handle; |
142 | ret = -EAGAIN; |
143 | goto unlock; |
144 | } |
145 | |
146 | reply_size = header->message_size - sizeof(*header); |
147 | if (reply_size > msg_out_size_max) { |
148 | drm_warn(&i915->drm, "caller with insufficient PXP reply size %u (%zu)\n" , |
149 | reply_size, msg_out_size_max); |
150 | reply_size = msg_out_size_max; |
151 | } |
152 | |
153 | if (msg_out) |
154 | memcpy(msg_out, exec_res->pkt_vaddr + PXP43_MAX_HECI_INOUT_SIZE + sizeof(*header), |
155 | reply_size); |
156 | if (msg_out_len) |
157 | *msg_out_len = reply_size; |
158 | |
159 | unlock: |
160 | mutex_unlock(lock: &pxp->tee_mutex); |
161 | return ret; |
162 | } |
163 | |
164 | static int |
165 | gsccs_send_message_retry_complete(struct intel_pxp *pxp, |
166 | void *msg_in, size_t msg_in_size, |
167 | void *msg_out, size_t msg_out_size_max, |
168 | size_t *msg_out_len) |
169 | { |
170 | u64 gsc_session_retry = 0; |
171 | int ret, tries = 0; |
172 | |
173 | /* |
174 | * Keep sending request if GSC firmware was busy. Based on fw specs + |
175 | * sw overhead (and testing) we expect a worst case pending-bit delay of |
176 | * GSC_PENDING_RETRY_MAXCOUNT x GSC_PENDING_RETRY_PAUSE_MS millisecs. |
177 | */ |
178 | do { |
179 | ret = gsccs_send_message(pxp, msg_in, msg_in_size, msg_out, msg_out_size_max, |
180 | msg_out_len, gsc_msg_handle_retry: &gsc_session_retry); |
181 | /* Only try again if gsc says so */ |
182 | if (ret != -EAGAIN) |
183 | break; |
184 | |
185 | msleep(GSC_PENDING_RETRY_PAUSE_MS); |
186 | } while (++tries < GSC_PENDING_RETRY_MAXCOUNT); |
187 | |
188 | return ret; |
189 | } |
190 | |
191 | bool intel_pxp_gsccs_is_ready_for_sessions(struct intel_pxp *pxp) |
192 | { |
193 | /* |
194 | * GSC-fw loading, HuC-fw loading, HuC-fw authentication and |
195 | * GSC-proxy init flow (requiring an mei component driver) |
196 | * must all occur first before we can start requesting for PXP |
197 | * sessions. Checking for completion on HuC authentication and |
198 | * gsc-proxy init flow (the last set of dependencies that |
199 | * are out of order) will suffice. |
200 | */ |
201 | if (intel_huc_is_authenticated(huc: &pxp->ctrl_gt->uc.huc, type: INTEL_HUC_AUTH_BY_GSC) && |
202 | intel_gsc_uc_fw_proxy_init_done(gsc: &pxp->ctrl_gt->uc.gsc, needs_wakeref: true)) |
203 | return true; |
204 | |
205 | return false; |
206 | } |
207 | |
208 | int intel_pxp_gsccs_create_session(struct intel_pxp *pxp, |
209 | int arb_session_id) |
210 | { |
211 | struct drm_i915_private *i915 = pxp->ctrl_gt->i915; |
212 | struct pxp43_create_arb_in msg_in = {}; |
213 | struct pxp43_create_arb_out msg_out = {}; |
214 | int ret; |
215 | |
216 | msg_in.header.api_version = PXP_APIVER(4, 3); |
217 | msg_in.header.command_id = PXP43_CMDID_INIT_SESSION; |
218 | msg_in.header.stream_id = (FIELD_PREP(PXP43_INIT_SESSION_APPID, arb_session_id) | |
219 | FIELD_PREP(PXP43_INIT_SESSION_VALID, 1) | |
220 | FIELD_PREP(PXP43_INIT_SESSION_APPTYPE, 0)); |
221 | msg_in.header.buffer_len = sizeof(msg_in) - sizeof(msg_in.header); |
222 | msg_in.protection_mode = PXP43_INIT_SESSION_PROTECTION_ARB; |
223 | |
224 | ret = gsccs_send_message_retry_complete(pxp, |
225 | msg_in: &msg_in, msg_in_size: sizeof(msg_in), |
226 | msg_out: &msg_out, msg_out_size_max: sizeof(msg_out), NULL); |
227 | if (ret) { |
228 | drm_err(&i915->drm, "Failed to init session %d, ret=[%d]\n" , arb_session_id, ret); |
229 | } else if (msg_out.header.status != 0) { |
230 | if (is_fw_err_platform_config(pxp, type: msg_out.header.status)) { |
231 | drm_info_once(&i915->drm, |
232 | "PXP init-session-%d failed due to BIOS/SOC:0x%08x:%s\n" , |
233 | arb_session_id, msg_out.header.status, |
234 | fw_err_to_string(msg_out.header.status)); |
235 | } else { |
236 | drm_dbg(&i915->drm, "PXP init-session-%d failed 0x%08x:%st:\n" , |
237 | arb_session_id, msg_out.header.status, |
238 | fw_err_to_string(msg_out.header.status)); |
239 | drm_dbg(&i915->drm, " cmd-detail: ID=[0x%08x],API-Ver-[0x%08x]\n" , |
240 | msg_in.header.command_id, msg_in.header.api_version); |
241 | } |
242 | } |
243 | |
244 | return ret; |
245 | } |
246 | |
247 | void intel_pxp_gsccs_end_arb_fw_session(struct intel_pxp *pxp, u32 session_id) |
248 | { |
249 | struct drm_i915_private *i915 = pxp->ctrl_gt->i915; |
250 | struct pxp42_inv_stream_key_in msg_in = {}; |
251 | struct pxp42_inv_stream_key_out msg_out = {}; |
252 | int ret = 0; |
253 | |
254 | /* |
255 | * Stream key invalidation reuses the same version 4.2 input/output |
256 | * command format but firmware requires 4.3 API interaction |
257 | */ |
258 | msg_in.header.api_version = PXP_APIVER(4, 3); |
259 | msg_in.header.command_id = PXP42_CMDID_INVALIDATE_STREAM_KEY; |
260 | msg_in.header.buffer_len = sizeof(msg_in) - sizeof(msg_in.header); |
261 | |
262 | msg_in.header.stream_id = FIELD_PREP(PXP_CMDHDR_EXTDATA_SESSION_VALID, 1); |
263 | msg_in.header.stream_id |= FIELD_PREP(PXP_CMDHDR_EXTDATA_APP_TYPE, 0); |
264 | msg_in.header.stream_id |= FIELD_PREP(PXP_CMDHDR_EXTDATA_SESSION_ID, session_id); |
265 | |
266 | ret = gsccs_send_message_retry_complete(pxp, |
267 | msg_in: &msg_in, msg_in_size: sizeof(msg_in), |
268 | msg_out: &msg_out, msg_out_size_max: sizeof(msg_out), NULL); |
269 | if (ret) { |
270 | drm_err(&i915->drm, "Failed to inv-stream-key-%u, ret=[%d]\n" , |
271 | session_id, ret); |
272 | } else if (msg_out.header.status != 0) { |
273 | if (is_fw_err_platform_config(pxp, type: msg_out.header.status)) { |
274 | drm_info_once(&i915->drm, |
275 | "PXP inv-stream-key-%u failed due to BIOS/SOC :0x%08x:%s\n" , |
276 | session_id, msg_out.header.status, |
277 | fw_err_to_string(msg_out.header.status)); |
278 | } else { |
279 | drm_dbg(&i915->drm, "PXP inv-stream-key-%u failed 0x%08x:%s:\n" , |
280 | session_id, msg_out.header.status, |
281 | fw_err_to_string(msg_out.header.status)); |
282 | drm_dbg(&i915->drm, " cmd-detail: ID=[0x%08x],API-Ver-[0x%08x]\n" , |
283 | msg_in.header.command_id, msg_in.header.api_version); |
284 | } |
285 | } |
286 | } |
287 | |
288 | static void |
289 | gsccs_cleanup_fw_host_session_handle(struct intel_pxp *pxp) |
290 | { |
291 | struct drm_i915_private *i915 = pxp->ctrl_gt->i915; |
292 | int ret; |
293 | |
294 | ret = gsccs_send_message_retry_complete(pxp, NULL, msg_in_size: 0, NULL, msg_out_size_max: 0, NULL); |
295 | if (ret) |
296 | drm_dbg(&i915->drm, "Failed to send gsccs msg host-session-cleanup: ret=[%d]\n" , |
297 | ret); |
298 | } |
299 | |
300 | static void |
301 | gsccs_destroy_execution_resource(struct intel_pxp *pxp) |
302 | { |
303 | struct gsccs_session_resources *exec_res = &pxp->gsccs_res; |
304 | |
305 | if (exec_res->host_session_handle) |
306 | gsccs_cleanup_fw_host_session_handle(pxp); |
307 | if (exec_res->ce) |
308 | intel_context_put(ce: exec_res->ce); |
309 | if (exec_res->bb_vma) |
310 | i915_vma_unpin_and_release(p_vma: &exec_res->bb_vma, I915_VMA_RELEASE_MAP); |
311 | if (exec_res->pkt_vma) |
312 | i915_vma_unpin_and_release(p_vma: &exec_res->pkt_vma, I915_VMA_RELEASE_MAP); |
313 | |
314 | memset(exec_res, 0, sizeof(*exec_res)); |
315 | } |
316 | |
317 | static int |
318 | gsccs_create_buffer(struct intel_gt *gt, |
319 | const char *bufname, size_t size, |
320 | struct i915_vma **vma, void **map) |
321 | { |
322 | struct drm_i915_private *i915 = gt->i915; |
323 | struct drm_i915_gem_object *obj; |
324 | int err = 0; |
325 | |
326 | obj = i915_gem_object_create_internal(i915, size); |
327 | if (IS_ERR(ptr: obj)) { |
328 | drm_err(&i915->drm, "Failed to allocate gsccs backend %s.\n" , bufname); |
329 | err = PTR_ERR(ptr: obj); |
330 | goto out_none; |
331 | } |
332 | |
333 | *vma = i915_vma_instance(obj, vm: gt->vm, NULL); |
334 | if (IS_ERR(ptr: *vma)) { |
335 | drm_err(&i915->drm, "Failed to vma-instance gsccs backend %s.\n" , bufname); |
336 | err = PTR_ERR(ptr: *vma); |
337 | goto out_put; |
338 | } |
339 | |
340 | /* return a virtual pointer */ |
341 | *map = i915_gem_object_pin_map_unlocked(obj, type: intel_gt_coherent_map_type(gt, obj, always_coherent: true)); |
342 | if (IS_ERR(ptr: *map)) { |
343 | drm_err(&i915->drm, "Failed to map gsccs backend %s.\n" , bufname); |
344 | err = PTR_ERR(ptr: *map); |
345 | goto out_put; |
346 | } |
347 | |
348 | /* all PXP sessions commands are treated as non-privileged */ |
349 | err = i915_vma_pin(vma: *vma, size: 0, alignment: 0, PIN_USER); |
350 | if (err) { |
351 | drm_err(&i915->drm, "Failed to vma-pin gsccs backend %s.\n" , bufname); |
352 | goto out_unmap; |
353 | } |
354 | |
355 | return 0; |
356 | |
357 | out_unmap: |
358 | i915_gem_object_unpin_map(obj); |
359 | out_put: |
360 | i915_gem_object_put(obj); |
361 | out_none: |
362 | *vma = NULL; |
363 | *map = NULL; |
364 | |
365 | return err; |
366 | } |
367 | |
368 | static int |
369 | gsccs_allocate_execution_resource(struct intel_pxp *pxp) |
370 | { |
371 | struct intel_gt *gt = pxp->ctrl_gt; |
372 | struct gsccs_session_resources *exec_res = &pxp->gsccs_res; |
373 | struct intel_engine_cs *engine = gt->engine[GSC0]; |
374 | struct intel_context *ce; |
375 | int err = 0; |
376 | |
377 | /* |
378 | * First, ensure the GSC engine is present. |
379 | * NOTE: Backend would only be called with the correct gt. |
380 | */ |
381 | if (!engine) |
382 | return -ENODEV; |
383 | |
384 | /* |
385 | * Now, allocate, pin and map two objects, one for the heci message packet |
386 | * and another for the batch buffer we submit into GSC engine (that includes the packet). |
387 | * NOTE: GSC-CS backend is currently only supported on MTL, so we allocate shmem. |
388 | */ |
389 | err = gsccs_create_buffer(gt: pxp->ctrl_gt, bufname: "Heci Packet" , |
390 | size: 2 * PXP43_MAX_HECI_INOUT_SIZE, |
391 | vma: &exec_res->pkt_vma, map: &exec_res->pkt_vaddr); |
392 | if (err) |
393 | return err; |
394 | |
395 | err = gsccs_create_buffer(gt: pxp->ctrl_gt, bufname: "Batch Buffer" , PAGE_SIZE, |
396 | vma: &exec_res->bb_vma, map: &exec_res->bb_vaddr); |
397 | if (err) |
398 | goto free_pkt; |
399 | |
400 | /* Finally, create an intel_context to be used during the submission */ |
401 | ce = intel_context_create(engine); |
402 | if (IS_ERR(ptr: ce)) { |
403 | drm_err(>->i915->drm, "Failed creating gsccs backend ctx\n" ); |
404 | err = PTR_ERR(ptr: ce); |
405 | goto free_batch; |
406 | } |
407 | |
408 | i915_vm_put(vm: ce->vm); |
409 | ce->vm = i915_vm_get(vm: pxp->ctrl_gt->vm); |
410 | exec_res->ce = ce; |
411 | |
412 | /* initialize host-session-handle (for all i915-to-gsc-firmware PXP cmds) */ |
413 | get_random_bytes(buf: &exec_res->host_session_handle, len: sizeof(exec_res->host_session_handle)); |
414 | |
415 | return 0; |
416 | |
417 | free_batch: |
418 | i915_vma_unpin_and_release(p_vma: &exec_res->bb_vma, I915_VMA_RELEASE_MAP); |
419 | free_pkt: |
420 | i915_vma_unpin_and_release(p_vma: &exec_res->pkt_vma, I915_VMA_RELEASE_MAP); |
421 | memset(exec_res, 0, sizeof(*exec_res)); |
422 | |
423 | return err; |
424 | } |
425 | |
426 | void intel_pxp_gsccs_fini(struct intel_pxp *pxp) |
427 | { |
428 | intel_wakeref_t wakeref; |
429 | |
430 | gsccs_destroy_execution_resource(pxp); |
431 | with_intel_runtime_pm(&pxp->ctrl_gt->i915->runtime_pm, wakeref) |
432 | intel_pxp_fini_hw(pxp); |
433 | } |
434 | |
435 | int intel_pxp_gsccs_init(struct intel_pxp *pxp) |
436 | { |
437 | int ret; |
438 | intel_wakeref_t wakeref; |
439 | |
440 | ret = gsccs_allocate_execution_resource(pxp); |
441 | if (!ret) { |
442 | with_intel_runtime_pm(&pxp->ctrl_gt->i915->runtime_pm, wakeref) |
443 | intel_pxp_init_hw(pxp); |
444 | } |
445 | return ret; |
446 | } |
447 | |