// SPDX-License-Identifier: GPL-2.0
/* Copyright (C) 2019-2021, Intel Corporation. */

#include "ice_common.h"

/**
 * ice_pkg_get_supported_vlan_mode - determine if DDP supports Double VLAN mode
 * @hw: pointer to the HW struct
 * @dvm: output variable to determine if DDP supports DVM(true) or SVM(false)
 */
static int
ice_pkg_get_supported_vlan_mode(struct ice_hw *hw, bool *dvm)
{
	u16 meta_init_size = sizeof(struct ice_meta_init_section);
	struct ice_meta_init_section *sect;
	struct ice_buf_build *bld;
	int status;

	/* if anything fails, we assume there is no DVM support */
	*dvm = false;

	bld = ice_pkg_buf_alloc_single_section(hw,
					       ICE_SID_RXPARSER_METADATA_INIT,
					       meta_init_size, (void **)&sect);
	if (!bld)
		return -ENOMEM;

	/* only need to read a single section */
	sect->count = cpu_to_le16(1);
	sect->offset = cpu_to_le16(ICE_META_VLAN_MODE_ENTRY);

	status = ice_aq_upload_section(hw,
				       (struct ice_buf_hdr *)ice_pkg_buf(bld),
				       ICE_PKG_BUF_SIZE, NULL);
	if (!status) {
		DECLARE_BITMAP(entry, ICE_META_INIT_BITS);
		u32 arr[ICE_META_INIT_DW_CNT];
		u16 i;

		/* convert to host bitmap format */
		for (i = 0; i < ICE_META_INIT_DW_CNT; i++)
			arr[i] = le32_to_cpu(sect->entry.bm[i]);

		bitmap_from_arr32(entry, arr, (u16)ICE_META_INIT_BITS);

		/* check if DVM is supported */
		*dvm = test_bit(ICE_META_VLAN_MODE_BIT, entry);
	}

	ice_pkg_buf_free(hw, bld);

	return status;
}

/**
 * ice_aq_get_vlan_mode - get the VLAN mode of the device
 * @hw: pointer to the HW structure
 * @get_params: structure FW fills in based on the current VLAN mode config
 *
 * Get VLAN Mode Parameters (0x020D)
 */
static int
ice_aq_get_vlan_mode(struct ice_hw *hw,
		     struct ice_aqc_get_vlan_mode *get_params)
{
	struct ice_aq_desc desc;

	if (!get_params)
		return -EINVAL;

	ice_fill_dflt_direct_cmd_desc(&desc,
				      ice_aqc_opc_get_vlan_mode_parameters);

	return ice_aq_send_cmd(hw, &desc, get_params, sizeof(*get_params),
			       NULL);
}

/**
 * ice_aq_is_dvm_ena - query FW to check if double VLAN mode is enabled
 * @hw: pointer to the HW structure
 *
 * Returns true if the hardware/firmware is configured in double VLAN mode,
 * else return false signaling that the hardware/firmware is configured in
 * single VLAN mode.
 *
 * Also, return false if this call fails for any reason (i.e. firmware doesn't
 * support this AQ call).
 */
static bool ice_aq_is_dvm_ena(struct ice_hw *hw)
{
	struct ice_aqc_get_vlan_mode get_params = { 0 };
	int status;

	status = ice_aq_get_vlan_mode(hw, &get_params);
	if (status) {
		ice_debug(hw, ICE_DBG_AQ, "Failed to get VLAN mode, status %d\n",
			  status);
		return false;
	}

	return (get_params.vlan_mode & ICE_AQ_VLAN_MODE_DVM_ENA);
}

/**
 * ice_is_dvm_ena - check if double VLAN mode is enabled
 * @hw: pointer to the HW structure
 *
 * The device is configured in single or double VLAN mode on initialization and
 * this cannot be dynamically changed during runtime. Based on this there is no
 * need to make an AQ call every time the driver needs to know the VLAN mode.
 * Instead, use the cached VLAN mode.
 */
bool ice_is_dvm_ena(struct ice_hw *hw)
{
	return hw->dvm_ena;
}

