1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License (the "License").
   6  * You may not use this file except in compliance with the License.
   7  *
   8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
   9  * or http://www.opensolaris.org/os/licensing.
  10  * See the License for the specific language governing permissions
  11  * and limitations under the License.
  12  *
  13  * When distributing Covered Code, include this CDDL HEADER in each
  14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  15  * If applicable, add the following below this CDDL HEADER, with the
  16  * fields enclosed by brackets "[]" replaced with your own identifying
  17  * information: Portions Copyright [yyyy] [name of copyright owner]
  18  *
  19  * CDDL HEADER END
  20  */
  21 
  22 /*
  23  * Copyright 2009 Solarflare Communications Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #include <sys/types.h>
  28 #include <sys/ddi.h>
  29 #include <sys/sunddi.h>
  30 #include <sys/stream.h>
  31 #include <sys/dlpi.h>
  32 
  33 #include "sfxge.h"
  34 #include "efsys.h"
  35 #include "efx.h"
  36 #include "efx_mcdi.h"
  37 #include "efx_regs_mcdi.h"
  38 
  39 
  40 /*
  41  * Notes on MCDI operation:
  42  * ------------------------
  43  * MCDI requests can be made in arbitrary thread context, and as a synchronous
  44  * API must therefore block until the response is available from the MC, or
  45  * a watchdog timeout occurs.
  46  *
  47  * This interacts badly with the limited number of worker threads (2 per CPU)
  48  * used by the Solaris callout subsystem to invoke timeout handlers. If both
  49  * worker threads are blocked (e.g. waiting for a condvar or mutex) then timeout
  50  * processing is deadlocked on that CPU, causing system failure.
  51  *
  52  * For this reason the driver does not use event based MCDI completion, as this
  53  * leads to numerous paths involving timeouts and reentrant GLDv3 entrypoints
  54  * that result in a deadlocked system.
  55  */
  56 #define SFXGE_MCDI_POLL_INTERVAL        10              /* 10us in 1us units */
  57 #define SFXGE_MCDI_WATCHDOG_INTERVAL    10000000        /* 10s in 1us units */
  58 
  59 
  60 /* Acquire exclusive access to MCDI for the duration of a request */
  61 static void
  62 sfxge_mcdi_acquire(sfxge_mcdi_t *smp)
  63 {
  64         mutex_enter(&(smp->sm_lock));
  65         ASSERT3U(smp->sm_state, !=, SFXGE_MCDI_UNINITIALIZED);
  66 
  67         while (smp->sm_state != SFXGE_MCDI_INITIALIZED) {
  68                 (void) cv_wait_sig(&(smp->sm_kv), &(smp->sm_lock));
  69         }
  70         smp->sm_state = SFXGE_MCDI_BUSY;
  71 
  72         mutex_exit(&(smp->sm_lock));
  73 }
  74 
  75 
  76 /* Release ownership of MCDI on request completion */
  77 static void
  78 sfxge_mcdi_release(sfxge_mcdi_t *smp)
  79 {
  80         mutex_enter(&(smp->sm_lock));
  81         ASSERT((smp->sm_state == SFXGE_MCDI_BUSY) ||
  82             (smp->sm_state == SFXGE_MCDI_COMPLETED));
  83 
  84         smp->sm_state = SFXGE_MCDI_INITIALIZED;
  85         cv_broadcast(&(smp->sm_kv));
  86 
  87         mutex_exit(&(smp->sm_lock));
  88 }
  89 
  90 
  91 static void
  92 sfxge_mcdi_timeout(sfxge_t *sp)
  93 {
  94         dev_info_t *dip = sp->s_dip;
  95 
  96         cmn_err(CE_WARN, SFXGE_CMN_ERR "[%s%d] MC_TIMEOUT",
  97             ddi_driver_name(dip), ddi_get_instance(dip));
  98 
  99         DTRACE_PROBE(mcdi_timeout);
 100         (void) sfxge_restart_dispatch(sp, DDI_SLEEP, SFXGE_HW_ERR,
 101             "MCDI timeout", 0);
 102 }
 103 
 104 
 105 static void
 106 sfxge_mcdi_poll(sfxge_t *sp)
 107 {
 108         efx_nic_t *enp = sp->s_enp;
 109         clock_t timeout;
 110         boolean_t aborted;
 111 
 112         /* Poll until request completes or timeout */
 113         timeout = ddi_get_lbolt() + drv_usectohz(SFXGE_MCDI_WATCHDOG_INTERVAL);
 114         while (efx_mcdi_request_poll(enp) == B_FALSE) {
 115 
 116                 /* No response received yet */
 117                 if (ddi_get_lbolt() > timeout) {
 118                         /* Timeout expired */
 119                         goto fail;
 120                 }
 121 
 122                 /* Short delay to avoid excessive PCIe traffic */
 123                 drv_usecwait(SFXGE_MCDI_POLL_INTERVAL);
 124         }
 125 
 126         /* Request completed (or polling failed) */
 127         return;
 128 
 129 fail:
 130         /* Timeout before request completion */
 131         DTRACE_PROBE(fail);
 132         aborted = efx_mcdi_request_abort(enp);
 133         ASSERT(aborted);
 134         sfxge_mcdi_timeout(sp);
 135 }
 136 
 137 
 138 static void
 139 sfxge_mcdi_execute(void *arg, efx_mcdi_req_t *emrp)
 140 {
 141         sfxge_t *sp = (sfxge_t *)arg;
 142         sfxge_mcdi_t *smp = &(sp->s_mcdi);
 143 
 144         sfxge_mcdi_acquire(smp);
 145 
 146         /* Issue request and poll for completion */
 147         efx_mcdi_request_start(sp->s_enp, emrp, B_FALSE);
 148         sfxge_mcdi_poll(sp);
 149 
 150         sfxge_mcdi_release(smp);
 151 }
 152 
 153 
 154 static void
 155 sfxge_mcdi_ev_cpl(void *arg)
 156 {
 157         sfxge_t *sp = (sfxge_t *)arg;
 158         sfxge_mcdi_t *smp = &(sp->s_mcdi);
 159 
 160         mutex_enter(&(smp->sm_lock));
 161         ASSERT(smp->sm_state == SFXGE_MCDI_BUSY);
 162         smp->sm_state = SFXGE_MCDI_COMPLETED;
 163         cv_broadcast(&(smp->sm_kv));
 164         mutex_exit(&(smp->sm_lock));
 165 }
 166 
 167 
 168 static void
 169 sfxge_mcdi_exception(void *arg, efx_mcdi_exception_t eme)
 170 {
 171         sfxge_t *sp = (sfxge_t *)arg;
 172         const char *reason;
 173 
 174         if (eme == EFX_MCDI_EXCEPTION_MC_REBOOT)
 175                 reason = "MC_REBOOT";
 176         else if (eme == EFX_MCDI_EXCEPTION_MC_BADASSERT)
 177                 reason = "MC_BADASSERT";
 178         else
 179                 reason = "MC_UNKNOWN";
 180 
 181         DTRACE_PROBE(mcdi_exception);
 182         /* sfxge_evq_t->se_lock held */
 183         (void) sfxge_restart_dispatch(sp, DDI_SLEEP, SFXGE_HW_ERR, reason, 0);
 184 }
 185 
 186 
 187 int
 188 sfxge_mcdi_init(sfxge_t *sp)
 189 {
 190         efx_nic_t *enp = sp->s_enp;
 191         sfxge_mcdi_t *smp = &(sp->s_mcdi);
 192         efx_mcdi_transport_t *emtp = &(smp->sm_emt);
 193         int rc;
 194 
 195         ASSERT3U(smp->sm_state, ==, SFXGE_MCDI_UNINITIALIZED);
 196 
 197         mutex_init(&(smp->sm_lock), NULL, MUTEX_DRIVER, NULL);
 198 
 199         smp->sm_state = SFXGE_MCDI_INITIALIZED;
 200 
 201         emtp->emt_context = sp;
 202         emtp->emt_execute = sfxge_mcdi_execute;
 203         emtp->emt_ev_cpl = sfxge_mcdi_ev_cpl;
 204         emtp->emt_exception = sfxge_mcdi_exception;
 205 
 206         cv_init(&(smp->sm_kv), NULL, CV_DRIVER, NULL);
 207 
 208         if ((rc = efx_mcdi_init(enp, emtp)) != 0)
 209                 goto fail1;
 210 
 211         return (0);
 212 
 213 fail1:
 214         DTRACE_PROBE1(fail1, int, rc);
 215 
 216         cv_destroy(&(smp->sm_kv));
 217         mutex_destroy(&(smp->sm_lock));
 218 
 219         smp->sm_state = SFXGE_MCDI_UNINITIALIZED;
 220         smp->sm_sp = NULL;
 221         SFXGE_OBJ_CHECK(smp, sfxge_mcdi_t);
 222 
 223         return (rc);
 224 }
 225 
 226 
 227 void
 228 sfxge_mcdi_fini(sfxge_t *sp)
 229 {
 230         efx_nic_t *enp = sp->s_enp;
 231         sfxge_mcdi_t *smp = &(sp->s_mcdi);
 232         efx_mcdi_transport_t *emtp;
 233 
 234         mutex_enter(&(smp->sm_lock));
 235         ASSERT3U(smp->sm_state, ==, SFXGE_MCDI_INITIALIZED);
 236 
 237         efx_mcdi_fini(enp);
 238         emtp = &(smp->sm_emt);
 239         bzero(emtp, sizeof (*emtp));
 240 
 241         smp->sm_sp = NULL;
 242 
 243         cv_destroy(&(smp->sm_kv));
 244         mutex_exit(&(smp->sm_lock));
 245 
 246         mutex_destroy(&(smp->sm_lock));
 247 
 248         smp->sm_state = SFXGE_MCDI_UNINITIALIZED;
 249         SFXGE_OBJ_CHECK(smp, sfxge_mcdi_t);
 250 }
 251 
 252 
 253 int
 254 sfxge_mcdi_ioctl(sfxge_t *sp, sfxge_mcdi_ioc_t *smip)
 255 {
 256         const efx_nic_cfg_t *encp = efx_nic_cfg_get(sp->s_enp);
 257         sfxge_mcdi_t *smp = &(sp->s_mcdi);
 258         efx_mcdi_req_t emr;
 259         uint8_t *out;
 260         int rc;
 261 
 262         if (smp->sm_state == SFXGE_MCDI_UNINITIALIZED) {
 263                 rc = ENODEV;
 264                 goto fail1;
 265         }
 266 
 267         if (!(encp->enc_features & EFX_FEATURE_MCDI)) {
 268                 rc = ENOTSUP;
 269                 goto fail2;
 270         }
 271 
 272         if ((out = kmem_zalloc(sizeof (smip->smi_payload), KM_SLEEP)) == NULL) {
 273                 rc = ENOMEM;
 274                 goto fail3;
 275         }
 276 
 277         emr.emr_cmd = smip->smi_cmd;
 278         emr.emr_in_buf = smip->smi_payload;
 279         emr.emr_in_length = smip->smi_len;
 280 
 281         emr.emr_out_buf = out;
 282         emr.emr_out_length = sizeof (smip->smi_payload);
 283 
 284         sfxge_mcdi_execute(sp, &emr);
 285 
 286         smip->smi_rc = (uint8_t)emr.emr_rc;
 287         smip->smi_cmd = (uint8_t)emr.emr_cmd;
 288         smip->smi_len = (uint8_t)emr.emr_out_length_used;
 289         memcpy(smip->smi_payload, out, smip->smi_len);
 290 
 291         /*
 292          * Helpfully trigger a device reset in response to an MCDI_CMD_REBOOT
 293          * Both ports will see ->emt_exception callbacks on the next MCDI poll
 294          */
 295         if (smip->smi_cmd == MC_CMD_REBOOT) {
 296 
 297                 DTRACE_PROBE(mcdi_ioctl_mc_reboot);
 298                 /* sfxge_t->s_state_lock held */
 299                 (void) sfxge_restart_dispatch(sp, DDI_SLEEP, SFXGE_HW_OK,
 300                     "MC_REBOOT triggering restart", 0);
 301         }
 302 
 303         kmem_free(out, sizeof (smip->smi_payload));
 304 
 305         return (0);
 306 
 307 fail3:
 308         DTRACE_PROBE(fail3);
 309 fail2:
 310         DTRACE_PROBE(fail2);
 311 fail1:
 312         DTRACE_PROBE1(fail1, int, rc);
 313         return (rc);
 314 }