/******************************************************************************
 *
 *   SPDX-License-Identifier: BSD-3-Clause
 *   Copyright(c) 2007-2026 Intel Corporation
 * 
 *   These contents may have been developed with support from one or more
 *   Intel-operated generative artificial intelligence solutions.
 *
 *****************************************************************************/

#include <errno.h>
#include <string.h>

#include "adf_pfvf_vf_msg.h"
#include "icp_platform.h"
#include "qat_log.h"

/* At the beginning we assume PF driver does not support PFVF.
 * If first init notification will be ACKed, VF2PF support will be marked as
 * available. Any further error in communication will disable it again.
 * Newer version received, fallthrough to handle the known parts. */
static int vf2pf_available = VF2PF_UNKNOWN;

void adf_set_vf2pf_available(int available)
{
    /* VF2PF error at first attempt of communication, assuming PF driver has no
     * PFVF support */
    if (vf2pf_available == VF2PF_UNKNOWN && available == VF2PF_NOT_AVAILABLE)
    {
        qat_log(LOG_LEVEL_INFO, "PF has not support for PFVF\n");
    }
    else if (vf2pf_available == VF2PF_AVAILABLE &&
             available == VF2PF_NOT_AVAILABLE)
    {
        qat_log(LOG_LEVEL_ERROR,
                "Error in PF2VF communication, disabling PFVF\n");
    }

    vf2pf_available = available;
}

int adf_vf2pf_available()
{
    if (vf2pf_available == VF2PF_NOT_AVAILABLE)
    {
        qat_log(LOG_LEVEL_INFO, "VF2PF is not available\n");
        return 0;
    }

    return 1;
}

int adf_vf2pf_notify_init(struct adf_pfvf_dev_data *dev)
{
    struct pfvf_message msg = { .type = ADF_VF2PF_MSGTYPE_INIT };

    ICP_CHECK_FOR_NULL_PARAM_RET_CODE(dev, -EINVAL);

    if (!adf_vf2pf_available())
        return -EIO;

    if (adf_send_vf2pf_msg(dev, msg))
    {
        qat_log(LOG_LEVEL_INFO, "Failed to send Init event to PF\n");
        adf_set_vf2pf_available(VF2PF_NOT_AVAILABLE);
        return -EFAULT;
    }
    vf2pf_available = VF2PF_AVAILABLE;
    dev->pfvf_initialized = 1;
    return 0;
}

void adf_vf2pf_notify_shutdown(struct adf_pfvf_dev_data *dev)
{
    ICP_CHECK_FOR_NULL_PARAM_VOID(dev);
    struct pfvf_message msg = { .type = ADF_VF2PF_MSGTYPE_SHUTDOWN };

    if (!adf_vf2pf_available())
        return;

    if (dev->pfvf_initialized)
    {
        if (adf_send_vf2pf_msg(dev, msg))
        {
            qat_log(LOG_LEVEL_ERROR, "Failed to send Shutdown event to PF\n");
            adf_set_vf2pf_available(VF2PF_NOT_AVAILABLE);
        }
        else
            dev->pfvf_initialized = 0;
    }
}

void adf_vf2pf_notify_restarting_complete(struct adf_pfvf_dev_data *dev)
{
    ICP_CHECK_FOR_NULL_PARAM_VOID(dev);
    struct pfvf_message msg = { .type = ADF_VF2PF_MSGTYPE_RESTARTING_COMPLETE };

    if (!adf_vf2pf_available())
        return;

    if (dev->pfvf_initialized)
    {
        if (adf_send_vf2pf_msg(dev, msg))
        {
            qat_log(LOG_LEVEL_ERROR,
                    "Failed to send Restarting complete event to PF\n");
            adf_set_vf2pf_available(VF2PF_NOT_AVAILABLE);
        }
        else
            dev->pfvf_initialized = 0;
    }
}

int adf_vf2pf_check_compat_version(struct adf_pfvf_dev_data *dev)
{
    int ret;
    struct pfvf_compat_message resp;
    struct pfvf_compat_message compat_req = {
        .type = ADF_VF2PF_MSGTYPE_COMPAT_VER_REQ,
        .version = ADF_PFVF_COMPAT_THIS_VERSION,
    };
    struct pfvf_message req;

    ICP_CHECK_FOR_NULL_PARAM_RET_CODE(dev, -EINVAL);
    if (!adf_vf2pf_available())
        return -EIO;

    /* memcpy between pfvf_compat_message and pfvf_message to prevent
     * strict-aliasing warnings */
    memcpy(&req, &compat_req, sizeof(req));

    ret = adf_send_vf2pf_req(dev, req, (struct pfvf_message *)&resp);
    if (ret)
    {
        qat_log(LOG_LEVEL_INFO,
                "Failed to send Compatibility Version Request\n");
        adf_set_vf2pf_available(VF2PF_NOT_AVAILABLE);
        return ret;
    }

    if (resp.type != ADF_PF2VF_MSGTYPE_VERSION_RESP)
    {
        qat_log(LOG_LEVEL_ERROR,
                "PFVF expecting Version Response, received msg type %u\n",
                resp.type);
        return -EFAULT;
    }

    if (resp.compat != ADF_PF2VF_VF_COMPATIBLE)
    {
        if (resp.compat == ADF_PF2VF_VF_COMPAT_UNKNOWN)
        {
            /* The PF driver has an older compat version than qatlib so it’s up
             * to qatlib to decide if it can work with the PF or not. The intree
             * kernel on gen4 started with ADF_PFVF_COMPAT_RING_TO_SVC_MAP
             * so no need to handle earlier versions. Note, earlier kernels
             * would not have responded to COMPAT message, qatlib doesn’t treat
             * them as incompatible, instead works with them based on assuming
             * default config. */
            if (resp.version >= ADF_PFVF_COMPAT_RING_TO_SVC_MAP &&
                resp.version < ADF_PFVF_COMPAT_THIS_VERSION)
            {
                qat_log(LOG_LEVEL_INFO, "Running in compatibility mode\n");
                qat_log(LOG_LEVEL_INFO,
                        " PF version %d, VF version %d\n",
                        resp.version,
                        compat_req.version);
            }
            else
            {
                qat_log(LOG_LEVEL_ERROR,
                        "VF version %d is incompatible with PF version %d\n",
                        compat_req.version,
                        resp.version);
                return -EFAULT;
            }
        }
        else
        {
            qat_log(LOG_LEVEL_ERROR,
                    "PF version %d is incompatible with VF version %d\n",
                    resp.version,
                    compat_req.version);
            return -EFAULT;
        }
    }

    dev->compat_version = resp.version;
    return 0;
}

