63424713e5
This is a temporary workaround until we determine a reliable sequence of operations for detecting MC reboots. Sponsored by: Solarflare Communications, Inc. Differential Revision: https://reviews.freebsd.org/D2084
743 lines
19 KiB
C
743 lines
19 KiB
C
/*-
|
|
* Copyright 2008-2009 Solarflare Communications Inc. All rights reserved.
|
|
*
|
|
* 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 <sys/cdefs.h>
|
|
__FBSDID("$FreeBSD$");
|
|
|
|
#include "efsys.h"
|
|
#include "efx.h"
|
|
#include "efx_types.h"
|
|
#include "efx_regs.h"
|
|
#include "efx_regs_mcdi.h"
|
|
#include "efx_impl.h"
|
|
|
|
#if EFSYS_OPT_MCDI
|
|
|
|
/*
|
|
* A reboot/assertion causes the MCDI status word to be set after the
|
|
* command word is set or a REBOOT event is sent. If we notice a reboot
|
|
* via these mechanisms then wait 10ms for the status word to be set.
|
|
*/
|
|
#define MCDI_STATUS_SLEEP_US 10000
|
|
|
|
void
|
|
efx_mcdi_request_start(
|
|
__in efx_nic_t *enp,
|
|
__in efx_mcdi_req_t *emrp,
|
|
__in boolean_t ev_cpl)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
efx_dword_t dword;
|
|
unsigned int seq;
|
|
unsigned int xflags;
|
|
unsigned int pdur;
|
|
unsigned int dbr;
|
|
unsigned int pos;
|
|
int state;
|
|
|
|
EFSYS_ASSERT3U(enp->en_magic, ==, EFX_NIC_MAGIC);
|
|
EFSYS_ASSERT3U(enp->en_mod_flags, &, EFX_MOD_MCDI);
|
|
EFSYS_ASSERT3U(enp->en_features, &, EFX_FEATURE_MCDI);
|
|
|
|
switch (emip->emi_port) {
|
|
case 1:
|
|
pdur = MC_SMEM_P0_PDU_OFST >> 2;
|
|
dbr = MC_SMEM_P0_DOORBELL_OFST >> 2;
|
|
break;
|
|
case 2:
|
|
pdur = MC_SMEM_P1_PDU_OFST >> 2;
|
|
dbr = MC_SMEM_P1_DOORBELL_OFST >> 2;
|
|
break;
|
|
default:
|
|
EFSYS_ASSERT(0);
|
|
pdur = dbr = 0;
|
|
};
|
|
|
|
/*
|
|
* efx_mcdi_request_start() is naturally serialised against both
|
|
* efx_mcdi_request_poll() and efx_mcdi_ev_cpl()/efx_mcdi_ev_death(),
|
|
* by virtue of there only being one outstanding MCDI request.
|
|
* Unfortunately, upper layers may also call efx_mcdi_request_abort()
|
|
* at any time, to timeout a pending mcdi request, That request may
|
|
* then subsequently complete, meaning efx_mcdi_ev_cpl() or
|
|
* efx_mcdi_ev_death() may end up running in parallel with
|
|
* efx_mcdi_request_start(). This race is handled by ensuring that
|
|
* %emi_pending_req, %emi_ev_cpl and %emi_seq are protected by the
|
|
* en_eslp lock.
|
|
*/
|
|
EFSYS_LOCK(enp->en_eslp, state);
|
|
EFSYS_ASSERT(emip->emi_pending_req == NULL);
|
|
emip->emi_pending_req = emrp;
|
|
emip->emi_ev_cpl = ev_cpl;
|
|
emip->emi_poll_cnt = 0;
|
|
seq = emip->emi_seq++ & 0xf;
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
|
|
xflags = 0;
|
|
if (ev_cpl)
|
|
xflags |= MCDI_HEADER_XFLAGS_EVREQ;
|
|
|
|
/* Construct the header in shared memory */
|
|
EFX_POPULATE_DWORD_6(dword,
|
|
MCDI_HEADER_CODE, emrp->emr_cmd,
|
|
MCDI_HEADER_RESYNC, 1,
|
|
MCDI_HEADER_DATALEN, emrp->emr_in_length,
|
|
MCDI_HEADER_SEQ, seq,
|
|
MCDI_HEADER_RESPONSE, 0,
|
|
MCDI_HEADER_XFLAGS, xflags);
|
|
EFX_BAR_TBL_WRITED(enp, FR_CZ_MC_TREG_SMEM, pdur, &dword, B_TRUE);
|
|
|
|
for (pos = 0; pos < emrp->emr_in_length; pos += sizeof (efx_dword_t)) {
|
|
memcpy(&dword, MCDI_IN(*emrp, efx_dword_t, pos),
|
|
MIN(sizeof (dword), emrp->emr_in_length - pos));
|
|
EFX_BAR_TBL_WRITED(enp, FR_CZ_MC_TREG_SMEM,
|
|
pdur + 1 + (pos >> 2), &dword, B_FALSE);
|
|
}
|
|
|
|
/* Ring the doorbell */
|
|
EFX_POPULATE_DWORD_1(dword, EFX_DWORD_0, 0xd004be11);
|
|
EFX_BAR_TBL_WRITED(enp, FR_CZ_MC_TREG_SMEM, dbr, &dword, B_FALSE);
|
|
}
|
|
|
|
static void
|
|
efx_mcdi_request_copyout(
|
|
__in efx_nic_t *enp,
|
|
__in efx_mcdi_req_t *emrp)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
unsigned int pos;
|
|
unsigned int pdur;
|
|
efx_dword_t data;
|
|
|
|
pdur = (emip->emi_port == 1)
|
|
? MC_SMEM_P0_PDU_OFST >> 2
|
|
: MC_SMEM_P1_PDU_OFST >> 2;
|
|
|
|
/* Copy payload out if caller supplied buffer */
|
|
if (emrp->emr_out_buf != NULL) {
|
|
size_t bytes = MIN(emrp->emr_out_length_used,
|
|
emrp->emr_out_length);
|
|
for (pos = 0; pos < bytes; pos += sizeof (efx_dword_t)) {
|
|
EFX_BAR_TBL_READD(enp, FR_CZ_MC_TREG_SMEM,
|
|
pdur + 1 + (pos >> 2), &data, B_FALSE);
|
|
memcpy(MCDI_OUT(*emrp, efx_dword_t, pos), &data,
|
|
MIN(sizeof (data), bytes - pos));
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
efx_mcdi_request_errcode(
|
|
__in unsigned int err)
|
|
{
|
|
|
|
switch (err) {
|
|
case MC_CMD_ERR_ENOENT:
|
|
return (ENOENT);
|
|
case MC_CMD_ERR_EINTR:
|
|
return (EINTR);
|
|
case MC_CMD_ERR_EACCES:
|
|
return (EACCES);
|
|
case MC_CMD_ERR_EBUSY:
|
|
return (EBUSY);
|
|
case MC_CMD_ERR_EINVAL:
|
|
return (EINVAL);
|
|
case MC_CMD_ERR_EDEADLK:
|
|
return (EDEADLK);
|
|
case MC_CMD_ERR_ENOSYS:
|
|
return (ENOTSUP);
|
|
case MC_CMD_ERR_ETIME:
|
|
return (ETIMEDOUT);
|
|
#ifdef WITH_MCDI_V2
|
|
case MC_CMD_ERR_EAGAIN:
|
|
return (EAGAIN);
|
|
case MC_CMD_ERR_ENOSPC:
|
|
return (ENOSPC);
|
|
#endif
|
|
default:
|
|
EFSYS_PROBE1(mc_pcol_error, int, err);
|
|
return (EIO);
|
|
}
|
|
}
|
|
|
|
static void
|
|
efx_mcdi_raise_exception(
|
|
__in efx_nic_t *enp,
|
|
__in_opt efx_mcdi_req_t *emrp,
|
|
__in int rc)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
const efx_mcdi_transport_t *emtp = emip->emi_mtp;
|
|
efx_mcdi_exception_t exception;
|
|
|
|
/* Reboot or Assertion failure only */
|
|
EFSYS_ASSERT(rc == EIO || rc == EINTR);
|
|
|
|
/*
|
|
* If MC_CMD_REBOOT causes a reboot (dependent on parameters),
|
|
* then the EIO is not worthy of an exception.
|
|
*/
|
|
if (emrp != NULL && emrp->emr_cmd == MC_CMD_REBOOT && rc == EIO)
|
|
return;
|
|
|
|
exception = (rc == EIO)
|
|
? EFX_MCDI_EXCEPTION_MC_REBOOT
|
|
: EFX_MCDI_EXCEPTION_MC_BADASSERT;
|
|
|
|
emtp->emt_exception(emtp->emt_context, exception);
|
|
}
|
|
|
|
static int
|
|
efx_mcdi_poll_reboot(
|
|
__in efx_nic_t *enp)
|
|
{
|
|
#ifndef EFX_GRACEFUL_MC_REBOOT
|
|
/*
|
|
* This function is not being used properly.
|
|
* Until its callers are fixed, it should always return 0.
|
|
*/
|
|
_NOTE(ARGUNUSED(enp))
|
|
return (0);
|
|
#else
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
unsigned int rebootr;
|
|
efx_dword_t dword;
|
|
uint32_t value;
|
|
|
|
EFSYS_ASSERT(emip->emi_port == 1 || emip->emi_port == 2);
|
|
rebootr = ((emip->emi_port == 1)
|
|
? MC_SMEM_P0_STATUS_OFST >> 2
|
|
: MC_SMEM_P1_STATUS_OFST >> 2);
|
|
|
|
EFX_BAR_TBL_READD(enp, FR_CZ_MC_TREG_SMEM, rebootr, &dword, B_FALSE);
|
|
value = EFX_DWORD_FIELD(dword, EFX_DWORD_0);
|
|
|
|
if (value == 0)
|
|
return (0);
|
|
|
|
EFX_ZERO_DWORD(dword);
|
|
EFX_BAR_TBL_WRITED(enp, FR_CZ_MC_TREG_SMEM, rebootr, &dword, B_FALSE);
|
|
|
|
if (value == MC_STATUS_DWORD_ASSERT)
|
|
return (EINTR);
|
|
else
|
|
return (EIO);
|
|
#endif
|
|
}
|
|
|
|
__checkReturn boolean_t
|
|
efx_mcdi_request_poll(
|
|
__in efx_nic_t *enp)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
efx_mcdi_req_t *emrp;
|
|
efx_dword_t dword;
|
|
unsigned int pdur;
|
|
unsigned int seq;
|
|
unsigned int length;
|
|
int state;
|
|
int rc;
|
|
|
|
EFSYS_ASSERT3U(enp->en_magic, ==, EFX_NIC_MAGIC);
|
|
EFSYS_ASSERT3U(enp->en_mod_flags, &, EFX_MOD_MCDI);
|
|
EFSYS_ASSERT3U(enp->en_family, ==, EFX_FAMILY_SIENA);
|
|
EFSYS_ASSERT3U(enp->en_features, &, EFX_FEATURE_MCDI);
|
|
|
|
/* Serialise against post-watchdog efx_mcdi_ev* */
|
|
EFSYS_LOCK(enp->en_eslp, state);
|
|
|
|
EFSYS_ASSERT(emip->emi_pending_req != NULL);
|
|
EFSYS_ASSERT(!emip->emi_ev_cpl);
|
|
emrp = emip->emi_pending_req;
|
|
|
|
/* Check for reboot atomically w.r.t efx_mcdi_request_start */
|
|
if (emip->emi_poll_cnt++ == 0) {
|
|
if ((rc = efx_mcdi_poll_reboot(enp)) != 0) {
|
|
emip->emi_pending_req = NULL;
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
|
|
goto fail1;
|
|
}
|
|
}
|
|
|
|
EFSYS_ASSERT(emip->emi_port == 1 || emip->emi_port == 2);
|
|
pdur = (emip->emi_port == 1)
|
|
? MC_SMEM_P0_PDU_OFST >> 2
|
|
: MC_SMEM_P1_PDU_OFST >> 2;
|
|
|
|
/* Read the command header */
|
|
EFX_BAR_TBL_READD(enp, FR_CZ_MC_TREG_SMEM, pdur, &dword, B_FALSE);
|
|
if (EFX_DWORD_FIELD(dword, MCDI_HEADER_RESPONSE) == 0) {
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
return (B_FALSE);
|
|
}
|
|
|
|
/* Request complete */
|
|
emip->emi_pending_req = NULL;
|
|
seq = (emip->emi_seq - 1) & 0xf;
|
|
|
|
/* Check for synchronous reboot */
|
|
if (EFX_DWORD_FIELD(dword, MCDI_HEADER_ERROR) != 0 &&
|
|
EFX_DWORD_FIELD(dword, MCDI_HEADER_DATALEN) == 0) {
|
|
/* Consume status word */
|
|
EFSYS_SPIN(MCDI_STATUS_SLEEP_US);
|
|
efx_mcdi_poll_reboot(enp);
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
rc = EIO;
|
|
goto fail2;
|
|
}
|
|
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
|
|
/* Check that the returned data is consistent */
|
|
if (EFX_DWORD_FIELD(dword, MCDI_HEADER_CODE) != emrp->emr_cmd ||
|
|
EFX_DWORD_FIELD(dword, MCDI_HEADER_SEQ) != seq) {
|
|
/* Response is for a different request */
|
|
rc = EIO;
|
|
goto fail3;
|
|
}
|
|
|
|
length = EFX_DWORD_FIELD(dword, MCDI_HEADER_DATALEN);
|
|
if (EFX_DWORD_FIELD(dword, MCDI_HEADER_ERROR)) {
|
|
efx_dword_t errdword;
|
|
int errcode;
|
|
|
|
EFSYS_ASSERT3U(length, ==, 4);
|
|
EFX_BAR_TBL_READD(enp, FR_CZ_MC_TREG_SMEM,
|
|
pdur + 1 + (MC_CMD_ERR_CODE_OFST >> 2),
|
|
&errdword, B_FALSE);
|
|
errcode = EFX_DWORD_FIELD(errdword, EFX_DWORD_0);
|
|
rc = efx_mcdi_request_errcode(errcode);
|
|
EFSYS_PROBE2(mcdi_err, int, emrp->emr_cmd, int, errcode);
|
|
goto fail4;
|
|
|
|
} else {
|
|
emrp->emr_out_length_used = length;
|
|
emrp->emr_rc = 0;
|
|
efx_mcdi_request_copyout(enp, emrp);
|
|
}
|
|
|
|
goto out;
|
|
|
|
fail4:
|
|
EFSYS_PROBE(fail4);
|
|
fail3:
|
|
EFSYS_PROBE(fail3);
|
|
fail2:
|
|
EFSYS_PROBE(fail2);
|
|
fail1:
|
|
EFSYS_PROBE1(fail1, int, rc);
|
|
|
|
/* Fill out error state */
|
|
emrp->emr_rc = rc;
|
|
emrp->emr_out_length_used = 0;
|
|
|
|
/* Reboot/Assertion */
|
|
if (rc == EIO || rc == EINTR)
|
|
efx_mcdi_raise_exception(enp, emrp, rc);
|
|
|
|
out:
|
|
return (B_TRUE);
|
|
}
|
|
|
|
void
|
|
efx_mcdi_execute(
|
|
__in efx_nic_t *enp,
|
|
__in efx_mcdi_req_t *emrp)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
const efx_mcdi_transport_t *emtp = emip->emi_mtp;
|
|
|
|
EFSYS_ASSERT3U(enp->en_mod_flags, &, EFX_MOD_MCDI);
|
|
EFSYS_ASSERT3U(enp->en_family, ==, EFX_FAMILY_SIENA);
|
|
EFSYS_ASSERT3U(enp->en_features, &, EFX_FEATURE_MCDI);
|
|
|
|
emtp->emt_execute(emtp->emt_context, emrp);
|
|
}
|
|
|
|
void
|
|
efx_mcdi_ev_cpl(
|
|
__in efx_nic_t *enp,
|
|
__in unsigned int seq,
|
|
__in unsigned int outlen,
|
|
__in int errcode)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
const efx_mcdi_transport_t *emtp = emip->emi_mtp;
|
|
efx_mcdi_req_t *emrp;
|
|
int state;
|
|
|
|
EFSYS_ASSERT3U(enp->en_mod_flags, &, EFX_MOD_MCDI);
|
|
EFSYS_ASSERT3U(enp->en_family, ==, EFX_FAMILY_SIENA);
|
|
EFSYS_ASSERT3U(enp->en_features, &, EFX_FEATURE_MCDI);
|
|
|
|
/*
|
|
* Serialise against efx_mcdi_request_poll()/efx_mcdi_request_start()
|
|
* when we're completing an aborted request.
|
|
*/
|
|
EFSYS_LOCK(enp->en_eslp, state);
|
|
if (emip->emi_pending_req == NULL || !emip->emi_ev_cpl ||
|
|
(seq != ((emip->emi_seq - 1) & 0xf))) {
|
|
EFSYS_ASSERT(emip->emi_aborted > 0);
|
|
if (emip->emi_aborted > 0)
|
|
--emip->emi_aborted;
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
return;
|
|
}
|
|
|
|
emrp = emip->emi_pending_req;
|
|
emip->emi_pending_req = NULL;
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
|
|
/*
|
|
* Fill out the remaining hdr fields, and copyout the payload
|
|
* if the user supplied an output buffer.
|
|
*/
|
|
if (errcode != 0) {
|
|
EFSYS_PROBE2(mcdi_err, int, emrp->emr_cmd,
|
|
int, errcode);
|
|
emrp->emr_out_length_used = 0;
|
|
emrp->emr_rc = efx_mcdi_request_errcode(errcode);
|
|
} else {
|
|
emrp->emr_out_length_used = outlen;
|
|
emrp->emr_rc = 0;
|
|
efx_mcdi_request_copyout(enp, emrp);
|
|
}
|
|
|
|
emtp->emt_ev_cpl(emtp->emt_context);
|
|
}
|
|
|
|
void
|
|
efx_mcdi_ev_death(
|
|
__in efx_nic_t *enp,
|
|
__in int rc)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
const efx_mcdi_transport_t *emtp = emip->emi_mtp;
|
|
efx_mcdi_req_t *emrp = NULL;
|
|
boolean_t ev_cpl;
|
|
int state;
|
|
|
|
/*
|
|
* The MCDI request (if there is one) has been terminated, either
|
|
* by a BADASSERT or REBOOT event.
|
|
*
|
|
* If there is an outstanding event-completed MCDI operation, then we
|
|
* will never receive the completion event (because both MCDI
|
|
* completions and BADASSERT events are sent to the same evq). So
|
|
* complete this MCDI op.
|
|
*
|
|
* This function might run in parallel with efx_mcdi_request_poll()
|
|
* for poll completed mcdi requests, and also with
|
|
* efx_mcdi_request_start() for post-watchdog completions.
|
|
*/
|
|
EFSYS_LOCK(enp->en_eslp, state);
|
|
emrp = emip->emi_pending_req;
|
|
ev_cpl = emip->emi_ev_cpl;
|
|
if (emrp != NULL && emip->emi_ev_cpl) {
|
|
emip->emi_pending_req = NULL;
|
|
|
|
emrp->emr_out_length_used = 0;
|
|
emrp->emr_rc = rc;
|
|
++emip->emi_aborted;
|
|
}
|
|
|
|
/*
|
|
* Since we're running in parallel with a request, consume the
|
|
* status word before dropping the lock.
|
|
*/
|
|
if (rc == EIO || rc == EINTR) {
|
|
EFSYS_SPIN(MCDI_STATUS_SLEEP_US);
|
|
(void) efx_mcdi_poll_reboot(enp);
|
|
}
|
|
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
|
|
efx_mcdi_raise_exception(enp, emrp, rc);
|
|
|
|
if (emrp != NULL && ev_cpl)
|
|
emtp->emt_ev_cpl(emtp->emt_context);
|
|
}
|
|
|
|
__checkReturn int
|
|
efx_mcdi_version(
|
|
__in efx_nic_t *enp,
|
|
__out_ecount_opt(4) uint16_t versionp[4],
|
|
__out_opt uint32_t *buildp,
|
|
__out_opt efx_mcdi_boot_t *statusp)
|
|
{
|
|
uint8_t outbuf[MAX(MC_CMD_GET_VERSION_OUT_LEN,
|
|
MC_CMD_GET_BOOT_STATUS_OUT_LEN)];
|
|
efx_mcdi_req_t req;
|
|
efx_word_t *ver_words;
|
|
uint16_t version[4];
|
|
uint32_t build;
|
|
efx_mcdi_boot_t status;
|
|
int rc;
|
|
|
|
EFSYS_ASSERT3U(enp->en_family, ==, EFX_FAMILY_SIENA);
|
|
EFSYS_ASSERT3U(enp->en_features, &, EFX_FEATURE_MCDI);
|
|
|
|
EFX_STATIC_ASSERT(MC_CMD_GET_VERSION_IN_LEN == 0);
|
|
req.emr_cmd = MC_CMD_GET_VERSION;
|
|
req.emr_in_buf = NULL;
|
|
req.emr_in_length = 0;
|
|
req.emr_out_buf = outbuf;
|
|
req.emr_out_length = MC_CMD_GET_VERSION_OUT_LEN;
|
|
|
|
efx_mcdi_execute(enp, &req);
|
|
|
|
if (req.emr_rc != 0) {
|
|
rc = req.emr_rc;
|
|
goto fail1;
|
|
}
|
|
|
|
/* bootrom support */
|
|
if (req.emr_out_length_used == MC_CMD_GET_VERSION_V0_OUT_LEN) {
|
|
version[0] = version[1] = version[2] = version[3] = 0;
|
|
build = MCDI_OUT_DWORD(req, GET_VERSION_OUT_FIRMWARE);
|
|
|
|
goto version;
|
|
}
|
|
|
|
if (req.emr_out_length_used < MC_CMD_GET_VERSION_OUT_LEN) {
|
|
rc = EMSGSIZE;
|
|
goto fail2;
|
|
}
|
|
|
|
ver_words = MCDI_OUT2(req, efx_word_t, GET_VERSION_OUT_VERSION);
|
|
version[0] = EFX_WORD_FIELD(ver_words[0], EFX_WORD_0);
|
|
version[1] = EFX_WORD_FIELD(ver_words[1], EFX_WORD_0);
|
|
version[2] = EFX_WORD_FIELD(ver_words[2], EFX_WORD_0);
|
|
version[3] = EFX_WORD_FIELD(ver_words[3], EFX_WORD_0);
|
|
build = MCDI_OUT_DWORD(req, GET_VERSION_OUT_FIRMWARE);
|
|
|
|
version:
|
|
/* The bootrom doesn't understand BOOT_STATUS */
|
|
if (build == MC_CMD_GET_VERSION_OUT_FIRMWARE_SIENA_BOOTROM) {
|
|
status = EFX_MCDI_BOOT_ROM;
|
|
goto out;
|
|
}
|
|
|
|
req.emr_cmd = MC_CMD_GET_BOOT_STATUS;
|
|
EFX_STATIC_ASSERT(MC_CMD_GET_BOOT_STATUS_IN_LEN == 0);
|
|
req.emr_in_buf = NULL;
|
|
req.emr_in_length = 0;
|
|
req.emr_out_buf = outbuf;
|
|
req.emr_out_length = MC_CMD_GET_BOOT_STATUS_OUT_LEN;
|
|
|
|
efx_mcdi_execute(enp, &req);
|
|
|
|
if (req.emr_rc != 0) {
|
|
rc = req.emr_rc;
|
|
goto fail3;
|
|
}
|
|
|
|
if (req.emr_out_length_used < MC_CMD_GET_BOOT_STATUS_OUT_LEN) {
|
|
rc = EMSGSIZE;
|
|
goto fail4;
|
|
}
|
|
|
|
if (MCDI_OUT_DWORD_FIELD(req, GET_BOOT_STATUS_OUT_FLAGS,
|
|
GET_BOOT_STATUS_OUT_FLAGS_PRIMARY))
|
|
status = EFX_MCDI_BOOT_PRIMARY;
|
|
else
|
|
status = EFX_MCDI_BOOT_SECONDARY;
|
|
|
|
out:
|
|
if (versionp != NULL)
|
|
memcpy(versionp, version, sizeof (version));
|
|
if (buildp != NULL)
|
|
*buildp = build;
|
|
if (statusp != NULL)
|
|
*statusp = status;
|
|
|
|
return (0);
|
|
|
|
fail4:
|
|
EFSYS_PROBE(fail4);
|
|
fail3:
|
|
EFSYS_PROBE(fail3);
|
|
fail2:
|
|
EFSYS_PROBE(fail2);
|
|
fail1:
|
|
EFSYS_PROBE1(fail1, int, rc);
|
|
|
|
return (rc);
|
|
}
|
|
|
|
__checkReturn int
|
|
efx_mcdi_init(
|
|
__in efx_nic_t *enp,
|
|
__in const efx_mcdi_transport_t *mtp)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
efx_oword_t oword;
|
|
unsigned int portnum;
|
|
int rc;
|
|
|
|
EFSYS_ASSERT3U(enp->en_mod_flags, ==, 0);
|
|
enp->en_mod_flags |= EFX_MOD_MCDI;
|
|
|
|
if (enp->en_family == EFX_FAMILY_FALCON)
|
|
return (0);
|
|
|
|
emip->emi_mtp = mtp;
|
|
|
|
/* Determine the port number to use for MCDI */
|
|
EFX_BAR_READO(enp, FR_AZ_CS_DEBUG_REG, &oword);
|
|
portnum = EFX_OWORD_FIELD(oword, FRF_CZ_CS_PORT_NUM);
|
|
|
|
if (portnum == 0) {
|
|
/* Presumably booted from ROM; only MCDI port 1 will work */
|
|
emip->emi_port = 1;
|
|
} else if (portnum <= 2) {
|
|
emip->emi_port = portnum;
|
|
} else {
|
|
rc = EINVAL;
|
|
goto fail1;
|
|
}
|
|
|
|
/*
|
|
* Wipe the atomic reboot status so subsequent MCDI requests succeed.
|
|
* BOOT_STATUS is preserved so eno_nic_probe() can boot out of the
|
|
* assertion handler.
|
|
*/
|
|
(void) efx_mcdi_poll_reboot(enp);
|
|
|
|
return (0);
|
|
|
|
fail1:
|
|
EFSYS_PROBE1(fail1, int, rc);
|
|
|
|
enp->en_mod_flags &= ~EFX_MOD_MCDI;
|
|
|
|
return (rc);
|
|
}
|
|
|
|
|
|
__checkReturn int
|
|
efx_mcdi_reboot(
|
|
__in efx_nic_t *enp)
|
|
{
|
|
uint8_t payload[MC_CMD_REBOOT_IN_LEN];
|
|
efx_mcdi_req_t req;
|
|
int rc;
|
|
|
|
/*
|
|
* We could require the caller to have caused en_mod_flags=0 to
|
|
* call this function. This doesn't help the other port though,
|
|
* who's about to get the MC ripped out from underneath them.
|
|
* Since they have to cope with the subsequent fallout of MCDI
|
|
* failures, we should as well.
|
|
*/
|
|
EFSYS_ASSERT3U(enp->en_magic, ==, EFX_NIC_MAGIC);
|
|
|
|
req.emr_cmd = MC_CMD_REBOOT;
|
|
req.emr_in_buf = payload;
|
|
req.emr_in_length = MC_CMD_REBOOT_IN_LEN;
|
|
req.emr_out_buf = NULL;
|
|
req.emr_out_length = 0;
|
|
|
|
MCDI_IN_SET_DWORD(req, REBOOT_IN_FLAGS, 0);
|
|
|
|
efx_mcdi_execute(enp, &req);
|
|
|
|
/* Invert EIO */
|
|
if (req.emr_rc != EIO) {
|
|
rc = EIO;
|
|
goto fail1;
|
|
}
|
|
|
|
return (0);
|
|
|
|
fail1:
|
|
EFSYS_PROBE1(fail1, int, rc);
|
|
|
|
return (rc);
|
|
}
|
|
|
|
__checkReturn boolean_t
|
|
efx_mcdi_request_abort(
|
|
__in efx_nic_t *enp)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
efx_mcdi_req_t *emrp;
|
|
boolean_t aborted;
|
|
int state;
|
|
|
|
EFSYS_ASSERT3U(enp->en_family, ==, EFX_FAMILY_SIENA);
|
|
EFSYS_ASSERT3U(enp->en_features, &, EFX_FEATURE_MCDI);
|
|
|
|
/*
|
|
* efx_mcdi_ev_* may have already completed this event, and be
|
|
* spinning/blocked on the upper layer lock. So it *is* legitimate
|
|
* to for emi_pending_req to be NULL. If there is a pending event
|
|
* completed request, then provide a "credit" to allow
|
|
* efx_mcdi_ev_cpl() to accept a single spurious completion.
|
|
*/
|
|
EFSYS_LOCK(enp->en_eslp, state);
|
|
emrp = emip->emi_pending_req;
|
|
aborted = (emrp != NULL);
|
|
if (aborted) {
|
|
emip->emi_pending_req = NULL;
|
|
|
|
/* Error the request */
|
|
emrp->emr_out_length_used = 0;
|
|
emrp->emr_rc = ETIMEDOUT;
|
|
|
|
/* Provide a credit for seqno/emr_pending_req mismatches */
|
|
if (emip->emi_ev_cpl)
|
|
++emip->emi_aborted;
|
|
|
|
/*
|
|
* The upper layer has called us, so we don't
|
|
* need to complete the request.
|
|
*/
|
|
}
|
|
EFSYS_UNLOCK(enp->en_eslp, state);
|
|
|
|
return (aborted);
|
|
}
|
|
|
|
void
|
|
efx_mcdi_fini(
|
|
__in efx_nic_t *enp)
|
|
{
|
|
efx_mcdi_iface_t *emip = &(enp->en_u.siena.enu_mip);
|
|
|
|
EFSYS_ASSERT3U(enp->en_mod_flags, ==, EFX_MOD_MCDI);
|
|
enp->en_mod_flags &= ~EFX_MOD_MCDI;
|
|
|
|
if (~(enp->en_features) & EFX_FEATURE_MCDI)
|
|
return;
|
|
|
|
emip->emi_mtp = NULL;
|
|
emip->emi_port = 0;
|
|
emip->emi_aborted = 0;
|
|
}
|
|
|
|
#endif /* EFSYS_OPT_MCDI */
|