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

#include "xe_gsc_proxy.h"

#include <linux/component.h>
#include <linux/delay.h>

#include <drm/drm_managed.h>
#include <drm/intel/i915_component.h>
#include <drm/intel/i915_gsc_proxy_mei_interface.h>

#include "abi/gsc_proxy_commands_abi.h"
#include "regs/xe_gsc_regs.h"
#include "xe_bo.h"
#include "xe_force_wake.h"
#include "xe_gsc.h"
#include "xe_gsc_submit.h"
#include "xe_gt_printk.h"
#include "xe_gt_types.h"
#include "xe_map.h"
#include "xe_mmio.h"
#include "xe_pm.h"
#include "xe_tile.h"

/*
 * GSC proxy:
 * The GSC uC needs to communicate with the CSME to perform certain operations.
 * Since the GSC can't perform this communication directly on platforms where it
 * is integrated in GT, the graphics driver needs to transfer the messages from
 * GSC to CSME and back. The proxy flow must be manually started after the GSC
 * is loaded to signal to GSC that we're ready to handle its messages and allow
 * it to query its init data from CSME; GSC will then trigger an HECI2 interrupt
 * if it needs to send messages to CSME again.
 * The proxy flow is as follow:
 * 1 - Xe submits a request to GSC asking for the message to CSME
 * 2 - GSC replies with the proxy header + payload for CSME
 * 3 - Xe sends the reply from GSC as-is to CSME via the mei proxy component
 * 4 - CSME replies with the proxy header + payload for GSC
 * 5 - Xe submits a request to GSC with the reply from CSME
 * 6 - GSC replies either with a new header + payload (same as step 2, so we
 *     restart from there) or with an end message.
 */

/*
 * 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 GSC_PROXY_INIT_TIMEOUT_MS 20000

/* shorthand define for code compactness */
#define PROXY_HDR_SIZE (sizeof(struct xe_gsc_proxy_header))

/* the protocol supports up to 32K in each direction */
#define GSC_PROXY_BUFFER_SIZE SZ_32K
#define GSC_PROXY_CHANNEL_SIZE (GSC_PROXY_BUFFER_SIZE * 2)

static struct xe_gt *
gsc_to_gt(struct xe_gsc *gsc)
{
        return container_of(gsc, struct xe_gt, uc.gsc);
}

bool xe_gsc_proxy_init_done(struct xe_gsc *gsc)
{
        struct xe_gt *gt = gsc_to_gt(gsc);
        u32 fwsts1 = xe_mmio_read32(&gt->mmio, HECI_FWSTS1(MTL_GSC_HECI1_BASE));

        return REG_FIELD_GET(HECI1_FWSTS1_CURRENT_STATE, fwsts1) ==
               HECI1_FWSTS1_PROXY_STATE_NORMAL;
}

int xe_gsc_wait_for_proxy_init_done(struct xe_gsc *gsc)
{
        struct xe_gt *gt = gsc_to_gt(gsc);

        /* Proxy init can take up to 500ms, so wait double that for safety */
        return xe_mmio_wait32(&gt->mmio, HECI_FWSTS1(MTL_GSC_HECI1_BASE),
                              HECI1_FWSTS1_CURRENT_STATE,
                              HECI1_FWSTS1_PROXY_STATE_NORMAL,
                              USEC_PER_SEC, NULL, false);
}

static void __gsc_proxy_irq_rmw(struct xe_gsc *gsc, u32 clr, u32 set)
{
        struct xe_gt *gt = gsc_to_gt(gsc);

        /* make sure we never accidentally write the RST bit */
        clr |= HECI_H_CSR_RST;

        xe_mmio_rmw32(&gt->mmio, HECI_H_CSR(MTL_GSC_HECI2_BASE), clr, set);
}

static void gsc_proxy_irq_clear(struct xe_gsc *gsc)
{
        /* The status bit is cleared by writing to it */
        __gsc_proxy_irq_rmw(gsc, 0, HECI_H_CSR_IS);
}

static void gsc_proxy_irq_toggle(struct xe_gsc *gsc, bool enabled)
{
        u32 set = enabled ? HECI_H_CSR_IE : 0;
        u32 clr = enabled ? 0 : HECI_H_CSR_IE;

        __gsc_proxy_irq_rmw(gsc, clr, set);
}

