465 lines
12 KiB
C
465 lines
12 KiB
C
// SPDX-License-Identifier: MIT
|
|
/*
|
|
* Copyright © 2025 Intel Corporation
|
|
*/
|
|
|
|
#include <linux/component.h>
|
|
#include <linux/delay.h>
|
|
#include <linux/firmware.h>
|
|
|
|
#include <drm/drm_managed.h>
|
|
#include <drm/intel/i915_component.h>
|
|
#include <drm/intel/intel_lb_mei_interface.h>
|
|
#include <drm/drm_print.h>
|
|
|
|
#include "xe_device.h"
|
|
#include "xe_late_bind_fw.h"
|
|
#include "xe_pcode.h"
|
|
#include "xe_pcode_api.h"
|
|
#include "xe_pm.h"
|
|
|
|
/*
|
|
* The component should load quite quickly in most cases, but it could take
|
|
* a bit. Using a very big timeout just to cover the worst case scenario
|
|
*/
|
|
#define LB_INIT_TIMEOUT_MS 20000
|
|
|
|
/*
|
|
* Retry interval set to 6 seconds, in steps of 200 ms, to allow time for
|
|
* other OS components to release the MEI CL handle
|
|
*/
|
|
#define LB_FW_LOAD_RETRY_MAXCOUNT 30
|
|
#define LB_FW_LOAD_RETRY_PAUSE_MS 200
|
|
|
|
static const u32 fw_id_to_type[] = {
|
|
[XE_LB_FW_FAN_CONTROL] = INTEL_LB_TYPE_FAN_CONTROL,
|
|
};
|
|
|
|
static const char * const fw_id_to_name[] = {
|
|
[XE_LB_FW_FAN_CONTROL] = "fan_control",
|
|
};
|
|
|
|
static struct xe_device *
|
|
late_bind_to_xe(struct xe_late_bind *late_bind)
|
|
{
|
|
return container_of(late_bind, struct xe_device, late_bind);
|
|
}
|
|
|
|
static struct xe_device *
|
|
late_bind_fw_to_xe(struct xe_late_bind_fw *lb_fw)
|
|
{
|
|
return container_of(lb_fw, struct xe_device, late_bind.late_bind_fw[lb_fw->id]);
|
|
}
|
|
|
|
/* Refer to the "Late Bind based Firmware Layout" documentation entry for details */
|
|
static int parse_cpd_header(struct xe_late_bind_fw *lb_fw,
|
|
const void *data, size_t size, const char *manifest_entry)
|
|
{
|
|
struct xe_device *xe = late_bind_fw_to_xe(lb_fw);
|
|
const struct gsc_cpd_header_v2 *header = data;
|
|
const struct gsc_manifest_header *manifest;
|
|
const struct gsc_cpd_entry *entry;
|
|
size_t min_size = sizeof(*header);
|
|
u32 offset = 0;
|
|
int i;
|
|
|
|
/* manifest_entry is mandatory */
|
|
xe_assert(xe, manifest_entry);
|
|
|
|
if (size < min_size || header->header_marker != GSC_CPD_HEADER_MARKER)
|
|
return -ENOENT;
|
|
|
|
if (header->header_length < sizeof(struct gsc_cpd_header_v2)) {
|
|
drm_err(&xe->drm, "%s late binding fw: Invalid CPD header length %u!\n",
|
|
fw_id_to_name[lb_fw->id], header->header_length);
|
|
return -EINVAL;
|
|
}
|
|
|
|
min_size = header->header_length + sizeof(struct gsc_cpd_entry) * header->num_of_entries;
|
|
if (size < min_size) {
|
|
drm_err(&xe->drm, "%s late binding fw: too small! %zu < %zu\n",
|
|
fw_id_to_name[lb_fw->id], size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
/* Look for the manifest first */
|
|
entry = (void *)header + header->header_length;
|
|
for (i = 0; i < header->num_of_entries; i++, entry++)
|
|
if (strcmp(entry->name, manifest_entry) == 0)
|
|
offset = entry->offset & GSC_CPD_ENTRY_OFFSET_MASK;
|
|
|
|
if (!offset) {
|
|
drm_err(&xe->drm, "%s late binding fw: Failed to find manifest_entry\n",
|
|
fw_id_to_name[lb_fw->id]);
|
|
return -ENODATA;
|
|
}
|
|
|
|
min_size = offset + sizeof(struct gsc_manifest_header);
|
|
if (size < min_size) {
|
|
drm_err(&xe->drm, "%s late binding fw: too small! %zu < %zu\n",
|
|
fw_id_to_name[lb_fw->id], size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
manifest = data + offset;
|
|
|
|
lb_fw->version = manifest->fw_version;
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* Refer to the "Late Bind based Firmware Layout" documentation entry for details */
|
|
static int parse_lb_layout(struct xe_late_bind_fw *lb_fw,
|
|
const void *data, size_t size, const char *fpt_entry)
|
|
{
|
|
struct xe_device *xe = late_bind_fw_to_xe(lb_fw);
|
|
const struct csc_fpt_header *header = data;
|
|
const struct csc_fpt_entry *entry;
|
|
size_t min_size = sizeof(*header);
|
|
u32 offset = 0;
|
|
int i;
|
|
|
|
/* fpt_entry is mandatory */
|
|
xe_assert(xe, fpt_entry);
|
|
|
|
if (size < min_size || header->header_marker != CSC_FPT_HEADER_MARKER)
|
|
return -ENOENT;
|
|
|
|
if (header->header_length < sizeof(struct csc_fpt_header)) {
|
|
drm_err(&xe->drm, "%s late binding fw: Invalid FPT header length %u!\n",
|
|
fw_id_to_name[lb_fw->id], header->header_length);
|
|
return -EINVAL;
|
|
}
|
|
|
|
min_size = header->header_length + sizeof(struct csc_fpt_entry) * header->num_of_entries;
|
|
if (size < min_size) {
|
|
drm_err(&xe->drm, "%s late binding fw: too small! %zu < %zu\n",
|
|
fw_id_to_name[lb_fw->id], size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
/* Look for the cpd header first */
|
|
entry = (void *)header + header->header_length;
|
|
for (i = 0; i < header->num_of_entries; i++, entry++)
|
|
if (strcmp(entry->name, fpt_entry) == 0)
|
|
offset = entry->offset;
|
|
|
|
if (!offset) {
|
|
drm_err(&xe->drm, "%s late binding fw: Failed to find fpt_entry\n",
|
|
fw_id_to_name[lb_fw->id]);
|
|
return -ENODATA;
|
|
}
|
|
|
|
min_size = offset + sizeof(struct gsc_cpd_header_v2);
|
|
if (size < min_size) {
|
|
drm_err(&xe->drm, "%s late binding fw: too small! %zu < %zu\n",
|
|
fw_id_to_name[lb_fw->id], size, min_size);
|
|
return -ENODATA;
|
|
}
|
|
|
|
return parse_cpd_header(lb_fw, data + offset, size - offset, "LTES.man");
|
|
}
|
|
|
|
static const char *xe_late_bind_parse_status(uint32_t status)
|
|
{
|
|
switch (status) {
|
|
case INTEL_LB_STATUS_SUCCESS:
|
|
return "success";
|
|
case INTEL_LB_STATUS_4ID_MISMATCH:
|
|
return "4Id Mismatch";
|
|
case INTEL_LB_STATUS_ARB_FAILURE:
|
|
return "ARB Failure";
|
|
case INTEL_LB_STATUS_GENERAL_ERROR:
|
|
return "General Error";
|
|
case INTEL_LB_STATUS_INVALID_PARAMS:
|
|
return "Invalid Params";
|
|
case INTEL_LB_STATUS_INVALID_SIGNATURE:
|
|
return "Invalid Signature";
|
|
case INTEL_LB_STATUS_INVALID_PAYLOAD:
|
|
return "Invalid Payload";
|
|
case INTEL_LB_STATUS_TIMEOUT:
|
|
return "Timeout";
|
|
default:
|
|
return "Unknown error";
|
|
}
|
|
}
|
|
|
|
static int xe_late_bind_fw_num_fans(struct xe_late_bind *late_bind, u32 *num_fans)
|
|
{
|
|
struct xe_device *xe = late_bind_to_xe(late_bind);
|
|
struct xe_tile *root_tile = xe_device_get_root_tile(xe);
|
|
|
|
return xe_pcode_read(root_tile,
|
|
PCODE_MBOX(FAN_SPEED_CONTROL, FSC_READ_NUM_FANS, 0), num_fans, NULL);
|
|
}
|
|
|
|
void xe_late_bind_wait_for_worker_completion(struct xe_late_bind *late_bind)
|
|
{
|
|
struct xe_device *xe = late_bind_to_xe(late_bind);
|
|
struct xe_late_bind_fw *lbfw;
|
|
int fw_id;
|
|
|
|
for (fw_id = 0; fw_id < XE_LB_FW_MAX_ID; fw_id++) {
|
|
lbfw = &late_bind->late_bind_fw[fw_id];
|
|
if (lbfw->payload && late_bind->wq) {
|
|
drm_dbg(&xe->drm, "Flush work: load %s firmware\n",
|
|
fw_id_to_name[lbfw->id]);
|
|
flush_work(&lbfw->work);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void xe_late_bind_work(struct work_struct *work)
|
|
{
|
|
struct xe_late_bind_fw *lbfw = container_of(work, struct xe_late_bind_fw, work);
|
|
struct xe_late_bind *late_bind = container_of(lbfw, struct xe_late_bind,
|
|
late_bind_fw[lbfw->id]);
|
|
struct xe_device *xe = late_bind_to_xe(late_bind);
|
|
int retry = LB_FW_LOAD_RETRY_MAXCOUNT;
|
|
int ret;
|
|
int slept;
|
|
|
|
xe_device_assert_mem_access(xe);
|
|
|
|
/* we can queue this before the component is bound */
|
|
for (slept = 0; slept < LB_INIT_TIMEOUT_MS; slept += 100) {
|
|
if (late_bind->component.ops)
|
|
break;
|
|
msleep(100);
|
|
}
|
|
|
|
if (!late_bind->component.ops) {
|
|
drm_err(&xe->drm, "Late bind component not bound\n");
|
|
/* Do not re-attempt fw load */
|
|
drmm_kfree(&xe->drm, (void *)lbfw->payload);
|
|
lbfw->payload = NULL;
|
|
goto out;
|
|
}
|
|
|
|
drm_dbg(&xe->drm, "Load %s firmware\n", fw_id_to_name[lbfw->id]);
|
|
|
|
do {
|
|
ret = late_bind->component.ops->push_payload(late_bind->component.mei_dev,
|
|
lbfw->type,
|
|
lbfw->flags,
|
|
lbfw->payload,
|
|
lbfw->payload_size);
|
|
if (!ret)
|
|
break;
|
|
msleep(LB_FW_LOAD_RETRY_PAUSE_MS);
|
|
} while (--retry && ret == -EBUSY);
|
|
|
|
if (!ret) {
|
|
drm_dbg(&xe->drm, "Load %s firmware successful\n",
|
|
fw_id_to_name[lbfw->id]);
|
|
goto out;
|
|
}
|
|
|
|
if (ret > 0)
|
|
drm_err(&xe->drm, "Load %s firmware failed with err %d, %s\n",
|
|
fw_id_to_name[lbfw->id], ret, xe_late_bind_parse_status(ret));
|
|
else
|
|
drm_err(&xe->drm, "Load %s firmware failed with err %d",
|
|
fw_id_to_name[lbfw->id], ret);
|
|
/* Do not re-attempt fw load */
|
|
drmm_kfree(&xe->drm, (void *)lbfw->payload);
|
|
lbfw->payload = NULL;
|
|
|
|
out:
|
|
xe_pm_runtime_put(xe);
|
|
}
|
|
|
|
int xe_late_bind_fw_load(struct xe_late_bind *late_bind)
|
|
{
|
|
struct xe_device *xe = late_bind_to_xe(late_bind);
|
|
struct xe_late_bind_fw *lbfw;
|
|
int fw_id;
|
|
|
|
if (!late_bind->component_added)
|
|
return -ENODEV;
|
|
|
|
if (late_bind->disable)
|
|
return 0;
|
|
|
|
for (fw_id = 0; fw_id < XE_LB_FW_MAX_ID; fw_id++) {
|
|
lbfw = &late_bind->late_bind_fw[fw_id];
|
|
if (lbfw->payload) {
|
|
xe_pm_runtime_get_noresume(xe);
|
|
queue_work(late_bind->wq, &lbfw->work);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int __xe_late_bind_fw_init(struct xe_late_bind *late_bind, u32 fw_id)
|
|
{
|
|
struct xe_device *xe = late_bind_to_xe(late_bind);
|
|
struct pci_dev *pdev = to_pci_dev(xe->drm.dev);
|
|
struct xe_late_bind_fw *lb_fw;
|
|
const struct firmware *fw;
|
|
u32 num_fans;
|
|
int ret;
|
|
|
|
if (fw_id >= XE_LB_FW_MAX_ID)
|
|
return -EINVAL;
|
|
|
|
lb_fw = &late_bind->late_bind_fw[fw_id];
|
|
|
|
lb_fw->id = fw_id;
|
|
lb_fw->type = fw_id_to_type[lb_fw->id];
|
|
lb_fw->flags &= ~INTEL_LB_FLAG_IS_PERSISTENT;
|
|
|
|
if (lb_fw->type == INTEL_LB_TYPE_FAN_CONTROL) {
|
|
ret = xe_late_bind_fw_num_fans(late_bind, &num_fans);
|
|
if (ret) {
|
|
drm_dbg(&xe->drm, "Failed to read number of fans: %d\n", ret);
|
|
return 0; /* Not a fatal error, continue without fan control */
|
|
}
|
|
drm_dbg(&xe->drm, "Number of Fans: %d\n", num_fans);
|
|
if (!num_fans)
|
|
return 0;
|
|
}
|
|
|
|
snprintf(lb_fw->blob_path, sizeof(lb_fw->blob_path), "xe/%s_8086_%04x_%04x_%04x.bin",
|
|
fw_id_to_name[lb_fw->id], pdev->device,
|
|
pdev->subsystem_vendor, pdev->subsystem_device);
|
|
|
|
drm_dbg(&xe->drm, "Request late binding firmware %s\n", lb_fw->blob_path);
|
|
ret = firmware_request_nowarn(&fw, lb_fw->blob_path, xe->drm.dev);
|
|
if (ret) {
|
|
drm_dbg(&xe->drm, "%s late binding fw not available for current device",
|
|
fw_id_to_name[lb_fw->id]);
|
|
return 0;
|
|
}
|
|
|
|
if (fw->size > XE_LB_MAX_PAYLOAD_SIZE) {
|
|
drm_err(&xe->drm, "Firmware %s size %zu is larger than max pay load size %u\n",
|
|
lb_fw->blob_path, fw->size, XE_LB_MAX_PAYLOAD_SIZE);
|
|
release_firmware(fw);
|
|
return -ENODATA;
|
|
}
|
|
|
|
ret = parse_lb_layout(lb_fw, fw->data, fw->size, "LTES");
|
|
if (ret)
|
|
return ret;
|
|
|
|
lb_fw->payload_size = fw->size;
|
|
lb_fw->payload = drmm_kzalloc(&xe->drm, lb_fw->payload_size, GFP_KERNEL);
|
|
if (!lb_fw->payload) {
|
|
release_firmware(fw);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
drm_info(&xe->drm, "Using %s firmware from %s version %u.%u.%u.%u\n",
|
|
fw_id_to_name[lb_fw->id], lb_fw->blob_path,
|
|
lb_fw->version.major, lb_fw->version.minor,
|
|
lb_fw->version.hotfix, lb_fw->version.build);
|
|
|
|
memcpy((void *)lb_fw->payload, fw->data, lb_fw->payload_size);
|
|
release_firmware(fw);
|
|
INIT_WORK(&lb_fw->work, xe_late_bind_work);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xe_late_bind_fw_init(struct xe_late_bind *late_bind)
|
|
{
|
|
int ret;
|
|
int fw_id;
|
|
|
|
late_bind->wq = alloc_ordered_workqueue("late-bind-ordered-wq", 0);
|
|
if (!late_bind->wq)
|
|
return -ENOMEM;
|
|
|
|
for (fw_id = 0; fw_id < XE_LB_FW_MAX_ID; fw_id++) {
|
|
ret = __xe_late_bind_fw_init(late_bind, fw_id);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xe_late_bind_component_bind(struct device *xe_kdev,
|
|
struct device *mei_kdev, void *data)
|
|
{
|
|
struct xe_device *xe = kdev_to_xe_device(xe_kdev);
|
|
struct xe_late_bind *late_bind = &xe->late_bind;
|
|
|
|
late_bind->component.ops = data;
|
|
late_bind->component.mei_dev = mei_kdev;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void xe_late_bind_component_unbind(struct device *xe_kdev,
|
|
struct device *mei_kdev, void *data)
|
|
{
|
|
struct xe_device *xe = kdev_to_xe_device(xe_kdev);
|
|
struct xe_late_bind *late_bind = &xe->late_bind;
|
|
|
|
xe_late_bind_wait_for_worker_completion(late_bind);
|
|
|
|
late_bind->component.ops = NULL;
|
|
}
|
|
|
|
static const struct component_ops xe_late_bind_component_ops = {
|
|
.bind = xe_late_bind_component_bind,
|
|
.unbind = xe_late_bind_component_unbind,
|
|
};
|
|
|
|
static void xe_late_bind_remove(void *arg)
|
|
{
|
|
struct xe_late_bind *late_bind = arg;
|
|
struct xe_device *xe = late_bind_to_xe(late_bind);
|
|
|
|
xe_late_bind_wait_for_worker_completion(late_bind);
|
|
|
|
late_bind->component_added = false;
|
|
|
|
component_del(xe->drm.dev, &xe_late_bind_component_ops);
|
|
if (late_bind->wq) {
|
|
destroy_workqueue(late_bind->wq);
|
|
late_bind->wq = NULL;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* xe_late_bind_init() - add xe mei late binding component
|
|
* @late_bind: pointer to late bind structure.
|
|
*
|
|
* Return: 0 if the initialization was successful, a negative errno otherwise.
|
|
*/
|
|
int xe_late_bind_init(struct xe_late_bind *late_bind)
|
|
{
|
|
struct xe_device *xe = late_bind_to_xe(late_bind);
|
|
int err;
|
|
|
|
if (!xe->info.has_late_bind)
|
|
return 0;
|
|
|
|
if (!IS_ENABLED(CONFIG_INTEL_MEI_LB) || !IS_ENABLED(CONFIG_INTEL_MEI_GSC)) {
|
|
drm_info(&xe->drm, "Can't init xe mei late bind missing mei component\n");
|
|
return 0;
|
|
}
|
|
|
|
err = component_add_typed(xe->drm.dev, &xe_late_bind_component_ops,
|
|
INTEL_COMPONENT_LB);
|
|
if (err < 0) {
|
|
drm_err(&xe->drm, "Failed to add mei late bind component (%pe)\n", ERR_PTR(err));
|
|
return err;
|
|
}
|
|
|
|
late_bind->component_added = true;
|
|
|
|
err = devm_add_action_or_reset(xe->drm.dev, xe_late_bind_remove, late_bind);
|
|
if (err)
|
|
return err;
|
|
|
|
err = xe_late_bind_fw_init(late_bind);
|
|
if (err)
|
|
return err;
|
|
|
|
return xe_late_bind_fw_load(late_bind);
|
|
}
|