/*-
 * Copyright 2016-2025 Microchip Technology, Inc. and/or its subsidiaries.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */


#include "smartpqi_includes.h"

/*
 * Checks a firmware feature status, given bit position.
 */
static inline boolean_t
pqi_is_firmware_feature_supported(
	struct pqi_config_table_firmware_features *firmware_features,
	unsigned int bit_position)
{
	unsigned int byte_index;

	byte_index = bit_position / BITS_PER_BYTE;

	if (byte_index >= firmware_features->num_elements) {
		DBG_ERR_NO_SOFTS("Invalid byte index for bit position %u\n",
			bit_position);
		return false;
	}

	return (firmware_features->features_supported[byte_index] &
		(1 << (bit_position % BITS_PER_BYTE))) ? true : false;
}

/*
 * Counts down into the enabled section of firmware
 * features and reports current enabled status, given
 * bit position.
 */
static inline boolean_t
pqi_is_firmware_feature_enabled(
	struct pqi_config_table_firmware_features *firmware_features,
	uint8_t *firmware_features_iomem_addr,
	unsigned int bit_position)
{
	unsigned int byte_index;
	uint8_t *features_enabled_iomem_addr;

	byte_index = (bit_position / BITS_PER_BYTE) +
		(firmware_features->num_elements * 2);

	features_enabled_iomem_addr = firmware_features_iomem_addr +
		offsetof(struct pqi_config_table_firmware_features,
			features_supported) + byte_index;

	return (*features_enabled_iomem_addr &
		(1 << (bit_position % BITS_PER_BYTE))) ? true : false;
}

/*
 * Sets the given bit position for the driver to request the indicated
 * firmware feature be enabled.
 */
static inline void
pqi_request_firmware_feature(
	struct pqi_config_table_firmware_features *firmware_features,
	unsigned int bit_position)
{
	unsigned int byte_index;

	/* byte_index adjusted to index into requested start bits */
	byte_index = (bit_position / BITS_PER_BYTE) +
		firmware_features->num_elements;

	/* setting requested bits of local firmware_features */
	firmware_features->features_supported[byte_index] |=
		(1 << (bit_position % BITS_PER_BYTE));
}

/*
 * Creates and sends the request for firmware to update the config
 * table.
 */
static int
pqi_config_table_update(pqisrc_softstate_t *softs,
	uint16_t first_section, uint16_t last_section)
{
	struct pqi_vendor_general_request request;
	int ret;

	memset(&request, 0, sizeof(request));

	request.header.iu_type = PQI_REQUEST_IU_VENDOR_GENERAL;
	request.header.iu_length = sizeof(request) - PQI_REQUEST_HEADER_LENGTH;
	request.function_code = PQI_VENDOR_GENERAL_CONFIG_TABLE_UPDATE;
	request.data.config_table_update.first_section = first_section;
	request.data.config_table_update.last_section = last_section;

	ret = pqisrc_build_send_vendor_request(softs, &request);

	if (ret != PQI_STATUS_SUCCESS) {
		DBG_ERR("Failed to submit vendor general request IU, Ret status: %d\n", ret);
	}

	return ret;
}

/*
 * Copies requested features bits into firmware config table,
 * checks for support, and returns status of updating the config table.
 */
static int
pqi_enable_firmware_features(pqisrc_softstate_t *softs,
	struct pqi_config_table_firmware_features *firmware_features,
	uint8_t *firmware_features_abs_addr)
{
	uint8_t *features_requested;
	uint8_t *features_requested_abs_addr;
	uint16_t *host_max_known_feature_iomem_addr;
	uint16_t pqi_max_feature = PQI_FIRMWARE_FEATURE_MAXIMUM;

	features_requested = firmware_features->features_supported +
		firmware_features->num_elements;

	features_requested_abs_addr = firmware_features_abs_addr +
		(features_requested - (uint8_t*)firmware_features);
	/*
	 * NOTE: This memcpy is writing to a BAR-mapped address
	 * which may not be safe for all OSes without proper API
	 */
	memcpy(features_requested_abs_addr, features_requested,
		firmware_features->num_elements);

	if (pqi_is_firmware_feature_supported(firmware_features,
		PQI_FIRMWARE_FEATURE_MAX_KNOWN_FEATURE)) {
		host_max_known_feature_iomem_addr =
			(uint16_t*)(features_requested_abs_addr +
			(firmware_features->num_elements * 2) + sizeof(uint16_t));
			/*
			 * NOTE: This writes to a BAR-mapped address
			 * which may not be safe for all OSes without proper API
			 */
			*host_max_known_feature_iomem_addr = pqi_max_feature;
	}

	return pqi_config_table_update(softs,
		PQI_CONF_TABLE_SECTION_FIRMWARE_FEATURES,
		PQI_CONF_TABLE_SECTION_FIRMWARE_FEATURES);
}