static int proxy_send_to_csme(struct xe_gsc *gsc, u32 size)
{
        struct xe_gt *gt = gsc_to_gt(gsc);
        struct i915_gsc_proxy_component *comp = gsc->proxy.component;
        int ret;

        ret = comp->ops->send(comp->mei_dev, gsc->proxy.to_csme, size);
        if (ret < 0) {
                xe_gt_err(gt, "Failed to send CSME proxy message\n");
                return ret;
        }

        ret = comp->ops->recv(comp->mei_dev, gsc->proxy.from_csme, GSC_PROXY_BUFFER_SIZE);
        if (ret < 0) {
                xe_gt_err(gt, "Failed to receive CSME proxy message\n");
                return ret;
        }

        return ret;
}

static int proxy_send_to_gsc(struct xe_gsc *gsc, u32 size)
{
        struct xe_gt *gt = gsc_to_gt(gsc);
        u64 addr_in = xe_bo_ggtt_addr(gsc->proxy.bo);
        u64 addr_out = addr_in + GSC_PROXY_BUFFER_SIZE;
        int err;

        /* the message must contain at least the gsc and proxy headers */
        if (size > GSC_PROXY_BUFFER_SIZE) {
                xe_gt_err(gt, "Invalid GSC proxy message size: %u\n", size);
                return -EINVAL;
        }

        err = xe_gsc_pkt_submit_kernel(gsc, addr_in, size,
                                       addr_out, GSC_PROXY_BUFFER_SIZE);
        if (err) {
                xe_gt_err(gt, "Failed to submit gsc proxy rq (%pe)\n", ERR_PTR(err));
                return err;
        }

        return 0;
}

static int validate_proxy_header(struct xe_gt *gt,
                                 struct xe_gsc_proxy_header *header,
                                 u32 source, u32 dest, u32 max_size)
{
        u32 type = FIELD_GET(GSC_PROXY_TYPE, header->hdr);
        u32 length = FIELD_GET(GSC_PROXY_PAYLOAD_LENGTH, header->hdr);
        int ret = 0;

        if (header->destination != dest || header->source != source) {
                ret = -ENOEXEC;
                goto out;
        }

        if (length + PROXY_HDR_SIZE > max_size) {
                ret = -E2BIG;
                goto out;
        }

        /* We only care about the status if this is a message for the driver */
        if (dest == GSC_PROXY_ADDRESSING_KMD && header->status != 0) {
                ret = -EIO;
                goto out;
        }

        switch (type) {
        case GSC_PROXY_MSG_TYPE_PROXY_PAYLOAD:
                if (length > 0)
                        break;
                fallthrough;
        case GSC_PROXY_MSG_TYPE_PROXY_INVALID:
                ret = -EIO;
                break;
        default:
                break;
        }

out:
        if (ret)
                xe_gt_err(gt,
                          "GSC proxy error: s=0x%x[0x%x], d=0x%x[0x%x], t=%u, l=0x%x, st=0x%x\n",
                          header->source, source, header->destination, dest,
                          type, length, header->status);

        return ret;
}

#define proxy_header_wr(xe_, map_, offset_, field_, val_) \
        xe_map_wr_field(xe_, map_, offset_, struct xe_gsc_proxy_header, field_, val_)

#define proxy_header_rd(xe_, map_, offset_, field_) \
        xe_map_rd_field(xe_, map_, offset_, struct xe_gsc_proxy_header, field_)

static u32 emit_proxy_header(struct xe_device *xe, struct iosys_map *map, u32 offset)
{
        xe_map_memset(xe, map, offset, 0, PROXY_HDR_SIZE);

        proxy_header_wr(xe, map, offset, hdr,
                        FIELD_PREP(GSC_PROXY_TYPE, GSC_PROXY_MSG_TYPE_PROXY_QUERY) |
                        FIELD_PREP(GSC_PROXY_PAYLOAD_LENGTH, 0));

        proxy_header_wr(xe, map, offset, source, GSC_PROXY_ADDRESSING_KMD);
        proxy_header_wr(xe, map, offset, destination, GSC_PROXY_ADDRESSING_GSC);
        proxy_header_wr(xe, map, offset, status, 0);

        return offset + PROXY_HDR_SIZE;
}

