root/sys/dev/qat/qat_common/adf_gen4_timer.c
/* SPDX-License-Identifier: BSD-3-Clause */
/* Copyright(c) 2007-2026 Intel Corporation */
#include "adf_accel_devices.h"
#include "adf_heartbeat.h"
#include "adf_common_drv.h"
#include "icp_qat_fw_init_admin.h"
#include "adf_gen4_timer.h"

#include "adf_dev_err.h"

#define ADF_GEN4_INT_TIMER_VALUE_IN_MS 200
/* Interval within timer interrupt. Value in miliseconds. */

#define ADF_GEN4_MAX_INT_TIMER_VALUE_IN_MS 0xFFFFFFFF
/* MAX Interval within timer interrupt. Value in miliseconds. */

static u64
adf_get_next_timeout(u32 timeout_val)
{
        u64 timeout = msecs_to_jiffies(timeout_val);

        return rounddown(jiffies + timeout, timeout);
}

static void
adf_hb_irq_bh_handler(struct work_struct *work)
{
        struct icp_qat_fw_init_admin_req req = { 0 };
        struct icp_qat_fw_init_admin_resp resp = { 0 };
        struct adf_hb_timer_data *hb_timer_data =
            container_of(work, struct adf_hb_timer_data, hb_int_timer_work);
        struct adf_accel_dev *accel_dev = hb_timer_data->accel_dev;
        struct adf_hw_device_data *hw_data = accel_dev->hw_device;
        u64 ae_mask = hw_data->ae_mask;

        if (!accel_dev->int_timer || !accel_dev->int_timer->enabled)
                goto end;

        /* Update heartbeat count via init/admin cmd */
        if (!accel_dev->admin) {
                device_printf(GET_DEV(accel_dev),
                              "adf_admin is not available\n");
                goto end;
        }

        req.cmd_id = ICP_QAT_FW_HEARTBEAT_SYNC;
        req.heartbeat_ticks = accel_dev->int_timer->int_cnt;

        if (adf_send_admin(accel_dev, &req, &resp, ae_mask))
                device_printf(GET_DEV(accel_dev),
                              "Failed to update qat's HB count\n");

end:
        kfree(hb_timer_data);
}

static void
timer_handler(struct timer_list *tl)
{
        struct adf_int_timer *int_timer =
            timer_container_of(int_timer, tl, timer);
        struct adf_accel_dev *accel_dev = int_timer->accel_dev;
        struct adf_hb_timer_data *hb_timer_data = NULL;
        u64 timeout_val = adf_get_next_timeout(int_timer->timeout_val);
        /* Update TL TBD */

        /* Schedule a heartbeat work queue to update HB */
        hb_timer_data = kzalloc(sizeof(*hb_timer_data), GFP_ATOMIC);
        if (hb_timer_data) {
                hb_timer_data->accel_dev = accel_dev;

                INIT_WORK(&hb_timer_data->hb_int_timer_work,
                          adf_hb_irq_bh_handler);
                queue_work(int_timer->timer_irq_wq,
                           &hb_timer_data->hb_int_timer_work);
        } else {
                device_printf(GET_DEV(accel_dev),
                              "Failed to alloc heartbeat timer data\n");
        }
        int_timer->int_cnt++;
        mod_timer(tl, timeout_val);
}

int
adf_int_timer_init(struct adf_accel_dev *accel_dev)
{
        u64 timeout_val = adf_get_next_timeout(ADF_GEN4_INT_TIMER_VALUE_IN_MS);
        struct adf_int_timer *int_timer = NULL;
        char wqname[32] = { 0 };

        if (!accel_dev)
                return 0;

        int_timer = kzalloc(sizeof(*int_timer), GFP_KERNEL);
        if (!int_timer)
                return -ENOMEM;

        sprintf(wqname, "qat_timer_wq_%d", accel_dev->accel_id);

        int_timer->timer_irq_wq = alloc_workqueue(wqname, WQ_MEM_RECLAIM, 1);

        if (!int_timer->timer_irq_wq) {
                kfree(int_timer);
                return -ENOMEM;
        }

        int_timer->accel_dev = accel_dev;
        int_timer->timeout_val = ADF_GEN4_INT_TIMER_VALUE_IN_MS;
        int_timer->int_cnt = 0;
        int_timer->enabled = true;
        accel_dev->int_timer = int_timer;

        timer_setup(&int_timer->timer, timer_handler, 0);
        mod_timer(&int_timer->timer, timeout_val);

        return 0;
}

void
adf_int_timer_exit(struct adf_accel_dev *accel_dev)
{
        if (accel_dev && accel_dev->int_timer) {
                del_timer_sync(&accel_dev->int_timer->timer);
                accel_dev->int_timer->enabled = false;

                if (accel_dev->int_timer->timer_irq_wq) {
                        flush_workqueue(accel_dev->int_timer->timer_irq_wq);
                        destroy_workqueue(accel_dev->int_timer->timer_irq_wq);
                }

                kfree(accel_dev->int_timer);
                accel_dev->int_timer = NULL;
        }
}