root/drivers/net/ethernet/huawei/hinic3/hinic3_mgmt.c
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) Huawei Technologies Co., Ltd. 2025. All rights reserved.

#include "hinic3_eqs.h"
#include "hinic3_hwdev.h"
#include "hinic3_hwif.h"
#include "hinic3_mbox.h"
#include "hinic3_mgmt.h"

#define HINIC3_MSG_TO_MGMT_MAX_LEN  2016

#define MGMT_MAX_PF_BUF_SIZE        2048UL
#define MGMT_SEG_LEN_MAX            48
#define MGMT_ASYNC_MSG_FLAG         0x8

#define HINIC3_MGMT_WQ_NAME         "hinic3_mgmt"

/* Bogus sequence ID to prevent accidental match following partial message */
#define MGMT_BOGUS_SEQ_ID  \
        (MGMT_MAX_PF_BUF_SIZE / MGMT_SEG_LEN_MAX + 1)

static void
hinic3_mgmt_resp_msg_handler(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt,
                             struct hinic3_recv_msg *recv_msg)
{
        struct device *dev = pf_to_mgmt->hwdev->dev;

        /* Ignore async msg */
        if (recv_msg->msg_id & MGMT_ASYNC_MSG_FLAG)
                return;

        spin_lock(&pf_to_mgmt->sync_event_lock);
        if (recv_msg->msg_id != pf_to_mgmt->sync_msg_id) {
                dev_err(dev, "msg id mismatch, send msg id: 0x%x, recv msg id: 0x%x, event state: %d\n",
                        pf_to_mgmt->sync_msg_id, recv_msg->msg_id,
                        pf_to_mgmt->event_flag);
        } else if (pf_to_mgmt->event_flag == COMM_SEND_EVENT_START) {
                pf_to_mgmt->event_flag = COMM_SEND_EVENT_SUCCESS;
                complete(&recv_msg->recv_done);
        } else {
                dev_err(dev, "Wait timeout, send msg id: 0x%x, recv msg id: 0x%x, event state: %d\n",
                        pf_to_mgmt->sync_msg_id, recv_msg->msg_id,
                        pf_to_mgmt->event_flag);
        }
        spin_unlock(&pf_to_mgmt->sync_event_lock);
}

static void hinic3_recv_mgmt_msg_work_handler(struct work_struct *work)
{
        struct hinic3_msg_pf_to_mgmt *pf_to_mgmt;
        struct mgmt_msg_handle_work *mgmt_work;
        struct mgmt_msg_head *ack_cmd;

        mgmt_work = container_of(work, struct mgmt_msg_handle_work, work);

        /* At the moment, we do not expect any meaningful messages but if the
         * sender expects an ACK we still need to provide one with "unsupported"
         * status.
         */
        if (mgmt_work->async_mgmt_to_pf)
                goto out;

        pf_to_mgmt = mgmt_work->pf_to_mgmt;
        ack_cmd = pf_to_mgmt->mgmt_ack_buf;
        memset(ack_cmd, 0, sizeof(*ack_cmd));
        ack_cmd->status = MGMT_STATUS_CMD_UNSUPPORTED;

        hinic3_response_mbox_to_mgmt(pf_to_mgmt->hwdev, mgmt_work->mod,
                                     mgmt_work->cmd, ack_cmd, sizeof(*ack_cmd),
                                     mgmt_work->msg_id);

out:
        kfree(mgmt_work->msg);
        kfree(mgmt_work);
}

static int hinic3_recv_msg_add_seg(struct hinic3_recv_msg *recv_msg,
                                   __le64 msg_header, const void *seg_data,
                                   bool *is_complete)
{
        u8 seq_id, msg_id, seg_len, is_last;
        char *msg_buff;
        u32 offset;

        seg_len = MBOX_MSG_HEADER_GET(msg_header, SEG_LEN);
        is_last = MBOX_MSG_HEADER_GET(msg_header, LAST);
        seq_id  = MBOX_MSG_HEADER_GET(msg_header, SEQID);
        msg_id = MBOX_MSG_HEADER_GET(msg_header, MSG_ID);

        if (seg_len > MGMT_SEG_LEN_MAX)
                return -EINVAL;

        /* All segments but last must be of maximal size */
        if (seg_len != MGMT_SEG_LEN_MAX && !is_last)
                return -EINVAL;

        if (seq_id == 0) {
                recv_msg->seq_id = seq_id;
                recv_msg->msg_id = msg_id;
        } else if (seq_id != recv_msg->seq_id + 1 ||
                   msg_id != recv_msg->msg_id) {
                return -EINVAL;
        }

        offset = seq_id * MGMT_SEG_LEN_MAX;
        if (offset + seg_len > MGMT_MAX_PF_BUF_SIZE)
                return -EINVAL;

        msg_buff = recv_msg->msg;
        memcpy(msg_buff + offset, seg_data, seg_len);
        recv_msg->msg_len = offset + seg_len;
        recv_msg->seq_id = seq_id;
        *is_complete = !!is_last;

        return 0;
}

