* Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/string.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/workqueue.h>
#include <linux/kthread.h>
#include <linux/time.h>
#include <linux/rculist.h>
#include <linux/workqueue.h>
#include "securec.h"
#include "ka_module_init.h"
#include "pbl_mem_alloc_interface.h"
#include "ka_memory_mng.h"
#include "pbl_ka_timer_pub.h"
#include "ka_timer.h"
#ifndef KA_TIMER_ST
#define TIMER_INVALID_ID 0xFFFF
struct ka_sched_task_timer {
struct list_head node_list;
struct mutex node_lock;
struct hrtimer timer;
struct workqueue_struct *workqueue;
u32 task_nums;
u16 ids[MAX_TASK_NUMS];
};
static struct ka_sched_task_timer g_timer = {{0}};
struct devdrv_sched_task_node {
struct list_head list;
struct rcu_head rcu;
u32 expire_count;
u32 cur_count;
u32 node_id;
u64 run_time;
u64 user_data;
int (*exec_task)(u64 data);
struct work_struct work_data;
struct workqueue_struct *workqueue;
timer_task_handler_mode handler_mode;
};
STATIC enum hrtimer_restart ka_timer_irq_handler(struct hrtimer *htr)
{
struct devdrv_sched_task_node *node = NULL;
rcu_read_lock();
list_for_each_entry_rcu(node, &g_timer.node_list, list) {
if (node->cur_count > 1) {
node->cur_count--;
continue;
}
node->cur_count = node->expire_count;
if (node->handler_mode == TIMER_IRQ) {
node->exec_task(node->user_data);
} else if (node->handler_mode == INDEPENDENCE_WORK || node->handler_mode == HIGH_PRI_WORK) {
#ifndef CFG_HOST_ENV
queue_work_on(0, node->workqueue, &node->work_data);
#else
#ifndef DRV_SOFT_UT
queue_work(node->workqueue, &node->work_data);
#endif
#endif
} else {
#ifndef CFG_HOST_ENV
queue_work_on(0, g_timer.workqueue, &node->work_data);
#else
queue_work(g_timer.workqueue, &node->work_data);
#endif
}
}
rcu_read_unlock();
(void)hrtimer_forward_now(htr, ms_to_ktime(TIMER_STEP_MS));
return HRTIMER_RESTART;
}
STATIC void ka_timer_callback_delay(struct work_struct *work)
{
struct devdrv_sched_task_node *sched_task = NULL;
sched_task = container_of(work, struct devdrv_sched_task_node, work_data);
sched_task->exec_task(sched_task->user_data);
}
STATIC void ka_timer_clear_node_id(u32 node_id)
{
if (node_id < MAX_TASK_NUMS) {
g_timer.ids[node_id] = TIMER_INVALID_ID;
}
}
* id Range: 1 ~ MAX_TASK_NUMS
* */
STATIC u32 ka_timer_get_valid_node_id(void)
{
int i;
u32 node_id = TIMER_INVALID_ID;
for (i = 0; i < MAX_TASK_NUMS; i++) {
if (g_timer.ids[i] == TIMER_INVALID_ID) {
g_timer.ids[i] = i;
node_id = (u32)g_timer.ids[i];
break;
}
}
return node_id;
}
STATIC int ka_timer_node_init(const struct ka_timer_task *task, struct devdrv_sched_task_node *node)
{
u32 node_id;
char queue_name[WORKQUEUE_NAME_LENGTH];
int ret;
node_id = ka_timer_get_valid_node_id();
if (node_id == TIMER_INVALID_ID) {
ka_err("Timer task list is full.\n");
return -ENOSPC;
}
node->node_id = node_id;
node->expire_count = task->expire_ms / TIMER_STEP_MS;
if (task->count_ms >= task->expire_ms) {
node->cur_count = 0;
} else {
node->cur_count = (task->expire_ms - task->count_ms) / TIMER_STEP_MS;
}
node->user_data = task->user_data;
node->exec_task = task->exec_task;
node->handler_mode = task->handler_mode;
node->run_time = 0;
node->workqueue = NULL;
INIT_WORK(&node->work_data, ka_timer_callback_delay);
if ((task->handler_mode != INDEPENDENCE_WORK) && (task->handler_mode != HIGH_PRI_WORK)) {
return 0;
}
ret = snprintf_s(queue_name, sizeof(queue_name), sizeof(queue_name) - 1, "ka_timer_%u", node_id);
if (ret <= 0) {
ka_timer_clear_node_id(node_id);
ka_err("Call snprintf_s failed. (node_id=%u; ret=%d)\n", node_id, ret);
return -EFAULT;
}
if (task->handler_mode == INDEPENDENCE_WORK) {
node->workqueue = create_singlethread_workqueue(queue_name);
} else {
node->workqueue = alloc_workqueue("%s", WQ_HIGHPRI, 1, queue_name);
}
if (node->workqueue == NULL) {
ka_timer_clear_node_id(node_id);
ka_err("Timer task queue is NULL.\n");
return -ENOMEM;
}
return 0;
}
#endif
int ka_timer_task_register(const struct ka_timer_task *task, u32 *node_id)
{
#ifndef KA_TIMER_ST
struct devdrv_sched_task_node *node = NULL;
int ret;
if ((task == NULL) || (node_id == NULL) || task->exec_task == NULL) {
ka_err("Timer task param invalid. node or task is null.\n");
return -EINVAL;
}
if (task->expire_ms < TIMER_STEP_MS) {
ka_err("Timer task param invalid. expire_ms(%u) < TIMER_STEP_MS.\n", task->expire_ms);
return -EINVAL;
}
if (g_timer.task_nums >= MAX_TASK_NUMS) {
ka_err("Timer task list is full.\n");
return -ENOSPC;
}
mutex_lock(&g_timer.node_lock);
node = kzalloc(sizeof(struct devdrv_sched_task_node), GFP_ATOMIC | __GFP_ACCOUNT);
if (node == NULL) {
mutex_unlock(&g_timer.node_lock);
ka_err("Timer task node malloc failed.\n");
return -ENOMEM;
}
ret = ka_timer_node_init(task, node);
if (ret != 0) {
kfree(node);
node = NULL;
mutex_unlock(&g_timer.node_lock);
ka_err("Timer task node init failed. (ret=%d)\n", ret);
return ret;
}
*node_id = node->node_id;
list_add_tail_rcu(&node->list, &g_timer.node_list);
g_timer.task_nums++;
mutex_unlock(&g_timer.node_lock);
ka_info("Timer register success. (node_id=%u; expire_ms=%u)\n", *node_id, task->expire_ms);
#endif
return 0;
}
EXPORT_SYMBOL_GPL(ka_timer_task_register);
int ka_timer_task_unregister(u32 node_id)
{
#ifndef KA_TIMER_ST
struct devdrv_sched_task_node *node = NULL;
mutex_lock(&g_timer.node_lock);
list_for_each_entry(node, &g_timer.node_list, list) {
if (node->node_id == node_id) {
g_timer.task_nums--;
list_del_rcu(&node->list);
synchronize_rcu();
if (node->workqueue != NULL) {
flush_workqueue(node->workqueue);
destroy_workqueue(node->workqueue);
} else {
flush_workqueue(g_timer.workqueue);
}
ka_timer_clear_node_id(node_id);
mutex_unlock(&g_timer.node_lock);
kfree(node);
ka_info("Timer unregister success. (node_id=%u)\n", node_id);
return 0;
}
}
mutex_unlock(&g_timer.node_lock);
ka_err("Timer node does not exist. (node_id=%u)\n", node_id);
return -EINVAL;
#endif
}
EXPORT_SYMBOL_GPL(ka_timer_task_unregister);
int ka_timer_init(void)
{
#ifndef KA_TIMER_ST
int i;
INIT_LIST_HEAD_RCU(&g_timer.node_list);
mutex_init(&g_timer.node_lock);
hrtimer_init(&g_timer.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
for (i = 0; i < MAX_TASK_NUMS; i++) {
g_timer.ids[i] = TIMER_INVALID_ID;
}
g_timer.timer.function = ka_timer_irq_handler;
g_timer.task_nums = 0;
g_timer.workqueue = create_workqueue("ka_timer_common");
if (g_timer.workqueue == NULL) {
ka_err("Timer task work queue is NULL.\n");
return -ENOMEM;
}
hrtimer_start(&g_timer.timer, ms_to_ktime(TIMER_STEP_MS), HRTIMER_MODE_REL);
ka_info("Dms timer init success, start timer.\n");
#endif
return 0;
}
void ka_timer_uninit(void)
{
#ifndef KA_TIMER_ST
struct devdrv_sched_task_node *node = NULL;
struct devdrv_sched_task_node *next = NULL;
mutex_lock(&g_timer.node_lock);
(void)hrtimer_cancel(&g_timer.timer);
ka_info("Dms timer uninit success, cancel timer.\n");
synchronize_rcu();
if (g_timer.workqueue != NULL) {
flush_workqueue(g_timer.workqueue);
destroy_workqueue(g_timer.workqueue);
g_timer.workqueue = NULL;
}
list_for_each_entry_safe(node, next, &g_timer.node_list, list) {
if (node->workqueue != NULL) {
flush_workqueue(node->workqueue);
destroy_workqueue(node->workqueue);
}
kfree(node);
node = NULL;
}
mutex_unlock(&g_timer.node_lock);
mutex_destroy(&g_timer.node_lock);
#endif
}