root/drivers/gpu/drm/xe/xe_uc_fw.c
// SPDX-License-Identifier: MIT
/*
 * Copyright © 2022 Intel Corporation
 */

#include <linux/bitfield.h>
#include <linux/fault-inject.h>
#include <linux/firmware.h>

#include <drm/drm_managed.h>

#include "regs/xe_guc_regs.h"
#include "xe_bo.h"
#include "xe_device_types.h"
#include "xe_force_wake.h"
#include "xe_gsc.h"
#include "xe_gt_printk.h"
#include "xe_gt_sriov_vf.h"
#include "xe_gt_types.h"
#include "xe_guc.h"
#include "xe_map.h"
#include "xe_mmio.h"
#include "xe_module.h"
#include "xe_sriov.h"
#include "xe_uc_fw.h"

/*
 * List of required GuC and HuC binaries per-platform. They must be ordered
 * based on platform, from newer to older.
 *
 * Versioning follows the guidelines from
 * Documentation/driver-api/firmware/firmware-usage-guidelines.rst. There is a
 * distinction for platforms being officially supported by the driver or not.
 * Platforms not available publicly or not yet officially supported by the
 * driver (under force-probe), use the mmp_ver(): the firmware autoselect logic
 * will select the firmware from disk with filename that matches the full
 * "mpp version", i.e. major.minor.patch. mmp_ver() should only be used for
 * this case.
 *
 * For platforms officially supported by the driver, the filename always only
 * ever contains the major version (GuC) or no version at all (HuC).
 *
 * After loading the file, the driver parses the versions embedded in the blob.
 * The major version needs to match a major version supported by the driver (if
 * any). The minor version is also checked and a notice emitted to the log if
 * the version found is smaller than the version wanted. This is done only for
 * informational purposes so users may have a chance to upgrade, but the driver
 * still loads and use the older firmware.
 *
 * Examples:
 *
 *      1) Platform officially supported by i915 - using Tigerlake as example.
 *         Driver loads the following firmware blobs from disk:
 *
 *              - i915/tgl_guc_<major>.bin
 *              - i915/tgl_huc.bin
 *
 *         <major> number for GuC is checked that it matches the version inside
 *         the blob. <minor> version is checked and if smaller than the expected
 *         an info message is emitted about that.
 *
 *      1) XE_<FUTUREINTELPLATFORM>, still under require_force_probe. Using
 *         "wipplat" as a short-name. Driver loads the following firmware blobs
 *         from disk:
 *
 *              - xe/wipplat_guc_<major>.<minor>.<patch>.bin
 *              - xe/wipplat_huc_<major>.<minor>.<patch>.bin
 *
 *         <major> and <minor> are checked that they match the version inside
 *         the blob. Both of them need to match exactly what the driver is
 *         expecting, otherwise it fails.
 *
 *      3) Platform officially supported by xe and out of force-probe. Using
 *         "plat" as a short-name. Except for the different directory, the
 *         behavior is the same as (1). Driver loads the following firmware
 *         blobs from disk:
 *
 *              - xe/plat_guc_<major>.bin
 *              - xe/plat_huc.bin
 *
 *         <major> number for GuC is checked that it matches the version inside
 *         the blob. <minor> version is checked and if smaller than the expected
 *         an info message is emitted about that.
 *
 * For the platforms already released with a major version, they should never be
 * removed from the table. Instead new entries with newer versions may be added
 * before them, so they take precedence.
 *
 * TODO: Currently there's no fallback on major version. That's because xe
 * driver only supports the one major version of each firmware in the table.
 * This needs to be fixed when the major version of GuC is updated.
 */

struct uc_fw_entry {
        enum xe_platform platform;
        enum xe_gt_type gt_type;

        struct {
                const char *path;
                u16 major;
                u16 minor;
                u16 patch;
                bool full_ver_required;
        };
};

struct fw_blobs_by_type {
        const struct uc_fw_entry *entries;
        u32 count;
};

/*
 * Add an "ANY" define just to convey the meaning it's given here.
 */
#define XE_GT_TYPE_ANY XE_GT_TYPE_UNINITIALIZED

#define XE_GUC_FIRMWARE_DEFS(fw_def, mmp_ver, major_ver)                                        \
        fw_def(NOVALAKE_S,      GT_TYPE_ANY,    mmp_ver(xe,     guc,    nvl,    70, 55, 4))     \
        fw_def(PANTHERLAKE,     GT_TYPE_ANY,    major_ver(xe,   guc,    ptl,    70, 54, 0))     \
        fw_def(BATTLEMAGE,      GT_TYPE_ANY,    major_ver(xe,   guc,    bmg,    70, 54, 0))     \
        fw_def(LUNARLAKE,       GT_TYPE_ANY,    major_ver(xe,   guc,    lnl,    70, 53, 0))     \
        fw_def(METEORLAKE,      GT_TYPE_ANY,    major_ver(i915, guc,    mtl,    70, 53, 0))     \
        fw_def(DG2,             GT_TYPE_ANY,    major_ver(i915, guc,    dg2,    70, 53, 0))     \
        fw_def(DG1,             GT_TYPE_ANY,    major_ver(i915, guc,    dg1,    70, 44, 1))     \
        fw_def(ALDERLAKE_N,     GT_TYPE_ANY,    major_ver(i915, guc,    tgl,    70, 44, 1))     \
        fw_def(ALDERLAKE_P,     GT_TYPE_ANY,    major_ver(i915, guc,    adlp,   70, 44, 1))     \
        fw_def(ALDERLAKE_S,     GT_TYPE_ANY,    major_ver(i915, guc,    tgl,    70, 44, 1))     \
        fw_def(ROCKETLAKE,      GT_TYPE_ANY,    major_ver(i915, guc,    tgl,    70, 44, 1))     \
        fw_def(TIGERLAKE,       GT_TYPE_ANY,    major_ver(i915, guc,    tgl,    70, 44, 1))

