|
|
@@ -82,6 +82,7 @@ int efx_mcdi_init(struct efx_nic *efx)
|
|
|
mcdi->logging_enabled = mcdi_logging_default;
|
|
|
#endif
|
|
|
init_waitqueue_head(&mcdi->wq);
|
|
|
+ init_waitqueue_head(&mcdi->proxy_rx_wq);
|
|
|
spin_lock_init(&mcdi->iface_lock);
|
|
|
mcdi->state = MCDI_STATE_QUIESCENT;
|
|
|
mcdi->mode = MCDI_MODE_POLL;
|
|
|
@@ -315,6 +316,7 @@ static void efx_mcdi_read_response_header(struct efx_nic *efx)
|
|
|
}
|
|
|
#endif
|
|
|
|
|
|
+ mcdi->resprc_raw = 0;
|
|
|
if (error && mcdi->resp_data_len == 0) {
|
|
|
netif_err(efx, hw, efx->net_dev, "MC rebooted\n");
|
|
|
mcdi->resprc = -EIO;
|
|
|
@@ -325,8 +327,8 @@ static void efx_mcdi_read_response_header(struct efx_nic *efx)
|
|
|
mcdi->resprc = -EIO;
|
|
|
} else if (error) {
|
|
|
efx->type->mcdi_read_response(efx, &hdr, mcdi->resp_hdr_len, 4);
|
|
|
- mcdi->resprc =
|
|
|
- efx_mcdi_errno(EFX_DWORD_FIELD(hdr, EFX_DWORD_0));
|
|
|
+ mcdi->resprc_raw = EFX_DWORD_FIELD(hdr, EFX_DWORD_0);
|
|
|
+ mcdi->resprc = efx_mcdi_errno(mcdi->resprc_raw);
|
|
|
} else {
|
|
|
mcdi->resprc = 0;
|
|
|
}
|
|
|
@@ -621,9 +623,30 @@ efx_mcdi_check_supported(struct efx_nic *efx, unsigned int cmd, size_t inlen)
|
|
|
return 0;
|
|
|
}
|
|
|
|
|
|
-static int _efx_mcdi_rpc_finish(struct efx_nic *efx, unsigned cmd, size_t inlen,
|
|
|
+static bool efx_mcdi_get_proxy_handle(struct efx_nic *efx,
|
|
|
+ size_t hdr_len, size_t data_len,
|
|
|
+ u32 *proxy_handle)
|
|
|
+{
|
|
|
+ MCDI_DECLARE_BUF_ERR(testbuf);
|
|
|
+ const size_t buflen = sizeof(testbuf);
|
|
|
+
|
|
|
+ if (!proxy_handle || data_len < buflen)
|
|
|
+ return false;
|
|
|
+
|
|
|
+ efx->type->mcdi_read_response(efx, testbuf, hdr_len, buflen);
|
|
|
+ if (MCDI_DWORD(testbuf, ERR_CODE) == MC_CMD_ERR_PROXY_PENDING) {
|
|
|
+ *proxy_handle = MCDI_DWORD(testbuf, ERR_PROXY_PENDING_HANDLE);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+}
|
|
|
+
|
|
|
+static int _efx_mcdi_rpc_finish(struct efx_nic *efx, unsigned int cmd,
|
|
|
+ size_t inlen,
|
|
|
efx_dword_t *outbuf, size_t outlen,
|
|
|
- size_t *outlen_actual, bool quiet)
|
|
|
+ size_t *outlen_actual, bool quiet,
|
|
|
+ u32 *proxy_handle, int *raw_rc)
|
|
|
{
|
|
|
struct efx_mcdi_iface *mcdi = efx_mcdi(efx);
|
|
|
MCDI_DECLARE_BUF_ERR(errbuf);
|
|
|
@@ -657,6 +680,9 @@ static int _efx_mcdi_rpc_finish(struct efx_nic *efx, unsigned cmd, size_t inlen,
|
|
|
spin_unlock_bh(&mcdi->iface_lock);
|
|
|
}
|
|
|
|
|
|
+ if (proxy_handle)
|
|
|
+ *proxy_handle = 0;
|
|
|
+
|
|
|
if (rc != 0) {
|
|
|
if (outlen_actual)
|
|
|
*outlen_actual = 0;
|
|
|
@@ -669,6 +695,8 @@ static int _efx_mcdi_rpc_finish(struct efx_nic *efx, unsigned cmd, size_t inlen,
|
|
|
* acquiring the iface_lock. */
|
|
|
spin_lock_bh(&mcdi->iface_lock);
|
|
|
rc = mcdi->resprc;
|
|
|
+ if (raw_rc)
|
|
|
+ *raw_rc = mcdi->resprc_raw;
|
|
|
hdr_len = mcdi->resp_hdr_len;
|
|
|
data_len = mcdi->resp_data_len;
|
|
|
err_len = min(sizeof(errbuf), data_len);
|
|
|
@@ -689,6 +717,12 @@ static int _efx_mcdi_rpc_finish(struct efx_nic *efx, unsigned cmd, size_t inlen,
|
|
|
netif_err(efx, hw, efx->net_dev, "MC fatal error %d\n",
|
|
|
-rc);
|
|
|
efx_schedule_reset(efx, RESET_TYPE_MC_FAILURE);
|
|
|
+ } else if (proxy_handle && (rc == -EPROTO) &&
|
|
|
+ efx_mcdi_get_proxy_handle(efx, hdr_len, data_len,
|
|
|
+ proxy_handle)) {
|
|
|
+ mcdi->proxy_rx_status = 0;
|
|
|
+ mcdi->proxy_rx_handle = 0;
|
|
|
+ mcdi->state = MCDI_STATE_PROXY_WAIT;
|
|
|
} else if (rc && !quiet) {
|
|
|
efx_mcdi_display_error(efx, cmd, inlen, errbuf, err_len,
|
|
|
rc);
|
|
|
@@ -701,34 +735,195 @@ static int _efx_mcdi_rpc_finish(struct efx_nic *efx, unsigned cmd, size_t inlen,
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- efx_mcdi_release(mcdi);
|
|
|
+ if (!proxy_handle || !*proxy_handle)
|
|
|
+ efx_mcdi_release(mcdi);
|
|
|
return rc;
|
|
|
}
|
|
|
|
|
|
-static int _efx_mcdi_rpc(struct efx_nic *efx, unsigned cmd,
|
|
|
+static void efx_mcdi_proxy_abort(struct efx_mcdi_iface *mcdi)
|
|
|
+{
|
|
|
+ if (mcdi->state == MCDI_STATE_PROXY_WAIT) {
|
|
|
+ /* Interrupt the proxy wait. */
|
|
|
+ mcdi->proxy_rx_status = -EINTR;
|
|
|
+ wake_up(&mcdi->proxy_rx_wq);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+static void efx_mcdi_ev_proxy_response(struct efx_nic *efx,
|
|
|
+ u32 handle, int status)
|
|
|
+{
|
|
|
+ struct efx_mcdi_iface *mcdi = efx_mcdi(efx);
|
|
|
+
|
|
|
+ WARN_ON(mcdi->state != MCDI_STATE_PROXY_WAIT);
|
|
|
+
|
|
|
+ mcdi->proxy_rx_status = efx_mcdi_errno(status);
|
|
|
+ /* Ensure the status is written before we update the handle, since the
|
|
|
+ * latter is used to check if we've finished.
|
|
|
+ */
|
|
|
+ wmb();
|
|
|
+ mcdi->proxy_rx_handle = handle;
|
|
|
+ wake_up(&mcdi->proxy_rx_wq);
|
|
|
+}
|
|
|
+
|
|
|
+static int efx_mcdi_proxy_wait(struct efx_nic *efx, u32 handle, bool quiet)
|
|
|
+{
|
|
|
+ struct efx_mcdi_iface *mcdi = efx_mcdi(efx);
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ /* Wait for a proxy event, or timeout. */
|
|
|
+ rc = wait_event_timeout(mcdi->proxy_rx_wq,
|
|
|
+ mcdi->proxy_rx_handle != 0 ||
|
|
|
+ mcdi->proxy_rx_status == -EINTR,
|
|
|
+ MCDI_RPC_TIMEOUT);
|
|
|
+
|
|
|
+ if (rc <= 0) {
|
|
|
+ netif_dbg(efx, hw, efx->net_dev,
|
|
|
+ "MCDI proxy timeout %d\n", handle);
|
|
|
+ return -ETIMEDOUT;
|
|
|
+ } else if (mcdi->proxy_rx_handle != handle) {
|
|
|
+ netif_warn(efx, hw, efx->net_dev,
|
|
|
+ "MCDI proxy unexpected handle %d (expected %d)\n",
|
|
|
+ mcdi->proxy_rx_handle, handle);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
+ return mcdi->proxy_rx_status;
|
|
|
+}
|
|
|
+
|
|
|
+static int _efx_mcdi_rpc(struct efx_nic *efx, unsigned int cmd,
|
|
|
const efx_dword_t *inbuf, size_t inlen,
|
|
|
efx_dword_t *outbuf, size_t outlen,
|
|
|
- size_t *outlen_actual, bool quiet)
|
|
|
+ size_t *outlen_actual, bool quiet, int *raw_rc)
|
|
|
{
|
|
|
+ u32 proxy_handle = 0; /* Zero is an invalid proxy handle. */
|
|
|
int rc;
|
|
|
|
|
|
+ if (inbuf && inlen && (inbuf == outbuf)) {
|
|
|
+ /* The input buffer can't be aliased with the output. */
|
|
|
+ WARN_ON(1);
|
|
|
+ return -EINVAL;
|
|
|
+ }
|
|
|
+
|
|
|
rc = efx_mcdi_rpc_start(efx, cmd, inbuf, inlen);
|
|
|
- if (rc) {
|
|
|
- if (outlen_actual)
|
|
|
- *outlen_actual = 0;
|
|
|
+ if (rc)
|
|
|
return rc;
|
|
|
+
|
|
|
+ rc = _efx_mcdi_rpc_finish(efx, cmd, inlen, outbuf, outlen,
|
|
|
+ outlen_actual, quiet, &proxy_handle, raw_rc);
|
|
|
+
|
|
|
+ if (proxy_handle) {
|
|
|
+ /* Handle proxy authorisation. This allows approval of MCDI
|
|
|
+ * operations to be delegated to the admin function, allowing
|
|
|
+ * fine control over (eg) multicast subscriptions.
|
|
|
+ */
|
|
|
+ struct efx_mcdi_iface *mcdi = efx_mcdi(efx);
|
|
|
+
|
|
|
+ netif_dbg(efx, hw, efx->net_dev,
|
|
|
+ "MCDI waiting for proxy auth %d\n",
|
|
|
+ proxy_handle);
|
|
|
+ rc = efx_mcdi_proxy_wait(efx, proxy_handle, quiet);
|
|
|
+
|
|
|
+ if (rc == 0) {
|
|
|
+ netif_dbg(efx, hw, efx->net_dev,
|
|
|
+ "MCDI proxy retry %d\n", proxy_handle);
|
|
|
+
|
|
|
+ /* We now retry the original request. */
|
|
|
+ mcdi->state = MCDI_STATE_RUNNING_SYNC;
|
|
|
+ efx_mcdi_send_request(efx, cmd, inbuf, inlen);
|
|
|
+
|
|
|
+ rc = _efx_mcdi_rpc_finish(efx, cmd, inlen,
|
|
|
+ outbuf, outlen, outlen_actual,
|
|
|
+ quiet, NULL, raw_rc);
|
|
|
+ } else {
|
|
|
+ netif_printk(efx, hw,
|
|
|
+ rc == -EPERM ? KERN_DEBUG : KERN_ERR,
|
|
|
+ efx->net_dev,
|
|
|
+ "MC command 0x%x failed after proxy auth rc=%d\n",
|
|
|
+ cmd, rc);
|
|
|
+
|
|
|
+ if (rc == -EINTR || rc == -EIO)
|
|
|
+ efx_schedule_reset(efx, RESET_TYPE_MC_FAILURE);
|
|
|
+ efx_mcdi_release(mcdi);
|
|
|
+ }
|
|
|
}
|
|
|
- return _efx_mcdi_rpc_finish(efx, cmd, inlen, outbuf, outlen,
|
|
|
- outlen_actual, quiet);
|
|
|
+
|
|
|
+ return rc;
|
|
|
}
|
|
|
|
|
|
+static int _efx_mcdi_rpc_evb_retry(struct efx_nic *efx, unsigned cmd,
|
|
|
+ const efx_dword_t *inbuf, size_t inlen,
|
|
|
+ efx_dword_t *outbuf, size_t outlen,
|
|
|
+ size_t *outlen_actual, bool quiet)
|
|
|
+{
|
|
|
+ int raw_rc = 0;
|
|
|
+ int rc;
|
|
|
+
|
|
|
+ rc = _efx_mcdi_rpc(efx, cmd, inbuf, inlen,
|
|
|
+ outbuf, outlen, outlen_actual, true, &raw_rc);
|
|
|
+
|
|
|
+ if ((rc == -EPROTO) && (raw_rc == MC_CMD_ERR_NO_EVB_PORT) &&
|
|
|
+ efx->type->is_vf) {
|
|
|
+ /* If the EVB port isn't available within a VF this may
|
|
|
+ * mean the PF is still bringing the switch up. We should
|
|
|
+ * retry our request shortly.
|
|
|
+ */
|
|
|
+ unsigned long abort_time = jiffies + MCDI_RPC_TIMEOUT;
|
|
|
+ unsigned int delay_us = 10000;
|
|
|
+
|
|
|
+ netif_dbg(efx, hw, efx->net_dev,
|
|
|
+ "%s: NO_EVB_PORT; will retry request\n",
|
|
|
+ __func__);
|
|
|
+
|
|
|
+ do {
|
|
|
+ usleep_range(delay_us, delay_us + 10000);
|
|
|
+ rc = _efx_mcdi_rpc(efx, cmd, inbuf, inlen,
|
|
|
+ outbuf, outlen, outlen_actual,
|
|
|
+ true, &raw_rc);
|
|
|
+ if (delay_us < 100000)
|
|
|
+ delay_us <<= 1;
|
|
|
+ } while ((rc == -EPROTO) &&
|
|
|
+ (raw_rc == MC_CMD_ERR_NO_EVB_PORT) &&
|
|
|
+ time_before(jiffies, abort_time));
|
|
|
+ }
|
|
|
+
|
|
|
+ if (rc && !quiet && !(cmd == MC_CMD_REBOOT && rc == -EIO))
|
|
|
+ efx_mcdi_display_error(efx, cmd, inlen,
|
|
|
+ outbuf, outlen, rc);
|
|
|
+
|
|
|
+ return rc;
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * efx_mcdi_rpc - Issue an MCDI command and wait for completion
|
|
|
+ * @efx: NIC through which to issue the command
|
|
|
+ * @cmd: Command type number
|
|
|
+ * @inbuf: Command parameters
|
|
|
+ * @inlen: Length of command parameters, in bytes. Must be a multiple
|
|
|
+ * of 4 and no greater than %MCDI_CTL_SDU_LEN_MAX_V1.
|
|
|
+ * @outbuf: Response buffer. May be %NULL if @outlen is 0.
|
|
|
+ * @outlen: Length of response buffer, in bytes. If the actual
|
|
|
+ * response is longer than @outlen & ~3, it will be truncated
|
|
|
+ * to that length.
|
|
|
+ * @outlen_actual: Pointer through which to return the actual response
|
|
|
+ * length. May be %NULL if this is not needed.
|
|
|
+ *
|
|
|
+ * This function may sleep and therefore must be called in an appropriate
|
|
|
+ * context.
|
|
|
+ *
|
|
|
+ * Return: A negative error code, or zero if successful. The error
|
|
|
+ * code may come from the MCDI response or may indicate a failure
|
|
|
+ * to communicate with the MC. In the former case, the response
|
|
|
+ * will still be copied to @outbuf and *@outlen_actual will be
|
|
|
+ * set accordingly. In the latter case, *@outlen_actual will be
|
|
|
+ * set to zero.
|
|
|
+ */
|
|
|
int efx_mcdi_rpc(struct efx_nic *efx, unsigned cmd,
|
|
|
const efx_dword_t *inbuf, size_t inlen,
|
|
|
efx_dword_t *outbuf, size_t outlen,
|
|
|
size_t *outlen_actual)
|
|
|
{
|
|
|
- return _efx_mcdi_rpc(efx, cmd, inbuf, inlen, outbuf, outlen,
|
|
|
- outlen_actual, false);
|
|
|
+ return _efx_mcdi_rpc_evb_retry(efx, cmd, inbuf, inlen, outbuf, outlen,
|
|
|
+ outlen_actual, false);
|
|
|
}
|
|
|
|
|
|
/* Normally, on receiving an error code in the MCDI response,
|
|
|
@@ -744,8 +939,8 @@ int efx_mcdi_rpc_quiet(struct efx_nic *efx, unsigned cmd,
|
|
|
efx_dword_t *outbuf, size_t outlen,
|
|
|
size_t *outlen_actual)
|
|
|
{
|
|
|
- return _efx_mcdi_rpc(efx, cmd, inbuf, inlen, outbuf, outlen,
|
|
|
- outlen_actual, true);
|
|
|
+ return _efx_mcdi_rpc_evb_retry(efx, cmd, inbuf, inlen, outbuf, outlen,
|
|
|
+ outlen_actual, true);
|
|
|
}
|
|
|
|
|
|
int efx_mcdi_rpc_start(struct efx_nic *efx, unsigned cmd,
|
|
|
@@ -866,7 +1061,7 @@ int efx_mcdi_rpc_finish(struct efx_nic *efx, unsigned cmd, size_t inlen,
|
|
|
size_t *outlen_actual)
|
|
|
{
|
|
|
return _efx_mcdi_rpc_finish(efx, cmd, inlen, outbuf, outlen,
|
|
|
- outlen_actual, false);
|
|
|
+ outlen_actual, false, NULL, NULL);
|
|
|
}
|
|
|
|
|
|
int efx_mcdi_rpc_finish_quiet(struct efx_nic *efx, unsigned cmd, size_t inlen,
|
|
|
@@ -874,7 +1069,7 @@ int efx_mcdi_rpc_finish_quiet(struct efx_nic *efx, unsigned cmd, size_t inlen,
|
|
|
size_t *outlen_actual)
|
|
|
{
|
|
|
return _efx_mcdi_rpc_finish(efx, cmd, inlen, outbuf, outlen,
|
|
|
- outlen_actual, true);
|
|
|
+ outlen_actual, true, NULL, NULL);
|
|
|
}
|
|
|
|
|
|
void efx_mcdi_display_error(struct efx_nic *efx, unsigned cmd,
|
|
|
@@ -887,9 +1082,10 @@ void efx_mcdi_display_error(struct efx_nic *efx, unsigned cmd,
|
|
|
code = MCDI_DWORD(outbuf, ERR_CODE);
|
|
|
if (outlen >= MC_CMD_ERR_ARG_OFST + 4)
|
|
|
err_arg = MCDI_DWORD(outbuf, ERR_ARG);
|
|
|
- netif_err(efx, hw, efx->net_dev,
|
|
|
- "MC command 0x%x inlen %d failed rc=%d (raw=%d) arg=%d\n",
|
|
|
- cmd, (int)inlen, rc, code, err_arg);
|
|
|
+ netif_printk(efx, hw, rc == -EPERM ? KERN_DEBUG : KERN_ERR,
|
|
|
+ efx->net_dev,
|
|
|
+ "MC command 0x%x inlen %zu failed rc=%d (raw=%d) arg=%d\n",
|
|
|
+ cmd, inlen, rc, code, err_arg);
|
|
|
}
|
|
|
|
|
|
/* Switch to polled MCDI completions. This can be called in various
|
|
|
@@ -1014,8 +1210,13 @@ static void efx_mcdi_ev_death(struct efx_nic *efx, int rc)
|
|
|
* receiving a REBOOT event after posting the MCDI
|
|
|
* request. Did the mc reboot before or after the copyout? The
|
|
|
* best we can do always is just return failure.
|
|
|
+ *
|
|
|
+ * If there is an outstanding proxy response expected it is not going
|
|
|
+ * to arrive. We should thus abort it.
|
|
|
*/
|
|
|
spin_lock(&mcdi->iface_lock);
|
|
|
+ efx_mcdi_proxy_abort(mcdi);
|
|
|
+
|
|
|
if (efx_mcdi_complete_sync(mcdi)) {
|
|
|
if (mcdi->mode == MCDI_MODE_EVENTS) {
|
|
|
mcdi->resprc = rc;
|
|
|
@@ -1063,6 +1264,8 @@ static void efx_mcdi_ev_bist(struct efx_nic *efx)
|
|
|
|
|
|
spin_lock(&mcdi->iface_lock);
|
|
|
efx->mc_bist_for_other_fn = true;
|
|
|
+ efx_mcdi_proxy_abort(mcdi);
|
|
|
+
|
|
|
if (efx_mcdi_complete_sync(mcdi)) {
|
|
|
if (mcdi->mode == MCDI_MODE_EVENTS) {
|
|
|
mcdi->resprc = -EIO;
|
|
|
@@ -1171,6 +1374,11 @@ void efx_mcdi_process_event(struct efx_channel *channel,
|
|
|
EFX_QWORD_VAL(*event));
|
|
|
efx_schedule_reset(efx, RESET_TYPE_DMA_ERROR);
|
|
|
break;
|
|
|
+ case MCDI_EVENT_CODE_PROXY_RESPONSE:
|
|
|
+ efx_mcdi_ev_proxy_response(efx,
|
|
|
+ MCDI_EVENT_FIELD(*event, PROXY_RESPONSE_HANDLE),
|
|
|
+ MCDI_EVENT_FIELD(*event, PROXY_RESPONSE_RC));
|
|
|
+ break;
|
|
|
default:
|
|
|
netif_err(efx, hw, efx->net_dev, "Unknown MCDI event 0x%x\n",
|
|
|
code);
|