// SPDX-License-Identifier: GPL-2.0
/* Huawei HiNIC PCI Express Linux driver
 * Copyright(c) 2017 Huawei Technologies Co., Ltd
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope 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.
 *
 */

#define pr_fmt(fmt) KBUILD_MODNAME ": [COMM]" fmt

#include <linux/kernel.h>
#include <linux/pci.h>
#include <linux/types.h>
#include <linux/module.h>
#include <linux/completion.h>
#include <linux/semaphore.h>
#include <linux/interrupt.h>

#include "ossl_knl.h"
#include "hinic_hw.h"
#include "hinic_hw_mgmt.h"
#include "hinic_hwdev.h"
#include "hinic_csr.h"
#include "hinic_hwif.h"
#include "hinic_nic_io.h"
#include "hinic_api_cmd.h"
#include "hinic_mgmt.h"
#include "hinic_mbox.h"
#include "hinic_nic_cfg.h"
#include "hinic_hwif.h"
#include "hinic_mgmt_interface.h"
#include "hinic_multi_host_mgmt.h"

#define SLAVE_HOST_STATUS_CLEAR(host_id, val)	\
			((val) & (~(1U << (host_id))))
#define SLAVE_HOST_STATUS_SET(host_id, enable)	\
			(((u8)(enable) & 1U) << (host_id))
#define SLAVE_HOST_STATUS_GET(host_id, val)	(!!((val) & (1U << (host_id))))

#define MULTI_HOST_PPF_GET(host_id, val) (((val) >> ((host_id) * 4 + 16)) & 0xf)

static inline u8 get_master_host_ppf_idx(struct hinic_hwdev *hwdev)
{
	u32 reg_val;

	reg_val = hinic_hwif_read_reg(hwdev->hwif,
				      HINIC_MULT_HOST_SLAVE_STATUS_ADDR);
	/* master host sets host_id to 0 */
	return MULTI_HOST_PPF_GET(0, reg_val);
}

void set_slave_host_enable(struct hinic_hwdev *hwdev, u8 host_id, bool enable)
{
	u32 reg_val;

	if (HINIC_FUNC_TYPE(hwdev) != TYPE_PPF)
		return;

	reg_val = hinic_hwif_read_reg(hwdev->hwif,
				      HINIC_MULT_HOST_SLAVE_STATUS_ADDR);

	reg_val = SLAVE_HOST_STATUS_CLEAR(host_id, reg_val);
	reg_val |= SLAVE_HOST_STATUS_SET(host_id, enable);
	hinic_hwif_write_reg(hwdev->hwif, HINIC_MULT_HOST_SLAVE_STATUS_ADDR,
			     reg_val);

	sdk_info(hwdev->dev_hdl, "Set slave host %d status %d, reg value: 0x%x\n",
		 host_id, enable, reg_val);
}

bool hinic_get_slave_host_enable(void *hwdev, u8 host_id)
{
	u32 reg_val;
	struct hinic_hwdev *dev = hwdev;

	if (HINIC_FUNC_TYPE(dev) != TYPE_PPF)
		return false;

	reg_val = hinic_hwif_read_reg(dev->hwif,
				      HINIC_MULT_HOST_SLAVE_STATUS_ADDR);

	return SLAVE_HOST_STATUS_GET(host_id, reg_val);
}
EXPORT_SYMBOL(hinic_get_slave_host_enable);

void set_master_host_mbox_enable(struct hinic_hwdev *hwdev, bool enable)
{
	u32 reg_val;

	if (!IS_MASTER_HOST(hwdev) || HINIC_FUNC_TYPE(hwdev) != TYPE_PPF)
		return;

	reg_val = hinic_hwif_read_reg(hwdev->hwif, HINIC_HOST_MODE_ADDR);
	reg_val = MULTI_HOST_REG_CLEAR(reg_val, MASTER_MBX_STS);
	reg_val |= MULTI_HOST_REG_SET((u8)enable, MASTER_MBX_STS);
	hinic_hwif_write_reg(hwdev->hwif, HINIC_HOST_MODE_ADDR, reg_val);

	sdk_info(hwdev->dev_hdl, "multi-host status %d, reg value: 0x%x\n",
		 enable, reg_val);
}

bool hinic_get_master_host_mbox_enable(void *hwdev)
{
	u32 reg_val;
	struct hinic_hwdev *dev = hwdev;

	if (!hwdev)
		return false;

	if (!IS_SLAVE_HOST(dev) || HINIC_FUNC_TYPE(dev) == TYPE_VF)
		return true;

	reg_val = hinic_hwif_read_reg(dev->hwif, HINIC_HOST_MODE_ADDR);

	return !!MULTI_HOST_REG_GET(reg_val, MASTER_MBX_STS);
}