#define XE_HUC_FIRMWARE_DEFS(fw_def, mmp_ver, no_ver)           \
        fw_def(PANTHERLAKE,     GT_TYPE_ANY,    no_ver(xe,      huc,            ptl))           \
        fw_def(BATTLEMAGE,      GT_TYPE_ANY,    no_ver(xe,      huc,            bmg))           \
        fw_def(LUNARLAKE,       GT_TYPE_ANY,    no_ver(xe,      huc,            lnl))           \
        fw_def(METEORLAKE,      GT_TYPE_ANY,    no_ver(i915,    huc_gsc,        mtl))           \
        fw_def(DG1,             GT_TYPE_ANY,    no_ver(i915,    huc,            dg1))           \
        fw_def(ALDERLAKE_P,     GT_TYPE_ANY,    no_ver(i915,    huc,            tgl))           \
        fw_def(ALDERLAKE_S,     GT_TYPE_ANY,    no_ver(i915,    huc,            tgl))           \
        fw_def(ROCKETLAKE,      GT_TYPE_ANY,    no_ver(i915,    huc,            tgl))           \
        fw_def(TIGERLAKE,       GT_TYPE_ANY,    no_ver(i915,    huc,            tgl))

/* for the GSC FW we match the compatibility version and not the release one */
#define XE_GSC_FIRMWARE_DEFS(fw_def, major_ver)         \
        fw_def(PANTHERLAKE,     GT_TYPE_ANY,    major_ver(xe,   gsc,    ptl,    105, 1, 0))     \
        fw_def(LUNARLAKE,       GT_TYPE_ANY,    major_ver(xe,   gsc,    lnl,    104, 1, 0))     \
        fw_def(METEORLAKE,      GT_TYPE_ANY,    major_ver(i915, gsc,    mtl,    102, 1, 0))

#define MAKE_FW_PATH(dir__, uc__, shortname__, version__)                       \
        __stringify(dir__) "/" __stringify(shortname__) "_" __stringify(uc__) version__ ".bin"