/**
 * ice_cache_vlan_mode - cache VLAN mode after DDP is downloaded
 * @hw: pointer to the HW structure
 *
 * This is only called after downloading the DDP and after the global
 * configuration lock has been released because all ports on a device need to
 * cache the VLAN mode.
 */
static void ice_cache_vlan_mode(struct ice_hw *hw)
{
	hw->dvm_ena = ice_aq_is_dvm_ena(hw) ? true : false;
}

/**
 * ice_pkg_supports_dvm - find out if DDP supports DVM
 * @hw: pointer to the HW structure
 */
static bool ice_pkg_supports_dvm(struct ice_hw *hw)
{
	bool pkg_supports_dvm;
	int status;

	status = ice_pkg_get_supported_vlan_mode(hw, &pkg_supports_dvm);
	if (status) {
		ice_debug(hw, ICE_DBG_PKG, "Failed to get supported VLAN mode, status %d\n",
			  status);
		return false;
	}

	return pkg_supports_dvm;
}

/**
 * ice_fw_supports_dvm - find out if FW supports DVM
 * @hw: pointer to the HW structure
 */
static bool ice_fw_supports_dvm(struct ice_hw *hw)
{
	struct ice_aqc_get_vlan_mode get_vlan_mode = { 0 };
	int status;

	/* If firmware returns success, then it supports DVM, else it only
	 * supports SVM
	 */
	status = ice_aq_get_vlan_mode(hw, &get_vlan_mode);
	if (status) {
		ice_debug(hw, ICE_DBG_NVM, "Failed to get VLAN mode, status %d\n",
			  status);
		return false;
	}

	return true;
}

/**
 * ice_is_dvm_supported - check if Double VLAN Mode is supported
 * @hw: pointer to the hardware structure
 *
 * Returns true if Double VLAN Mode (DVM) is supported and false if only Single
 * VLAN Mode (SVM) is supported. In order for DVM to be supported the DDP and
 * firmware must support it, otherwise only SVM is supported. This function
 * should only be called while the global config lock is held and after the
 * package has been successfully downloaded.
 */
static bool ice_is_dvm_supported(struct ice_hw *hw)
{
	if (!ice_pkg_supports_dvm(hw)) {
		ice_debug(hw, ICE_DBG_PKG, "DDP doesn't support DVM\n");
		return false;
	}

	if (!ice_fw_supports_dvm(hw)) {
		ice_debug(hw, ICE_DBG_PKG, "FW doesn't support DVM\n");
		return false;
	}

	return true;
}

#define ICE_EXTERNAL_VLAN_ID_FV_IDX			11
#define ICE_SW_LKUP_VLAN_LOC_LKUP_IDX			1
#define ICE_SW_LKUP_VLAN_PKT_FLAGS_LKUP_IDX		2
#define ICE_SW_LKUP_PROMISC_VLAN_LOC_LKUP_IDX		2
#define ICE_PKT_FLAGS_0_TO_15_FV_IDX			1
static struct ice_update_recipe_lkup_idx_params ice_dvm_dflt_recipes[] = {
	{
		/* Update recipe ICE_SW_LKUP_VLAN to filter based on the
		 * outer/single VLAN in DVM
		 */
		.rid = ICE_SW_LKUP_VLAN,
		.fv_idx = ICE_EXTERNAL_VLAN_ID_FV_IDX,
		.ignore_valid = true,
		.mask = 0,
		.mask_valid = false, /* use pre-existing mask */
		.lkup_idx = ICE_SW_LKUP_VLAN_LOC_LKUP_IDX,
	},
	{
		/* Update recipe ICE_SW_LKUP_VLAN to filter based on the VLAN
		 * packet flags to support VLAN filtering on multiple VLAN
		 * ethertypes (i.e. 0x8100 and 0x88a8) in DVM
		 */
		.rid = ICE_SW_LKUP_VLAN,
		.fv_idx = ICE_PKT_FLAGS_0_TO_15_FV_IDX,
		.ignore_valid = false,
		.mask = ICE_PKT_VLAN_MASK,
		.mask_valid = true,
		.lkup_idx = ICE_SW_LKUP_VLAN_PKT_FLAGS_LKUP_IDX,
	},
	{
		/* Update recipe ICE_SW_LKUP_PROMISC_VLAN to filter based on the
		 * outer/single VLAN in DVM
		 */
		.rid = ICE_SW_LKUP_PROMISC_VLAN,
		.fv_idx = ICE_EXTERNAL_VLAN_ID_FV_IDX,
		.ignore_valid = true,
		.mask = 0,
		.mask_valid = false,  /* use pre-existing mask */
		.lkup_idx = ICE_SW_LKUP_PROMISC_VLAN_LOC_LKUP_IDX,
	},
};

