* 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 "svm_log.h"
#include "devmm_adapt.h"
#include "devmm_common.h"
#include "devmm_mem_alloc_interface.h"
#include "svm_srcu_work.h"
struct devmm_srcu_node {
srcu_subwork_func work_func;
u64 *arg;
u64 arg_size;
ka_list_head_t node;
u32 type;
};
struct devmm_srcu_work default_srcu_work;
void devmm_srcu_work_stats_print(struct devmm_srcu_work *srcu_work)
{
devmm_drv_info("Srcu work stats info. (subwork_num=%llu; subwork_peak_num=%llu)\n",
srcu_work->subwork_num, srcu_work->subwork_peak_num);
}
static struct devmm_srcu_node *_devmm_erase_one_srcu_node(struct devmm_srcu_work *work)
{
struct devmm_srcu_node *srcu_node = NULL;
if (ka_list_empty(&work->head) != 0) {
return NULL;
}
srcu_node = ka_list_last_entry(&work->head, struct devmm_srcu_node, node);
ka_list_del(&srcu_node->node);
work->subwork_num--;
return srcu_node;
}
static struct devmm_srcu_node *devmm_erase_one_srcu_node(struct devmm_srcu_work *srcu_work)
{
struct devmm_srcu_node *srcu_node = NULL;
ka_task_spin_lock_bh(&srcu_work->lock);
srcu_node = _devmm_erase_one_srcu_node(srcu_work);
ka_task_spin_unlock_bh(&srcu_work->lock);
return srcu_node;
}
static void devmm_srcu_base_work(ka_work_struct_t *work)
{
struct devmm_srcu_work *srcu_work = ka_container_of(work, struct devmm_srcu_work, dwork.work);
struct devmm_srcu_node *srcu_node = NULL;
bool is_empty;
srcu_node = devmm_erase_one_srcu_node(srcu_work);
if (srcu_node == NULL) {
return;
}
srcu_node->work_func(srcu_node->arg, srcu_node->arg_size);
devmm_kvfree_ex(srcu_node->arg);
devmm_kvfree_ex(srcu_node);
srcu_node = NULL;
is_empty = (bool)ka_list_empty(&srcu_work->head);
if (is_empty == false) {
(void)ka_task_schedule_delayed_work(&srcu_work->dwork, ka_system_msecs_to_jiffies(0));
#ifndef EMU_ST
ka_task_cond_resched();
#endif
}
}
static void _devmm_srcu_subwork_add(struct devmm_srcu_work *work, struct devmm_srcu_node *subwork)
{
ka_list_add(&subwork->node, &work->head);
work->subwork_num++;
work->subwork_peak_num = (work->subwork_num > work->subwork_peak_num) ? work->subwork_num : work->subwork_peak_num;
}
int devmm_srcu_subwork_add(struct devmm_srcu_work *srcu_work, u32 type, srcu_subwork_func func, u64 *arg, u64 arg_size)
{
struct devmm_srcu_work *srcu_work_tmp = srcu_work;
struct devmm_srcu_node *srcu_node = NULL;
ka_gfp_t flags = (ka_base_in_softirq() != 0) ? (KA_GFP_ATOMIC | __KA_GFP_ACCOUNT) : (KA_GFP_KERNEL | __KA_GFP_ACCOUNT);
int ret;
if (srcu_work_tmp == NULL) {
srcu_work_tmp = &default_srcu_work;
}
srcu_node = devmm_kvzalloc_ex(sizeof(struct devmm_srcu_node), flags);
if (srcu_node == NULL) {
return -ENOMEM;
}
srcu_node->arg = devmm_kvzalloc_ex(arg_size, flags);
if (srcu_node->arg == NULL) {
devmm_kvfree_ex(srcu_node);
return -ENOMEM;
}
ret = memcpy_s(srcu_node->arg, arg_size, arg, arg_size);
if (ret != 0) {
devmm_kvfree_ex(srcu_node->arg);
devmm_kvfree_ex(srcu_node);
return ret;
}
srcu_node->work_func = func;
srcu_node->arg_size = arg_size;
srcu_node->type = type;
ka_task_spin_lock_bh(&srcu_work_tmp->lock);
_devmm_srcu_subwork_add(srcu_work_tmp, srcu_node);
ka_task_spin_unlock_bh(&srcu_work_tmp->lock);
(void)ka_task_schedule_delayed_work(&srcu_work_tmp->dwork, ka_system_msecs_to_jiffies(0));
return 0;
}
void devmm_srcu_work_init(struct devmm_srcu_work *srcu_work)
{
ka_task_spin_lock_init(&srcu_work->lock);
KA_INIT_LIST_HEAD(&srcu_work->head);
KA_TASK_INIT_DELAYED_WORK(&srcu_work->dwork, devmm_srcu_base_work);
srcu_work->subwork_num = 0;
srcu_work->subwork_peak_num = 0;
}
static void devmm_srcu_nodes_destroy(struct devmm_srcu_work *srcu_work)
{
struct devmm_srcu_node *srcu_node = NULL;
u32 stamp = (u32)ka_jiffies;
while (1) {
srcu_node = devmm_erase_one_srcu_node(srcu_work);
if (srcu_node == NULL) {
return;
}
if (srcu_node->type == DEVMM_SRCU_SUBWORK_ENSURE_EXEC_TYPE) {
srcu_node->work_func(srcu_node->arg, srcu_node->arg_size);
}
devmm_kvfree_ex(srcu_node->arg);
devmm_kvfree_ex(srcu_node);
devmm_try_cond_resched(&stamp);
}
}
void devmm_srcu_work_uninit(struct devmm_srcu_work *srcu_work)
{
if (ka_task_cancel_delayed_work_sync(&srcu_work->dwork) == true) {
devmm_drv_debug("Cancel delayed_work return true.\n");
}
devmm_srcu_nodes_destroy(srcu_work);
}
void devmm_default_srcu_work_init(void)
{
devmm_srcu_work_init(&default_srcu_work);
}