#define fw_filename_mmp_ver(dir_, uc_, shortname_, a, b, c)                     \
        MAKE_FW_PATH(dir_, uc_, shortname_, "_" __stringify(a ## . ## b ## . ## c))
#define fw_filename_major_ver(dir_, uc_, shortname_, a, b, c)                   \
        MAKE_FW_PATH(dir_, uc_, shortname_, "_" __stringify(a))
#define fw_filename_no_ver(dir_, uc_, shortname_)                               \
        MAKE_FW_PATH(dir_, uc_, shortname_, "")
#define fw_filename_gsc(dir_, uc_, shortname_, a, b, c)                         \
        MAKE_FW_PATH(dir_, uc_, shortname_, "_" __stringify(b))

#define uc_fw_entry_mmp_ver(dir_, uc_, shortname_, a, b, c)                     \
        { fw_filename_mmp_ver(dir_, uc_, shortname_, a, b, c),                  \
          a, b, c, true }
#define uc_fw_entry_major_ver(dir_, uc_, shortname_, a, b, c)                   \
        { fw_filename_major_ver(dir_, uc_, shortname_, a, b, c),                \
          a, b, c }
#define uc_fw_entry_no_ver(dir_, uc_, shortname_)                               \
        { fw_filename_no_ver(dir_, uc_, shortname_),                            \
          0, 0 }
#define uc_fw_entry_gsc(dir_, uc_, shortname_, a, b, c)                         \
        { fw_filename_gsc(dir_, uc_, shortname_, a, b, c),                      \
          a, b, c }

/* All blobs need to be declared via MODULE_FIRMWARE() */
#define XE_UC_MODULE_FIRMWARE(platform__, gt_type__, fw_filename)               \
        MODULE_FIRMWARE(fw_filename);

#define XE_UC_FW_ENTRY(platform__, gt_type__, entry__)                          \
        {                                                                       \
                .platform = XE_ ## platform__,                                  \
                .gt_type = XE_ ## gt_type__,                                    \
                entry__,                                                        \
        },

XE_GUC_FIRMWARE_DEFS(XE_UC_MODULE_FIRMWARE,
                     fw_filename_mmp_ver, fw_filename_major_ver)
XE_HUC_FIRMWARE_DEFS(XE_UC_MODULE_FIRMWARE,
                     fw_filename_mmp_ver, fw_filename_no_ver)
XE_GSC_FIRMWARE_DEFS(XE_UC_MODULE_FIRMWARE, fw_filename_gsc)

static struct xe_gt *
__uc_fw_to_gt(struct xe_uc_fw *uc_fw, enum xe_uc_fw_type type)
{
        XE_WARN_ON(type >= XE_UC_FW_NUM_TYPES);

        switch (type) {
        case XE_UC_FW_TYPE_GUC:
                return container_of(uc_fw, struct xe_gt, uc.guc.fw);
        case XE_UC_FW_TYPE_HUC:
                return container_of(uc_fw, struct xe_gt, uc.huc.fw);
        case XE_UC_FW_TYPE_GSC:
                return container_of(uc_fw, struct xe_gt, uc.gsc.fw);
        default:
                return NULL;
        }
}

static struct xe_gt *uc_fw_to_gt(struct xe_uc_fw *uc_fw)
{
        return __uc_fw_to_gt(uc_fw, uc_fw->type);
}

static struct xe_device *uc_fw_to_xe(struct xe_uc_fw *uc_fw)
{
        return gt_to_xe(uc_fw_to_gt(uc_fw));
}

static void
uc_fw_auto_select(struct xe_device *xe, struct xe_uc_fw *uc_fw)
{
        static const struct uc_fw_entry entries_guc[] = {
                XE_GUC_FIRMWARE_DEFS(XE_UC_FW_ENTRY,
                                     uc_fw_entry_mmp_ver,
                                     uc_fw_entry_major_ver)
        };
        static const struct uc_fw_entry entries_huc[] = {
                XE_HUC_FIRMWARE_DEFS(XE_UC_FW_ENTRY,
                                     uc_fw_entry_mmp_ver,
                                     uc_fw_entry_no_ver)
        };
        static const struct uc_fw_entry entries_gsc[] = {
                XE_GSC_FIRMWARE_DEFS(XE_UC_FW_ENTRY, uc_fw_entry_gsc)
        };
        static const struct fw_blobs_by_type blobs_all[XE_UC_FW_NUM_TYPES] = {
                [XE_UC_FW_TYPE_GUC] = { entries_guc, ARRAY_SIZE(entries_guc) },
                [XE_UC_FW_TYPE_HUC] = { entries_huc, ARRAY_SIZE(entries_huc) },
                [XE_UC_FW_TYPE_GSC] = { entries_gsc, ARRAY_SIZE(entries_gsc) },
        };
        struct xe_gt *gt = uc_fw_to_gt(uc_fw);
        enum xe_platform p = xe->info.platform;
        const struct uc_fw_entry *entries;
        u32 count;
        int i;

        xe_gt_assert(gt, uc_fw->type < ARRAY_SIZE(blobs_all));
        xe_gt_assert(gt, gt->info.type != XE_GT_TYPE_UNINITIALIZED);

        entries = blobs_all[uc_fw->type].entries;
        count = blobs_all[uc_fw->type].count;

        for (i = 0; i < count && p <= entries[i].platform; i++) {
                if (p != entries[i].platform)
                        continue;

                if (entries[i].gt_type != XE_GT_TYPE_ANY &&
                    entries[i].gt_type != gt->info.type)
                        continue;

                uc_fw->path = entries[i].path;
                uc_fw->versions.wanted.major = entries[i].major;
                uc_fw->versions.wanted.minor = entries[i].minor;
                uc_fw->versions.wanted.patch = entries[i].patch;
                uc_fw->full_ver_required = entries[i].full_ver_required;

                if (uc_fw->type == XE_UC_FW_TYPE_GSC)
                        uc_fw->versions.wanted_type = XE_UC_FW_VER_COMPATIBILITY;
                else
                        uc_fw->versions.wanted_type = XE_UC_FW_VER_RELEASE;

                break;
        }
}

static void
uc_fw_override(struct xe_uc_fw *uc_fw)
{
        char *path_override = NULL;

        /* empty string disables, but it's not allowed for GuC */
        switch (uc_fw->type) {
        case XE_UC_FW_TYPE_GUC:
                if (xe_modparam.guc_firmware_path && *xe_modparam.guc_firmware_path)
                        path_override = xe_modparam.guc_firmware_path;
                break;
        case XE_UC_FW_TYPE_HUC:
                path_override = xe_modparam.huc_firmware_path;
                break;
        case XE_UC_FW_TYPE_GSC:
                path_override = xe_modparam.gsc_firmware_path;
                break;
        default:
                break;
        }

        if (path_override) {
                uc_fw->path = path_override;
                uc_fw->user_overridden = true;
        }
}

/**
 * xe_uc_fw_copy_rsa - copy fw RSA to buffer
 *
 * @uc_fw: uC firmware
 * @dst: dst buffer
 * @max_len: max number of bytes to copy
 *
 * Return: number of copied bytes.
 */
size_t xe_uc_fw_copy_rsa(struct xe_uc_fw *uc_fw, void *dst, u32 max_len)
{
        struct xe_device *xe = uc_fw_to_xe(uc_fw);
        u32 size = min_t(u32, uc_fw->rsa_size, max_len);

        xe_assert(xe, !(size % 4));
        xe_assert(xe, xe_uc_fw_is_available(uc_fw));

        xe_map_memcpy_from(xe, dst, &uc_fw->bo->vmap,
                           xe_uc_fw_rsa_offset(uc_fw), size);

        return size;
}

static void uc_fw_fini(struct drm_device *drm, void *arg)
{
        struct xe_uc_fw *uc_fw = arg;

        if (!xe_uc_fw_is_available(uc_fw))
                return;

        xe_uc_fw_change_status(uc_fw, XE_UC_FIRMWARE_SELECTED);
}

static int guc_read_css_info(struct xe_uc_fw *uc_fw, struct uc_css_guc_info *guc_info)
{
        struct xe_gt *gt = uc_fw_to_gt(uc_fw);
        struct xe_uc_fw_version *release = &uc_fw->versions.found[XE_UC_FW_VER_RELEASE];
        struct xe_uc_fw_version *compatibility = &uc_fw->versions.found[XE_UC_FW_VER_COMPATIBILITY];

        xe_gt_assert(gt, uc_fw->type == XE_UC_FW_TYPE_GUC);

        /* We don't support GuC releases older than 70.29.2 */
        if (MAKE_GUC_VER_STRUCT(*release) < MAKE_GUC_VER(70, 29, 2)) {
                xe_gt_err(gt, "Unsupported GuC v%u.%u.%u! v70.29.2 or newer is required\n",
                          release->major, release->minor, release->patch);
                return -EINVAL;
        }

        compatibility->major = FIELD_GET(CSS_SW_VERSION_UC_MAJOR, guc_info->submission_version);
        compatibility->minor = FIELD_GET(CSS_SW_VERSION_UC_MINOR, guc_info->submission_version);
        compatibility->patch = FIELD_GET(CSS_SW_VERSION_UC_PATCH, guc_info->submission_version);

        uc_fw->build_type = FIELD_GET(CSS_UKERNEL_INFO_BUILDTYPE, guc_info->ukernel_info);
        uc_fw->private_data_size = guc_info->private_data_size;

        return 0;
}

int xe_uc_fw_check_version_requirements(struct xe_uc_fw *uc_fw)
{
        struct xe_device *xe = uc_fw_to_xe(uc_fw);
        struct xe_uc_fw_version *wanted = &uc_fw->versions.wanted;
        struct xe_uc_fw_version *found = &uc_fw->versions.found[uc_fw->versions.wanted_type];

        /* Driver has no requirement on any version, any is good. */
        if (!wanted->major)
                return 0;

        /*
         * If full version is required, both major and minor should match.
         * Otherwise, at least the major version.
         */
        if (wanted->major != found->major ||
            (uc_fw->full_ver_required &&
             ((wanted->minor != found->minor) ||
              (wanted->patch != found->patch)))) {
                drm_notice(&xe->drm, "%s firmware %s: unexpected version: %u.%u.%u != %u.%u.%u\n",
                           xe_uc_fw_type_repr(uc_fw->type), uc_fw->path,
                           found->major, found->minor, found->patch,
                           wanted->major, wanted->minor, wanted->patch);
                goto fail;
        }

        if (wanted->minor > found->minor ||
            (wanted->minor == found->minor && wanted->patch > found->patch)) {
                drm_notice(&xe->drm, "%s firmware (%u.%u.%u) is recommended, but only (%u.%u.%u) was found in %s\n",
                           xe_uc_fw_type_repr(uc_fw->type),
                           wanted->major, wanted->minor, wanted->patch,
                           found->major, found->minor, found->patch,
                           uc_fw->path);
                drm_info(&xe->drm, "Consider updating your linux-firmware pkg or downloading from %s\n",
                         XE_UC_FIRMWARE_URL);
        }

        return 0;

fail:
        if (xe_uc_fw_is_overridden(uc_fw))
                return 0;

        return -ENOEXEC;
}

/* Refer to the "CSS-based Firmware Layout" documentation entry for details */
static int parse_css_header(struct xe_uc_fw *uc_fw, const void *fw_data, size_t fw_size)
{
        struct xe_device *xe = uc_fw_to_xe(uc_fw);
        struct xe_uc_fw_version *release = &uc_fw->versions.found[XE_UC_FW_VER_RELEASE];
        struct uc_css_header *css;
        size_t size;

        /* Check the size of the blob before examining buffer contents */
        if (unlikely(fw_size < sizeof(struct uc_css_header))) {
                drm_warn(&xe->drm, "%s firmware %s: invalid size: %zu < %zu\n",
                         xe_uc_fw_type_repr(uc_fw->type), uc_fw->path,
                         fw_size, sizeof(struct uc_css_header));
                return -ENODATA;
        }

        css = (struct uc_css_header *)fw_data;

        /* Check integrity of size values inside CSS header */
        size = (css->header_size_dw - css->rsa_info.key_size_dw - css->rsa_info.modulus_size_dw -
                css->rsa_info.exponent_size_dw) * sizeof(u32);
        if (unlikely(size != sizeof(struct uc_css_header))) {
                drm_warn(&xe->drm,
                         "%s firmware %s: unexpected header size: %zu != %zu\n",
                         xe_uc_fw_type_repr(uc_fw->type), uc_fw->path,
                         fw_size, sizeof(struct uc_css_header));
                return -EPROTO;
        }

        /* uCode size must calculated from other sizes */
        uc_fw->ucode_size = (css->size_dw - css->header_size_dw) * sizeof(u32);

        /* now RSA */
        uc_fw->rsa_size = css->rsa_info.key_size_dw * sizeof(u32);

        /* At least, it should have header, uCode and RSA. Size of all three. */
        size = sizeof(struct uc_css_header) + uc_fw->ucode_size +
                uc_fw->rsa_size;
        if (unlikely(fw_size < size)) {
                drm_warn(&xe->drm, "%s firmware %s: invalid size: %zu < %zu\n",
                         xe_uc_fw_type_repr(uc_fw->type), uc_fw->path,
                         fw_size, size);
                return -ENOEXEC;
        }

        /* Get version numbers from the CSS header */
        release->major = FIELD_GET(CSS_SW_VERSION_UC_MAJOR, css->guc_info.sw_version);
        release->minor = FIELD_GET(CSS_SW_VERSION_UC_MINOR, css->guc_info.sw_version);
        release->patch = FIELD_GET(CSS_SW_VERSION_UC_PATCH, css->guc_info.sw_version);

        if (uc_fw->type == XE_UC_FW_TYPE_GUC)
                return guc_read_css_info(uc_fw, &css->guc_info);

        return 0;
}

static bool is_cpd_header(const void *data)
{
        const u32 *marker = data;

        return *marker == GSC_CPD_HEADER_MARKER;
}

static u32 entry_offset(const struct gsc_cpd_header_v2 *header, const char *name)
{
        const struct gsc_cpd_entry *entry;
        int i;

        entry = (void *)header + header->header_length;

        for (i = 0; i < header->num_of_entries; i++, entry++)
                if (strcmp(entry->name, name) == 0)
                        return entry->offset & GSC_CPD_ENTRY_OFFSET_MASK;

        return 0;
}

/* Refer to the "GSC-based Firmware Layout" documentation entry for details */
static int parse_cpd_header(struct xe_uc_fw *uc_fw, const void *data, size_t size,
                            const char *manifest_entry, const char *css_entry)
{
        struct xe_gt *gt = uc_fw_to_gt(uc_fw);
        struct xe_device *xe = gt_to_xe(gt);
        const struct gsc_cpd_header_v2 *header = data;
        struct xe_uc_fw_version *release = &uc_fw->versions.found[XE_UC_FW_VER_RELEASE];
        const struct gsc_manifest_header *manifest;
        size_t min_size = sizeof(*header);
        u32 offset;

        /* manifest_entry is mandatory, css_entry is optional */
        xe_assert(xe, manifest_entry);

        if (size < min_size || !is_cpd_header(header))
                return -ENOENT;

        if (header->header_length < sizeof(struct gsc_cpd_header_v2)) {
                xe_gt_err(gt, "invalid CPD header length %u!\n", header->header_length);
                return -EINVAL;
        }

        min_size = header->header_length + sizeof(struct gsc_cpd_entry) * header->num_of_entries;
        if (size < min_size) {
                xe_gt_err(gt, "FW too small! %zu < %zu\n", size, min_size);
                return -ENODATA;
        }

        /* Look for the manifest first */
        offset = entry_offset(header, manifest_entry);
        if (!offset) {
                xe_gt_err(gt, "Failed to find %s manifest!\n",
                          xe_uc_fw_type_repr(uc_fw->type));
                return -ENODATA;
        }

        min_size = offset + sizeof(struct gsc_manifest_header);
        if (size < min_size) {
                xe_gt_err(gt, "FW too small! %zu < %zu\n", size, min_size);
                return -ENODATA;
        }

        manifest = data + offset;

        release->major = manifest->fw_version.major;
        release->minor = manifest->fw_version.minor;
        release->patch = manifest->fw_version.hotfix;

        if (uc_fw->type == XE_UC_FW_TYPE_GSC) {
                struct xe_gsc *gsc = container_of(uc_fw, struct xe_gsc, fw);

                release->build = manifest->fw_version.build;
                gsc->security_version = manifest->security_version;
        }

        /* then optionally look for the css header */
        if (css_entry) {
                int ret;

                /*
                 * This section does not contain a CSS entry on DG2. We
                 * don't support DG2 HuC right now, so no need to handle
                 * it, just add a reminder in case that changes.
                 */
                xe_assert(xe, xe->info.platform != XE_DG2);

                offset = entry_offset(header, css_entry);

                /* the CSS header parser will check that the CSS header fits */
                if (offset > size) {
                        xe_gt_err(gt, "FW too small! %zu < %u\n", size, offset);
                        return -ENODATA;
                }

                ret = parse_css_header(uc_fw, data + offset, size - offset);
                if (ret)
                        return ret;

                uc_fw->css_offset = offset;
        }

        uc_fw->has_gsc_headers = true;

        return 0;
}

static int parse_gsc_layout(struct xe_uc_fw *uc_fw, const void *data, size_t size)
{
        struct xe_gt *gt = uc_fw_to_gt(uc_fw);
        const struct gsc_layout_pointers *layout = data;
        const struct gsc_bpdt_header *bpdt_header = NULL;
        const struct gsc_bpdt_entry *bpdt_entry = NULL;
        size_t min_size = sizeof(*layout);
        int i;

        if (size < min_size) {
                xe_gt_err(gt, "GSC FW too small! %zu < %zu\n", size, min_size);
                return -ENODATA;
        }

        min_size = layout->boot1.offset + layout->boot1.size;
        if (size < min_size) {
                xe_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) {
                xe_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 != GSC_BPDT_HEADER_SIGNATURE) {
                xe_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) {
                xe_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 & GSC_BPDT_ENTRY_TYPE_MASK) !=
                    GSC_BPDT_ENTRY_TYPE_GSC_RBE)
                        continue;

                min_size = bpdt_entry->sub_partition_offset;

                /* the CPD header parser will check that the CPD header fits */
                if (layout->boot1.size < min_size) {
                        xe_gt_err(gt, "GSC FW boot section too small for CPD offset: %u < %zu\n",
                                  layout->boot1.size, min_size);
                        return -ENODATA;
                }

                return parse_cpd_header(uc_fw,
                                        (void *)bpdt_header + min_size,
                                        layout->boot1.size - min_size,
                                        "RBEP.man", NULL);
        }

        xe_gt_err(gt, "couldn't find CPD header in GSC binary!\n");
        return -ENODATA;
}