void set_func_host_mode(struct hinic_hwdev *hwdev, enum hinic_func_mode mode)
{
	switch (mode) {
	case FUNC_MOD_MULTI_BM_MASTER:
		sdk_info(hwdev->dev_hdl, "Detect multi-host BM master host\n");
		hwdev->func_mode = FUNC_MOD_MULTI_BM_MASTER;
		hwdev->feature_cap = HINIC_MULTI_BM_MASTER;
		break;
	case FUNC_MOD_MULTI_BM_SLAVE:
		sdk_info(hwdev->dev_hdl, "Detect multi-host BM slave host\n");
		hwdev->func_mode = FUNC_MOD_MULTI_BM_SLAVE;
		hwdev->feature_cap = HINIC_MULTI_BM_SLAVE;
		break;
	case FUNC_MOD_MULTI_VM_MASTER:
		sdk_info(hwdev->dev_hdl, "Detect multi-host VM master host\n");
		hwdev->func_mode = FUNC_MOD_MULTI_VM_MASTER;
		hwdev->feature_cap = HINIC_MULTI_VM_MASTER;
		break;
	case FUNC_MOD_MULTI_VM_SLAVE:
		sdk_info(hwdev->dev_hdl, "Detect multi-host VM slave host\n");
		hwdev->func_mode = FUNC_MOD_MULTI_VM_SLAVE;
		hwdev->feature_cap = HINIC_MULTI_VM_SLAVE;
		break;
	default:
		hwdev->func_mode = FUNC_MOD_NORMAL_HOST;
		hwdev->feature_cap = HINIC_NORMAL_HOST_CAP;
		break;
	}
}

bool is_multi_vm_slave(void *hwdev)
{
	struct hinic_hwdev *hw_dev = hwdev;

	if (!hwdev)
		return false;

	return (hw_dev->func_mode == FUNC_MOD_MULTI_VM_SLAVE) ? true : false;
}

bool is_multi_bm_slave(void *hwdev)
{
	struct hinic_hwdev *hw_dev = hwdev;

	if (!hwdev)
		return false;

	return (hw_dev->func_mode == FUNC_MOD_MULTI_BM_SLAVE) ? true : false;
}

int rectify_host_mode(struct hinic_hwdev *hwdev)
{
	u16 cur_sdi_mode;
	int err;

	if (hwdev->board_info.board_type !=
	    HINIC_BOARD_TYPE_MULTI_HOST_ETH_25GE)
		return 0;

	sdk_info(hwdev->dev_hdl, "Rectify host mode, host_id: %d\n",
		 hinic_pcie_itf_id(hwdev));

	err = hinic_get_sdi_mode(hwdev, &cur_sdi_mode);
	if (err == HINIC_MGMT_CMD_UNSUPPORTED)
		cur_sdi_mode = HINIC_SDI_MODE_BM;
	else if (err)
		return err;

	switch (cur_sdi_mode) {
	case HINIC_SDI_MODE_BM:
		if (hinic_pcie_itf_id(hwdev) == 0)
			set_func_host_mode(hwdev, FUNC_MOD_MULTI_BM_MASTER);
		else
			set_func_host_mode(hwdev, FUNC_MOD_MULTI_BM_SLAVE);
		break;
	case HINIC_SDI_MODE_VM:
		if (hinic_pcie_itf_id(hwdev) == 0)
			set_func_host_mode(hwdev, FUNC_MOD_MULTI_VM_MASTER);
		else
			set_func_host_mode(hwdev, FUNC_MOD_MULTI_VM_SLAVE);
		break;
	default:
		sdk_warn(hwdev->dev_hdl, "Unknown sdi mode %d\n", cur_sdi_mode);
		break;
	}

	return 0;
}

void detect_host_mode_pre(struct hinic_hwdev *hwdev)
{
	enum hinic_chip_mode chip_mode;

	/* all pf can set HOST_MODE REG, so don't trust HOST_MODE REG for host0,
	 * get chip mode from mgmt cpu for host0
	 * VF have not right to read HOST_MODE REG, detect mode from board info
	 */
	if (hinic_pcie_itf_id(hwdev) == 0 ||
	    HINIC_FUNC_TYPE(hwdev) == TYPE_VF) {
		set_func_host_mode(hwdev, FUNC_MOD_NORMAL_HOST);
		return;
	}

	chip_mode = hinic_hwif_read_reg(hwdev->hwif, HINIC_HOST_MODE_ADDR);
	switch (MULTI_HOST_REG_GET(chip_mode, CHIP_MODE)) {
	case CHIP_MODE_VMGW:
		set_func_host_mode(hwdev, FUNC_MOD_MULTI_VM_SLAVE);
		/* mbox has not initialized, set slave host disable */
		set_slave_host_enable(hwdev, hinic_pcie_itf_id(hwdev), false);
		break;
	case CHIP_MODE_BMGW:
		set_func_host_mode(hwdev, FUNC_MOD_MULTI_BM_SLAVE);
		/* mbox has not initialized, set slave host disable */
		set_slave_host_enable(hwdev, hinic_pcie_itf_id(hwdev), false);
		break;

	default:
		set_func_host_mode(hwdev, FUNC_MOD_NORMAL_HOST);
		break;
	}
}