/**
 * ice_dvm_update_dflt_recipes - update default switch recipes in DVM
 * @hw: hardware structure used to update the recipes
 */
static int ice_dvm_update_dflt_recipes(struct ice_hw *hw)
{
	unsigned long i;

	for (i = 0; i < ARRAY_SIZE(ice_dvm_dflt_recipes); i++) {
		struct ice_update_recipe_lkup_idx_params *params;
		int status;

		params = &ice_dvm_dflt_recipes[i];

		status = ice_update_recipe_lkup_idx(hw, params);
		if (status) {
			ice_debug(hw, ICE_DBG_INIT, "Failed to update RID %d lkup_idx %d fv_idx %d mask_valid %s mask 0x%04x\n",
				  params->rid, params->lkup_idx, params->fv_idx,
				  params->mask_valid ? "true" : "false",
				  params->mask);
			return status;
		}
	}

	return 0;
}

/**
 * ice_aq_set_vlan_mode - set the VLAN mode of the device
 * @hw: pointer to the HW structure
 * @set_params: requested VLAN mode configuration
 *
 * Set VLAN Mode Parameters (0x020C)
 */
static int
ice_aq_set_vlan_mode(struct ice_hw *hw,
		     struct ice_aqc_set_vlan_mode *set_params)
{
	u8 rdma_packet, mng_vlan_prot_id;
	struct ice_aq_desc desc;

	if (!set_params)
		return -EINVAL;

	if (set_params->l2tag_prio_tagging > ICE_AQ_VLAN_PRIO_TAG_MAX)
		return -EINVAL;

	rdma_packet = set_params->rdma_packet;
	if (rdma_packet != ICE_AQ_SVM_VLAN_RDMA_PKT_FLAG_SETTING &&
	    rdma_packet != ICE_AQ_DVM_VLAN_RDMA_PKT_FLAG_SETTING)
		return -EINVAL;

	mng_vlan_prot_id = set_params->mng_vlan_prot_id;
	if (mng_vlan_prot_id != ICE_AQ_VLAN_MNG_PROTOCOL_ID_OUTER &&
	    mng_vlan_prot_id != ICE_AQ_VLAN_MNG_PROTOCOL_ID_INNER)
		return -EINVAL;

	ice_fill_dflt_direct_cmd_desc(&desc,
				      ice_aqc_opc_set_vlan_mode_parameters);
	desc.flags |= cpu_to_le16(ICE_AQ_FLAG_RD);

	return ice_aq_send_cmd(hw, &desc, set_params, sizeof(*set_params),
			       NULL);
}

/**
 * ice_set_dvm - sets up software and hardware for double VLAN mode
 * @hw: pointer to the hardware structure
 */
static int ice_set_dvm(struct ice_hw *hw)
{
	struct ice_aqc_set_vlan_mode params = { 0 };
	int status;

	params.l2tag_prio_tagging = ICE_AQ_VLAN_PRIO_TAG_OUTER_CTAG;
	params.rdma_packet = ICE_AQ_DVM_VLAN_RDMA_PKT_FLAG_SETTING;
	params.mng_vlan_prot_id = ICE_AQ_VLAN_MNG_PROTOCOL_ID_OUTER;

	status = ice_aq_set_vlan_mode(hw, &params);
	if (status) {
		ice_debug(hw, ICE_DBG_INIT, "Failed to set double VLAN mode parameters, status %d\n",
			  status);
		return status;
	}

	status = ice_dvm_update_dflt_recipes(hw);
	if (status) {
		ice_debug(hw, ICE_DBG_INIT, "Failed to update default recipes for double VLAN mode, status %d\n",
			  status);
		return status;
	}

	status = ice_aq_set_port_params(hw->port_info, true, NULL);
	if (status) {
		ice_debug(hw, ICE_DBG_INIT, "Failed to set port in double VLAN mode, status %d\n",
			  status);
		return status;
	}

	status = ice_set_dvm_boost_entries(hw);
	if (status) {
		ice_debug(hw, ICE_DBG_INIT, "Failed to set boost TCAM entries for double VLAN mode, status %d\n",
			  status);
		return status;
	}

	return 0;
}

