root/sys/dev/smartpqi/smartpqi_features.c
/*-
 * 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;
}