typedef struct pqi_firmware_feature pqi_firmware_feature_t;
typedef void (*feature_status_fn)(pqisrc_softstate_t *softs,
	pqi_firmware_feature_t *firmware_feature);

struct pqi_firmware_feature {
	char		*feature_name;
	unsigned int	feature_bit;
	boolean_t		supported;
	boolean_t		enabled;
	feature_status_fn	feature_status;
};

static void
pqi_firmware_feature_status(pqisrc_softstate_t *softs,
	struct pqi_firmware_feature *firmware_feature)
{
	if (!firmware_feature->supported) {
		DBG_NOTE("%s not supported by controller\n",
			firmware_feature->feature_name);
		return;
	}

	if (firmware_feature->enabled) {
		DBG_NOTE("%s enabled\n", firmware_feature->feature_name);
		return;
	}

	DBG_NOTE("failed to enable %s\n", firmware_feature->feature_name);
}

static void
pqi_ctrl_update_feature_flags(pqisrc_softstate_t *softs,
	struct pqi_firmware_feature *firmware_feature)
{
	switch (firmware_feature->feature_bit) {
	case PQI_FIRMWARE_FEATURE_RAID_1_WRITE_BYPASS:
		softs->aio_raid1_write_bypass = firmware_feature->enabled;
		break;
	case PQI_FIRMWARE_FEATURE_RAID_5_WRITE_BYPASS:
		softs->aio_raid5_write_bypass = firmware_feature->enabled;
		break;
	case PQI_FIRMWARE_FEATURE_RAID_6_WRITE_BYPASS:
		softs->aio_raid6_write_bypass = firmware_feature->enabled;
		break;
	case PQI_FIRMWARE_FEATURE_RAID_IU_TIMEOUT:
		softs->timeout_in_passthrough = true;
		break;
	case PQI_FIRMWARE_FEATURE_TMF_IU_TIMEOUT:
		softs->timeout_in_tmf = true;
		break;
	case PQI_FIRMWARE_FEATURE_UNIQUE_SATA_WWN:
		break;
	case PQI_FIRMWARE_FEATURE_PAGE83_IDENTIFIER_FOR_RPL_WWID:
		softs->page83id_in_rpl = true;
		break;
	default:
		DBG_NOTE("Nothing to do\n");
		return;
		break;
	}
	/* for any valid feature, also go update the feature status. */
	pqi_firmware_feature_status(softs, firmware_feature);
}


static inline void
pqi_firmware_feature_update(pqisrc_softstate_t *softs,
	struct pqi_firmware_feature *firmware_feature)
{
	if (firmware_feature->feature_status)
		firmware_feature->feature_status(softs, firmware_feature);
}

