root/drivers/crypto/intel/qat/qat_common/adf_telemetry.c
// SPDX-License-Identifier: GPL-2.0-only
/* Copyright (c) 2023 Intel Corporation. */
#define dev_fmt(fmt) "Telemetry: " fmt

#include <asm/errno.h>
#include <linux/atomic.h>
#include <linux/device.h>
#include <linux/dev_printk.h>
#include <linux/dma-mapping.h>
#include <linux/jiffies.h>
#include <linux/kernel.h>
#include <linux/mutex.h>
#include <linux/slab.h>
#include <linux/string.h>
#include <linux/workqueue.h>

#include "adf_admin.h"
#include "adf_accel_devices.h"
#include "adf_common_drv.h"
#include "adf_telemetry.h"

#define TL_IS_ZERO(input)       ((input) == 0)

static bool is_tl_supported(struct adf_accel_dev *accel_dev)
{
        u16 fw_caps =  GET_HW_DATA(accel_dev)->fw_capabilities;

        return fw_caps & TL_CAPABILITY_BIT;
}

static int validate_tl_data(struct adf_tl_hw_data *tl_data)
{
        if (!tl_data->dev_counters ||
            TL_IS_ZERO(tl_data->num_dev_counters) ||
            !tl_data->sl_util_counters ||
            !tl_data->sl_exec_counters ||
            !tl_data->rp_counters ||
            TL_IS_ZERO(tl_data->num_rp_counters))
                return -EOPNOTSUPP;

        return 0;
}

static int validate_tl_slice_counters(struct icp_qat_fw_init_admin_slice_cnt *slice_count,
                                      u8 max_slices_per_type)
{
        u8 *sl_counter = (u8 *)slice_count;
        int i;

        for (i = 0; i < ADF_TL_SL_CNT_COUNT; i++) {
                if (sl_counter[i] > max_slices_per_type)
                        return -EINVAL;
        }

        return 0;
}

static int adf_tl_alloc_mem(struct adf_accel_dev *accel_dev)
{
        struct adf_tl_hw_data *tl_data = &GET_TL_DATA(accel_dev);
        struct device *dev = &GET_DEV(accel_dev);
        size_t regs_sz = tl_data->layout_sz;
        struct adf_telemetry *telemetry;
        int node = dev_to_node(dev);
        void *tl_data_regs;
        unsigned int i;

        telemetry = kzalloc_node(sizeof(*telemetry), GFP_KERNEL, node);
        if (!telemetry)
                return -ENOMEM;

        telemetry->rp_num_indexes = kmalloc_array(tl_data->max_rp,
                                                  sizeof(*telemetry->rp_num_indexes),
                                                  GFP_KERNEL);
        if (!telemetry->rp_num_indexes)
                goto err_free_tl;

        telemetry->regs_hist_buff = kmalloc_objs(*telemetry->regs_hist_buff,
                                                 tl_data->num_hbuff);
        if (!telemetry->regs_hist_buff)
                goto err_free_rp_indexes;

        telemetry->regs_data = dma_alloc_coherent(dev, regs_sz,
                                                  &telemetry->regs_data_p,
                                                  GFP_KERNEL);
        if (!telemetry->regs_data)
                goto err_free_regs_hist_buff;

        for (i = 0; i < tl_data->num_hbuff; i++) {
                tl_data_regs = kzalloc_node(regs_sz, GFP_KERNEL, node);
                if (!tl_data_regs)
                        goto err_free_dma;

                telemetry->regs_hist_buff[i] = tl_data_regs;
        }

        accel_dev->telemetry = telemetry;

        return 0;

err_free_dma:
        dma_free_coherent(dev, regs_sz, telemetry->regs_data,
                          telemetry->regs_data_p);

        while (i--)
                kfree(telemetry->regs_hist_buff[i]);

err_free_regs_hist_buff:
        kfree(telemetry->regs_hist_buff);
err_free_rp_indexes:
        kfree(telemetry->rp_num_indexes);
err_free_tl:
        kfree(telemetry);

        return -ENOMEM;
}