int __mbox_to_host(struct hinic_hwdev *hwdev, enum hinic_mod_type mod,
		   u8 cmd, void *buf_in, u16 in_size, void *buf_out,
		   u16 *out_size, u32 timeout,
		   enum hinic_mbox_ack_type ack_type)
{
	struct hinic_hwdev *mbox_hwdev = hwdev;
	u8 dst_host_func_idx;
	int err;

	if (!IS_MULTI_HOST(hwdev) || HINIC_IS_VF(hwdev))
		return -EPERM;

	if (hinic_func_type(hwdev) == TYPE_PF) {
		down(&hwdev->ppf_sem);
		mbox_hwdev = hwdev->ppf_hwdev;
		if (!mbox_hwdev) {
			err = -EINVAL;
			goto release_lock;
		}

		if (!hinic_is_hwdev_mod_inited(mbox_hwdev,
					       HINIC_HWDEV_MBOX_INITED)) {
			err = -EPERM;
			goto release_lock;
		}
	}

	if (!mbox_hwdev->chip_present_flag) {
		err = -EPERM;
		goto release_lock;
	}

	if (!hinic_get_master_host_mbox_enable(hwdev)) {
		sdk_err(hwdev->dev_hdl, "Master host not initialized\n");
		err = -EFAULT;
		goto release_lock;
	}

	if (!mbox_hwdev->mhost_mgmt) {
		/* send to master host in default */
		dst_host_func_idx = get_master_host_ppf_idx(hwdev);
	} else {
		dst_host_func_idx = IS_MASTER_HOST(hwdev) ?
				mbox_hwdev->mhost_mgmt->shost_ppf_idx :
				mbox_hwdev->mhost_mgmt->mhost_ppf_idx;
	}

	if (ack_type == MBOX_ACK)
		err = hinic_mbox_to_host(mbox_hwdev, dst_host_func_idx,
					 mod, cmd, buf_in, in_size,
					 buf_out, out_size,
					 timeout);
	else
		err = hinic_mbox_to_func_no_ack(mbox_hwdev, dst_host_func_idx,
						mod, cmd, buf_in, in_size);

release_lock:
	if (hinic_func_type(hwdev) == TYPE_PF)
		up(&hwdev->ppf_sem);

	return err;
}

int hinic_mbox_to_host_sync(void *hwdev, enum hinic_mod_type mod,
			    u8 cmd, void *buf_in, u16 in_size, void *buf_out,
			    u16 *out_size, u32 timeout)
{
	if (!hwdev)
		return -EINVAL;

	return __mbox_to_host((struct hinic_hwdev *)hwdev, mod, cmd, buf_in,
			in_size, buf_out, out_size, timeout, MBOX_ACK);
}
EXPORT_SYMBOL(hinic_mbox_to_host_sync);

int hinic_mbox_to_host_no_ack(struct hinic_hwdev *hwdev,
			      enum hinic_mod_type mod, u8 cmd, void *buf_in,
			      u16 in_size)
{
	return __mbox_to_host(hwdev, mod, cmd, buf_in, in_size, NULL, NULL,
			      0, MBOX_NO_ACK);
}

static int __get_func_nic_state_from_pf(struct hinic_hwdev *hwdev,
					u16 glb_func_idx, u8 *en);

int sw_func_pf_mbox_handler(void *handle, u16 vf_id, u8 cmd, void *buf_in,
			    u16 in_size, void *buf_out, u16 *out_size)
{
	struct hinic_hwdev *hwdev = handle;
	struct hinic_slave_func_nic_state *nic_state, *out_state;
	int err;

	switch (cmd) {
	case HINIC_SW_CMD_GET_SLAVE_FUNC_NIC_STATE:
		nic_state = buf_in;
		out_state = buf_out;
		*out_size = sizeof(*nic_state);

		/* find nic state in ppf func_nic_en bitmap */
		err = __get_func_nic_state_from_pf(hwdev, nic_state->func_idx,
						   &out_state->enable);
		if (err)
			out_state->status = 1;
		else
			out_state->status = 0;

		break;
	default:
		break;
	}

	return 0;
}