static int parse_headers(struct xe_uc_fw *uc_fw, const struct firmware *fw)
{
        int ret;

        /*
         * All GuC releases and older HuC ones use CSS headers, while newer HuC
         * releases use GSC CPD headers.
         */
        switch (uc_fw->type) {
        case XE_UC_FW_TYPE_GSC:
                return parse_gsc_layout(uc_fw, fw->data, fw->size);
        case XE_UC_FW_TYPE_HUC:
                ret = parse_cpd_header(uc_fw, fw->data, fw->size, "HUCP.man", "huc_fw");
                if (!ret || ret != -ENOENT)
                        return ret;
                fallthrough;
        case XE_UC_FW_TYPE_GUC:
                return parse_css_header(uc_fw, fw->data, fw->size);
        default:
                return -EINVAL;
        }

        return 0;
}

#define print_uc_fw_version(p_, version_, prefix_, ...) \
do { \
        struct xe_uc_fw_version *ver_ = (version_); \
        if (ver_->build) \
                drm_printf(p_, prefix_ " version %u.%u.%u.%u\n", ##__VA_ARGS__, \
                           ver_->major, ver_->minor, \
                           ver_->patch, ver_->build); \
        else \
                drm_printf(p_, prefix_ " version %u.%u.%u\n", ##__VA_ARGS__, \
                          ver_->major, ver_->minor, ver_->patch); \
} while (0)

