mirror of
https://github.com/freebsd/freebsd-src
synced 2024-10-06 16:40:47 +00:00
7ea28254ec
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
521 lines
16 KiB
C
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;
|
|
}
|