static void hinic3_init_mgmt_msg_work(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt,
                                      struct hinic3_recv_msg *recv_msg)
{
        struct mgmt_msg_handle_work *mgmt_work;

        mgmt_work = kmalloc_obj(*mgmt_work);
        if (!mgmt_work)
                return;

        if (recv_msg->msg_len) {
                mgmt_work->msg = kmemdup(recv_msg->msg, recv_msg->msg_len,
                                         GFP_KERNEL);
                if (!mgmt_work->msg) {
                        kfree(mgmt_work);
                        return;
                }
        } else {
                mgmt_work->msg = NULL;
        }

        mgmt_work->pf_to_mgmt = pf_to_mgmt;
        mgmt_work->msg_len = recv_msg->msg_len;
        mgmt_work->msg_id = recv_msg->msg_id;
        mgmt_work->mod = recv_msg->mod;
        mgmt_work->cmd = recv_msg->cmd;
        mgmt_work->async_mgmt_to_pf = recv_msg->async_mgmt_to_pf;

        INIT_WORK(&mgmt_work->work, hinic3_recv_mgmt_msg_work_handler);
        queue_work(pf_to_mgmt->workq, &mgmt_work->work);
}

static void
hinic3_recv_mgmt_msg_handler(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt,
                             const u8 *header,
                             struct hinic3_recv_msg *recv_msg)
{
        struct hinic3_hwdev *hwdev = pf_to_mgmt->hwdev;
        const void *seg_data;
        __le64 msg_header;
        bool is_complete;
        u8 dir, msg_id;
        int err;

        msg_header = *(__force __le64 *)header;
        dir = MBOX_MSG_HEADER_GET(msg_header, DIRECTION);
        msg_id = MBOX_MSG_HEADER_GET(msg_header, MSG_ID);
        /* Don't need to get anything from hw when cmd is async */
        if (dir == MBOX_MSG_RESP && (msg_id & MGMT_ASYNC_MSG_FLAG))
                return;

        seg_data = header + sizeof(msg_header);
        err = hinic3_recv_msg_add_seg(recv_msg, msg_header,
                                      seg_data, &is_complete);
        if (err) {
                dev_err(hwdev->dev, "invalid receive segment\n");
                /* set seq_id to invalid seq_id */
                recv_msg->seq_id = MGMT_BOGUS_SEQ_ID;

                return;
        }

        if (!is_complete)
                return;

        recv_msg->cmd = MBOX_MSG_HEADER_GET(msg_header, CMD);
        recv_msg->mod = MBOX_MSG_HEADER_GET(msg_header, MODULE);
        recv_msg->async_mgmt_to_pf = MBOX_MSG_HEADER_GET(msg_header, NO_ACK);
        recv_msg->seq_id = MGMT_BOGUS_SEQ_ID;

        if (dir == MBOX_MSG_RESP)
                hinic3_mgmt_resp_msg_handler(pf_to_mgmt, recv_msg);
        else
                hinic3_init_mgmt_msg_work(pf_to_mgmt, recv_msg);
}

static int alloc_recv_msg(struct hinic3_recv_msg *recv_msg)
{
        recv_msg->seq_id = MGMT_BOGUS_SEQ_ID;

        recv_msg->msg = kzalloc(MGMT_MAX_PF_BUF_SIZE, GFP_KERNEL);
        if (!recv_msg->msg)
                return -ENOMEM;

        return 0;
}

static void free_recv_msg(struct hinic3_recv_msg *recv_msg)
{
        kfree(recv_msg->msg);
}