/* Defines PQI features that driver wishes to support */
static struct pqi_firmware_feature pqi_firmware_features[] = {
#if 0
	{
		.feature_name = "Online Firmware Activation",
		.feature_bit = PQI_FIRMWARE_FEATURE_OFA,
		.feature_status = pqi_firmware_feature_status,
	},
	{
		.feature_name = "Serial Management Protocol",
		.feature_bit = PQI_FIRMWARE_FEATURE_SMP,
		.feature_status = pqi_firmware_feature_status,
	},
#endif
	{
		.feature_name = "SATA WWN Unique ID",
		.feature_bit = PQI_FIRMWARE_FEATURE_UNIQUE_SATA_WWN,
		.feature_status = pqi_ctrl_update_feature_flags,
	},
	{
		.feature_name = "RAID IU Timeout",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_IU_TIMEOUT,
		.feature_status = pqi_ctrl_update_feature_flags,
	},
	{
		.feature_name = "TMF IU Timeout",
		.feature_bit = PQI_FIRMWARE_FEATURE_TMF_IU_TIMEOUT,
		.feature_status = pqi_ctrl_update_feature_flags,
	},
	{
		.feature_name = "Support for RPL WWID filled by Page83 identifier",
		.feature_bit = PQI_FIRMWARE_FEATURE_PAGE83_IDENTIFIER_FOR_RPL_WWID,
		.feature_status = pqi_ctrl_update_feature_flags,
	},
	/* Features independent of Maximum Known Feature should be added
	before Maximum Known Feature*/
	{
		.feature_name = "Maximum Known Feature",
		.feature_bit = PQI_FIRMWARE_FEATURE_MAX_KNOWN_FEATURE,
		.feature_status = pqi_firmware_feature_status,
	},
	{
		.feature_name = "RAID 0 Read Bypass",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_0_READ_BYPASS,
		.feature_status = pqi_firmware_feature_status,
	},
	{
		.feature_name = "RAID 1 Read Bypass",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_1_READ_BYPASS,
		.feature_status = pqi_firmware_feature_status,
	},
	{
		.feature_name = "RAID 5 Read Bypass",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_5_READ_BYPASS,
		.feature_status = pqi_firmware_feature_status,
	},
	{
		.feature_name = "RAID 6 Read Bypass",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_6_READ_BYPASS,
		.feature_status = pqi_firmware_feature_status,
	},
	{
		.feature_name = "RAID 0 Write Bypass",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_0_WRITE_BYPASS,
		.feature_status = pqi_firmware_feature_status,
	},
	{
		.feature_name = "RAID 1 Write Bypass",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_1_WRITE_BYPASS,
		.feature_status = pqi_ctrl_update_feature_flags,
	},
	{
		.feature_name = "RAID 5 Write Bypass",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_5_WRITE_BYPASS,
		.feature_status = pqi_ctrl_update_feature_flags,
	},
	{
		.feature_name = "RAID 6 Write Bypass",
		.feature_bit = PQI_FIRMWARE_FEATURE_RAID_6_WRITE_BYPASS,
		.feature_status = pqi_ctrl_update_feature_flags,
	},
#if 0
	{
		.feature_name = "New Soft Reset Handshake",
		.feature_bit = PQI_FIRMWARE_FEATURE_SOFT_RESET_HANDSHAKE,
		.feature_status = pqi_ctrl_update_feature_flags,
	},
#endif

};

static void
pqi_process_firmware_features(pqisrc_softstate_t *softs,
	void *features, void *firmware_features_abs_addr)
{
	int rc;
	struct pqi_config_table_firmware_features *firmware_features = features;
	unsigned int i;
	unsigned int num_features_supported;

	/* Iterates through local PQI feature support list to
	see if the controller also supports the feature */
	for (i = 0, num_features_supported = 0;
		i < ARRAY_SIZE(pqi_firmware_features); i++) {
		/*Check if SATA_WWN_FOR_DEV_UNIQUE_ID feature enabled by setting module
		parameter if not avoid checking for the feature*/
		if ((pqi_firmware_features[i].feature_bit ==
			PQI_FIRMWARE_FEATURE_UNIQUE_SATA_WWN) &&
			(!softs->sata_unique_wwn)) {
			continue;
		}
		if (pqi_is_firmware_feature_supported(firmware_features,
			pqi_firmware_features[i].feature_bit)) {
			pqi_firmware_features[i].supported = true;
			num_features_supported++;
		} else {
			DBG_WARN("Feature %s is not supported by firmware\n",
			pqi_firmware_features[i].feature_name);
			pqi_firmware_feature_update(softs,
				&pqi_firmware_features[i]);

			/* if max known feature bit isn't supported,
 			 * then no other feature bits are supported.
 			 */
			if (pqi_firmware_features[i].feature_bit ==
				PQI_FIRMWARE_FEATURE_MAX_KNOWN_FEATURE)
				break;
		}
	}

	DBG_INFO("Num joint features supported : %u \n", num_features_supported);

	if (num_features_supported == 0)
		return;

	/* request driver features that are also on firmware-supported list */
	for (i = 0; i < ARRAY_SIZE(pqi_firmware_features); i++) {
		if (!pqi_firmware_features[i].supported)
			continue;
#ifdef DEVICE_HINT
		if (check_device_hint_status(softs, pqi_firmware_features[i].feature_bit))
			continue;
#endif
		pqi_request_firmware_feature(firmware_features,
			pqi_firmware_features[i].feature_bit);
	}

	/* enable the features that were successfully requested. */
	rc = pqi_enable_firmware_features(softs, firmware_features,
		firmware_features_abs_addr);
	if (rc) {
		DBG_ERR("failed to enable firmware features in PQI configuration table\n");
		for (i = 0; i < ARRAY_SIZE(pqi_firmware_features); i++) {
			if (!pqi_firmware_features[i].supported)
				continue;
			pqi_firmware_feature_update(softs,
				&pqi_firmware_features[i]);
		}
		return;
	}

	/* report the features that were successfully enabled. */
	for (i = 0; i < ARRAY_SIZE(pqi_firmware_features); i++) {
		if (!pqi_firmware_features[i].supported)
			continue;
		if (pqi_is_firmware_feature_enabled(firmware_features,
			firmware_features_abs_addr,
			pqi_firmware_features[i].feature_bit)) {
				pqi_firmware_features[i].enabled = true;
		} else {
			DBG_WARN("Feature %s could not be enabled.\n",
				pqi_firmware_features[i].feature_name);
		}
		pqi_firmware_feature_update(softs,
			&pqi_firmware_features[i]);
	}
}

