freebsd-src/sys/dev/smartpqi/smartpqi_features.c
John Hall 7ea28254ec smartpqi: update to version 4410.0.2005
This updates the smartpqi driver to Microsemi's latest code. This will
be the driver for FreeBSD 14 (with updates), but no MFC is planned.

Reviewed by: imp
Differential Revision: https://reviews.freebsd.org/D41550
2023-08-24 15:25:09 -06:00

521 lines
16 KiB
C

/*-
* Copyright 2016-2023 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;
}