* The Hardware Abstraction Layer (HW) insulates the higher-level code from the SLI-4
* message details, but the higher level code must still manage domains, ports,
* IT nexuses, and IOs. The HW API is designed to help the higher level manage
* these objects.
*
* The HW uses function callbacks to notify the higher-level code of events
* that are received from the chip. There are currently three types of
* functions that may be registered:
*
*
- domain – This function is called whenever a domain event is generated
* within the HW. Examples include a new FCF is discovered, a connection
* to a domain is disrupted, and allocation callbacks.
* - unsolicited – This function is called whenever new data is received in
* the SLI-4 receive queue.
* - rnode – This function is called for remote node events, such as attach status
* and allocation callbacks.
*
* Upper layer functions may be registered by using the ocs_hw_callback() function.
*
*
*
FC/FCoE HW API
* The FC/FCoE HW component builds upon the SLI-4 component to establish a flexible
* interface for creating the necessary common objects and sending I/Os. It may be used
* “as is” in customer implementations or it can serve as an example of typical interactions
* between a driver and the SLI-4 hardware. The broad categories of functionality include:
*
*
- Setting-up and tearing-down of the HW.
* - Allocating and using the common objects (SLI Port, domain, remote node).
* - Sending and receiving I/Os.
*
*
HW Setup
* To set up the HW:
*
*
* - Set up the HW object using ocs_hw_setup().
* This step performs a basic configuration of the SLI-4 component and the HW to
* enable querying the hardware for its capabilities. At this stage, the HW is not
* capable of general operations (such as, receiving events or sending I/Os).
* - Configure the HW according to the driver requirements.
* The HW provides functions to discover hardware capabilities (ocs_hw_get()), as
* well as configures the amount of resources required (ocs_hw_set()). The driver
* must also register callback functions (ocs_hw_callback()) to receive notification of
* various asynchronous events.
* @b Note: Once configured, the driver must initialize the HW (ocs_hw_init()). This
* step creates the underlying queues, commits resources to the hardware, and
* prepares the hardware for operation. While the hardware is operational, the
* port is not online, and cannot send or receive data.
*
* - Finally, the driver can bring the port online (ocs_hw_port_control()).
* When the link comes up, the HW determines if a domain is present and notifies the
* driver using the domain callback function. This is the starting point of the driver's
* interaction with the common objects.
* @b Note: For FCoE, there may be more than one domain available and, therefore,
* more than one callback.
*
*
*
Allocating and Using Common Objects
* Common objects provide a mechanism through which the various OneCore Storage
* driver components share and track information. These data structures are primarily
* used to track SLI component information but can be extended by other components, if
* needed. The main objects are:
*
*
- DMA – the ocs_dma_t object describes a memory region suitable for direct
* memory access (DMA) transactions.
* - SCSI domain – the ocs_domain_t object represents the SCSI domain, including
* any infrastructure devices such as FC switches and FC forwarders. The domain
* object contains both an FCFI and a VFI.
* - SLI Port (sport) – the ocs_sli_port_t object represents the connection between
* the driver and the SCSI domain. The SLI Port object contains a VPI.
* - Remote node – the ocs_remote_node_t represents a connection between the SLI
* Port and another device in the SCSI domain. The node object contains an RPI.
*
* Before the driver can send I/Os, it must allocate the SCSI domain, SLI Port, and remote
* node common objects and establish the connections between them. The goal is to
* connect the driver to the SCSI domain to exchange I/Os with other devices. These
* common object connections are shown in the following figure, FC Driver Common Objects:
*
*
* The first step is to create a connection to the domain by allocating an SLI Port object.
* The SLI Port object represents a particular FC ID and must be initialized with one. With
* the SLI Port object, the driver can discover the available SCSI domain(s). On identifying
* a domain, the driver allocates a domain object and attaches to it using the previous SLI
* port object.
*
* @b Note: In some cases, the driver may need to negotiate service parameters (that is,
* FLOGI) with the domain before attaching.
*
* Once attached to the domain, the driver can discover and attach to other devices
* (remote nodes). The exact discovery method depends on the driver, but it typically
* includes using a position map, querying the fabric name server, or an out-of-band
* method. In most cases, it is necessary to log in with devices before performing I/Os.
* Prior to sending login-related ELS commands (ocs_hw_srrs_send()), the driver must
* allocate a remote node object (ocs_hw_node_alloc()). If the login negotiation is
* successful, the driver must attach the nodes (ocs_hw_node_attach()) to the SLI Port
* before exchanging FCP I/O.
*
* @b Note: The HW manages both the well known fabric address and the name server as
* nodes in the domain. Therefore, the driver must allocate node objects prior to
* communicating with either of these entities.
*
*
Sending and Receiving I/Os
* The HW provides separate interfaces for sending BLS/ ELS/ FC-CT and FCP, but the
* commands are conceptually similar. Since the commands complete asynchronously,
* the caller must provide a HW I/O object that maintains the I/O state, as well as
* provide a callback function. The driver may use the same callback function for all I/O
* operations, but each operation must use a unique HW I/O object. In the SLI-4
* architecture, there is a direct association between the HW I/O object and the SGL used
* to describe the data. Therefore, a driver typically performs the following operations:
*
*
- Allocates a HW I/O object (ocs_hw_io_alloc()).
* - Formats the SGL, specifying both the HW I/O object and the SGL.
* (ocs_hw_io_init_sges() and ocs_hw_io_add_sge()).
* - Sends the HW I/O (ocs_hw_io_send()).
*
*
HW Tear Down
* To tear-down the HW:
*
*
- Take the port offline (ocs_hw_port_control()) to prevent receiving further
* data andevents.
* - Destroy the HW object (ocs_hw_teardown()).
* - Free any memory used by the HW, such as buffers for unsolicited data.
*
*
*
*/
/**
* This contains all hw runtime workaround code. Based on the asic type,
* asic revision, and range of fw revisions, a particular workaround may be enabled.
*
* A workaround may consist of overriding a particular HW/SLI4 value that was initialized
* during ocs_hw_setup() (for example the MAX_QUEUE overrides for mis-reported queue
* sizes). Or if required, elements of the ocs_hw_workaround_t structure may be set to
* control specific runtime behavior.
*
* It is intended that the controls in ocs_hw_workaround_t be defined functionally. So we
* would have the driver look like: "if (hw->workaround.enable_xxx) then ...", rather than
* what we might previously see as "if this is a BE3, then do xxx"
*
*/
#define HW_FWREV_ZERO (0ull)
#define HW_FWREV_MAX (~0ull)
#define SLI4_ASIC_TYPE_ANY 0
#define SLI4_ASIC_REV_ANY 0
/**
* @brief Internal definition of workarounds
*/
typedef enum {
HW_WORKAROUND_TEST = 1,
HW_WORKAROUND_MAX_QUEUE, /**< Limits all queues */
HW_WORKAROUND_MAX_RQ, /**< Limits only the RQ */
HW_WORKAROUND_RETAIN_TSEND_IO_LENGTH,
HW_WORKAROUND_WQE_COUNT_METHOD,
HW_WORKAROUND_RQE_COUNT_METHOD,
HW_WORKAROUND_USE_UNREGISTERD_RPI,
HW_WORKAROUND_DISABLE_AR_TGT_DIF, /**< Disable of auto-response target DIF */
HW_WORKAROUND_DISABLE_SET_DUMP_LOC,
HW_WORKAROUND_USE_DIF_QUARANTINE,
HW_WORKAROUND_USE_DIF_SEC_XRI, /**< Use secondary xri for multiple data phases */
HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB, /**< FCFI reported in SRB not correct, use "first" registered domain */
HW_WORKAROUND_FW_VERSION_TOO_LOW, /**< The FW version is not the min version supported by this driver */
HW_WORKAROUND_SGLC_MISREPORTED, /**< Chip supports SGL Chaining but SGLC is not set in SLI4_PARAMS */
HW_WORKAROUND_IGNORE_SEND_FRAME_CAPABLE, /**< Don't use SEND_FRAME capable if FW version is too old */
} hw_workaround_e;
/**
* @brief Internal workaround structure instance
*/
typedef struct {
sli4_asic_type_e asic_type;
sli4_asic_rev_e asic_rev;
uint64_t fwrev_low;
uint64_t fwrev_high;
hw_workaround_e workaround;
uint32_t value;
} hw_workaround_t;
static hw_workaround_t hw_workarounds[] = {
{SLI4_ASIC_TYPE_ANY, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_TEST, 999},
/* Bug: 127585: if_type == 2 returns 0 for total length placed on
* FCP_TSEND64_WQE completions. Note, original driver code enables this
* workaround for all asic types
*/
{SLI4_ASIC_TYPE_ANY, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_RETAIN_TSEND_IO_LENGTH, 0},
/* Bug: unknown, Lancer A0 has mis-reported max queue depth */
{SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_A0, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_MAX_QUEUE, 2048},
/* Bug: 143399, BE3 has mis-reported max RQ queue depth */
{SLI4_ASIC_TYPE_BE3, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(4,6,293,0),
HW_WORKAROUND_MAX_RQ, 2048},
/* Bug: 143399, skyhawk has mis-reported max RQ queue depth */
{SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(10,0,594,0),
HW_WORKAROUND_MAX_RQ, 2048},
/* Bug: 103487, BE3 before f/w 4.2.314.0 has mis-reported WQE count method */
{SLI4_ASIC_TYPE_BE3, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(4,2,314,0),
HW_WORKAROUND_WQE_COUNT_METHOD, 1},
/* Bug: 103487, BE3 before f/w 4.2.314.0 has mis-reported RQE count method */
{SLI4_ASIC_TYPE_BE3, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(4,2,314,0),
HW_WORKAROUND_RQE_COUNT_METHOD, 1},
/* Bug: 142968, BE3 UE with RPI == 0xffff */
{SLI4_ASIC_TYPE_BE3, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_USE_UNREGISTERD_RPI, 0},
/* Bug: unknown, Skyhawk won't support auto-response on target T10-PI */
{SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_DISABLE_AR_TGT_DIF, 0},
{SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV(1,1,65,0),
HW_WORKAROUND_DISABLE_SET_DUMP_LOC, 0},
/* Bug: 160124, Skyhawk quarantine DIF XRIs */
{SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_USE_DIF_QUARANTINE, 0},
/* Bug: 161832, Skyhawk use secondary XRI for multiple data phase TRECV */
{SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_USE_DIF_SEC_XRI, 0},
/* Bug: xxxxxx, FCFI reported in SRB not corrrect */
{SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB, 0},
#if 0
/* Bug: 165642, FW version check for driver */
{SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_1(OCS_MIN_FW_VER_LANCER),
HW_WORKAROUND_FW_VERSION_TOO_LOW, 0},
#endif
{SLI4_ASIC_TYPE_SKYHAWK, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_1(OCS_MIN_FW_VER_SKYHAWK),
HW_WORKAROUND_FW_VERSION_TOO_LOW, 0},
/* Bug 177061, Lancer FW does not set the SGLC bit */
{SLI4_ASIC_TYPE_LANCER, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_SGLC_MISREPORTED, 0},
/* BZ 181208/183914, enable this workaround for ALL revisions */
{SLI4_ASIC_TYPE_ANY, SLI4_ASIC_REV_ANY, HW_FWREV_ZERO, HW_FWREV_MAX,
HW_WORKAROUND_IGNORE_SEND_FRAME_CAPABLE, 0},
};
/**
* @brief Function prototypes
*/
static int32_t ocs_hw_workaround_match(ocs_hw_t *hw, hw_workaround_t *w);
/**
* @brief Parse the firmware version (name)
*
* Parse a string of the form a.b.c.d, returning a uint64_t packed as defined
* by the HW_FWREV() macro
*
* @param fwrev_string pointer to the firmware string
*
* @return packed firmware revision value
*/
static uint64_t
parse_fw_version(const char *fwrev_string)
{
int v[4] = {0};
const char *p;
int i;
for (p = fwrev_string, i = 0; *p && (i < 4); i ++) {
v[i] = ocs_strtoul(p, 0, 0);
while(*p && *p != '.') {
p ++;
}
if (*p) {
p ++;
}
}
/* Special case for bootleg releases with f/w rev 0.0.9999.0, set to max value */
if (v[2] == 9999) {
return HW_FWREV_MAX;
} else {
return HW_FWREV(v[0], v[1], v[2], v[3]);
}
}
/**
* @brief Test for a workaround match
*
* Looks at the asic type, asic revision, and fw revision, and returns TRUE if match.
*
* @param hw Pointer to the HW structure
* @param w Pointer to a workaround structure entry
*
* @return Return TRUE for a match
*/
static int32_t
ocs_hw_workaround_match(ocs_hw_t *hw, hw_workaround_t *w)
{
return (((w->asic_type == SLI4_ASIC_TYPE_ANY) || (w->asic_type == hw->sli.asic_type)) &&
((w->asic_rev == SLI4_ASIC_REV_ANY) || (w->asic_rev == hw->sli.asic_rev)) &&
(w->fwrev_low <= hw->workaround.fwrev) &&
((w->fwrev_high == HW_FWREV_MAX) || (hw->workaround.fwrev < w->fwrev_high)));
}
/**
* @brief Setup HW runtime workarounds
*
* The function is called at the end of ocs_hw_setup() to setup any runtime workarounds
* based on the HW/SLI setup.
*
* @param hw Pointer to HW structure
*
* @return none
*/
void
ocs_hw_workaround_setup(struct ocs_hw_s *hw)
{
hw_workaround_t *w;
sli4_t *sli4 = &hw->sli;
uint32_t i;
/* Initialize the workaround settings */
ocs_memset(&hw->workaround, 0, sizeof(hw->workaround));
/* If hw_war_version is non-null, then its a value that was set by a module parameter
* (sorry for the break in abstraction, but workarounds are ... well, workarounds)
*/
if (hw->hw_war_version) {
hw->workaround.fwrev = parse_fw_version(hw->hw_war_version);
} else {
hw->workaround.fwrev = parse_fw_version((char*) sli4->config.fw_name[0]);
}
/* Walk the workaround list, if a match is found, then handle it */
for (i = 0, w = hw_workarounds; i < ARRAY_SIZE(hw_workarounds); i++, w++) {
if (ocs_hw_workaround_match(hw, w)) {
switch(w->workaround) {
case HW_WORKAROUND_TEST: {
ocs_log_debug(hw->os, "Override: test: %d\n", w->value);
break;
}
case HW_WORKAROUND_RETAIN_TSEND_IO_LENGTH: {
ocs_log_debug(hw->os, "HW Workaround: retain TSEND IO length\n");
hw->workaround.retain_tsend_io_length = 1;
break;
}
case HW_WORKAROUND_MAX_QUEUE: {
sli4_qtype_e q;
ocs_log_debug(hw->os, "HW Workaround: override max_qentries: %d\n", w->value);
for (q = SLI_QTYPE_EQ; q < SLI_QTYPE_MAX; q++) {
if (hw->num_qentries[q] > w->value) {
hw->num_qentries[q] = w->value;
}
}
break;
}
case HW_WORKAROUND_MAX_RQ: {
ocs_log_debug(hw->os, "HW Workaround: override RQ max_qentries: %d\n", w->value);
if (hw->num_qentries[SLI_QTYPE_RQ] > w->value) {
hw->num_qentries[SLI_QTYPE_RQ] = w->value;
}
break;
}
case HW_WORKAROUND_WQE_COUNT_METHOD: {
ocs_log_debug(hw->os, "HW Workaround: set WQE count method=%d\n", w->value);
sli4->config.count_method[SLI_QTYPE_WQ] = w->value;
sli_calc_max_qentries(sli4);
break;
}
case HW_WORKAROUND_RQE_COUNT_METHOD: {
ocs_log_debug(hw->os, "HW Workaround: set RQE count method=%d\n", w->value);
sli4->config.count_method[SLI_QTYPE_RQ] = w->value;
sli_calc_max_qentries(sli4);
break;
}
case HW_WORKAROUND_USE_UNREGISTERD_RPI:
ocs_log_debug(hw->os, "HW Workaround: use unreg'd RPI if rnode->indicator == 0xFFFF\n");
hw->workaround.use_unregistered_rpi = TRUE;
/*
* Allocate an RPI that is never registered, to be used in the case where
* a node has been unregistered, and its indicator (RPI) value is set to 0xFFFF
*/
if (sli_resource_alloc(&hw->sli, SLI_RSRC_FCOE_RPI, &hw->workaround.unregistered_rid,
&hw->workaround.unregistered_index)) {
ocs_log_err(hw->os, "sli_resource_alloc unregistered RPI failed\n");
hw->workaround.use_unregistered_rpi = FALSE;
}
break;
case HW_WORKAROUND_DISABLE_AR_TGT_DIF:
ocs_log_debug(hw->os, "HW Workaround: disable AR on T10-PI TSEND\n");
hw->workaround.disable_ar_tgt_dif = TRUE;
break;
case HW_WORKAROUND_DISABLE_SET_DUMP_LOC:
ocs_log_debug(hw->os, "HW Workaround: disable set_dump_loc\n");
hw->workaround.disable_dump_loc = TRUE;
break;
case HW_WORKAROUND_USE_DIF_QUARANTINE:
ocs_log_debug(hw->os, "HW Workaround: use DIF quarantine\n");
hw->workaround.use_dif_quarantine = TRUE;
break;
case HW_WORKAROUND_USE_DIF_SEC_XRI:
ocs_log_debug(hw->os, "HW Workaround: use DIF secondary xri\n");
hw->workaround.use_dif_sec_xri = TRUE;
break;
case HW_WORKAROUND_OVERRIDE_FCFI_IN_SRB:
ocs_log_debug(hw->os, "HW Workaround: override FCFI in SRB\n");
hw->workaround.override_fcfi = TRUE;
break;
case HW_WORKAROUND_FW_VERSION_TOO_LOW:
ocs_log_debug(hw->os, "HW Workaround: fw version is below the minimum for this driver\n");
hw->workaround.fw_version_too_low = TRUE;
break;
case HW_WORKAROUND_SGLC_MISREPORTED:
ocs_log_debug(hw->os, "HW Workaround: SGLC misreported - chaining is enabled\n");
hw->workaround.sglc_misreported = TRUE;
break;
case HW_WORKAROUND_IGNORE_SEND_FRAME_CAPABLE:
ocs_log_debug(hw->os, "HW Workaround: not SEND_FRAME capable - disabled\n");
hw->workaround.ignore_send_frame = TRUE;
break;
} /* switch(w->workaround) */
}
}
}