static int __master_host_sw_func_handler(struct hinic_hwdev *hwdev, u16 pf_idx,
					 u8 cmd, void *buf_in, u16 in_size,
					 void *buf_out, u16 *out_size)
{
	struct hinic_multi_host_mgmt *mhost_mgmt = hwdev->mhost_mgmt;
	struct register_slave_host *slave_host, *out_shost;
	int err = 0;

	if (!mhost_mgmt)
		return -ENXIO;

	switch (cmd) {
	case HINIC_SW_CMD_SLAVE_HOST_PPF_REGISTER:
		slave_host = buf_in;
		out_shost = buf_out;
		*out_size = sizeof(*slave_host);
		mhost_mgmt->shost_registered = true;
		mhost_mgmt->shost_host_idx = slave_host->host_id;
		mhost_mgmt->shost_ppf_idx = slave_host->ppf_idx;

		bitmap_copy((ulong *)out_shost->funcs_nic_en,
			    mhost_mgmt->func_nic_en, HINIC_MAX_FUNCTIONS);
		sdk_info(hwdev->dev_hdl, "slave host register ppf, host_id: %d, ppf_idx: %d\n",
			 slave_host->host_id, slave_host->ppf_idx);

		out_shost->status = 0;
		break;
	case HINIC_SW_CMD_SLAVE_HOST_PPF_UNREGISTER:
		slave_host = buf_in;
		mhost_mgmt->shost_registered = false;
		sdk_info(hwdev->dev_hdl, "slave host unregister ppf, host_id: %d, ppf_idx: %d\n",
			 slave_host->host_id, slave_host->ppf_idx);

		*out_size = sizeof(*slave_host);
		((struct register_slave_host *)buf_out)->status = 0;
		break;

	default:
		err = -EINVAL;
		break;
	}

	return err;
}

static int __event_set_func_nic_state(struct hinic_hwdev *hwdev, void *buf_in,
				      u16 in_size, void *buf_out, u16 *out_size)
{
	struct hinic_event_info event_info = {0};
	struct hinic_mhost_nic_func_state nic_state = {0};
	struct hinic_slave_func_nic_state *out_state, *func_nic_state = buf_in;

	event_info.type = HINIC_EVENT_MULTI_HOST_MGMT;
	event_info.mhost_mgmt.sub_cmd = HINIC_MHOST_NIC_STATE_CHANGE;
	event_info.mhost_mgmt.data = &nic_state;

	nic_state.func_idx = func_nic_state->func_idx;
	nic_state.enable = func_nic_state->enable;

	if (!hwdev->event_callback)
		return -EFAULT;

	hwdev->event_callback(hwdev->event_pri_handle, &event_info);

	*out_size = sizeof(*out_state);
	out_state = buf_out;
	out_state->status = nic_state.status;

	return nic_state.status;
}

static int multi_host_event_handler(struct hinic_hwdev *hwdev,
				    u8 cmd, void *buf_in,
				    u16 in_size, void *buf_out, u16 *out_size)
{
	int err;

	switch (cmd) {
	case HINIC_SW_CMD_SET_SLAVE_FUNC_NIC_STATE:
		err = __event_set_func_nic_state(hwdev, buf_in, in_size,
						 buf_out, out_size);
		break;
	default:
		err = -EOPNOTSUPP;
		break;
	}

	return err;
}

static int sw_fwd_msg_to_vf(struct hinic_hwdev *hwdev,
			    void *buf_in, u16 in_size, void *buf_out,
			    u16 *out_size)
{
	struct hinic_host_fwd_head *fwd_head;
	u16 fwd_head_len;
	void *msg;
	int err;

	fwd_head = buf_in;
	fwd_head_len = sizeof(struct hinic_host_fwd_head);
	msg = (void *)((u8 *)buf_in + fwd_head_len);
	err = hinic_mbox_ppf_to_vf(hwdev, fwd_head->mod,
				   fwd_head->dst_glb_func_idx, fwd_head->cmd,
				   msg, in_size - fwd_head_len,
				   buf_out, out_size, 0);
	if (err)
		nic_err(hwdev->dev_hdl,
			"Fwd msg to func %u failed, err: %d\n",
			fwd_head->dst_glb_func_idx, err);

	return err;
}