static void uc_fw_vf_override(struct xe_uc_fw *uc_fw)
{
        struct xe_uc_fw_version *compat = &uc_fw->versions.found[XE_UC_FW_VER_COMPATIBILITY];
        struct xe_uc_fw_version *wanted = &uc_fw->versions.wanted;

        /* Only GuC/HuC are supported */
        if (uc_fw->type != XE_UC_FW_TYPE_GUC && uc_fw->type != XE_UC_FW_TYPE_HUC)
                uc_fw->path = NULL;

        /* VF will support only firmwares that driver can autoselect */
        xe_uc_fw_change_status(uc_fw, uc_fw->path ?
                               XE_UC_FIRMWARE_PRELOADED :
                               XE_UC_FIRMWARE_NOT_SUPPORTED);

        if (!xe_uc_fw_is_supported(uc_fw))
                return;

        /* PF is doing the loading, so we don't need a path on the VF */
        uc_fw->path = "Loaded by PF";

        /* The GuC versions are set up during the VF bootstrap */
        if (uc_fw->type == XE_UC_FW_TYPE_GUC) {
                uc_fw->versions.wanted_type = XE_UC_FW_VER_COMPATIBILITY;
                xe_gt_sriov_vf_guc_versions(uc_fw_to_gt(uc_fw), wanted, compat);
        }
}

