/******************************************************************************
 *
 *   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.
 *
 *****************************************************************************/

/******************************************************************************
 * @file qat_mgr_client.c
 *
 * @description
 * Implements the qat manager client side. It provides functions to open
 * a connection to the manager and exchanging messages.
 *****************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/param.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <sys/file.h>
#include <fcntl.h>
#include <errno.h>
#include "icp_platform.h"
#include "qat_log.h"
#include "qat_mgr.h"

#define QAT_ENV_POLICY "QAT_POLICY"
#define MAX_DEVS_NO_POLICY 6
#define MAX_DEVS_STATIC_CFG 256

static int qatmgr_sock = -1;
static OsalMutex qatmgr_mutex;
static struct qatmgr_transport transport_mgr = { 0 };
static int qat_transport_mgr_init = 0;

/*
 * This array does not need to be global, it is only used locally to
 * adf_vfio_build_sconfig().
 * However, it is global to avoid excessive use of stack memory and potential
 * stack-overflow in this function.
 */
static struct qatmgr_dev_data dev_list[MAX_DEVS];

static int qatmgr_socket_open(struct qatmgr_transport *t_mgr)
{
    struct sockaddr_un sockaddr;
    int ret = 0;

    if (qatmgr_sock > 0)
    {
        qat_log(LOG_LEVEL_ERROR, "Failed to open socket\n");
        return -1;
    }

    qatmgr_sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (qatmgr_sock < 0)
    {
        qat_log(LOG_LEVEL_ERROR, "Failed to create socket\n");
        return -1;
    }

    memset(&sockaddr, 0, sizeof(sockaddr));
    sockaddr.sun_family = AF_UNIX;
    snprintf(sockaddr.sun_path, sizeof(sockaddr.sun_path), "%s", QATMGR_SOCKET);

    ret = connect(qatmgr_sock, (struct sockaddr *)&sockaddr, sizeof(sockaddr));
    if (ret < 0)
    {
        qat_log(LOG_LEVEL_INFO, "Failed to connect to QAT manager\n");
        close(qatmgr_sock);
        qatmgr_sock = -1;
        return -1;
    }

    if (OSAL_SUCCESS != osalMutexInit(&qatmgr_mutex))
    {
        close(qatmgr_sock);
        qatmgr_sock = -1;
        return -1;
    }

    return 0;
}

int adf_vfio_build_sconfig(void)
{
    int ret, lock_fd;
    char *env;
    long long devs = -1;
    unsigned n;
    int i;
    char *fin;

    env = getenv(QAT_ENV_POLICY);
    if (env)
    {
        devs = strtoll(env, &fin, 10);
        if (errno == ERANGE || *fin != 0 || devs < 0 || devs > MAX_DEVS)
        {
            qat_log(LOG_LEVEL_ERROR, "Invalid environment value \"%s\"\n", env);
            return -EINVAL;
        }
        if (devs == 0)
        {
            qat_log(LOG_LEVEL_ERROR,
                    "QAT_POLICY=0 is not supported in standalone mode. "
                    "Use QAT_POLICY >= 1 or unset QAT_POLICY\n");
            return -EINVAL;
        }
    }

    /* Acquire exclusive lock on /dev/vfio to serialize enumeration */
    lock_fd = open("/dev/vfio", O_RDONLY | O_DIRECTORY);
    if (lock_fd < 0)
    {
        qat_log(LOG_LEVEL_ERROR,
                "Failed to open /dev/vfio for locking: errno=%d\n",
                errno);
        return -errno;
    }

    if (flock(lock_fd, LOCK_EX) < 0)
    {
        qat_log(LOG_LEVEL_ERROR,
                "Failed to acquire /dev/vfio lock: errno=%d\n",
                errno);
        close(lock_fd);
        return -errno;
    }

    /* If QAT_POLICY is not set, reserve all devices but then use only
     * the first MAX_DEVS_NO_POLICY
     * if QAT_POLICY is set to >0, reserve the first n devices */
    if (devs < 0)
        ret = qat_mgr_get_vfio_dev_list(&n, dev_list, MAX_DEVS);
    else
        ret = qat_mgr_get_vfio_dev_list(&n, dev_list, devs);

    if (ret)
    {
        flock(lock_fd, LOCK_UN);
        close(lock_fd);
        return ret;
    }

    /* If no device is found return an error */
    if (n == 0)
    {
        qat_log(LOG_LEVEL_ERROR, "No device found\n");
        flock(lock_fd, LOCK_UN);
        close(lock_fd);
        return -ENODEV;
    }

    /* Check if sufficient devices are available for the requested policy */
    if (devs > 0 && n < devs)
    {
        qat_log(LOG_LEVEL_ERROR,
                "Insufficient devices: requested %lld devices via QAT_POLICY, "
                "but only %u devices available\n",
                devs,
                n);
        flock(lock_fd, LOCK_UN);
        close(lock_fd);
        return -ENODEV;
    }

    /* Set device count when no policy is specified */
    if (devs < 0)
    {
        if (n > MAX_DEVS_NO_POLICY)
            devs = MAX_DEVS_NO_POLICY;
        else
            devs = n;
    }

    for (i = 0; i < n; i++)
    {
        qat_log(LOG_LEVEL_INFO,
                "Device %d, %X, %04x:%02x:%02x.%01x\n",
                i,
                dev_list[i].bdf,
                BDF_DOMAIN(dev_list[i].bdf),
                BDF_BUS(dev_list[i].bdf),
                BDF_DEV(dev_list[i].bdf),
                BDF_FUN(dev_list[i].bdf));
    }

    ret = qat_mgr_vfio_build_data(dev_list, n, devs, 1);

    /* Release lock */
    flock(lock_fd, LOCK_UN);
    close(lock_fd);
    return ret;
}

