* 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 "ka_base_pub.h"
#include "ka_common_pub.h"
#include "ka_compiler_pub.h"
#include "ka_dfx_pub.h"
#include "ka_ioctl_pub.h"
#include "apm_auto_init.h"
#include "pbl/pbl_uda.h"
#include "securec.h"
#include "apm_kernel_ioctl.h"
#include "apm_fops.h"
#include "apm_res_map_ops.h"
#include "apm_res_map.h"
#include "apm_slab.h"
static struct task_ctx_domain *res_map_domain = NULL;
static struct apm_task_res_map_ops *task_res_ops = NULL;
void apm_task_res_map_ops_register(struct apm_task_res_map_ops *ops)
{
task_res_ops = ops;
}
int apm_fops_res_info_check(struct res_map_info_in *res_info)
{
if (res_info == NULL) {
apm_err("Null ptr.\n");
return -EINVAL;
}
if ((res_info->res_type < 0) || (res_info->res_type >= RES_ADDR_TYPE_MAX)) {
apm_err("Invalid para. (res_type=%d)\n", res_info->res_type);
return -ERANGE;
}
if ((res_info->target_proc_type < 0) || (res_info->target_proc_type >= PROCESS_CPTYPE_MAX)) {
apm_err("Invalid para. (target_proc_type=%d)\n", res_info->target_proc_type);
return -ERANGE;
}
if (res_info->flag != 0) {
apm_err("Invalid para. (flag=0x%x)\n", res_info->flag);
return -EINVAL;
}
return 0;
}
static int apm_res_map_get_map_tgid(u32 udevid, struct res_map_info_in *res_info, int *master_tgid, int *slave_tgid)
{
int current_tgid = ka_task_get_current_tgid();
int ret = 0;
*master_tgid = current_tgid;
ret = hal_kernel_apm_query_slave_tgid_by_master(current_tgid, udevid, res_info->target_proc_type, slave_tgid);
if (ret != 0) {
u32 proc_type_bitmap, query_udevid;
int mode;
*slave_tgid = current_tgid;
ret = apm_query_master_info_by_slave(*slave_tgid, master_tgid, &query_udevid, &mode, &proc_type_bitmap);
if ((ret != 0) || (query_udevid != udevid)) {
apm_err("Current is not apm task. (udevid=%u; q_udevid=%u; res_type=%u; res_id=%u; proc_type=%d)\n",
udevid, query_udevid, res_info->res_type, res_info->res_id, res_info->target_proc_type);
return -ESRCH;
}
if ((proc_type_bitmap & (0x1 << res_info->target_proc_type)) == 0) {
ret = hal_kernel_apm_query_slave_tgid_by_master(*master_tgid, udevid, res_info->target_proc_type,
slave_tgid);
if (ret != 0) {
apm_err("Get slave tgid failed. (master_tgid=%d; udevid=%u; res_type=%u; res_id=%u; proc_type=%d)\n",
*master_tgid, udevid, res_info->res_type, res_info->res_id, res_info->target_proc_type);
return -ESRCH;
}
}
}
return 0;
}
int apm_res_addr_map(u32 udevid, struct res_map_info_in *res_info, u64 *va, u32 *len)
{
int ret, master_tgid, slave_tgid;
int tgid = ka_task_get_current_tgid();
struct apm_res_map_info para = {0};
ret = apm_fops_res_info_check(res_info);
if (ret != 0) {
return ret;
}
ret = apm_res_map_get_map_tgid(udevid, res_info, &master_tgid, &slave_tgid);
if (ret != 0) {
apm_err("Get map tgids failed. (ret=%d; udevid=%u; res_type=%u; res_id=%u; proc_type=%d)\n",
ret, udevid, res_info->res_type, res_info->res_id, res_info->target_proc_type);
return ret;
}
if (!apm_res_is_belong_to_proc(master_tgid, slave_tgid, udevid, res_info)) {
apm_err("Current not has res. (udevid=%u; res_type=%u; res_id=%u; proc_type=%d)\n",
udevid, res_info->res_type, res_info->res_id, res_info->target_proc_type);
return -EPERM;
}
ret = apm_update_res_info(udevid, res_info);
if (ret != 0) {
return ret;
}
para.slave_tgid = slave_tgid;
para.udevid = udevid;
para.res_info = *res_info;
ret = task_res_ops->res_map(¶);
if (ret != 0) {
apm_err("Res map failed. (ret=%d; udevid=%u; res_type=%u; res_id=%u; proc_type=%d)\n",
ret, udevid, res_info->res_type, res_info->res_id, res_info->target_proc_type);
return ret;
}
ret = apm_res_map_add_node(res_map_domain, tgid, ¶);
if (ret != 0) {
(void)task_res_ops->res_unmap(¶);
apm_err("Add node failed. (ret=%d; udevid=%u; res_type=%u; res_id=%u; proc_type=%d)\n",
ret, udevid, res_info->res_type, res_info->res_id, res_info->target_proc_type);
return ret;
}
*va = (u64)para.va;
*len = para.len;
return 0;
}
int apm_res_addr_unmap(u32 udevid, struct res_map_info_in *res_info)
{
int ret, tgid = ka_task_get_current_tgid();
struct apm_res_map_info para = {0};
ret = apm_fops_res_info_check(res_info);
if (ret != 0) {
return ret;
}
ret = apm_update_res_info(udevid, res_info);
if (ret != 0) {
return ret;
}
para.slave_tgid = 0;
para.udevid = udevid;
para.res_info = *res_info;
para.va = 0;
ret = apm_res_map_query_node(res_map_domain, tgid, ¶);
if (ret == 0) {
(void)apm_res_map_del_node(res_map_domain, tgid, ¶);
}
ret = task_res_ops->res_unmap(¶);
if (ret != 0) {
#ifndef EMU_ST
apm_info_ratelimited("Res unmap. (ret=%d; udevid=%u; res_type=%u; res_id=%u; proc_type=%d)\n",
ret, udevid, res_info->res_type, res_info->res_id, res_info->target_proc_type);
#endif
}
return ret;
}
static int _apm_res_addr_query(u32 udevid, struct res_map_info_in *res_info, u64 *va, u32 *len)
{
struct apm_res_map_info para;
int ret, tgid = ka_task_get_current_tgid();
para.udevid = udevid;
para.res_info = *res_info;
ret = apm_res_map_query_node(res_map_domain, tgid, ¶);
if (ret != 0) {
ret = task_res_ops->res_map_query(¶);
if (ret != 0) {
apm_debug("Res not map. (ret=%d; tgid=%d; udevid=%u; res_type=%u; res_id=%u; proc_type=%d)\n",
ret, tgid, udevid, res_info->res_type, res_info->res_id, res_info->target_proc_type);
return ret;
}
}
*va = (u64)para.va;
*len = para.len;
return 0;
}
int apm_res_addr_query(u32 udevid, struct res_map_info_in *res_info, u64 *va, u32 *len)
{
int ret;
if ((va == NULL) || (len == NULL)) {
apm_err("Null ptr.\n");
return -EINVAL;
}
ret = apm_fops_res_info_check(res_info);
if (ret != 0) {
return ret;
}
return _apm_res_addr_query(udevid, res_info, va, len);
}
static bool apm_res_map_support_priv(struct res_map_info_in *res_info)
{
if ((res_info->res_type == RES_ADDR_TYPE_HCCP_URMA_JETTY) || (res_info->res_type == RES_ADDR_TYPE_HCCP_URMA_JFC) ||
(res_info->res_type == RES_ADDR_TYPE_HCCP_URMA_DB) || (res_info->res_type == RES_ADDR_TYPE_NDA_URMA_DB) ||
(res_info->res_type == RES_ADDR_TYPE_STARS_NOTIFY_RECORD)) {
return true;
}
return false;
}
static int apm_init_res_map_info_priv(struct res_map_info_in *res_info)
{
char priv_tmp_buffer[APM_RES_MAP_INFO_PRIV_LEN_MAX] = {0};
void *priv_buffer = NULL;
int ret = 0;
if (((res_info->res_type == RES_ADDR_TYPE_HCCP_URMA_JETTY) || (res_info->res_type == RES_ADDR_TYPE_HCCP_URMA_JFC) ||
(res_info->res_type == RES_ADDR_TYPE_HCCP_URMA_DB) || (res_info->res_type == RES_ADDR_TYPE_NDA_URMA_DB)) &&
((res_info->priv == NULL) || (res_info->priv_len == 0) || (res_info->priv_len > APM_RES_MAP_INFO_PRIV_LEN_MAX))) {
apm_err("Invalid para. (res_type=%d)\n", res_info->res_type);
return -EINVAL;
}
if (res_info->priv == NULL) {
return 0;
}
if (!apm_res_map_support_priv(res_info)) {
return -EOPNOTSUPP;
}
if ((res_info->priv != NULL) && ((res_info->priv_len == 0) || (res_info->priv_len > APM_RES_MAP_INFO_PRIV_LEN_MAX))) {
apm_err("Invalid para. (priv_len=%u)\n", res_info->priv_len);
return -EINVAL;
}
ret = (int)ka_base_copy_from_user(priv_tmp_buffer, res_info->priv, res_info->priv_len);
if (ret != 0) {
apm_err("Copy from user failed. (ret=%d)\n", ret);
return -EINVAL;
}
priv_buffer = apm_kmalloc(res_info->priv_len, KA_GFP_KERNEL | __KA_GFP_ACCOUNT);
if (priv_buffer == NULL) {
apm_err("Kmalloc priv bufferfailed. (len=0x%x)\n", res_info->priv_len);
return -ENOMEM;
}
ret = memcpy_s(priv_buffer, res_info->priv_len, (void *)priv_tmp_buffer, res_info->priv_len);
if (ret != 0) {
apm_kfree(priv_buffer);
return -EINVAL;
}
res_info->priv = priv_buffer;
return 0;
}
static void apm_uninit_res_map_info_priv(struct res_map_info_in *res_info)
{
if (res_info->priv != NULL) {
apm_kfree(res_info->priv);
res_info->priv = NULL;
}
}
static int apm_fops_res_map(u32 cmd, unsigned long arg)
{
struct apm_cmd_res_map *usr_arg = (struct apm_cmd_res_map __ka_user *)(uintptr_t)arg;
struct apm_cmd_res_map para;
int ret;
ret = (int)ka_base_copy_from_user(¶, usr_arg, sizeof(para));
if (ret != 0) {
apm_err("Copy from user failed. (ret=%d)\n", ret);
return ret;
}
ret = uda_devid_to_udevid(para.devid, ¶.devid);
if (ret != 0) {
return -ENODEV;
}
ret = apm_init_res_map_info_priv(¶.res_info);
if (ret != 0) {
return ret;
}
ret = apm_res_addr_map(para.devid, ¶.res_info, (u64 *)¶.va, ¶.len);
if (ret != 0) {
apm_uninit_res_map_info_priv(¶.res_info);
return ret;
}
apm_uninit_res_map_info_priv(¶.res_info);
return (int)ka_base_copy_to_user(usr_arg, ¶, sizeof(para));
}
static int apm_fops_res_unmap(u32 cmd, unsigned long arg)
{
struct apm_cmd_res_unmap *usr_arg = (struct apm_cmd_res_unmap __ka_user *)(uintptr_t)arg;
struct apm_cmd_res_unmap para;
int ret;
ret = (int)ka_base_copy_from_user(¶, usr_arg, sizeof(para));
if (ret != 0) {
apm_err("Copy from user failed. (ret=%d)\n", ret);
return ret;
}
ret = uda_devid_to_udevid(para.devid, ¶.devid);
if (ret != 0) {
return -ENODEV;
}
return apm_res_addr_unmap(para.devid, ¶.res_info);
}
void apm_res_map_domain_task_exit(u32 udevid, int tgid, struct task_start_time *start_time)
{
if (!apm_notify_task_exit(tgid, start_time)) {
apm_res_map_ctx_destroy(res_map_domain, tgid);
}
}
DECLAER_FEATURE_AUTO_UNINIT_TASK(apm_res_map_domain_task_exit, FEATURE_LOADER_STAGE_6);
static int apm_res_map_domain_task_exit_notifier(ka_notifier_block_t *self, unsigned long val, void *data)
{
apm_debug("Res map exit notifier. (tgid=%d; stage=%d)\n", apm_get_exit_tgid(val), apm_get_exit_stage(val));
if (apm_get_exit_stage(val) == APM_STAGE_DO_EXIT) {
apm_res_map_ctx_destroy(res_map_domain, apm_get_exit_tgid(val));
}
return KA_NOTIFY_OK;
}
static ka_notifier_block_t apm_res_map_master_task_exit_nb = {
.notifier_call = apm_res_map_domain_task_exit_notifier,
.priority = APM_EXIT_NOTIFIY_PRI_APM_RES,
};
static ka_notifier_block_t apm_res_map_slave_task_exit_nb = {
.notifier_call = apm_res_map_domain_task_exit_notifier,
.priority = APM_EXIT_NOTIFIY_PRI_APM_RES,
};
void apm_res_map_domain_task_show(u32 udevid, int tgid, int feature_id, ka_seq_file_t *seq)
{
if (tgid != 0) {
apm_res_map_ctx_show(res_map_domain, tgid, seq);
} else {
task_ctx_domain_show(res_map_domain, seq);
}
}
DECLAER_FEATURE_AUTO_SHOW_TASK(apm_res_map_domain_task_show, FEATURE_LOADER_STAGE_6);
int apm_res_map_init(void)
{
res_map_domain = task_ctx_domain_create("res_map_domain", 0);
if (res_map_domain == NULL) {
return -ENOMEM;
}
apm_register_ioctl_cmd_func(_KA_IOC_NR(APM_RES_ADDR_MAP), apm_fops_res_map);
apm_register_ioctl_cmd_func(_KA_IOC_NR(APM_RES_ADDR_UNMAP), apm_fops_res_unmap);
(void)apm_master_exit_register(&apm_res_map_master_task_exit_nb);
(void)apm_slave_exit_register(&apm_res_map_slave_task_exit_nb);
return 0;
}
DECLAER_FEATURE_AUTO_INIT(apm_res_map_init, FEATURE_LOADER_STAGE_6);
void apm_res_map_uninit(void)
{
apm_master_exit_unregister(&apm_res_map_master_task_exit_nb);
apm_slave_exit_unregister(&apm_res_map_slave_task_exit_nb);
task_ctx_domain_destroy(res_map_domain);
res_map_domain = NULL;
}
DECLAER_FEATURE_AUTO_UNINIT(apm_res_map_uninit, FEATURE_LOADER_STAGE_6);