static int uc_fw_request(struct xe_uc_fw *uc_fw, const struct firmware **firmware_p)
{
        struct xe_device *xe = uc_fw_to_xe(uc_fw);
        struct xe_gt *gt = uc_fw_to_gt(uc_fw);
        struct drm_printer p = xe_gt_info_printer(gt);
        struct device *dev = xe->drm.dev;
        const struct firmware *fw = NULL;
        int err;

        /*
         * we use FIRMWARE_UNINITIALIZED to detect checks against uc_fw->status
         * before we're looked at the HW caps to see if we have uc support
         */
        BUILD_BUG_ON(XE_UC_FIRMWARE_UNINITIALIZED);
        xe_gt_assert(gt, !uc_fw->status);
        xe_gt_assert(gt, !uc_fw->path);

        uc_fw_auto_select(xe, uc_fw);

        if (IS_SRIOV_VF(xe)) {
                uc_fw_vf_override(uc_fw);
                return 0;
        }

        uc_fw_override(uc_fw);

        xe_uc_fw_change_status(uc_fw, uc_fw->path ?
                               XE_UC_FIRMWARE_SELECTED :
                               XE_UC_FIRMWARE_NOT_SUPPORTED);

        if (!xe_uc_fw_is_supported(uc_fw)) {
                if (uc_fw->type == XE_UC_FW_TYPE_GUC) {
                        xe_gt_err(gt, "No GuC firmware defined for platform\n");
                        return -ENOENT;
                }
                return 0;
        }

        /* an empty path means the firmware is disabled */
        if (!xe_device_uc_enabled(xe) || !(*uc_fw->path)) {
                xe_uc_fw_change_status(uc_fw, XE_UC_FIRMWARE_DISABLED);
                xe_gt_dbg(gt, "%s disabled\n", xe_uc_fw_type_repr(uc_fw->type));
                return 0;
        }

        err = firmware_request_nowarn(&fw, uc_fw->path, dev);
        if (err)
                goto fail;

        err = parse_headers(uc_fw, fw);
        if (err)
                goto fail;

        print_uc_fw_version(&p,
                            &uc_fw->versions.found[XE_UC_FW_VER_RELEASE],
                            "Using %s firmware from %s",
                            xe_uc_fw_type_repr(uc_fw->type), uc_fw->path);

        /* for GSC FW we want the compatibility version, which we query after load */
        if (uc_fw->type != XE_UC_FW_TYPE_GSC) {
                err = xe_uc_fw_check_version_requirements(uc_fw);
                if (err)
                        goto fail;
        }

        *firmware_p = fw;

        return 0;

fail:
        xe_uc_fw_change_status(uc_fw, err == -ENOENT ?
                               XE_UC_FIRMWARE_MISSING :
                               XE_UC_FIRMWARE_ERROR);

        if (err == -ENOENT)
                xe_gt_info(gt, "%s firmware %s not found\n",
                           xe_uc_fw_type_repr(uc_fw->type), uc_fw->path);
        else
                xe_gt_notice(gt, "%s firmware %s: fetch failed with error %pe\n",
                             xe_uc_fw_type_repr(uc_fw->type), uc_fw->path, ERR_PTR(err));
        xe_gt_info(gt, "%s firmware(s) can be downloaded from %s\n",
                   xe_uc_fw_type_repr(uc_fw->type), XE_UC_FIRMWARE_URL);

        release_firmware(fw);           /* OK even if fw is NULL */

        return err;
}