int adf_vf2pf_get_ring_to_svc(struct adf_pfvf_dev_data *dev)
{
    struct ring_to_svc_map_v1 rts_map_msg = {
        { 0 },
    };

    uint16_t len = sizeof(rts_map_msg);

    ICP_CHECK_FOR_NULL_PARAM_RET_CODE(dev, -EINVAL);
    if (!adf_vf2pf_available())
        return -EIO;

    if (dev->compat_version < ADF_PFVF_COMPAT_RING_TO_SVC_MAP)
        /* Use already set default mappings */
        return -EFAULT;

    if (adf_send_vf2pf_blkmsg_req(dev,
                                  ADF_VF2PF_BLKMSG_REQ_RING_SVC_MAP,
                                  (uint8_t *)&rts_map_msg,
                                  &len))
    {
        qat_log(LOG_LEVEL_ERROR, "Failed to get block message response\n");
        adf_set_vf2pf_available(VF2PF_NOT_AVAILABLE);
        return -EFAULT;
    }

    if (len < sizeof(struct ring_to_svc_map_v1))
    {
        qat_log(LOG_LEVEL_ERROR,
                "RING_TO_SVC message truncated to %d bytes\n",
                len);
        return -EFAULT;
    }

    /* Only v1 at present */
    dev->ring_to_svc_map = rts_map_msg.map;
    return 0;
}

int adf_vf2pf_get_capabilities(struct adf_pfvf_dev_data *dev)
{
    struct capabilities_v4 cap_msg = {
        { 0 },
    };
    uint16_t len = sizeof(cap_msg);
    int ret = 0;

    ICP_CHECK_FOR_NULL_PARAM_RET_CODE(dev, -EINVAL);
    if (!adf_vf2pf_available())
        return -EIO;

    if (dev->compat_version < ADF_PFVF_COMPAT_CAPABILITIES)
    {
        /* The PF is too old to support the extended capabilities */
        return -EFAULT;
    }

    if (adf_send_vf2pf_blkmsg_req(
            dev, ADF_VF2PF_BLKMSG_REQ_CAP_SUMMARY, (uint8_t *)&cap_msg, &len))
    {
        qat_log(LOG_LEVEL_ERROR, "Failed to get CAP_SUMMARY response\n");
        adf_set_vf2pf_available(VF2PF_NOT_AVAILABLE);
        return -EFAULT;
    }

    switch (cap_msg.hdr.version)
    {
        default:
        /* Newer version received, fallthrough to handle the know parts */
        case ADF_PFVF_CAPABILITIES_V4_VERSION:
            if (len >= sizeof(struct capabilities_v4))
            {
                dev->fw_caps.comp_algos = cap_msg.comp_algos;
                dev->fw_caps.cksum_algos = cap_msg.cksum_algos;
                dev->fw_caps.deflate_caps = cap_msg.deflate_caps;
                dev->fw_caps.lz4_caps = cap_msg.lz4_caps;
                dev->fw_caps.lz4s_caps = cap_msg.lz4s_caps;
                dev->fw_caps.zstd_caps = cap_msg.zstd_caps;
                dev->fw_caps.is_fw_caps = 1;
            }
            else
            {
                qat_log(LOG_LEVEL_ERROR,
                        "Could not get firmware capabilities\n");
                ret = -EFAULT;
            }
        case ADF_PFVF_CAPABILITIES_V3_VERSION:
            if (len >= sizeof(struct capabilities_v3))
            {
                dev->frequency = cap_msg.frequency;
            }
            else
            {
                qat_log(LOG_LEVEL_ERROR, "Could not get frequency\n");
                ret = -EFAULT;
            }
        case ADF_PFVF_CAPABILITIES_V2_VERSION:
            if (len >= sizeof(struct capabilities_v2))
            {
                dev->capabilities = cap_msg.capabilities;
            }
            else
            {
                qat_log(LOG_LEVEL_ERROR, "Could not get capabilities\n");
                ret = -EFAULT;
            }
        case ADF_PFVF_CAPABILITIES_V1_VERSION:
            if (len >= sizeof(struct capabilities_v1))
            {
                dev->ext_dc_caps = cap_msg.ext_dc_caps;
            }
            else
            {
                qat_log(LOG_LEVEL_ERROR,
                        "CAPABILITIES message truncated to %d bytes\n",
                        len);
                ret = -EFAULT;
            }
    }

    return ret;
}

int adf_check_pf2vf_notification(struct adf_pfvf_dev_data *dev)
{
    struct pfvf_message msg;
    ICP_CHECK_FOR_NULL_PARAM_RET_CODE(dev, -EINVAL);
    msg = adf_recv_pf2vf_msg(dev);

    return msg.type;
}