static int __slave_host_sw_func_handler(struct hinic_hwdev *hwdev, u16 pf_idx,
					u8 cmd, void *buf_in, u16 in_size,
					void *buf_out, u16 *out_size)
{
	struct hinic_multi_host_mgmt *mhost_mgmt = hwdev->mhost_mgmt;
	struct hinic_slave_func_nic_state *nic_state;
	int err = 0;

	if (!mhost_mgmt)
		return -ENXIO;

	switch (cmd) {
	case HINIC_SW_CMD_SET_SLAVE_FUNC_NIC_STATE:
		nic_state = buf_in;

		*out_size = sizeof(*nic_state);
		((struct hinic_slave_func_nic_state *)buf_out)->status = 0;

		sdk_info(hwdev->dev_hdl, "slave func %d %s nic\n",
			 nic_state->func_idx,
			 nic_state->enable ? "register" : "unregister");

		if (nic_state->enable)
			set_bit(nic_state->func_idx, mhost_mgmt->func_nic_en);
		else
			clear_bit(nic_state->func_idx, mhost_mgmt->func_nic_en);

		multi_host_event_handler(hwdev, cmd, buf_in, in_size, buf_out,
					 out_size);

		break;

	case HINIC_SW_CMD_SEND_MSG_TO_VF:
		err = sw_fwd_msg_to_vf(hwdev, buf_in, in_size,
				       buf_out, out_size);
		break;

	case HINIC_SW_CMD_MIGRATE_READY:
		hinic_migrate_report(hwdev);
		break;
	default:
		err = -EINVAL;
		break;
	}

	return err;
}

int sw_func_ppf_mbox_handler(void *handle, u16 pf_idx, u16 vf_id, u8 cmd,
			     void *buf_in, u16 in_size, void *buf_out,
			     u16 *out_size)
{
	struct hinic_hwdev *hwdev = handle;
	int err;

	if (IS_MASTER_HOST(hwdev))
		err = __master_host_sw_func_handler(hwdev, pf_idx, cmd, buf_in,
						    in_size, buf_out, out_size);
	else if (IS_SLAVE_HOST(hwdev))
		err = __slave_host_sw_func_handler(hwdev, pf_idx, cmd, buf_in,
						   in_size, buf_out, out_size);
	else
		err = -EINVAL;

	if (err)
		sdk_err(hwdev->dev_hdl, "PPF process sw funcs cmd %d failed, err: %d\n",
			cmd, err);

	return err;
}

int __ppf_process_mbox_msg(struct hinic_hwdev *hwdev, u16 pf_idx, u16 vf_id,
			   enum hinic_mod_type mod, u8 cmd, void *buf_in,
			   u16 in_size, void *buf_out, u16 *out_size)
{
	int err;

	if (IS_SLAVE_HOST(hwdev)) {
		err = hinic_mbox_to_host_sync(hwdev, mod, cmd,
					      buf_in, in_size, buf_out,
					      out_size, 0);
		if (err)
			sdk_err(hwdev->dev_hdl, "send to mpf failed, err: %d\n",
				err);
	} else if (IS_MASTER_HOST(hwdev)) {
		if (mod == HINIC_MOD_COMM && cmd == HINIC_MGMT_CMD_START_FLR)
			err = hinic_pf_to_mgmt_no_ack(hwdev, mod, cmd, buf_in,
						      in_size);
		else
			err = hinic_pf_msg_to_mgmt_sync(hwdev, mod, cmd, buf_in,
							in_size, buf_out,
							out_size, 0U);
		if (err && err != HINIC_DEV_BUSY_ACTIVE_FW &&
		    err != HINIC_MBOX_PF_BUSY_ACTIVE_FW)
			sdk_err(hwdev->dev_hdl, "PF mbox common callback handler err: %d\n",
				err);
	} else {
		/* not support */
		err = -EFAULT;
	}

	return err;
}

int hinic_ppf_process_mbox_msg(struct hinic_hwdev *hwdev, u16 pf_idx, u16 vf_id,
			       enum hinic_mod_type mod, u8 cmd, void *buf_in,
			       u16 in_size, void *buf_out, u16 *out_size)
{
	bool same_host = false;
	int err = -EFAULT;

	/* modify same_host according to hinic_get_hw_pf_infos */

	switch (hwdev->func_mode) {
	case FUNC_MOD_MULTI_VM_MASTER:
	case FUNC_MOD_MULTI_BM_MASTER:
		if (!same_host)
			err = __ppf_process_mbox_msg(hwdev, pf_idx, vf_id,
						     mod, cmd, buf_in, in_size,
						     buf_out, out_size);
		else
			sdk_warn(hwdev->dev_hdl, "Don't support ppf mbox message in BM master\n");

		break;
	case FUNC_MOD_MULTI_VM_SLAVE:
	case FUNC_MOD_MULTI_BM_SLAVE:
		same_host = true;
		if (same_host)
			err = __ppf_process_mbox_msg(hwdev, pf_idx, vf_id,
						     mod, cmd, buf_in, in_size,
						     buf_out, out_size);
		else
			sdk_warn(hwdev->dev_hdl, "Receive control message from BM master, don't support for now\n");

		break;
	default:
		sdk_warn(hwdev->dev_hdl, "Don't support ppf mbox message\n");

		break;
	}

	return err;
}

