* 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 "uda_dev.h"
#include "pbl_mem_alloc_interface.h"
#include "uda_notifier.h"
#include "ka_kernel_def_pub.h"
#include "ka_memory_pub.h"
#include "ka_list_pub.h"
#include "ka_task_pub.h"
#include "ka_base_pub.h"
#define UDA_PRI_DEC_ORDER 0
#define UDA_PRI_INC_ORDER 1
static struct uda_notifiers uda_notifiers[UDA_HW_MAX][UDA_OBJECT_MAX][UDA_LOCATION_MAX][UDA_PROP_MAX];
static struct uda_notifiers *uda_get_notifiers(struct uda_dev_type *type)
{
return &uda_notifiers[type->hw][type->object][type->location][type->prop];
}
static int uda_get_action_notifier_order(enum uda_notified_action action)
{
return ((action == UDA_UNINIT) || (action == UDA_RESUME) ||
(action == UDA_SHUTDOWN) || (action == UDA_HOTRESET_CANCEL) ||
(action == UDA_PRE_HOTRESET_CANCEL)) ? UDA_PRI_DEC_ORDER : UDA_PRI_INC_ORDER;
}
static bool uda_is_ctrl_action(enum uda_notified_action action)
{
return ((action >= UDA_SUSPEND) && (action < UDA_ACTION_MAX));
}
static int uda_single_notifier_call(struct uda_notifier_node *nf, u32 udevid, enum uda_notified_action action)
{
int ret;
if (uda_is_action_conflict(nf->status[udevid], action)) {
uda_info("Conflict action. (notifier=%s; action=%d; udevid=%u; status=%x)\n",
nf->notifier, action, udevid, nf->status[udevid]);
return 0;
}
nf->call_count++;
ret = nf->func(udevid, action);
if (ret == 0) {
uda_update_status_by_action(&nf->status[udevid], action);
}
nf->call_finish++;
return ret;
}
static int uda_single_pri_notifier_call(ka_list_head_t *nf_head, u32 udevid, enum uda_notified_action action)
{
struct uda_notifier_node *nf = NULL;
ka_list_for_each_entry(nf, nf_head, node) {
int ret = uda_single_notifier_call(nf, udevid, action);
if (ret != 0) {
uda_warn("Notifier call warn. (notifier=%s; action=%d; ret=%d)\n", nf->notifier, action, ret);
if (uda_is_ctrl_action(action)) {
return ret;
}
}
}
return 0;
}
static int _uda_notifier_call(struct uda_notifiers *notifiers, u32 udevid, enum uda_notified_action action)
{
int i;
for (i = 0; i < UDA_PRI_MAX; i++) {
int pri = (uda_get_action_notifier_order(action) == UDA_PRI_DEC_ORDER) ? (UDA_PRI_MAX - 1 - i) : i;
int ret = uda_single_pri_notifier_call(¬ifiers->pri_head[pri], udevid, action);
if (ret != 0) {
return ret;
}
}
return 0;
}
int uda_notifier_call(u32 udevid, struct uda_dev_type *type, enum uda_notified_action action)
{
struct uda_notifiers *notifiers = uda_get_notifiers(type);
int ret;
ka_task_down_read(¬ifiers->sem);
ret = _uda_notifier_call(notifiers, udevid, action);
ka_task_up_read(¬ifiers->sem);
return ret;
}
static struct uda_notifier_node *uda_find_notifier_node(struct uda_notifiers *notifiers, const char *notifier)
{
int i;
for (i = 0; i < UDA_PRI_MAX; i++) {
ka_list_head_t *nf_head = ¬ifiers->pri_head[i];
struct uda_notifier_node *nf = NULL;
ka_list_for_each_entry(nf, nf_head, node) {
if (ka_base_strcmp(nf->notifier, notifier) == 0) {
return nf;
}
}
}
return NULL;
}
static struct uda_notifier_node *uda_create_notifier(const char *notifier, uda_notify func)
{
struct uda_notifier_node *nf = dbl_kzalloc(sizeof(struct uda_notifier_node), KA_GFP_KERNEL);
if (nf == NULL) {
uda_err("Out of memory.\n");
return NULL;
}
nf->notifier = notifier;
nf->func = func;
return nf;
}
static void uda_destroy_notifier(struct uda_notifier_node *nf)
{
dbl_kfree(nf);
}
static void uda_notifier_change_action(struct uda_notifier_node *nf, struct uda_dev_type *type,
enum uda_notified_action action)
{
u32 i;
struct uda_dev_inst *dev_inst = NULL;
struct uda_dev_inst *agent_dev_inst = NULL;
for (i = 0; i < UDA_UDEV_MAX_NUM; i++) {
dev_inst = uda_dev_inst_get(i);
if (dev_inst == NULL) {
continue;
}
if (!uda_is_removed_status(dev_inst->status)) {
if (uda_dev_type_is_match(type, &dev_inst->type)) {
uda_warn("Action. (notifier=%s; udevid=%d; action=%d)\n", nf->notifier, i, action);
(void)uda_single_notifier_call(nf, i, action);
}
agent_dev_inst = dev_inst->agent_dev;
if ((agent_dev_inst != NULL) && (uda_dev_type_is_match(type, &agent_dev_inst->type))) {
uda_warn("Action agent. (notifier=%s; udevid=%d; action=%d)\n", nf->notifier, i, action);
(void)uda_single_notifier_call(nf, i, action);
}
}
uda_dev_inst_put(dev_inst);
}
}
static int _uda_notifier_register(struct uda_notifiers *notifiers, struct uda_dev_type *type,
const char *notifier, enum uda_priority pri, uda_notify func)
{
struct uda_notifier_node *nf = uda_find_notifier_node(notifiers, notifier);
if (nf != NULL) {
uda_err("Repeat register. (notifier=%s)\n", notifier);
return -EINVAL;
}
nf = uda_create_notifier(notifier, func);
if (nf == NULL) {
uda_err("Create notifier node failed. (notifier=%s)\n", notifier);
return -ENOMEM;
}
ka_list_add_tail(&nf->node, ¬ifiers->pri_head[pri]);
uda_notifier_change_action(nf, type, UDA_INIT);
uda_info("Register notifier success. (notifier=%s; pri=%d)\n", notifier, pri);
return 0;
}
int hal_kernel_uda_notifier_register(const char *notifier, struct uda_dev_type *type, enum uda_priority pri, uda_notify func)
{
return uda_notifier_register(notifier,type,pri,func);
}
KA_EXPORT_SYMBOL(hal_kernel_uda_notifier_register);
int uda_notifier_register(const char *notifier, struct uda_dev_type *type, enum uda_priority pri, uda_notify func)
{
struct uda_notifiers *notifiers = NULL;
int ret;
if ((notifier == NULL) || (type == NULL) || (func == NULL)) {
uda_err("Null ptr.\n");
return -EINVAL;
}
ret = uda_dev_type_valid_check(type);
if (ret != 0) {
uda_err("Invalid type. (notifier=%s)\n", notifier);
return ret;
}
if ((pri < 0) || (pri >= UDA_PRI_MAX)) {
uda_err("Invalid pri. (notifier=%s; pri=%d)\n", notifier, pri);
return -EINVAL;
}
notifiers = uda_get_notifiers(type);
ka_task_down_write(¬ifiers->sem);
ret = _uda_notifier_register(notifiers, type, notifier, pri, func);
ka_task_up_write(¬ifiers->sem);
return ret;
}
KA_EXPORT_SYMBOL(uda_notifier_register);
static int _uda_notifier_unregister(struct uda_notifiers *notifiers, struct uda_dev_type *type, const char *notifier)
{
struct uda_notifier_node *nf = NULL;
nf = uda_find_notifier_node(notifiers, notifier);
if (nf == NULL) {
uda_err("Repeat unregister. (notifier=%s)\n", notifier);
return -EINVAL;
}
uda_notifier_change_action(nf, type, UDA_UNINIT);
ka_list_del(&nf->node);
uda_destroy_notifier(nf);
uda_info("Unregister notifier success. (notifier=%s)\n", notifier);
return 0;
}
int hal_kernel_uda_notifier_unregister(const char *notifier, struct uda_dev_type *type)
{
return uda_notifier_unregister(notifier,type);
}
KA_EXPORT_SYMBOL(hal_kernel_uda_notifier_unregister);
int uda_notifier_unregister(const char *notifier, struct uda_dev_type *type)
{
struct uda_notifiers *notifiers = NULL;
int ret;
if ((notifier == NULL) || (type == NULL)) {
uda_err("Null ptr.\n");
return -EINVAL;
}
ret = uda_dev_type_valid_check(type);
if (ret != 0) {
uda_err("Invalid type. (notifier=%s)\n", notifier);
return ret;
}
notifiers = uda_get_notifiers(type);
ka_task_down_write(¬ifiers->sem);
ret = _uda_notifier_unregister(notifiers, type, notifier);
ka_task_up_write(¬ifiers->sem);
return ret;
}
KA_EXPORT_SYMBOL(uda_notifier_unregister);
void uda_for_each_notifiers(void *priv,
void (*func)(struct uda_dev_type *type, struct uda_notifiers *notifiers, void *priv))
{
struct uda_dev_type type;
for (type.hw = 0; type.hw < UDA_HW_MAX; type.hw++) {
for (type.object = 0; type.object < UDA_OBJECT_MAX; type.object++) {
for (type.location = 0; type.location < UDA_LOCATION_MAX; type.location++) {
for (type.prop = 0; type.prop < UDA_PROP_MAX; type.prop++) {
func(&type, uda_get_notifiers(&type), priv);
}
}
}
}
}
static void _uda_notifiers_init(struct uda_dev_type *type, struct uda_notifiers *notifiers, void *priv)
{
int i;
for (i = 0; i < UDA_PRI_MAX; i++) {
KA_INIT_LIST_HEAD(¬ifiers->pri_head[i]);
}
ka_task_init_rwsem(¬ifiers->sem);
}
int uda_notifier_init(void)
{
uda_for_each_notifiers(NULL, _uda_notifiers_init);
return 0;
}
static void _uda_notifiers_uninit(struct uda_dev_type *type, struct uda_notifiers *notifiers, void *priv)
{
int i;
for (i = 0; i < UDA_PRI_MAX; i++) {
ka_list_head_t *nf_head = ¬ifiers->pri_head[i];
struct uda_notifier_node *nf = NULL, *n = NULL;
ka_list_for_each_entry_safe(nf, n, nf_head, node) {
ka_list_del(&nf->node);
uda_destroy_notifier(nf);
}
}
}
void uda_notifier_uninit(void)
{
uda_for_each_notifiers(NULL, _uda_notifiers_uninit);
}