static int proxy_query(struct xe_gsc *gsc)
{
        struct xe_gt *gt = gsc_to_gt(gsc);
        struct xe_device *xe = gt_to_xe(gt);
        struct xe_gsc_proxy_header *to_csme_hdr = gsc->proxy.to_csme;
        void *to_csme_payload = gsc->proxy.to_csme + PROXY_HDR_SIZE;
        u32 wr_offset;
        u32 reply_offset;
        u32 size;
        int ret;

        wr_offset = xe_gsc_emit_header(xe, &gsc->proxy.to_gsc, 0,
                                       HECI_MEADDRESS_PROXY, 0, PROXY_HDR_SIZE);
        wr_offset = emit_proxy_header(xe, &gsc->proxy.to_gsc, wr_offset);

        size = wr_offset;

        while (1) {
                /*
                 * Poison the GSC response header space to make sure we don't
                 * read a stale reply.
                 */
                xe_gsc_poison_header(xe, &gsc->proxy.from_gsc, 0);

                /* send proxy message to GSC */
                ret = proxy_send_to_gsc(gsc, size);
                if (ret)
                        goto proxy_error;

                /* check the reply from GSC */
                ret = xe_gsc_read_out_header(xe, &gsc->proxy.from_gsc, 0,
                                             PROXY_HDR_SIZE, &reply_offset);
                if (ret) {
                        xe_gt_err(gt, "Invalid gsc header in proxy reply (%pe)\n",
                                  ERR_PTR(ret));
                        goto proxy_error;
                }

                /* copy the proxy header reply from GSC */
                xe_map_memcpy_from(xe, to_csme_hdr, &gsc->proxy.from_gsc,
                                   reply_offset, PROXY_HDR_SIZE);

                /* Check the status and stop if this was the last message */
                if (FIELD_GET(GSC_PROXY_TYPE, to_csme_hdr->hdr) == GSC_PROXY_MSG_TYPE_PROXY_END) {
                        ret = validate_proxy_header(gt, to_csme_hdr,
                                                    GSC_PROXY_ADDRESSING_GSC,
                                                    GSC_PROXY_ADDRESSING_KMD,
                                                    GSC_PROXY_BUFFER_SIZE - reply_offset);
                        break;
                }

                /* make sure the GSC-to-CSME proxy header is sane */
                ret = validate_proxy_header(gt, to_csme_hdr,
                                            GSC_PROXY_ADDRESSING_GSC,
                                            GSC_PROXY_ADDRESSING_CSME,
                                            GSC_PROXY_BUFFER_SIZE - reply_offset);
                if (ret) {
                        xe_gt_err(gt, "invalid GSC to CSME proxy header! (%pe)\n",
                                  ERR_PTR(ret));
                        goto proxy_error;
                }

                /* copy the rest of the message */
                size = FIELD_GET(GSC_PROXY_PAYLOAD_LENGTH, to_csme_hdr->hdr);
                xe_map_memcpy_from(xe, to_csme_payload, &gsc->proxy.from_gsc,
                                   reply_offset + PROXY_HDR_SIZE, size);

                /* send the GSC message to the CSME */
                ret = proxy_send_to_csme(gsc, size + PROXY_HDR_SIZE);
                if (ret < 0)
                        goto proxy_error;

                /* reply size from CSME, including the proxy header */
                size = ret;
                if (size < PROXY_HDR_SIZE) {
                        xe_gt_err(gt, "CSME to GSC proxy msg too small: 0x%x\n", size);
                        ret = -EPROTO;
                        goto proxy_error;
                }

                /* make sure the CSME-to-GSC proxy header is sane */
                ret = validate_proxy_header(gt, gsc->proxy.from_csme,
                                            GSC_PROXY_ADDRESSING_CSME,
                                            GSC_PROXY_ADDRESSING_GSC,
                                            GSC_PROXY_BUFFER_SIZE - reply_offset);
                if (ret) {
                        xe_gt_err(gt, "invalid CSME to GSC proxy header! %d\n", ret);
                        goto proxy_error;
                }

                /* Emit a new header for sending the reply to the GSC */
                wr_offset = xe_gsc_emit_header(xe, &gsc->proxy.to_gsc, 0,
                                               HECI_MEADDRESS_PROXY, 0, size);

                /* copy the CSME reply and update the total msg size to include the GSC header */
                xe_map_memcpy_to(xe, &gsc->proxy.to_gsc, wr_offset, gsc->proxy.from_csme, size);

                size += wr_offset;
        }

proxy_error:
        return ret < 0 ? ret : 0;
}