int comm_ppf_mbox_handler(void *handle, u16 pf_idx, u16 vf_id, u8 cmd,
			  void *buf_in, u16 in_size, void *buf_out,
			  u16 *out_size)
{
	return hinic_ppf_process_mbox_msg(handle, pf_idx, vf_id, HINIC_MOD_COMM,
					  cmd, buf_in, in_size, buf_out,
					  out_size);
}

void comm_ppf_to_pf_handler(void *handle, u8 cmd,
			    void *buf_in, u16 in_size,
			    void *buf_out, u16 *out_size)
{
	struct hinic_hwdev *hwdev = handle;

	sdk_err(hwdev->dev_hdl, "pf receive ppf common mbox msg, don't supported for now\n");
}

int hilink_ppf_mbox_handler(void *handle, u16 pf_idx, u16 vf_id, u8 cmd,
			    void *buf_in, u16 in_size, void *buf_out,
			    u16 *out_size)
{
	return hinic_ppf_process_mbox_msg(handle, pf_idx, vf_id,
					  HINIC_MOD_HILINK, cmd, buf_in,
					  in_size, buf_out, out_size);
}

int hinic_nic_ppf_mbox_handler(void *handle, u16 pf_idx, u16 vf_id, u8 cmd,
			       void *buf_in, u16 in_size, void *buf_out,
			       u16 *out_size)
{
	return hinic_ppf_process_mbox_msg(handle, pf_idx, vf_id,
					  HINIC_MOD_L2NIC, cmd, buf_in, in_size,
					  buf_out, out_size);
}

void hinic_nic_ppf_to_pf_handler(void *handle, u8 cmd,
				 void *buf_in, u16 in_size,
				 void *buf_out, u16 *out_size)
{
	struct hinic_hwdev *hwdev = handle;

	sdk_err(hwdev->dev_hdl, "ppf receive other pf l2nic mbox msg, don't supported for now\n");
}

int hinic_register_slave_ppf(struct hinic_hwdev *hwdev, bool registered)
{
	struct register_slave_host host_info = {0};
	u16 out_size = sizeof(host_info);
	u8 cmd;
	int err;

	if (!IS_SLAVE_HOST(hwdev))
		return -EINVAL;

	cmd = registered ? HINIC_SW_CMD_SLAVE_HOST_PPF_REGISTER :
		HINIC_SW_CMD_SLAVE_HOST_PPF_UNREGISTER;

	host_info.host_id = hinic_pcie_itf_id(hwdev);
	host_info.ppf_idx = hinic_ppf_idx(hwdev);

	err = hinic_mbox_to_host_sync(hwdev, HINIC_MOD_SW_FUNC, cmd,
				      &host_info, sizeof(host_info), &host_info,
				      &out_size, 0);
	if (err || !out_size || host_info.status) {
		sdk_err(hwdev->dev_hdl, "Failed to %s slave host, err: %d, out_size: 0x%x, status: 0x%x\n",
			registered ? "register" : "unregister", err, out_size,
			host_info.status);
		return -EFAULT;
	}
	bitmap_copy(hwdev->mhost_mgmt->func_nic_en,
		    (ulong *)host_info.funcs_nic_en,
		    HINIC_MAX_FUNCTIONS);
	return 0;
}

static int get_host_id_by_func_id(struct hinic_hwdev *hwdev, u16 func_idx,
				  u8 *host_id)
{
	struct hinic_hw_pf_infos *pf_infos;
	u16 vf_id_start, vf_id_end;
	int i;

	if (!hwdev || !host_id || !hwdev->mhost_mgmt)
		return -EINVAL;

	pf_infos = &hwdev->mhost_mgmt->pf_infos;

	for (i = 0; i < pf_infos->num_pfs; i++) {
		if (func_idx == pf_infos->infos[i].glb_func_idx) {
			*host_id = pf_infos->infos[i].itf_idx;
			return 0;
		}

		vf_id_start = pf_infos->infos[i].glb_pf_vf_offset + 1;
		vf_id_end = pf_infos->infos[i].glb_pf_vf_offset +
				pf_infos->infos[i].max_vfs;
		if (func_idx >= vf_id_start && func_idx <= vf_id_end) {
			*host_id = pf_infos->infos[i].itf_idx;
			return 0;
		}
	}

	return -EFAULT;
}