static int alloc_msg_buf(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt)
{
        struct device *dev = pf_to_mgmt->hwdev->dev;
        int err;

        err = alloc_recv_msg(&pf_to_mgmt->recv_msg_from_mgmt);
        if (err) {
                dev_err(dev, "Failed to allocate recv msg\n");
                return err;
        }

        err = alloc_recv_msg(&pf_to_mgmt->recv_resp_msg_from_mgmt);
        if (err) {
                dev_err(dev, "Failed to allocate resp recv msg\n");
                goto err_free_msg_from_mgmt;
        }

        pf_to_mgmt->mgmt_ack_buf = kzalloc(MGMT_MAX_PF_BUF_SIZE, GFP_KERNEL);
        if (!pf_to_mgmt->mgmt_ack_buf) {
                err = -ENOMEM;
                goto err_free_resp_msg_from_mgmt;
        }

        return 0;

err_free_resp_msg_from_mgmt:
        free_recv_msg(&pf_to_mgmt->recv_resp_msg_from_mgmt);
err_free_msg_from_mgmt:
        free_recv_msg(&pf_to_mgmt->recv_msg_from_mgmt);

        return err;
}

static void free_msg_buf(struct hinic3_msg_pf_to_mgmt *pf_to_mgmt)
{
        kfree(pf_to_mgmt->mgmt_ack_buf);

        free_recv_msg(&pf_to_mgmt->recv_resp_msg_from_mgmt);
        free_recv_msg(&pf_to_mgmt->recv_msg_from_mgmt);
}

int hinic3_pf_to_mgmt_init(struct hinic3_hwdev *hwdev)
{
        struct hinic3_msg_pf_to_mgmt *pf_to_mgmt;
        int err;

        pf_to_mgmt = kzalloc_obj(*pf_to_mgmt);
        if (!pf_to_mgmt)
                return -ENOMEM;

        hwdev->pf_to_mgmt = pf_to_mgmt;
        pf_to_mgmt->hwdev = hwdev;
        spin_lock_init(&pf_to_mgmt->sync_event_lock);
        pf_to_mgmt->workq = create_singlethread_workqueue(HINIC3_MGMT_WQ_NAME);
        if (!pf_to_mgmt->workq) {
                dev_err(hwdev->dev, "Failed to initialize MGMT workqueue\n");
                err = -ENOMEM;
                goto err_free_pf_to_mgmt;
        }

        err = alloc_msg_buf(pf_to_mgmt);
        if (err) {
                dev_err(hwdev->dev, "Failed to allocate msg buffers\n");
                goto err_destroy_workqueue;
        }

        return 0;

err_destroy_workqueue:
        destroy_workqueue(pf_to_mgmt->workq);
err_free_pf_to_mgmt:
        kfree(pf_to_mgmt);

        return err;
}

void hinic3_pf_to_mgmt_free(struct hinic3_hwdev *hwdev)
{
        struct hinic3_msg_pf_to_mgmt *pf_to_mgmt = hwdev->pf_to_mgmt;

        /* destroy workqueue before free related pf_to_mgmt resources in case of
         * illegal resource access
         */
        destroy_workqueue(pf_to_mgmt->workq);

        free_msg_buf(pf_to_mgmt);
        kfree(pf_to_mgmt);
}

void hinic3_flush_mgmt_workq(struct hinic3_hwdev *hwdev)
{
        if (hwdev->aeqs)
                flush_workqueue(hwdev->aeqs->workq);

        if (HINIC3_IS_PF(hwdev) && hwdev->pf_to_mgmt)
                flush_workqueue(hwdev->pf_to_mgmt->workq);
}

void hinic3_mgmt_msg_aeqe_handler(struct hinic3_hwdev *hwdev, u8 *header,
                                  u8 size)
{
        struct hinic3_msg_pf_to_mgmt *pf_to_mgmt;
        struct hinic3_recv_msg *recv_msg;
        __le64 msg_header;
        bool is_send_dir;

        if (MBOX_MSG_HEADER_GET(*(__force __le64 *)header, SOURCE) ==
            MBOX_MSG_FROM_MBOX) {
                hinic3_mbox_func_aeqe_handler(hwdev, header, size);

                return;
        }

        pf_to_mgmt = hwdev->pf_to_mgmt;
        msg_header = *(__force __le64 *)header;

        is_send_dir = (MBOX_MSG_HEADER_GET(msg_header, DIRECTION) ==
                       MBOX_MSG_SEND) ? true : false;

        recv_msg = is_send_dir ? &pf_to_mgmt->recv_msg_from_mgmt :
                   &pf_to_mgmt->recv_resp_msg_from_mgmt;

        hinic3_recv_mgmt_msg_handler(pf_to_mgmt, header, recv_msg);
}