static void adf_tl_free_mem(struct adf_accel_dev *accel_dev)
{
        struct adf_tl_hw_data *tl_data = &GET_TL_DATA(accel_dev);
        struct adf_telemetry *telemetry = accel_dev->telemetry;
        struct device *dev = &GET_DEV(accel_dev);
        size_t regs_sz = tl_data->layout_sz;
        unsigned int i;

        for (i = 0; i < tl_data->num_hbuff; i++)
                kfree(telemetry->regs_hist_buff[i]);

        dma_free_coherent(dev, regs_sz, telemetry->regs_data,
                          telemetry->regs_data_p);

        kfree(telemetry->regs_hist_buff);
        kfree(telemetry->rp_num_indexes);
        kfree(telemetry);
        accel_dev->telemetry = NULL;
}

static unsigned long get_next_timeout(void)
{
        return msecs_to_jiffies(ADF_TL_TIMER_INT_MS);
}

static void snapshot_regs(struct adf_telemetry *telemetry, size_t size)
{
        void *dst = telemetry->regs_hist_buff[telemetry->hb_num];
        void *src = telemetry->regs_data;

        memcpy(dst, src, size);
}

static void tl_work_handler(struct work_struct *work)
{
        struct delayed_work *delayed_work;
        struct adf_telemetry *telemetry;
        struct adf_tl_hw_data *tl_data;
        u32 msg_cnt, old_msg_cnt;
        size_t layout_sz;
        u32 *regs_data;
        size_t id;

        delayed_work = to_delayed_work(work);
        telemetry = container_of(delayed_work, struct adf_telemetry, work_ctx);
        tl_data = &GET_TL_DATA(telemetry->accel_dev);
        regs_data = telemetry->regs_data;

        id = tl_data->msg_cnt_off / sizeof(*regs_data);
        layout_sz = tl_data->layout_sz;

        if (!atomic_read(&telemetry->state)) {
                cancel_delayed_work_sync(&telemetry->work_ctx);
                return;
        }

        msg_cnt = regs_data[id];
        old_msg_cnt = msg_cnt;
        if (msg_cnt == telemetry->msg_cnt)
                goto out;

        mutex_lock(&telemetry->regs_hist_lock);

        snapshot_regs(telemetry, layout_sz);

        /* Check if data changed while updating it */
        msg_cnt = regs_data[id];
        if (old_msg_cnt != msg_cnt)
                snapshot_regs(telemetry, layout_sz);

        telemetry->msg_cnt = msg_cnt;
        telemetry->hb_num++;
        telemetry->hb_num %= telemetry->hbuffs;

        mutex_unlock(&telemetry->regs_hist_lock);

out:
        adf_misc_wq_queue_delayed_work(&telemetry->work_ctx, get_next_timeout());
}

int adf_tl_halt(struct adf_accel_dev *accel_dev)
{
        struct adf_telemetry *telemetry = accel_dev->telemetry;
        struct device *dev = &GET_DEV(accel_dev);
        int ret;

        cancel_delayed_work_sync(&telemetry->work_ctx);
        atomic_set(&telemetry->state, 0);

        ret = adf_send_admin_tl_stop(accel_dev);
        if (ret)
                dev_err(dev, "failed to stop telemetry\n");

        return ret;
}

static void adf_set_cmdq_cnt(struct adf_accel_dev *accel_dev,
                             struct adf_tl_hw_data *tl_data)
{
        struct icp_qat_fw_init_admin_slice_cnt *slice_cnt, *cmdq_cnt;

        slice_cnt = &accel_dev->telemetry->slice_cnt;
        cmdq_cnt = &accel_dev->telemetry->cmdq_cnt;