int set_slave_func_nic_state(struct hinic_hwdev *hwdev, u16 func_idx, u8 en)
{
	struct hinic_slave_func_nic_state nic_state = {0};
	u16 out_size = sizeof(nic_state);
	int err;

	nic_state.func_idx = func_idx;
	nic_state.enable = en;

	err = hinic_mbox_to_host_sync(hwdev, HINIC_MOD_SW_FUNC,
				      HINIC_SW_CMD_SET_SLAVE_FUNC_NIC_STATE,
				      &nic_state, sizeof(nic_state), &nic_state,
				      &out_size, 0);
	if (err == MBOX_ERRCODE_UNKNOWN_DES_FUNC) {
		sdk_warn(hwdev->dev_hdl, "Can not notify func %d nic state because slave host not initialized\n",
			 func_idx);
	} else if (err || !out_size || nic_state.status) {
		sdk_err(hwdev->dev_hdl, "Failed to set slave host functions nic state, err: %d, out_size: 0x%x, status: 0x%x\n",
			err, out_size, nic_state.status);
		return -EFAULT;
	}

	return 0;
}

int hinic_set_func_nic_state(void *hwdev, struct hinic_func_nic_state *state)
{
	struct hinic_hwdev *ppf_hwdev = hwdev;
	struct hinic_multi_host_mgmt *mhost_mgmt;
	u8 host_id = 0;
	bool host_enable;
	int err;
	int old_state;

	if (!hwdev || !state)
		return -EINVAL;

	if (hinic_func_type(hwdev) != TYPE_PPF)
		ppf_hwdev = ((struct hinic_hwdev *)hwdev)->ppf_hwdev;

	if (!ppf_hwdev || !IS_MASTER_HOST(ppf_hwdev))
		return -EINVAL;

	mhost_mgmt = ppf_hwdev->mhost_mgmt;
	if (!mhost_mgmt || state->func_idx >= HINIC_MAX_FUNCTIONS)
		return -EINVAL;

	old_state = test_bit(state->func_idx, mhost_mgmt->func_nic_en) ? 1 : 0;
	if (state->state == HINIC_FUNC_NIC_DEL)
		clear_bit(state->func_idx, mhost_mgmt->func_nic_en);
	else if (state->state == HINIC_FUNC_NIC_ADD)
		set_bit(state->func_idx, mhost_mgmt->func_nic_en);
	else
		return -EINVAL;

	err = get_host_id_by_func_id(ppf_hwdev, state->func_idx, &host_id);
	if (err) {
		sdk_err(ppf_hwdev->dev_hdl, "Failed to get function %d host id, err: %d\n",
			state->func_idx, err);
		old_state ? set_bit(state->func_idx, mhost_mgmt->func_nic_en) :
			clear_bit(state->func_idx, mhost_mgmt->func_nic_en);
		return -EFAULT;
	}

	host_enable = hinic_get_slave_host_enable(hwdev, host_id);
	sdk_info(ppf_hwdev->dev_hdl, "Set slave host %d(status: %d) func %d %s nic\n",
		 host_id, host_enable,
		 state->func_idx, state->state ? "enable" : "disable");

	if (!host_enable)
		return 0;

	/* notify slave host */
	err = set_slave_func_nic_state(hwdev, state->func_idx, state->state);
	if (err) {
		old_state ? set_bit(state->func_idx, mhost_mgmt->func_nic_en) :
			clear_bit(state->func_idx, mhost_mgmt->func_nic_en);
		return err;
	}

	return 0;
}
EXPORT_SYMBOL(hinic_set_func_nic_state);

static int __get_func_nic_state_from_pf(struct hinic_hwdev *hwdev,
					u16 glb_func_idx, u8 *en)
{
	struct hinic_multi_host_mgmt *mhost_mgmt;
	struct hinic_hwdev *ppf_hwdev = hwdev;

	if (hinic_func_type(hwdev) != TYPE_PPF)
		ppf_hwdev = ((struct hinic_hwdev *)hwdev)->ppf_hwdev;

	if (!ppf_hwdev || !ppf_hwdev->mhost_mgmt)
		return -EFAULT;

	mhost_mgmt = ppf_hwdev->mhost_mgmt;
	*en = !!(test_bit(glb_func_idx, mhost_mgmt->func_nic_en));

	sdk_info(ppf_hwdev->dev_hdl, "slave host func %d nic %d\n",
		 glb_func_idx, *en);

	return 0;
}