static void
pqi_init_firmware_features(void)
{
	unsigned int i;

	for (i = 0; i < ARRAY_SIZE(pqi_firmware_features); i++) {
		pqi_firmware_features[i].supported = false;
		pqi_firmware_features[i].enabled = false;
	}
}

static void
pqi_process_firmware_features_section(pqisrc_softstate_t *softs,
	void *features, void *firmware_features_abs_addr)
{
	pqi_init_firmware_features();
	pqi_process_firmware_features(softs, features, firmware_features_abs_addr);
}


/*
 * Get the PQI configuration table parameters.
 * Currently using for heart-beat counter scratch-pad register.
 */
int
pqisrc_process_config_table(pqisrc_softstate_t *softs)
{
	int ret = PQI_STATUS_FAILURE;
	uint32_t config_table_size;
	uint32_t section_off;
	uint8_t *config_table_abs_addr;
	struct pqi_conf_table *conf_table;
	struct pqi_conf_table_section_header *section_hdr;

	config_table_size = softs->pqi_cap.conf_tab_sz;

	if (config_table_size < sizeof(*conf_table) ||
		config_table_size > PQI_CONF_TABLE_MAX_LEN) {
		DBG_ERR("Invalid PQI conf table length of %u\n",
			config_table_size);
		return ret;
	}

	conf_table = os_mem_alloc(softs, config_table_size);
	if (!conf_table) {
		DBG_ERR("Failed to allocate memory for PQI conf table\n");
		return ret;
	}

	config_table_abs_addr = (uint8_t *)(softs->pci_mem_base_vaddr +
					softs->pqi_cap.conf_tab_off);

	PCI_MEM_GET_BUF(softs, config_table_abs_addr,
			softs->pqi_cap.conf_tab_off,
			(uint8_t*)conf_table, config_table_size);

	if (memcmp(conf_table->sign, PQI_CONF_TABLE_SIGNATURE,
			sizeof(conf_table->sign)) != 0) {
		DBG_ERR("Invalid PQI config signature\n");
		goto out;
	}

	section_off = LE_32(conf_table->first_section_off);

	while (section_off) {

		if (section_off+ sizeof(*section_hdr) >= config_table_size) {
			DBG_INFO("Reached end of PQI config table. Breaking off.\n");
			break;
		}

		section_hdr = (struct pqi_conf_table_section_header *)((uint8_t *)conf_table + section_off);

		switch (LE_16(section_hdr->section_id)) {
		case PQI_CONF_TABLE_SECTION_GENERAL_INFO:
			break;
		case PQI_CONF_TABLE_SECTION_FIRMWARE_FEATURES:
			pqi_process_firmware_features_section(softs, section_hdr, (config_table_abs_addr + section_off));
			break;
		case PQI_CONF_TABLE_SECTION_FIRMWARE_ERRATA:
		case PQI_CONF_TABLE_SECTION_DEBUG:
			break;
		case PQI_CONF_TABLE_SECTION_HEARTBEAT:
			softs->heartbeat_counter_off = softs->pqi_cap.conf_tab_off +
				section_off +
				offsetof(struct pqi_conf_table_heartbeat, heartbeat_counter);
			softs->heartbeat_counter_abs_addr = (uint64_t *)(softs->pci_mem_base_vaddr +
				softs->heartbeat_counter_off);
			ret = PQI_STATUS_SUCCESS;
			break;
		case PQI_CONF_TABLE_SOFT_RESET:
			break;
		default:
			DBG_NOTE("unrecognized PQI config table section ID: 0x%x\n",
				LE_16(section_hdr->section_id));
			break;
		}
		section_off = LE_16(section_hdr->next_section_off);
	}
out:
	os_mem_free(softs, (void *)conf_table,config_table_size);
	return ret;
}