static void uc_fw_release(const struct firmware *fw)
{
        release_firmware(fw);
}

static int uc_fw_copy(struct xe_uc_fw *uc_fw, const void *data, size_t size, u32 flags)
{
        struct xe_device *xe = uc_fw_to_xe(uc_fw);
        struct xe_gt *gt = uc_fw_to_gt(uc_fw);
        struct xe_tile *tile = gt_to_tile(gt);
        struct xe_bo *obj;
        int err;

        obj = xe_managed_bo_create_from_data(xe, tile, data, size, flags);
        if (IS_ERR(obj)) {
                drm_notice(&xe->drm, "%s firmware %s: failed to create / populate bo",
                           xe_uc_fw_type_repr(uc_fw->type), uc_fw->path);
                err = PTR_ERR(obj);
                goto fail;
        }

        uc_fw->bo = obj;
        uc_fw->size = size;

        xe_uc_fw_change_status(uc_fw, XE_UC_FIRMWARE_AVAILABLE);

        err = drmm_add_action_or_reset(&xe->drm, uc_fw_fini, uc_fw);
        if (err)
                goto fail;

        return 0;

fail:
        xe_uc_fw_change_status(uc_fw, XE_UC_FIRMWARE_ERROR);
        drm_notice(&xe->drm, "%s firmware %s: copy failed with error %d\n",
                   xe_uc_fw_type_repr(uc_fw->type), uc_fw->path, err);

        return err;
}

int xe_uc_fw_init(struct xe_uc_fw *uc_fw)
{
        const struct firmware *fw = NULL;
        int err;

        err = uc_fw_request(uc_fw, &fw);
        if (err)
                return err;

        /* no error and no firmware means nothing to copy */
        if (!fw)
                return 0;

        err = uc_fw_copy(uc_fw, fw->data, fw->size,
                         XE_BO_FLAG_SYSTEM | XE_BO_FLAG_GGTT |
                         XE_BO_FLAG_GGTT_INVALIDATE);

        uc_fw_release(fw);

        return err;
}
ALLOW_ERROR_INJECTION(xe_uc_fw_init, ERRNO); /* See xe_pci_probe() */