        cmdq_cnt->cpr_cnt = slice_cnt->cpr_cnt * tl_data->multiplier.cpr_cnt;
        cmdq_cnt->dcpr_cnt = slice_cnt->dcpr_cnt * tl_data->multiplier.dcpr_cnt;
        cmdq_cnt->pke_cnt = slice_cnt->pke_cnt * tl_data->multiplier.pke_cnt;
        cmdq_cnt->wat_cnt = slice_cnt->wat_cnt * tl_data->multiplier.wat_cnt;
        cmdq_cnt->wcp_cnt = slice_cnt->wcp_cnt * tl_data->multiplier.wcp_cnt;
        cmdq_cnt->ucs_cnt = slice_cnt->ucs_cnt * tl_data->multiplier.ucs_cnt;
        cmdq_cnt->ath_cnt = slice_cnt->ath_cnt * tl_data->multiplier.ath_cnt;
}

int adf_tl_run(struct adf_accel_dev *accel_dev, int state)
{
        struct adf_tl_hw_data *tl_data = &GET_TL_DATA(accel_dev);
        struct adf_telemetry *telemetry = accel_dev->telemetry;
        struct device *dev = &GET_DEV(accel_dev);
        size_t layout_sz = tl_data->layout_sz;
        int ret;

        ret = adf_send_admin_tl_start(accel_dev, telemetry->regs_data_p,
                                      layout_sz, telemetry->rp_num_indexes,
                                      &telemetry->slice_cnt);
        if (ret) {
                dev_err(dev, "failed to start telemetry\n");
                return ret;
        }

        ret = validate_tl_slice_counters(&telemetry->slice_cnt, tl_data->max_sl_cnt);
        if (ret) {
                dev_err(dev, "invalid value returned by FW\n");
                adf_send_admin_tl_stop(accel_dev);
                return ret;
        }

        adf_set_cmdq_cnt(accel_dev, tl_data);

        telemetry->hbuffs = state;
        atomic_set(&telemetry->state, state);

        adf_misc_wq_queue_delayed_work(&telemetry->work_ctx, get_next_timeout());

        return 0;
}

int adf_tl_init(struct adf_accel_dev *accel_dev)
{
        struct adf_tl_hw_data *tl_data = &GET_TL_DATA(accel_dev);
        u8 max_rp = GET_TL_DATA(accel_dev).max_rp;
        struct device *dev = &GET_DEV(accel_dev);
        struct adf_telemetry *telemetry;
        unsigned int i;
        int ret;

        ret = validate_tl_data(tl_data);
        if (ret)
                return ret;

        ret = adf_tl_alloc_mem(accel_dev);
        if (ret) {
                dev_err(dev, "failed to initialize: %d\n", ret);
                return ret;
        }

        telemetry = accel_dev->telemetry;
        telemetry->accel_dev = accel_dev;

        mutex_init(&telemetry->wr_lock);
        mutex_init(&telemetry->regs_hist_lock);
        INIT_DELAYED_WORK(&telemetry->work_ctx, tl_work_handler);

        for (i = 0; i < max_rp; i++)
                telemetry->rp_num_indexes[i] = ADF_TL_RP_REGS_DISABLED;

        return 0;
}

int adf_tl_start(struct adf_accel_dev *accel_dev)
{
        struct device *dev = &GET_DEV(accel_dev);

        if (!accel_dev->telemetry)
                return -EOPNOTSUPP;

        if (!is_tl_supported(accel_dev)) {
                dev_info(dev, "feature not supported by FW\n");
                adf_tl_free_mem(accel_dev);
                return -EOPNOTSUPP;
        }

        return 0;
}

void adf_tl_stop(struct adf_accel_dev *accel_dev)
{
        if (!accel_dev->telemetry)
                return;

        if (atomic_read(&accel_dev->telemetry->state))
                adf_tl_halt(accel_dev);
}

void adf_tl_shutdown(struct adf_accel_dev *accel_dev)
{
        if (!accel_dev->telemetry)
                return;

        adf_tl_free_mem(accel_dev);
}