int xe_gsc_proxy_request_handler(struct xe_gsc *gsc)
{
        struct xe_gt *gt = gsc_to_gt(gsc);
        int slept;
        int err;

        if (!gsc->proxy.component_added)
                return -ENODEV;

        /* when GSC is loaded, we can queue this before the component is bound */
        for (slept = 0; slept < GSC_PROXY_INIT_TIMEOUT_MS; slept += 100) {
                if (gsc->proxy.component)
                        break;

                msleep(100);
        }

        mutex_lock(&gsc->proxy.mutex);
        if (!gsc->proxy.component) {
                xe_gt_err(gt, "GSC proxy component not bound!\n");
                err = -EIO;
        } else {
                /*
                 * clear the pending interrupt and allow new proxy requests to
                 * be generated while we handle the current one
                 */
                gsc_proxy_irq_clear(gsc);
                err = proxy_query(gsc);
        }
        mutex_unlock(&gsc->proxy.mutex);
        return err;
}

void xe_gsc_proxy_irq_handler(struct xe_gsc *gsc, u32 iir)
{
        struct xe_gt *gt = gsc_to_gt(gsc);

        if (unlikely(!iir))
                return;

        if (!gsc->proxy.component) {
                xe_gt_err(gt, "GSC proxy irq received without the component being bound!\n");
                return;
        }

        spin_lock(&gsc->lock);
        gsc->work_actions |= GSC_ACTION_SW_PROXY;
        spin_unlock(&gsc->lock);

        queue_work(gsc->wq, &gsc->work);
}

static int xe_gsc_proxy_component_bind(struct device *xe_kdev,
                                       struct device *mei_kdev, void *data)
{
        struct xe_device *xe = kdev_to_xe_device(xe_kdev);
        struct xe_gt *gt = xe->tiles[0].media_gt;
        struct xe_gsc *gsc = &gt->uc.gsc;

        mutex_lock(&gsc->proxy.mutex);
        gsc->proxy.component = data;
        gsc->proxy.component->mei_dev = mei_kdev;
        mutex_unlock(&gsc->proxy.mutex);

        return 0;
}

static void xe_gsc_proxy_component_unbind(struct device *xe_kdev,
                                          struct device *mei_kdev, void *data)
{
        struct xe_device *xe = kdev_to_xe_device(xe_kdev);
        struct xe_gt *gt = xe->tiles[0].media_gt;
        struct xe_gsc *gsc = &gt->uc.gsc;

        xe_gsc_wait_for_worker_completion(gsc);

        mutex_lock(&gsc->proxy.mutex);
        gsc->proxy.component = NULL;
        mutex_unlock(&gsc->proxy.mutex);
}

static const struct component_ops xe_gsc_proxy_component_ops = {
        .bind   = xe_gsc_proxy_component_bind,
        .unbind = xe_gsc_proxy_component_unbind,
};

static int proxy_channel_alloc(struct xe_gsc *gsc)
{
        struct xe_gt *gt = gsc_to_gt(gsc);
        struct xe_tile *tile = gt_to_tile(gt);
        struct xe_device *xe = gt_to_xe(gt);
        struct xe_bo *bo;
        void *csme;

        csme = drmm_kzalloc(&xe->drm, GSC_PROXY_CHANNEL_SIZE, GFP_KERNEL);
        if (!csme)
                return -ENOMEM;

        bo = xe_managed_bo_create_pin_map(xe, tile, GSC_PROXY_CHANNEL_SIZE,
                                          XE_BO_FLAG_SYSTEM |
                                          XE_BO_FLAG_GGTT);
        if (IS_ERR(bo))
                return PTR_ERR(bo);

        gsc->proxy.bo = bo;
        gsc->proxy.to_gsc = IOSYS_MAP_INIT_OFFSET(&bo->vmap, 0);
        gsc->proxy.from_gsc = IOSYS_MAP_INIT_OFFSET(&bo->vmap, GSC_PROXY_BUFFER_SIZE);
        gsc->proxy.to_csme = csme;
        gsc->proxy.from_csme = csme + GSC_PROXY_BUFFER_SIZE;

        return 0;
}