int qatmgr_open(void)
{
    int ret;
    struct qatmgr_transport *t_mgr = NULL;

    t_mgr = get_transport_mgr();

    ret = qatmgr_socket_open(t_mgr);
    if (ret)
    {
        qat_log(LOG_LEVEL_INFO, "Build static configuration\n");
        ret = t_mgr->adf_build_sconfig();
        if (!ret)
        {
            /** mutex isn't needed for static path but client use
             * common qat_mgr_lib code which is used also by qatmgr
             * where mutex is needed
             * so it should be initialized to make lib working properly
             **/
            ret = init_section_data_mutex();
        }
    }

    return ret;
}

int qatmgr_close(void)
{
    if (qatmgr_sock <= 0)
    {
        qat_log(LOG_LEVEL_DEBUG, "Cleanup static configuration\n");
        qat_mgr_cleanup_cfg();
        destroy_section_data_mutex();
        return 0;
    }

    close(qatmgr_sock);
    qatmgr_sock = -1;
    if (osalMutexDestroy(&qatmgr_mutex) == OSAL_FAIL)
        return -1;

    return 0;
}

int qatmgr_query(struct qatmgr_msg_req *req,
                 struct qatmgr_msg_rsp *rsp,
                 uint16_t type)
{
    int size_tx = 0;
    int size_rx = 0;
    ssize_t numchars;
    static int index = -1;
    pid_t pid = 0;
    static char *section_name = NULL;

    ICP_CHECK_FOR_NULL_PARAM_RET_CODE(req, -1);
    ICP_CHECK_FOR_NULL_PARAM_RET_CODE(rsp, -1);

    switch (type)
    {
        case QATMGR_MSGTYPE_SECTION_GET:
        case QATMGR_MSGTYPE_SECTION_PUT:
            pid = getpid();
            size_tx = strnlen(req->name, sizeof(req->name) - 1) + 1;
            break;
        case QATMGR_MSGTYPE_NUM_DEVICES:
        case QATMGR_MSGTYPE_NUM_PF_DEVS:
            size_tx = 0;
            break;
        case QATMGR_MSGTYPE_DEVICE_INFO:
        case QATMGR_MSGTYPE_DEVICE_ID:
        case QATMGR_MSGTYPE_VFIO_FILE:
        case QATMGR_MSGTYPE_PF_DEV_INFO:
            size_tx = sizeof(req->device_num);
            break;
        case QATMGR_MSGTYPE_INSTANCE_INFO:
        case QATMGR_MSGTYPE_INSTANCE_NAME:
            size_tx = sizeof(req->inst);
            break;
        default:
            qat_log(
                LOG_LEVEL_ERROR, "Unknown qat manager message type %d\n", type);
            return -1;
    }

    req->hdr.type = type;
    req->hdr.version = THIS_LIB_VERSION;
    req->hdr.len = sizeof(req->hdr) + size_tx;

    if (qatmgr_sock < 0)
        return handle_message(req, rsp, &section_name, pid, &index);

    osalMutexLock(&qatmgr_mutex, OSAL_WAIT_FOREVER);

    numchars = write(qatmgr_sock, req, req->hdr.len);
    if (numchars != req->hdr.len)
    {
        qat_log(LOG_LEVEL_ERROR,
                "Failed write to qatmgr socket %lu, expected %u\n",
                numchars,
                req->hdr.len);
        osalMutexUnlock(&qatmgr_mutex);
        return -1;
    }

    numchars = read(qatmgr_sock, rsp, sizeof(*rsp));

    osalMutexUnlock(&qatmgr_mutex);

    if (rsp->hdr.version != THIS_LIB_VERSION)
    {
        char qatlib_ver_str[VER_STR_LEN];
        char qatmgr_ver_str[VER_STR_LEN];
        VER_STR(rsp->hdr.version, qatmgr_ver_str);
        VER_STR(THIS_LIB_VERSION, qatlib_ver_str);

        qat_log(
            LOG_LEVEL_ERROR,
            "This qatlib v%s received response from incompatible qatmgr v%s\n",
            qatlib_ver_str,
            qatmgr_ver_str);
        return -1;
    }
    if (rsp->hdr.type != type)
    {
        if (rsp->hdr.type == QATMGR_MSGTYPE_BAD)
            qat_log(LOG_LEVEL_ERROR,
                    "Bad qatmgr response to request %d, %s\n",
                    req->hdr.type,
                    rsp->error_text);
        else
            qat_log(LOG_LEVEL_ERROR,
                    "Unexpected qatmgr response %d to request %d\n",
                    rsp->hdr.type,
                    req->hdr.type);
        return -1;
    }

    switch (type)
    {
        case QATMGR_MSGTYPE_SECTION_GET:
        case QATMGR_MSGTYPE_INSTANCE_NAME:
            size_rx = strnlen(rsp->name, sizeof(rsp->name));
            break;
        case QATMGR_MSGTYPE_VFIO_FILE:
            size_rx = sizeof(rsp->vfio_file.fd) +
                      strnlen(rsp->vfio_file.name, sizeof(rsp->vfio_file.name));
            break;
        case QATMGR_MSGTYPE_SECTION_PUT:
            size_rx = 0;
            break;
        case QATMGR_MSGTYPE_NUM_DEVICES:
        case QATMGR_MSGTYPE_NUM_PF_DEVS:
            size_rx = sizeof(rsp->num_devices);
            break;
        case QATMGR_MSGTYPE_DEVICE_INFO:
            size_rx = sizeof(rsp->device_info);
            break;
        case QATMGR_MSGTYPE_DEVICE_ID:
            size_rx = strnlen(rsp->device_id, sizeof(rsp->device_id));
            break;
        case QATMGR_MSGTYPE_PF_DEV_INFO:
            size_rx = sizeof(rsp->pf_info);
            break;
        case QATMGR_MSGTYPE_INSTANCE_INFO:
            size_rx = sizeof(rsp->instance_info);
            break;
        default:
            qat_log(
                LOG_LEVEL_ERROR, "Unknown qat manager message type %d\n", type);
            return -1;
    }
    if (numchars < sizeof(rsp->hdr) + size_rx)
    {
        qat_log(LOG_LEVEL_ERROR,
                "Failed to read from qatmgr socket, %lu expected %lu\n",
                numchars,
                sizeof(rsp->hdr) + size_rx);
        return -1;
    }

    return 0;
}

void qatmgr_transport_init(void)
{
    qat_transport_mgr_init = 1;
        transport_mgr.adf_build_sconfig = adf_vfio_build_sconfig;
        transport_mgr.qat_mgr_is_dev_available = qat_mgr_is_vfio_dev_available;
        transport_mgr.adf_create_accel = adf_vfio_create_accel;
        transport_mgr.adf_reinit_accel = adf_vfio_reinit_accel;
        transport_mgr.adf_free_bundle = adf_vfio_free_bundle;
        transport_mgr.adf_populate_bundle = adf_vfio_populate_bundle;
        transport_mgr.adf_destroy_accel = adf_vfio_destroy_accel;
        transport_mgr.adf_io_poll_proxy_event = adf_vfio_poll_proxy_event;
}

struct qatmgr_transport *get_transport_mgr(void)
{
    if (!qat_transport_mgr_init)
        qatmgr_transport_init();
    return &transport_mgr;
}