static u32 uc_fw_ggtt_offset(struct xe_uc_fw *uc_fw)
{
        return xe_bo_ggtt_addr(uc_fw->bo);
}

static int uc_fw_xfer(struct xe_uc_fw *uc_fw, u32 offset, u32 dma_flags)
{
        struct xe_device *xe = uc_fw_to_xe(uc_fw);
        struct xe_gt *gt = uc_fw_to_gt(uc_fw);
        struct xe_mmio *mmio = &gt->mmio;
        u64 src_offset;
        u32 dma_ctrl;
        int ret;

        xe_force_wake_assert_held(gt_to_fw(gt), XE_FW_GT);

        /* Set the source address for the uCode */
        src_offset = uc_fw_ggtt_offset(uc_fw) + uc_fw->css_offset;
        xe_mmio_write32(mmio, DMA_ADDR_0_LOW, lower_32_bits(src_offset));
        xe_mmio_write32(mmio, DMA_ADDR_0_HIGH,
                        upper_32_bits(src_offset) | DMA_ADDRESS_SPACE_GGTT);

        /* Set the DMA destination */
        xe_mmio_write32(mmio, DMA_ADDR_1_LOW, offset);
        xe_mmio_write32(mmio, DMA_ADDR_1_HIGH, DMA_ADDRESS_SPACE_WOPCM);

        /*
         * Set the transfer size. The header plus uCode will be copied to WOPCM
         * via DMA, excluding any other components
         */
        xe_mmio_write32(mmio, DMA_COPY_SIZE,
                        sizeof(struct uc_css_header) + uc_fw->ucode_size);

        /* Start the DMA */
        xe_mmio_write32(mmio, DMA_CTRL,
                        _MASKED_BIT_ENABLE(dma_flags | START_DMA));

        /* Wait for DMA to finish */
        ret = xe_mmio_wait32(mmio, DMA_CTRL, START_DMA, 0, 100000, &dma_ctrl,
                             false);
        if (ret)
                drm_err(&xe->drm, "DMA for %s fw failed, DMA_CTRL=%u\n",
                        xe_uc_fw_type_repr(uc_fw->type), dma_ctrl);

        /* Disable the bits once DMA is over */
        xe_mmio_write32(mmio, DMA_CTRL, _MASKED_BIT_DISABLE(dma_flags));

        return ret;
}

int xe_uc_fw_upload(struct xe_uc_fw *uc_fw, u32 offset, u32 dma_flags)
{
        struct xe_device *xe = uc_fw_to_xe(uc_fw);
        int err;

        /* make sure the status was cleared the last time we reset the uc */
        xe_assert(xe, !xe_uc_fw_is_loaded(uc_fw));

        if (!xe_uc_fw_is_loadable(uc_fw))
                return -ENOEXEC;

        /* Call custom loader */
        err = uc_fw_xfer(uc_fw, offset, dma_flags);
        if (err)
                goto fail;

        xe_uc_fw_change_status(uc_fw, XE_UC_FIRMWARE_TRANSFERRED);
        return 0;

fail:
        drm_err(&xe->drm, "Failed to load %s firmware %s (%d)\n",
                xe_uc_fw_type_repr(uc_fw->type), uc_fw->path,
                err);
        xe_uc_fw_change_status(uc_fw, XE_UC_FIRMWARE_LOAD_FAIL);
        return err;
}

static const char *version_type_repr(enum xe_uc_fw_version_types type)
{
        switch (type) {
        case XE_UC_FW_VER_RELEASE:
                return "release";
        case XE_UC_FW_VER_COMPATIBILITY:
                return "compatibility";
        default:
                return "Unknown version type";
        }
}

void xe_uc_fw_print(struct xe_uc_fw *uc_fw, struct drm_printer *p)
{
        int i;

        drm_printf(p, "%s firmware: %s\n",
                   xe_uc_fw_type_repr(uc_fw->type), uc_fw->path);
        drm_printf(p, "\tstatus: %s\n",
                   xe_uc_fw_status_repr(uc_fw->status));

        print_uc_fw_version(p, &uc_fw->versions.wanted, "\twanted %s",
                            version_type_repr(uc_fw->versions.wanted_type));

        for (i = 0; i < XE_UC_FW_VER_TYPE_COUNT; i++) {
                struct xe_uc_fw_version *ver = &uc_fw->versions.found[i];

                if (ver->major)
                        print_uc_fw_version(p, ver, "\tfound %s",
                                            version_type_repr(i));
        }

        if (uc_fw->ucode_size)
                drm_printf(p, "\tuCode: %u bytes\n", uc_fw->ucode_size);
        if (uc_fw->rsa_size)
                drm_printf(p, "\tRSA: %u bytes\n", uc_fw->rsa_size);
}