Get the guc reference from the gt using the gt_to_guc() helper. Signed-off-by: Andi Shyti <andi.shyti@linux.intel.com> Reviewed-by: Nirmoy Das <nirmoy.das@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20231229102734.674362-3-andi.shyti@linux.intel.com
479 lines
14 KiB
C
479 lines
14 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2022 Intel Corporation
|
|
*/
|
|
|
|
#include "gem/i915_gem_lmem.h"
|
|
#include "gt/intel_engine_pm.h"
|
|
#include "gt/intel_gpu_commands.h"
|
|
#include "gt/intel_gt.h"
|
|
#include "gt/intel_gt_print.h"
|
|
#include "gt/intel_ring.h"
|
|
#include "intel_gsc_binary_headers.h"
|
|
#include "intel_gsc_fw.h"
|
|
#include "intel_gsc_uc_heci_cmd_submit.h"
|
|
#include "i915_reg.h"
|
|
|
|
static bool gsc_is_in_reset(struct intel_uncore *uncore)
|
|
{
|
|
u32 fw_status = intel_uncore_read(uncore, HECI_FWSTS(MTL_GSC_HECI1_BASE, 1));
|
|
|
|
return REG_FIELD_GET(HECI1_FWSTS1_CURRENT_STATE, fw_status) ==
|
|
HECI1_FWSTS1_CURRENT_STATE_RESET;
|
|
}
|
|
|
|
static u32 gsc_uc_get_fw_status(struct intel_uncore *uncore, bool needs_wakeref)
|
|
{
|
|
intel_wakeref_t wakeref;
|
|
u32 fw_status = 0;
|
|
|
|
if (needs_wakeref)
|
|
wakeref = intel_runtime_pm_get(uncore->rpm);
|
|
|
|
fw_status = intel_uncore_read(uncore, HECI_FWSTS(MTL_GSC_HECI1_BASE, 1));
|
|
|
|
if (needs_wakeref)
|
|
intel_runtime_pm_put(uncore->rpm, wakeref);
|
|
return fw_status;
|
|
}
|
|
|
|
bool intel_gsc_uc_fw_proxy_init_done(struct intel_gsc_uc *gsc, bool needs_wakeref)
|
|
{
|
|
return REG_FIELD_GET(HECI1_FWSTS1_CURRENT_STATE,
|
|
gsc_uc_get_fw_status(gsc_uc_to_gt(gsc)->uncore,
|
|
needs_wakeref)) ==
|
|
HECI1_FWSTS1_PROXY_STATE_NORMAL;
|
|
}
|
|
|
|
int intel_gsc_uc_fw_proxy_get_status(struct intel_gsc_uc *gsc)
|
|
{
|
|
if (!(IS_ENABLED(CONFIG_INTEL_MEI_GSC_PROXY)))
|
|
return -ENODEV;
|
|
if (!intel_uc_fw_is_loadable(&gsc->fw))
|
|
return -ENODEV;
|
|
if (__intel_uc_fw_status(&gsc->fw) == INTEL_UC_FIRMWARE_LOAD_FAIL)
|
|
return -ENOLINK;
|
|
if (!intel_gsc_uc_fw_proxy_init_done(gsc, true))
|
|
return -EAGAIN;
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool intel_gsc_uc_fw_init_done(struct intel_gsc_uc *gsc)
|
|
{
|
|
return gsc_uc_get_fw_status(gsc_uc_to_gt(gsc)->uncore, false) &
|
|
HECI1_FWSTS1_INIT_COMPLETE;
|
|
}
|
|
|
|
static inline u32 cpd_entry_offset(const struct intel_gsc_cpd_entry *entry)
|
|
{
|
|
return entry->offset & INTEL_GSC_CPD_ENTRY_OFFSET_MASK;
|
|
}
|
|
|
|
int intel_gsc_fw_get_binary_info(struct intel_uc_fw *gsc_fw, const void *data, size_t size)
|
|
{
|
|
struct intel_gsc_uc *gsc = container_of(gsc_fw, struct intel_gsc_uc, fw);
|
|
struct intel_gt *gt = gsc_uc_to_gt(gsc);
|
|
const struct intel_gsc_layout_pointers *layout = data;
|
|
const struct intel_gsc_bpdt_header *bpdt_header = NULL;
|
|
const struct intel_gsc_bpdt_entry *bpdt_entry = NULL;
|
|
const struct intel_gsc_cpd_header_v2 *cpd_header = NULL;
|
|
const struct intel_gsc_cpd_entry *cpd_entry = NULL;
|
|
const struct intel_gsc_manifest_header *manifest;
|
|
size_t min_size = sizeof(*layout);
|
|
int i;
|
|
|
|
if (size < min_size) {
|
|
gt_err(gt, "GSC FW too small! %zu < %zu\n", size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
/*
|
|
* The GSC binary starts with the pointer layout, which contains the
|
|
* locations of the various partitions of the binary. The one we're
|
|
* interested in to get the version is the boot1 partition, where we can
|
|
* find a BPDT header followed by entries, one of which points to the
|
|
* RBE sub-section of the partition. From here, we can parse the CPD
|
|
* header and the following entries to find the manifest location
|
|
* (entry identified by the "RBEP.man" name), from which we can finally
|
|
* extract the version.
|
|
*
|
|
* --------------------------------------------------
|
|
* [ intel_gsc_layout_pointers ]
|
|
* [ ... ]
|
|
* [ boot1.offset >---------------------------]------o
|
|
* [ ... ] |
|
|
* -------------------------------------------------- |
|
|
* |
|
|
* -------------------------------------------------- |
|
|
* [ intel_gsc_bpdt_header ]<-----o
|
|
* --------------------------------------------------
|
|
* [ intel_gsc_bpdt_entry[] ]
|
|
* [ entry1 ]
|
|
* [ ... ]
|
|
* [ entryX ]
|
|
* [ type == GSC_RBE ]
|
|
* [ offset >-----------------------------]------o
|
|
* [ ... ] |
|
|
* -------------------------------------------------- |
|
|
* |
|
|
* -------------------------------------------------- |
|
|
* [ intel_gsc_cpd_header_v2 ]<-----o
|
|
* --------------------------------------------------
|
|
* [ intel_gsc_cpd_entry[] ]
|
|
* [ entry1 ]
|
|
* [ ... ]
|
|
* [ entryX ]
|
|
* [ "RBEP.man" ]
|
|
* [ ... ]
|
|
* [ offset >----------------------------]------o
|
|
* [ ... ] |
|
|
* -------------------------------------------------- |
|
|
* |
|
|
* -------------------------------------------------- |
|
|
* [ intel_gsc_manifest_header ]<-----o
|
|
* [ ... ]
|
|
* [ intel_gsc_version fw_version ]
|
|
* [ ... ]
|
|
* --------------------------------------------------
|
|
*/
|
|
|
|
min_size = layout->boot1.offset + layout->boot1.size;
|
|
if (size < min_size) {
|
|
gt_err(gt, "GSC FW too small for boot section! %zu < %zu\n",
|
|
size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
min_size = sizeof(*bpdt_header);
|
|
if (layout->boot1.size < min_size) {
|
|
gt_err(gt, "GSC FW boot section too small for BPDT header: %u < %zu\n",
|
|
layout->boot1.size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
bpdt_header = data + layout->boot1.offset;
|
|
if (bpdt_header->signature != INTEL_GSC_BPDT_HEADER_SIGNATURE) {
|
|
gt_err(gt, "invalid signature for BPDT header: 0x%08x!\n",
|
|
bpdt_header->signature);
|
|
return -EINVAL;
|
|
}
|
|
|
|
min_size += sizeof(*bpdt_entry) * bpdt_header->descriptor_count;
|
|
if (layout->boot1.size < min_size) {
|
|
gt_err(gt, "GSC FW boot section too small for BPDT entries: %u < %zu\n",
|
|
layout->boot1.size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
bpdt_entry = (void *)bpdt_header + sizeof(*bpdt_header);
|
|
for (i = 0; i < bpdt_header->descriptor_count; i++, bpdt_entry++) {
|
|
if ((bpdt_entry->type & INTEL_GSC_BPDT_ENTRY_TYPE_MASK) !=
|
|
INTEL_GSC_BPDT_ENTRY_TYPE_GSC_RBE)
|
|
continue;
|
|
|
|
cpd_header = (void *)bpdt_header + bpdt_entry->sub_partition_offset;
|
|
min_size = bpdt_entry->sub_partition_offset + sizeof(*cpd_header);
|
|
break;
|
|
}
|
|
|
|
if (!cpd_header) {
|
|
gt_err(gt, "couldn't find CPD header in GSC binary!\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
if (layout->boot1.size < min_size) {
|
|
gt_err(gt, "GSC FW boot section too small for CPD header: %u < %zu\n",
|
|
layout->boot1.size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
if (cpd_header->header_marker != INTEL_GSC_CPD_HEADER_MARKER) {
|
|
gt_err(gt, "invalid marker for CPD header in GSC bin: 0x%08x!\n",
|
|
cpd_header->header_marker);
|
|
return -EINVAL;
|
|
}
|
|
|
|
min_size += sizeof(*cpd_entry) * cpd_header->num_of_entries;
|
|
if (layout->boot1.size < min_size) {
|
|
gt_err(gt, "GSC FW boot section too small for CPD entries: %u < %zu\n",
|
|
layout->boot1.size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
cpd_entry = (void *)cpd_header + cpd_header->header_length;
|
|
for (i = 0; i < cpd_header->num_of_entries; i++, cpd_entry++) {
|
|
if (strcmp(cpd_entry->name, "RBEP.man") == 0) {
|
|
manifest = (void *)cpd_header + cpd_entry_offset(cpd_entry);
|
|
intel_uc_fw_version_from_gsc_manifest(&gsc->release,
|
|
manifest);
|
|
gsc->security_version = manifest->security_version;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int emit_gsc_fw_load(struct i915_request *rq, struct intel_gsc_uc *gsc)
|
|
{
|
|
u32 offset = i915_ggtt_offset(gsc->local);
|
|
u32 *cs;
|
|
|
|
cs = intel_ring_begin(rq, 4);
|
|
if (IS_ERR(cs))
|
|
return PTR_ERR(cs);
|
|
|
|
*cs++ = GSC_FW_LOAD;
|
|
*cs++ = lower_32_bits(offset);
|
|
*cs++ = upper_32_bits(offset);
|
|
*cs++ = (gsc->local->size / SZ_4K) | HECI1_FW_LIMIT_VALID;
|
|
|
|
intel_ring_advance(rq, cs);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gsc_fw_load(struct intel_gsc_uc *gsc)
|
|
{
|
|
struct intel_context *ce = gsc->ce;
|
|
struct i915_request *rq;
|
|
int err;
|
|
|
|
if (!ce)
|
|
return -ENODEV;
|
|
|
|
rq = i915_request_create(ce);
|
|
if (IS_ERR(rq))
|
|
return PTR_ERR(rq);
|
|
|
|
if (ce->engine->emit_init_breadcrumb) {
|
|
err = ce->engine->emit_init_breadcrumb(rq);
|
|
if (err)
|
|
goto out_rq;
|
|
}
|
|
|
|
err = emit_gsc_fw_load(rq, gsc);
|
|
if (err)
|
|
goto out_rq;
|
|
|
|
err = ce->engine->emit_flush(rq, 0);
|
|
|
|
out_rq:
|
|
i915_request_get(rq);
|
|
|
|
if (unlikely(err))
|
|
i915_request_set_error_once(rq, err);
|
|
|
|
i915_request_add(rq);
|
|
|
|
if (!err && i915_request_wait(rq, 0, msecs_to_jiffies(500)) < 0)
|
|
err = -ETIME;
|
|
|
|
i915_request_put(rq);
|
|
|
|
if (err)
|
|
gt_err(gsc_uc_to_gt(gsc), "Request submission for GSC load failed %pe\n",
|
|
ERR_PTR(err));
|
|
|
|
return err;
|
|
}
|
|
|
|
static int gsc_fw_load_prepare(struct intel_gsc_uc *gsc)
|
|
{
|
|
struct intel_gt *gt = gsc_uc_to_gt(gsc);
|
|
void *src;
|
|
|
|
if (!gsc->local)
|
|
return -ENODEV;
|
|
|
|
if (gsc->local->size < gsc->fw.size)
|
|
return -ENOSPC;
|
|
|
|
src = i915_gem_object_pin_map_unlocked(gsc->fw.obj,
|
|
intel_gt_coherent_map_type(gt, gsc->fw.obj, true));
|
|
if (IS_ERR(src))
|
|
return PTR_ERR(src);
|
|
|
|
memcpy_toio(gsc->local_vaddr, src, gsc->fw.size);
|
|
memset_io(gsc->local_vaddr + gsc->fw.size, 0, gsc->local->size - gsc->fw.size);
|
|
|
|
intel_guc_write_barrier(gt_to_guc(gt));
|
|
|
|
i915_gem_object_unpin_map(gsc->fw.obj);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int gsc_fw_wait(struct intel_gt *gt)
|
|
{
|
|
return intel_wait_for_register(gt->uncore,
|
|
HECI_FWSTS(MTL_GSC_HECI1_BASE, 1),
|
|
HECI1_FWSTS1_INIT_COMPLETE,
|
|
HECI1_FWSTS1_INIT_COMPLETE,
|
|
500);
|
|
}
|
|
|
|
struct intel_gsc_mkhi_header {
|
|
u8 group_id;
|
|
#define MKHI_GROUP_ID_GFX_SRV 0x30
|
|
|
|
u8 command;
|
|
#define MKHI_GFX_SRV_GET_HOST_COMPATIBILITY_VERSION (0x42)
|
|
|
|
u8 reserved;
|
|
u8 result;
|
|
} __packed;
|
|
|
|
struct mtl_gsc_ver_msg_in {
|
|
struct intel_gsc_mtl_header header;
|
|
struct intel_gsc_mkhi_header mkhi;
|
|
} __packed;
|
|
|
|
struct mtl_gsc_ver_msg_out {
|
|
struct intel_gsc_mtl_header header;
|
|
struct intel_gsc_mkhi_header mkhi;
|
|
u16 proj_major;
|
|
u16 compat_major;
|
|
u16 compat_minor;
|
|
u16 reserved[5];
|
|
} __packed;
|
|
|
|
#define GSC_VER_PKT_SZ SZ_4K
|
|
|
|
static int gsc_fw_query_compatibility_version(struct intel_gsc_uc *gsc)
|
|
{
|
|
struct intel_gt *gt = gsc_uc_to_gt(gsc);
|
|
struct mtl_gsc_ver_msg_in *msg_in;
|
|
struct mtl_gsc_ver_msg_out *msg_out;
|
|
struct i915_vma *vma;
|
|
u64 offset;
|
|
void *vaddr;
|
|
int err;
|
|
|
|
err = intel_guc_allocate_and_map_vma(gt_to_guc(gt), GSC_VER_PKT_SZ * 2,
|
|
&vma, &vaddr);
|
|
if (err) {
|
|
gt_err(gt, "failed to allocate vma for GSC version query\n");
|
|
return err;
|
|
}
|
|
|
|
offset = i915_ggtt_offset(vma);
|
|
msg_in = vaddr;
|
|
msg_out = vaddr + GSC_VER_PKT_SZ;
|
|
|
|
intel_gsc_uc_heci_cmd_emit_mtl_header(&msg_in->header,
|
|
HECI_MEADDRESS_MKHI,
|
|
sizeof(*msg_in), 0);
|
|
msg_in->mkhi.group_id = MKHI_GROUP_ID_GFX_SRV;
|
|
msg_in->mkhi.command = MKHI_GFX_SRV_GET_HOST_COMPATIBILITY_VERSION;
|
|
|
|
err = intel_gsc_uc_heci_cmd_submit_packet(>->uc.gsc,
|
|
offset,
|
|
sizeof(*msg_in),
|
|
offset + GSC_VER_PKT_SZ,
|
|
GSC_VER_PKT_SZ);
|
|
if (err) {
|
|
gt_err(gt,
|
|
"failed to submit GSC request for compatibility version: %d\n",
|
|
err);
|
|
goto out_vma;
|
|
}
|
|
|
|
if (msg_out->header.message_size != sizeof(*msg_out)) {
|
|
gt_err(gt, "invalid GSC reply length %u [expected %zu], s=0x%x, f=0x%x, r=0x%x\n",
|
|
msg_out->header.message_size, sizeof(*msg_out),
|
|
msg_out->header.status, msg_out->header.flags, msg_out->mkhi.result);
|
|
err = -EPROTO;
|
|
goto out_vma;
|
|
}
|
|
|
|
gsc->fw.file_selected.ver.major = msg_out->compat_major;
|
|
gsc->fw.file_selected.ver.minor = msg_out->compat_minor;
|
|
|
|
out_vma:
|
|
i915_vma_unpin_and_release(&vma, I915_VMA_RELEASE_MAP);
|
|
return err;
|
|
}
|
|
|
|
int intel_gsc_uc_fw_upload(struct intel_gsc_uc *gsc)
|
|
{
|
|
struct intel_gt *gt = gsc_uc_to_gt(gsc);
|
|
struct intel_uc_fw *gsc_fw = &gsc->fw;
|
|
int err;
|
|
|
|
/* check current fw status */
|
|
if (intel_gsc_uc_fw_init_done(gsc)) {
|
|
if (GEM_WARN_ON(!intel_uc_fw_is_loaded(gsc_fw)))
|
|
intel_uc_fw_change_status(gsc_fw, INTEL_UC_FIRMWARE_TRANSFERRED);
|
|
return -EEXIST;
|
|
}
|
|
|
|
if (!intel_uc_fw_is_loadable(gsc_fw))
|
|
return -ENOEXEC;
|
|
|
|
/* FW blob is ok, so clean the status */
|
|
intel_uc_fw_sanitize(&gsc->fw);
|
|
|
|
if (!gsc_is_in_reset(gt->uncore))
|
|
return -EIO;
|
|
|
|
err = gsc_fw_load_prepare(gsc);
|
|
if (err)
|
|
goto fail;
|
|
|
|
/*
|
|
* GSC is only killed by an FLR, so we need to trigger one on unload to
|
|
* make sure we stop it. This is because we assign a chunk of memory to
|
|
* the GSC as part of the FW load , so we need to make sure it stops
|
|
* using it when we release it to the system on driver unload. Note that
|
|
* this is not a problem of the unload per-se, because the GSC will not
|
|
* touch that memory unless there are requests for it coming from the
|
|
* driver; therefore, no accesses will happen while i915 is not loaded,
|
|
* but if we re-load the driver then the GSC might wake up and try to
|
|
* access that old memory location again.
|
|
* Given that an FLR is a very disruptive action (see the FLR function
|
|
* for details), we want to do it as the last action before releasing
|
|
* the access to the MMIO bar, which means we need to do it as part of
|
|
* the primary uncore cleanup.
|
|
* An alternative approach to the FLR would be to use a memory location
|
|
* that survives driver unload, like e.g. stolen memory, and keep the
|
|
* GSC loaded across reloads. However, this requires us to make sure we
|
|
* preserve that memory location on unload and then determine and
|
|
* reserve its offset on each subsequent load, which is not trivial, so
|
|
* it is easier to just kill everything and start fresh.
|
|
*/
|
|
intel_uncore_set_flr_on_fini(>->i915->uncore);
|
|
|
|
err = gsc_fw_load(gsc);
|
|
if (err)
|
|
goto fail;
|
|
|
|
err = gsc_fw_wait(gt);
|
|
if (err)
|
|
goto fail;
|
|
|
|
err = gsc_fw_query_compatibility_version(gsc);
|
|
if (err)
|
|
goto fail;
|
|
|
|
/* we only support compatibility version 1.0 at the moment */
|
|
err = intel_uc_check_file_version(gsc_fw, NULL);
|
|
if (err)
|
|
goto fail;
|
|
|
|
/* FW is not fully operational until we enable SW proxy */
|
|
intel_uc_fw_change_status(gsc_fw, INTEL_UC_FIRMWARE_TRANSFERRED);
|
|
|
|
gt_info(gt, "Loaded GSC firmware %s (cv%u.%u, r%u.%u.%u.%u, svn %u)\n",
|
|
gsc_fw->file_selected.path,
|
|
gsc_fw->file_selected.ver.major, gsc_fw->file_selected.ver.minor,
|
|
gsc->release.major, gsc->release.minor,
|
|
gsc->release.patch, gsc->release.build,
|
|
gsc->security_version);
|
|
|
|
return 0;
|
|
|
|
fail:
|
|
return intel_uc_fw_mark_load_failed(gsc_fw, err);
|
|
}
|