int hinic_get_func_nic_enable(void *hwdev, u16 glb_func_idx, bool *en)
{
	struct hinic_slave_func_nic_state nic_state = {0};
	u16 out_size = sizeof(nic_state);
	u8 nic_en;
	int err;

	if (!hwdev || !en)
		return -EINVAL;
	/*if card mode is OVS, VFs donot need attach_uld, so return false.*/
	if (!IS_SLAVE_HOST((struct hinic_hwdev *)hwdev)) {
		if (hinic_func_type(hwdev) == TYPE_VF &&
		    hinic_support_ovs(hwdev, NULL)) {
			*en = false;
		} else {
			*en = true;
		}
		return 0;
	}

	if (hinic_func_type(hwdev) == TYPE_VF) {
		nic_state.func_idx = glb_func_idx;
		err = hinic_msg_to_mgmt_sync(hwdev, HINIC_MOD_SW_FUNC,
					     HINIC_SW_CMD_GET_SLAVE_FUNC_NIC_STATE,
					     &nic_state, sizeof(nic_state),
					     &nic_state, &out_size, 0);
		if (err || !out_size || nic_state.status) {
			sdk_err(((struct hinic_hwdev *)hwdev)->dev_hdl, "Failed to get func %d nic state, err: %d, out_size: 0x%x, status: 0x%x\n",
				glb_func_idx, err, out_size, nic_state.status);
			return -EFAULT;
		}

		*en = !!nic_state.enable;

		return 0;
	}

	/* pf in slave host should be probe in CHIP_MODE_VMGW
	 * mode for pxe install
	 */
	if (IS_VM_SLAVE_HOST((struct hinic_hwdev *)hwdev)) {
		*en = true;
		return 0;
	}

	/* pf/ppf get function nic state in sdk diretly */
	err = __get_func_nic_state_from_pf(hwdev, glb_func_idx, &nic_en);
	if (err)
		return err;

	*en = !!nic_en;

	return 0;
}

int hinic_multi_host_mgmt_init(struct hinic_hwdev *hwdev)
{
	int err;

	if (!IS_MULTI_HOST(hwdev) || !HINIC_IS_PPF(hwdev))
		return 0;

	hwdev->mhost_mgmt = kzalloc(sizeof(*hwdev->mhost_mgmt), GFP_KERNEL);
	if (!hwdev->mhost_mgmt)
		return -ENOMEM;

	hwdev->mhost_mgmt->mhost_ppf_idx = get_master_host_ppf_idx(hwdev);
	hwdev->mhost_mgmt->shost_ppf_idx = 0;
	hwdev->mhost_mgmt->shost_host_idx = 2;

	err = hinic_get_hw_pf_infos(hwdev, &hwdev->mhost_mgmt->pf_infos);
	if (err)
		goto out_free_mhost_mgmt;

	hinic_register_ppf_mbox_cb(hwdev, HINIC_MOD_COMM,
				   comm_ppf_mbox_handler);
	hinic_register_ppf_mbox_cb(hwdev, HINIC_MOD_L2NIC,
				   hinic_nic_ppf_mbox_handler);
	hinic_register_ppf_mbox_cb(hwdev, HINIC_MOD_HILINK,
				   hilink_ppf_mbox_handler);
	hinic_register_ppf_mbox_cb(hwdev, HINIC_MOD_SW_FUNC,
				   sw_func_ppf_mbox_handler);

	bitmap_zero(hwdev->mhost_mgmt->func_nic_en, HINIC_MAX_FUNCTIONS);

	/* Slave host:
	 * register slave host ppf functions
	 * Get function's nic state
	 */
	if (IS_SLAVE_HOST(hwdev)) {
		/* PXE don't support to receive mbox from master host */
		set_slave_host_enable(hwdev, hinic_pcie_itf_id(hwdev), true);
		if ((IS_VM_SLAVE_HOST(hwdev) &&
		     hinic_get_master_host_mbox_enable(hwdev)) ||
		     IS_BMGW_SLAVE_HOST(hwdev)) {
			err = hinic_register_slave_ppf(hwdev, true);
			if (err) {
				set_slave_host_enable(hwdev,
						      hinic_pcie_itf_id(hwdev),
						      false);
				goto out_free_mhost_mgmt;
			}
		}
	} else {
		/* slave host can send message to mgmt cpu after setup master
		 * mbox
		 */
		set_master_host_mbox_enable(hwdev, true);
	}

	return 0;

out_free_mhost_mgmt:
	kfree(hwdev->mhost_mgmt);
	hwdev->mhost_mgmt = NULL;

	return err;
}

int hinic_multi_host_mgmt_free(struct hinic_hwdev *hwdev)
{
	if (!IS_MULTI_HOST(hwdev) || !HINIC_IS_PPF(hwdev))
		return 0;

	if (IS_SLAVE_HOST(hwdev)) {
		hinic_register_slave_ppf(hwdev, false);

		set_slave_host_enable(hwdev, hinic_pcie_itf_id(hwdev), false);
	} else {
		set_master_host_mbox_enable(hwdev, false);
	}

	hinic_unregister_ppf_mbox_cb(hwdev, HINIC_MOD_COMM);
	hinic_unregister_ppf_mbox_cb(hwdev, HINIC_MOD_L2NIC);
	hinic_unregister_ppf_mbox_cb(hwdev, HINIC_MOD_HILINK);
	hinic_unregister_ppf_mbox_cb(hwdev, HINIC_MOD_SW_FUNC);

	kfree(hwdev->mhost_mgmt);
	hwdev->mhost_mgmt = NULL;

	return 0;
}