static void xe_gsc_proxy_stop(struct xe_gsc *gsc)
{
        struct xe_gt *gt = gsc_to_gt(gsc);
        struct xe_device *xe = gt_to_xe(gt);

        /* disable HECI2 IRQs */
        scoped_guard(xe_pm_runtime, xe) {
                CLASS(xe_force_wake, fw_ref)(gt_to_fw(gt), XE_FW_GSC);
                if (!fw_ref.domains)
                        xe_gt_err(gt, "failed to get forcewake to disable GSC interrupts\n");

                /* try do disable irq even if forcewake failed */
                gsc_proxy_irq_toggle(gsc, false);
        }

        xe_gsc_wait_for_worker_completion(gsc);
        gsc->proxy.started = false;
}

static void xe_gsc_proxy_remove(void *arg)
{
        struct xe_gsc *gsc = arg;
        struct xe_gt *gt = gsc_to_gt(gsc);
        struct xe_device *xe = gt_to_xe(gt);

        if (!gsc->proxy.component_added)
                return;

        /*
         * GSC proxy start is an async process that can be ongoing during
         * Xe module load/unload. Using devm managed action to register
         * xe_gsc_proxy_stop could cause issues if Xe module unload has
         * already started when the action is registered, potentially leading
         * to the cleanup being called at the wrong time. Therefore, instead
         * of registering a separate devm action to undo what is done in
         * proxy start, we call it from here, but only if the start has
         * completed successfully (tracked with the 'started' flag).
         */
        if (gsc->proxy.started)
                xe_gsc_proxy_stop(gsc);

        component_del(xe->drm.dev, &xe_gsc_proxy_component_ops);
        gsc->proxy.component_added = false;
}

/**
 * xe_gsc_proxy_init() - init objects and MEI component required by GSC proxy
 * @gsc: the GSC uC
 *
 * Return: 0 if the initialization was successful, a negative errno otherwise.
 */
int xe_gsc_proxy_init(struct xe_gsc *gsc)
{
        int err;
        struct xe_gt *gt = gsc_to_gt(gsc);
        struct xe_tile *tile = gt_to_tile(gt);
        struct xe_device *xe = tile_to_xe(tile);

        mutex_init(&gsc->proxy.mutex);

        if (!IS_ENABLED(CONFIG_INTEL_MEI_GSC_PROXY)) {
                xe_gt_info(gt, "can't init GSC proxy due to missing mei component\n");
                return -ENODEV;
        }

        /* no multi-tile devices with this feature yet */
        if (!xe_tile_is_root(tile)) {
                xe_gt_err(gt, "unexpected GSC proxy init on tile %u\n", tile->id);
                return -EINVAL;
        }

        err = proxy_channel_alloc(gsc);
        if (err)
                return err;

        err = component_add_typed(xe->drm.dev, &xe_gsc_proxy_component_ops,
                                  I915_COMPONENT_GSC_PROXY);
        if (err < 0) {
                xe_gt_err(gt, "Failed to add GSC_PROXY component (%pe)\n", ERR_PTR(err));
                return err;
        }

        gsc->proxy.component_added = true;

        return devm_add_action_or_reset(xe->drm.dev, xe_gsc_proxy_remove, gsc);
}

/**
 * xe_gsc_proxy_start() - start the proxy by submitting the first request
 * @gsc: the GSC uC
 *
 * Return: 0 if the proxy are now enabled, a negative errno otherwise.
 */
int xe_gsc_proxy_start(struct xe_gsc *gsc)
{
        struct xe_gt *gt = gsc_to_gt(gsc);
        int err;

        /* enable the proxy interrupt in the GSC shim layer */
        gsc_proxy_irq_toggle(gsc, true);

        /*
         * The handling of the first proxy request must be manually triggered to
         * notify the GSC that we're ready to support the proxy flow.
         */
        err = xe_gsc_proxy_request_handler(gsc);
        if (err)
                goto err_irq_disable;

        if (!xe_gsc_proxy_init_done(gsc)) {
                xe_gt_err(gt, "GSC FW reports proxy init not completed\n");
                err = -EIO;
                goto err_irq_disable;
        }

        gsc->proxy.started = true;
        return 0;

err_irq_disable:
        gsc_proxy_irq_toggle(gsc, false);
        return err;
}