/**
 * ice_set_svm - set single VLAN mode
 * @hw: pointer to the HW structure
 */
static int ice_set_svm(struct ice_hw *hw)
{
	struct ice_aqc_set_vlan_mode *set_params;
	int status;

	status = ice_aq_set_port_params(hw->port_info, false, NULL);
	if (status) {
		ice_debug(hw, ICE_DBG_INIT, "Failed to set port parameters for single VLAN mode\n");
		return status;
	}

	set_params = devm_kzalloc(ice_hw_to_dev(hw), sizeof(*set_params),
				  GFP_KERNEL);
	if (!set_params)
		return -ENOMEM;

	/* default configuration for SVM configurations */
	set_params->l2tag_prio_tagging = ICE_AQ_VLAN_PRIO_TAG_INNER_CTAG;
	set_params->rdma_packet = ICE_AQ_SVM_VLAN_RDMA_PKT_FLAG_SETTING;
	set_params->mng_vlan_prot_id = ICE_AQ_VLAN_MNG_PROTOCOL_ID_INNER;

	status = ice_aq_set_vlan_mode(hw, set_params);
	if (status)
		ice_debug(hw, ICE_DBG_INIT, "Failed to configure port in single VLAN mode\n");

	devm_kfree(ice_hw_to_dev(hw), set_params);
	return status;
}

/**
 * ice_set_vlan_mode
 * @hw: pointer to the HW structure
 */
int ice_set_vlan_mode(struct ice_hw *hw)
{
	if (!ice_is_dvm_supported(hw))
		return 0;

	if (!ice_set_dvm(hw))
		return 0;

	return ice_set_svm(hw);
}

/**
 * ice_print_dvm_not_supported - print if DDP and/or FW doesn't support DVM
 * @hw: pointer to the HW structure
 *
 * The purpose of this function is to print that  QinQ is not supported due to
 * incompatibilty from the DDP and/or FW. This will give a hint to the user to
 * update one and/or both components if they expect QinQ functionality.
 */
static void ice_print_dvm_not_supported(struct ice_hw *hw)
{
	bool pkg_supports_dvm = ice_pkg_supports_dvm(hw);
	bool fw_supports_dvm = ice_fw_supports_dvm(hw);

	if (!fw_supports_dvm && !pkg_supports_dvm)
		dev_info(ice_hw_to_dev(hw), "QinQ functionality cannot be enabled on this device. Update your DDP package and NVM to versions that support QinQ.\n");
	else if (!pkg_supports_dvm)
		dev_info(ice_hw_to_dev(hw), "QinQ functionality cannot be enabled on this device. Update your DDP package to a version that supports QinQ.\n");
	else if (!fw_supports_dvm)
		dev_info(ice_hw_to_dev(hw), "QinQ functionality cannot be enabled on this device. Update your NVM to a version that supports QinQ.\n");
}

/**
 * ice_post_pkg_dwnld_vlan_mode_cfg - configure VLAN mode after DDP download
 * @hw: pointer to the HW structure
 *
 * This function is meant to configure any VLAN mode specific functionality
 * after the global configuration lock has been released and the DDP has been
 * downloaded.
 *
 * Since only one PF downloads the DDP and configures the VLAN mode there needs
 * to be a way to configure the other PFs after the DDP has been downloaded and
 * the global configuration lock has been released. All such code should go in
 * this function.
 */
void ice_post_pkg_dwnld_vlan_mode_cfg(struct ice_hw *hw)
{
	ice_cache_vlan_mode(hw);

	if (ice_is_dvm_ena(hw))
		ice_change_proto_id_to_dvm();
	else
		ice_print_dvm_not_supported(hw);
}
