Print this page
MFV: illumos-gate@fd6d41c5025e9fb45a115fc82d86e9983d1e9fd6
9815 Want basic AHCI enclosure services
Reviewed by: Patrick Mooney <patrick.mooney@joyent.com>
Reviewed by: Rob Johnston <rob.johnston@joyent.com>
Reviewed by: Yuri Pankov <yuripv@yuripv.net>
Approved by: Dan McDonald <danmcd@joyent.com>
Author: Robert Mustacchi <rm@joyent.com>
Conflicts:
        usr/src/cmd/Makefile
9772 Panic in ahci when the failed slot spkt is NULL
Reviewed by: Andy Stormont <astormont@racktopsystems.com>
Reviewed by: Toomas Soome <tsoome@me.com>
Approved by: Robert Mustacchi <rm@joyent.com>
NEX-17502 Slow crash dumps, significantly slower than live core
Reviewed by: Dan Fields <dan.fields@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Sanjay Nadkarni <sanjay.nadkarni@nexenta.com>
re #12164 Marvell 88SE9128: Appliance hard hangs on boot probing duplicated ahci device
        
*** 19,29 ****
   * CDDL HEADER END
   */
  
  /*
   * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2013 Nexenta Systems, Inc.  All rights reserved.
   */
  
  /*
   * AHCI (Advanced Host Controller Interface) SATA HBA Driver
   *
--- 19,31 ----
   * CDDL HEADER END
   */
  
  /*
   * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
!  * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
!  * Copyright (c) 2018, Joyent, Inc.
!  * Copyright 2018 OmniOS Community Edition (OmniOSce) Association.
   */
  
  /*
   * AHCI (Advanced Host Controller Interface) SATA HBA Driver
   *
*** 39,48 ****
--- 41,86 ----
   * and DDI_RESUME entries, and don't need to take care of new requests
   * sent down after suspend because the target driver (sd) has already
   * handled these conditions, and blocked these requests. For the detailed
   * information, please check with sdopen, sdclose and sdioctl routines.
   *
+  *
+  * Enclosure Management Support
+  * ----------------------------
+  *
+  * The ahci driver has basic support for AHCI Enclosure Management (EM)
+  * services. The AHCI specification provides an area in the primary ahci BAR for
+  * posting data to send out to the enclosure management and provides a register
+  * that provides both information and control about this. While the
+  * specification allows for multiple forms of enclosure management, the only
+  * supported, and commonly found form, is the AHCI specified LED format. The LED
+  * format is often implemented as a one-way communication mechanism. Software
+  * can write out what it cares about into the aforementioned data buffer and
+  * then we wait for the transmission to be sent.
+  *
+  * This has some drawbacks. It means that we cannot know whether or not it has
+  * succeeded. This means we cannot ask hardware what it thinks the LEDs are
+  * set to. There's also the added unfortunate reality that firmware on the
+  * microcontroller driving this will often not show the LEDs if no drive is
+  * present and that actions taken may potentially cause this to get out of sync
+  * with what we expect it to be. For example, the specification does not
+  * describe what should happen if a drive is removed from the enclosure while
+  * this is set and what should happen when it returns. We can only infer that it
+  * should be the same.
+  *
+  * Because only a single command can be sent at any time and we don't want to
+  * interfere with controller I/O, we create a taskq dedicated to this that has a
+  * single thread. Both resets (which occur on attach and resume) and normal
+  * changes to the LED state will be driven through this taskq. Because the taskq
+  * has a single thread, this guarantees serial processing.
+  *
+  * Each userland-submitted task (basically not resets) has a reference counted
+  * task structure. This allows the thread that called it to be cancelled and
+  * have the system clean itself up. The user thread in ioctl blocks on a CV that
+  * can receive signals as it waits for completion.  Note, there is no guarantee
+  * provided by the kernel that the first thread to enter the kernel will be the
+  * first one to change state.
   */
  
  #include <sys/note.h>
  #include <sys/scsi/scsi.h>
  #include <sys/pci.h>
*** 58,67 ****
--- 96,116 ----
  #include <sys/fm/protocol.h>
  #include <sys/fm/util.h>
  #include <sys/fm/io/ddi.h>
  
  /*
+  * EM Control header files
+  */
+ #include <sys/types.h>
+ #include <sys/file.h>
+ #include <sys/errno.h>
+ #include <sys/open.h>
+ #include <sys/cred.h>
+ #include <sys/ddi.h>
+ #include <sys/sunddi.h>
+ 
+ /*
   * This is the string displayed by modinfo, etc.
   */
  static char ahci_ident[] = "ahci driver";
  
  /*
*** 219,229 ****
--- 268,284 ----
  static  void ahci_log_serror_message(ahci_ctl_t *, uint8_t, uint32_t, int);
  #if AHCI_DEBUG
  static  void ahci_log(ahci_ctl_t *, uint_t, char *, ...);
  #endif
  
+ static  boolean_t ahci_em_init(ahci_ctl_t *);
+ static  void ahci_em_fini(ahci_ctl_t *);
+ static  void ahci_em_suspend(ahci_ctl_t *);
+ static  void ahci_em_resume(ahci_ctl_t *);
+ static  int ahci_em_ioctl(dev_info_t *, int, intptr_t);
  
+ 
  /*
   * DMA attributes for the data buffer
   *
   * dma_attr_addr_hi will be changed to 0xffffffffull if the HBA
   * does not support 64-bit addressing
*** 313,333 ****
          DDI_STRUCTURE_LE_ACC,
          DDI_STRICTORDER_ACC,
          DDI_DEFAULT_ACC
  };
  
- 
  static struct dev_ops ahcictl_dev_ops = {
          DEVO_REV,               /* devo_rev */
          0,                      /* refcnt  */
          ahci_getinfo,           /* info */
          nulldev,                /* identify */
          nulldev,                /* probe */
          ahci_attach,            /* attach */
          ahci_detach,            /* detach */
          nodev,                  /* no reset */
!         (struct cb_ops *)0,     /* driver operations */
          NULL,                   /* bus operations */
          NULL,                   /* power */
          ahci_quiesce,           /* quiesce */
  };
  
--- 368,387 ----
          DDI_STRUCTURE_LE_ACC,
          DDI_STRICTORDER_ACC,
          DDI_DEFAULT_ACC
  };
  
  static struct dev_ops ahcictl_dev_ops = {
          DEVO_REV,               /* devo_rev */
          0,                      /* refcnt  */
          ahci_getinfo,           /* info */
          nulldev,                /* identify */
          nulldev,                /* probe */
          ahci_attach,            /* attach */
          ahci_detach,            /* detach */
          nodev,                  /* no reset */
!         NULL,                   /* driver operations */
          NULL,                   /* bus operations */
          NULL,                   /* power */
          ahci_quiesce,           /* quiesce */
  };
  
*** 408,418 ****
--- 462,481 ----
   * SB600/700/710/750/800. If the users want to have a try with 64-bit dma,
   * please change the below value to enable it.
   */
  boolean_t sbxxx_commu_64bit_dma_disable = B_TRUE;
  
+ /*
+  * These values control the default delay and default number of times to wait
+  * for an enclosure message to complete.
+  */
+ uint_t  ahci_em_reset_delay_ms = 1;
+ uint_t  ahci_em_reset_delay_count = 1000;
+ uint_t  ahci_em_tx_delay_ms = 1;
+ uint_t  ahci_em_tx_delay_count = 1000;
  
+ 
  /*
   * End of global tunable variable definition
   */
  
  #if AHCI_DEBUG
*** 578,587 ****
--- 641,655 ----
                              "Failed to initialize the controller "
                              "during DDI_RESUME", NULL);
                          return (DDI_FAILURE);
                  }
  
+                 /*
+                  * Reset the enclosure services.
+                  */
+                 ahci_em_resume(ahci_ctlp);
+ 
                  mutex_enter(&ahci_ctlp->ahcictl_mutex);
                  ahci_ctlp->ahcictl_flags &= ~AHCI_SUSPEND;
                  mutex_exit(&ahci_ctlp->ahcictl_mutex);
  
                  return (DDI_SUCCESS);
*** 712,721 ****
--- 780,799 ----
  
                  AHCIDBG(AHCIDBG_INIT, ahci_ctlp,
                      "hba capabilities extended = 0x%x", cap2_status);
          }
  
+         if (cap_status & AHCI_HBA_CAP_EMS) {
+                 ahci_ctlp->ahcictl_cap |= AHCI_CAP_EMS;
+                 ahci_ctlp->ahcictl_em_loc =
+                     ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+                     (uint32_t *)AHCI_GLOBAL_EM_LOC(ahci_ctlp));
+                 ahci_ctlp->ahcictl_em_ctl =
+                     ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+                     (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp));
+         }
+ 
  #if AHCI_DEBUG
          /* Get the interface speed supported by the HBA */
          speed = (cap_status & AHCI_HBA_CAP_ISS) >> AHCI_HBA_CAP_ISS_SHIFT;
          if (speed == 0x01) {
                  AHCIDBG(AHCIDBG_INIT, ahci_ctlp,
*** 963,972 ****
--- 1041,1057 ----
              (void (*)(void *))ahci_watchdog_handler,
              (caddr_t)ahci_ctlp, ahci_watchdog_tick);
  
          attach_state |= AHCI_ATTACH_STATE_TIMEOUT_ENABLED;
  
+         if (!ahci_em_init(ahci_ctlp)) {
+                 cmn_err(CE_WARN, "!ahci%d: failed to initialize enclosure "
+                     "services", instance);
+                 goto err_out;
+         }
+         attach_state |= AHCI_ATTACH_STATE_ENCLOSURE;
+ 
          if (ahci_register_sata_hba_tran(ahci_ctlp, cap_status)) {
                  cmn_err(CE_WARN, "!ahci%d: sata hba tran registration failed",
                      instance);
                  goto err_out;
          }
*** 987,996 ****
--- 1072,1085 ----
  err_out:
          /* FMA message */
          ahci_fm_ereport(ahci_ctlp, DDI_FM_DEVICE_NO_RESPONSE);
          ddi_fm_service_impact(ahci_ctlp->ahcictl_dip, DDI_SERVICE_LOST);
  
+         if (attach_state & AHCI_ATTACH_STATE_ENCLOSURE) {
+                 ahci_em_fini(ahci_ctlp);
+         }
+ 
          if (attach_state & AHCI_ATTACH_STATE_TIMEOUT_ENABLED) {
                  mutex_enter(&ahci_ctlp->ahcictl_mutex);
                  (void) untimeout(ahci_ctlp->ahcictl_timeout_id);
                  ahci_ctlp->ahcictl_timeout_id = 0;
                  mutex_exit(&ahci_ctlp->ahcictl_mutex);
*** 1061,1070 ****
--- 1150,1161 ----
                          ahci_enable_all_intrs(ahci_ctlp);
                          mutex_exit(&ahci_ctlp->ahcictl_mutex);
                          return (DDI_FAILURE);
                  }
  
+                 ahci_em_fini(ahci_ctlp);
+ 
                  mutex_enter(&ahci_ctlp->ahcictl_mutex);
  
                  /* stop the watchdog handler */
                  (void) untimeout(ahci_ctlp->ahcictl_timeout_id);
                  ahci_ctlp->ahcictl_timeout_id = 0;
*** 1110,1119 ****
--- 1201,1212 ----
                          return (DDI_SUCCESS);
                  }
  
                  ahci_ctlp->ahcictl_flags |= AHCI_SUSPEND;
  
+                 ahci_em_suspend(ahci_ctlp);
+ 
                  /* stop the watchdog handler */
                  if (ahci_ctlp->ahcictl_timeout_id) {
                          (void) untimeout(ahci_ctlp->ahcictl_timeout_id);
                          ahci_ctlp->ahcictl_timeout_id = 0;
                  }
*** 1260,1270 ****
          /*
           * When SATA framework adds support for pwrmgt the
           * pwrmgt_ops needs to be updated
           */
          sata_hba_tran->sata_tran_pwrmgt_ops = NULL;
!         sata_hba_tran->sata_tran_ioctl = NULL;
  
          ahci_ctlp->ahcictl_sata_hba_tran = sata_hba_tran;
  
          mutex_exit(&ahci_ctlp->ahcictl_mutex);
  
--- 1353,1363 ----
          /*
           * When SATA framework adds support for pwrmgt the
           * pwrmgt_ops needs to be updated
           */
          sata_hba_tran->sata_tran_pwrmgt_ops = NULL;
!         sata_hba_tran->sata_tran_ioctl = ahci_em_ioctl;
  
          ahci_ctlp->ahcictl_sata_hba_tran = sata_hba_tran;
  
          mutex_exit(&ahci_ctlp->ahcictl_mutex);
  
*** 1813,1831 ****
  
                  pkt_timeout_ticks =
                      drv_usectohz((clock_t)spkt->satapkt_time * 1000000);
  
                  while (spkt->satapkt_reason == SATA_PKT_BUSY) {
-                         mutex_exit(&ahci_portp->ahciport_mutex);
- 
                          /* Simulate the interrupt */
                          ahci_port_intr(ahci_ctlp, ahci_portp, port);
  
!                         drv_usecwait(AHCI_10MS_USECS);
  
                          mutex_enter(&ahci_portp->ahciport_mutex);
!                         pkt_timeout_ticks -= AHCI_10MS_TICKS;
                          if (pkt_timeout_ticks < 0) {
                                  cmn_err(CE_WARN, "!ahci%d: ahci_do_sync_start "
                                      "port %d satapkt 0x%p timed out\n",
                                      instance, port, (void *)spkt);
                                  timeout_tags = (0x1 << rval);
--- 1906,1928 ----
  
                  pkt_timeout_ticks =
                      drv_usectohz((clock_t)spkt->satapkt_time * 1000000);
  
                  while (spkt->satapkt_reason == SATA_PKT_BUSY) {
                          /* Simulate the interrupt */
+                         mutex_exit(&ahci_portp->ahciport_mutex);
                          ahci_port_intr(ahci_ctlp, ahci_portp, port);
+                         mutex_enter(&ahci_portp->ahciport_mutex);
  
!                         if (spkt->satapkt_reason != SATA_PKT_BUSY)
!                                 break;
  
+                         mutex_exit(&ahci_portp->ahciport_mutex);
+                         drv_usecwait(AHCI_1MS_USECS);
                          mutex_enter(&ahci_portp->ahciport_mutex);
! 
!                         pkt_timeout_ticks -= AHCI_1MS_TICKS;
                          if (pkt_timeout_ticks < 0) {
                                  cmn_err(CE_WARN, "!ahci%d: ahci_do_sync_start "
                                      "port %d satapkt 0x%p timed out\n",
                                      instance, port, (void *)spkt);
                                  timeout_tags = (0x1 << rval);
*** 1833,1842 ****
--- 1930,1940 ----
                                  ahci_timeout_pkts(ahci_ctlp, ahci_portp,
                                      port, timeout_tags);
                                  mutex_enter(&ahci_portp->ahciport_mutex);
                          }
                  }
+ 
                  ahci_portp->ahciport_flags &= ~AHCI_PORT_FLAG_POLLING;
                  return (AHCI_SUCCESS);
  
          } else {
                  if ((rval = ahci_deliver_satapkt(ahci_ctlp, ahci_portp,
*** 9472,9482 ****
  }
  
  /*
   * Used to recovery a PMULT pmport fatal error under FIS-based switching.
   *      1. device specific.PxFBS.SDE=1
!  *      2. Non-Deivce specific.
   * Nothing will be done when Command-based switching is employed.
   *
   * Currently code is neither completed nor tested.
   */
  static void
--- 9570,9580 ----
  }
  
  /*
   * Used to recovery a PMULT pmport fatal error under FIS-based switching.
   *      1. device specific.PxFBS.SDE=1
!  *      2. Non Device specific.
   * Nothing will be done when Command-based switching is employed.
   *
   * Currently code is neither completed nor tested.
   */
  static void
*** 9986,9996 ****
                  if (tmp_slot == -1) {
                          break;
                  }
  
                  spkt = ahci_portp->ahciport_slot_pkts[tmp_slot];
!                 ASSERT(spkt != NULL);
                  cmd = spkt->satapkt_cmd;
  
                  cmn_err(CE_WARN, "!satapkt 0x%p: cmd_reg = 0x%x "
                      "features_reg = 0x%x sec_count_msb = 0x%x "
                      "lba_low_msb = 0x%x lba_mid_msb = 0x%x "
--- 10084,10094 ----
                  if (tmp_slot == -1) {
                          break;
                  }
  
                  spkt = ahci_portp->ahciport_slot_pkts[tmp_slot];
!                 if (spkt != NULL) {
                          cmd = spkt->satapkt_cmd;
  
                          cmn_err(CE_WARN, "!satapkt 0x%p: cmd_reg = 0x%x "
                              "features_reg = 0x%x sec_count_msb = 0x%x "
                              "lba_low_msb = 0x%x lba_mid_msb = 0x%x "
*** 10003,10012 ****
--- 10101,10111 ----
                              cmd.satacmd_lba_mid_msb, cmd.satacmd_lba_high_msb,
                              cmd.satacmd_sec_count_lsb, cmd.satacmd_lba_low_lsb,
                              cmd.satacmd_lba_mid_lsb, cmd.satacmd_lba_high_lsb,
                              cmd.satacmd_device_reg, cmd.satacmd_addr_type,
                              *((uint32_t *)&(cmd.satacmd_flags)));
+                 }
  
                  CLEAR_BIT(slot_tags, tmp_slot);
          }
  }
  
*** 10202,10212 ****
  /*
   * quiesce(9E) entry point.
   *
   * This function is called when the system is single-threaded at high
   * PIL with preemption disabled. Therefore, this function must not be
!  * blocked.
   *
   * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
   * DDI_FAILURE indicates an error condition and should almost never happen.
   */
  static int
--- 10301,10314 ----
  /*
   * quiesce(9E) entry point.
   *
   * This function is called when the system is single-threaded at high
   * PIL with preemption disabled. Therefore, this function must not be
!  * blocked. Because no taskqs are running, there is no need for us to
!  * take any action for enclosure services which are running in the
!  * taskq context, especially as no interrupts are generated by it nor
!  * are any messages expected to come in.
   *
   * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
   * DDI_FAILURE indicates an error condition and should almost never happen.
   */
  static int
*** 10319,10324 ****
--- 10421,10888 ----
                          satapkt = next;
                  }
  
                  mutex_enter(&ahci_portp->ahciport_mutex);
          }
+ }
+ 
+ /*
+  * Sets the state for the specified port on the controller to desired state.
+  * This must be run in the context of the enclosure taskq which ensures that
+  * only one event is outstanding at any time.
+  */
+ static boolean_t
+ ahci_em_set_led(ahci_ctl_t *ahci_ctlp, uint8_t port, ahci_em_led_state_t desire)
+ {
+         ahci_em_led_msg_t msg;
+         ahci_em_msg_hdr_t hdr;
+         uint32_t msgval, hdrval;
+         uint_t i, max_delay = ahci_em_tx_delay_count;
+ 
+         msg.alm_hba = port;
+         msg.alm_pminfo = 0;
+         msg.alm_value = 0;
+ 
+         if (desire & AHCI_EM_LED_IDENT_ENABLE) {
+                 msg.alm_value |= AHCI_LED_ON << AHCI_LED_IDENT_OFF;
+         }
+ 
+         if (desire & AHCI_EM_LED_FAULT_ENABLE) {
+                 msg.alm_value |= AHCI_LED_ON << AHCI_LED_FAULT_OFF;
+         }
+ 
+         if ((ahci_ctlp->ahcictl_em_ctl & AHCI_HBA_EM_CTL_ATTR_ALHD) == 0 &&
+             (desire & AHCI_EM_LED_ACTIVITY_DISABLE) == 0) {
+                 msg.alm_value |= AHCI_LED_ON << AHCI_LED_ACTIVITY_OFF;
+         }
+ 
+         hdr.aemh_rsvd = 0;
+         hdr.aemh_mlen = sizeof (ahci_em_led_msg_t);
+         hdr.aemh_dlen = 0;
+         hdr.aemh_mtype = AHCI_EM_MSG_TYPE_LED;
+ 
+         bcopy(&msg, &msgval, sizeof (msgval));
+         bcopy(&hdr, &hdrval, sizeof (hdrval));
+ 
+         /*
+          * First, make sure we can transmit. We should not have been placed in a
+          * situation where an outstanding transmission is going on.
+          */
+         for (i = 0; i < max_delay; i++) {
+                 uint32_t val;
+ 
+                 val = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+                     (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp));
+                 if ((val & AHCI_HBA_EM_CTL_CTL_TM) == 0)
+                         break;
+ 
+                 delay(drv_usectohz(ahci_em_tx_delay_ms * 1000));
+         }
+ 
+         if (i == max_delay)
+                 return (B_FALSE);
+ 
+         ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle,
+             (uint32_t *)ahci_ctlp->ahcictl_em_tx_off, hdrval);
+         ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle,
+             (uint32_t *)(ahci_ctlp->ahcictl_em_tx_off + 4), msgval);
+         ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle,
+             (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp), AHCI_HBA_EM_CTL_CTL_TM);
+ 
+         for (i = 0; i < max_delay; i++) {
+                 uint32_t val;
+ 
+                 val = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+                     (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp));
+                 if ((val & AHCI_HBA_EM_CTL_CTL_TM) == 0)
+                         break;
+ 
+                 delay(drv_usectohz(ahci_em_tx_delay_ms * 1000));
+         }
+ 
+         if (i == max_delay)
+                 return (B_FALSE);
+ 
+         return (B_TRUE);
+ }
+ 
+ typedef struct ahci_em_led_task_arg {
+         ahci_ctl_t              *aelta_ctl;
+         uint8_t                 aelta_port;
+         uint_t                  aelta_op;
+         ahci_em_led_state_t     aelta_state;
+         uint_t                  aelta_ret;
+         kcondvar_t              aelta_cv;
+         uint_t                  aelta_ref;
+ } ahci_em_led_task_arg_t;
+ 
+ static void
+ ahci_em_led_task_free(ahci_em_led_task_arg_t *task)
+ {
+         ASSERT3U(task->aelta_ref, ==, 0);
+         cv_destroy(&task->aelta_cv);
+         kmem_free(task, sizeof (*task));
+ }
+ 
+ static void
+ ahci_em_led_task(void *arg)
+ {
+         boolean_t ret, cleanup = B_FALSE;
+         ahci_em_led_task_arg_t *led = arg;
+         ahci_em_led_state_t state;
+ 
+         mutex_enter(&led->aelta_ctl->ahcictl_mutex);
+         if (led->aelta_ctl->ahcictl_em_flags != AHCI_EM_USABLE) {
+                 led->aelta_ret = EIO;
+                 mutex_exit(&led->aelta_ctl->ahcictl_mutex);
+                 return;
+         }
+ 
+         state = led->aelta_ctl->ahcictl_em_state[led->aelta_port];
+         mutex_exit(&led->aelta_ctl->ahcictl_mutex);
+ 
+         switch (led->aelta_op) {
+         case AHCI_EM_IOC_SET_OP_ADD:
+                 state |= led->aelta_state;
+                 break;
+         case AHCI_EM_IOC_SET_OP_REM:
+                 state &= ~led->aelta_state;
+                 break;
+         case AHCI_EM_IOC_SET_OP_SET:
+                 state = led->aelta_state;
+                 break;
+         default:
+                 led->aelta_ret = ENOTSUP;
+                 return;
+         }
+ 
+         ret = ahci_em_set_led(led->aelta_ctl, led->aelta_port, state);
+ 
+         mutex_enter(&led->aelta_ctl->ahcictl_mutex);
+         if (ret) {
+                 led->aelta_ctl->ahcictl_em_state[led->aelta_port] =
+                     led->aelta_state;
+                 led->aelta_ret = 0;
+         } else {
+                 led->aelta_ret = EIO;
+                 led->aelta_ctl->ahcictl_em_flags |= AHCI_EM_TIMEOUT;
+         }
+         led->aelta_ref--;
+         if (led->aelta_ref > 0) {
+                 cv_signal(&led->aelta_cv);
+         } else {
+                 cleanup = B_TRUE;
+         }
+         mutex_exit(&led->aelta_ctl->ahcictl_mutex);
+ 
+         if (cleanup) {
+                 ahci_em_led_task_free(led);
+         }
+ }
+ 
+ static void
+ ahci_em_reset(void *arg)
+ {
+         uint_t i, max_delay = ahci_em_reset_delay_count;
+         ahci_ctl_t *ahci_ctlp = arg;
+ 
+         /*
+          * We've been asked to reset the device. The caller should have set the
+          * resetting flag. Make sure that we don't have a request to quiesce.
+          */
+         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+         ASSERT(ahci_ctlp->ahcictl_em_flags & AHCI_EM_RESETTING);
+         if (ahci_ctlp->ahcictl_em_flags & AHCI_EM_QUIESCE) {
+                 ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_RESETTING;
+                 mutex_exit(&ahci_ctlp->ahcictl_mutex);
+                 return;
+         }
+         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ 
+         ddi_put32(ahci_ctlp->ahcictl_ahci_acc_handle,
+             (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp), AHCI_HBA_EM_CTL_CTL_RST);
+         for (i = 0; i < max_delay; i++) {
+                 uint32_t val;
+ 
+                 val = ddi_get32(ahci_ctlp->ahcictl_ahci_acc_handle,
+                     (uint32_t *)AHCI_GLOBAL_EM_CTL(ahci_ctlp));
+                 if ((val & AHCI_HBA_EM_CTL_CTL_RST) == 0)
+                         break;
+ 
+                 delay(drv_usectohz(ahci_em_reset_delay_ms * 1000));
+         }
+ 
+         if (i == max_delay) {
+                 mutex_enter(&ahci_ctlp->ahcictl_mutex);
+                 ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_RESETTING;
+                 ahci_ctlp->ahcictl_em_flags |= AHCI_EM_TIMEOUT;
+                 mutex_exit(&ahci_ctlp->ahcictl_mutex);
+                 cmn_err(CE_WARN, "!ahci%d: enclosure timed out resetting",
+                     ddi_get_instance(ahci_ctlp->ahcictl_dip));
+                 return;
+         }
+ 
+         for (i = 0; i < ahci_ctlp->ahcictl_num_ports; i++) {
+ 
+                 if (!AHCI_PORT_IMPLEMENTED(ahci_ctlp, i))
+                         continue;
+ 
+                 /*
+                  * Try to flush all the LEDs as part of reset. If it fails,
+                  * drive on.
+                  */
+                 if (!ahci_em_set_led(ahci_ctlp, i,
+                     ahci_ctlp->ahcictl_em_state[i])) {
+                         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+                         ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_RESETTING;
+                         ahci_ctlp->ahcictl_em_flags |= AHCI_EM_TIMEOUT;
+                         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+                         cmn_err(CE_WARN, "!ahci%d: enclosure timed out "
+                             "setting port %u",
+                             ddi_get_instance(ahci_ctlp->ahcictl_dip), i);
+                         return;
+                 }
+         }
+ 
+         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+         ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_RESETTING;
+         ahci_ctlp->ahcictl_em_flags |= AHCI_EM_READY;
+         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ }
+ 
+ static boolean_t
+ ahci_em_init(ahci_ctl_t *ahci_ctlp)
+ {
+         char name[128];
+ 
+         /*
+          * First make sure we actually have enclosure services and if so, that
+          * we have the hardware support that we care about for this.
+          */
+         if (ahci_ctlp->ahcictl_em_loc == 0 ||
+             (ahci_ctlp->ahcictl_em_ctl & AHCI_HBA_EM_CTL_SUPP_LED) == 0)
+                 return (B_TRUE);
+ 
+         /*
+          * Next, make sure that the buffer is large enough for us. We need two
+          * dwords or 8 bytes. The location register is stored in dwords.
+          */
+         if ((ahci_ctlp->ahcictl_em_loc & AHCI_HBA_EM_LOC_SZ_MASK) <
+             AHCI_EM_BUFFER_MIN) {
+                 return (B_TRUE);
+         }
+ 
+         ahci_ctlp->ahcictl_em_flags |= AHCI_EM_PRESENT;
+ 
+         ahci_ctlp->ahcictl_em_tx_off = ((ahci_ctlp->ahcictl_em_loc &
+             AHCI_HBA_EM_LOC_OFST_MASK) >> AHCI_HBA_EM_LOC_OFST_SHIFT) * 4;
+         ahci_ctlp->ahcictl_em_tx_off += ahci_ctlp->ahcictl_ahci_addr;
+ 
+         bzero(ahci_ctlp->ahcictl_em_state,
+             sizeof (ahci_ctlp->ahcictl_em_state));
+ 
+         (void) snprintf(name, sizeof (name), "ahcti_em_taskq%d",
+             ddi_get_instance(ahci_ctlp->ahcictl_dip));
+         if ((ahci_ctlp->ahcictl_em_taskq =
+             ddi_taskq_create(ahci_ctlp->ahcictl_dip, name, 1,
+             TASKQ_DEFAULTPRI, 0)) == NULL) {
+                 cmn_err(CE_WARN, "!ahci%d: ddi_tasq_create failed for em "
+                     "services", ddi_get_instance(ahci_ctlp->ahcictl_dip));
+                 return (B_FALSE);
+         }
+ 
+         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+         ahci_ctlp->ahcictl_em_flags |= AHCI_EM_RESETTING;
+         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+         (void) ddi_taskq_dispatch(ahci_ctlp->ahcictl_em_taskq, ahci_em_reset,
+             ahci_ctlp, DDI_SLEEP);
+ 
+         return (B_TRUE);
+ }
+ 
+ static int
+ ahci_em_ioctl_get(ahci_ctl_t *ahci_ctlp, intptr_t arg)
+ {
+         int i;
+         ahci_ioc_em_get_t get;
+ 
+         bzero(&get, sizeof (get));
+         get.aiemg_nports = ahci_ctlp->ahcictl_ports_implemented;
+         if ((ahci_ctlp->ahcictl_em_ctl & AHCI_HBA_EM_CTL_ATTR_ALHD) == 0) {
+                 get.aiemg_flags |= AHCI_EM_FLAG_CONTROL_ACTIVITY;
+         }
+ 
+         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+         for (i = 0; i < ahci_ctlp->ahcictl_num_ports; i++) {
+                 if (!AHCI_PORT_IMPLEMENTED(ahci_ctlp, i)) {
+                         continue;
+                 }
+                 get.aiemg_status[i] = ahci_ctlp->ahcictl_em_state[i];
+         }
+         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ 
+         if (ddi_copyout(&get, (void *)arg, sizeof (get), 0) != 0)
+                 return (EFAULT);
+ 
+         return (0);
+ }
+ 
+ static int
+ ahci_em_ioctl_set(ahci_ctl_t *ahci_ctlp, intptr_t arg)
+ {
+         int ret;
+         ahci_ioc_em_set_t set;
+         ahci_em_led_task_arg_t *task;
+         boolean_t signal, cleanup;
+ 
+         if (ddi_copyin((void *)arg, &set, sizeof (set), 0) != 0)
+                 return (EFAULT);
+ 
+         if (set.aiems_port > ahci_ctlp->ahcictl_num_ports)
+                 return (EINVAL);
+ 
+         if (!AHCI_PORT_IMPLEMENTED(ahci_ctlp, set.aiems_port)) {
+                 return (EINVAL);
+         }
+ 
+         if ((set.aiems_leds & ~(AHCI_EM_LED_IDENT_ENABLE |
+             AHCI_EM_LED_FAULT_ENABLE |
+             AHCI_EM_LED_ACTIVITY_DISABLE)) != 0) {
+                 return (EINVAL);
+         }
+ 
+         switch (set.aiems_op) {
+         case AHCI_EM_IOC_SET_OP_ADD:
+         case AHCI_EM_IOC_SET_OP_REM:
+         case AHCI_EM_IOC_SET_OP_SET:
+                 break;
+         default:
+                 return (EINVAL);
+         }
+ 
+         if ((set.aiems_leds & AHCI_EM_LED_ACTIVITY_DISABLE) != 0 &&
+             ((ahci_ctlp->ahcictl_em_ctl & AHCI_HBA_EM_CTL_ATTR_ALHD) != 0)) {
+                 return (ENOTSUP);
+         }
+ 
+         task = kmem_alloc(sizeof (*task), KM_NOSLEEP | KM_NORMALPRI);
+         if (task == NULL) {
+                 return (ENOMEM);
+         }
+ 
+         task->aelta_ctl = ahci_ctlp;
+         task->aelta_port = (uint8_t)set.aiems_port;
+         task->aelta_op = set.aiems_op;
+         task->aelta_state = set.aiems_leds;
+ 
+         cv_init(&task->aelta_cv, NULL, CV_DRIVER, NULL);
+ 
+         /*
+          * Initialize the reference count to two. One for us and one for the
+          * taskq. This will be used in case we get canceled.
+          */
+         task->aelta_ref = 2;
+ 
+         /*
+          * Once dispatched, the task state is protected by our global mutex.
+          */
+         (void) ddi_taskq_dispatch(ahci_ctlp->ahcictl_em_taskq,
+             ahci_em_led_task, task, DDI_SLEEP);
+ 
+         signal = B_FALSE;
+         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+         while (task->aelta_ref > 1) {
+                 if (cv_wait_sig(&task->aelta_cv, &ahci_ctlp->ahcictl_mutex) ==
+                     0) {
+                         signal = B_TRUE;
+                         break;
+                 }
+         }
+ 
+         /*
+          * Remove our reference count. If we were woken up because of a signal
+          * then the taskq may still be dispatched. In which case we shouldn't
+          * free this memory until it is done. In that case, the taskq will take
+          * care of it.
+          */
+         task->aelta_ref--;
+         cleanup = (task->aelta_ref == 0);
+         if (signal) {
+                 ret = EINTR;
+         } else {
+                 ret = task->aelta_ret;
+         }
+         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ 
+         if (cleanup) {
+                 ahci_em_led_task_free(task);
+         }
+ 
+         return (ret);
+ }
+ 
+ static int
+ ahci_em_ioctl(dev_info_t *dip, int cmd, intptr_t arg)
+ {
+         int inst;
+         ahci_ctl_t *ahci_ctlp;
+ 
+         inst = ddi_get_instance(dip);
+         if ((ahci_ctlp = ddi_get_soft_state(ahci_statep, inst)) == NULL) {
+                 return (ENXIO);
+         }
+ 
+         switch (cmd) {
+         case AHCI_EM_IOC_GET:
+                 return (ahci_em_ioctl_get(ahci_ctlp, arg));
+         case AHCI_EM_IOC_SET:
+                 return (ahci_em_ioctl_set(ahci_ctlp, arg));
+         default:
+                 return (ENOTTY);
+         }
+ 
+ }
+ 
+ static void
+ ahci_em_quiesce(ahci_ctl_t *ahci_ctlp)
+ {
+         ASSERT(ahci_ctlp->ahcictl_em_flags & AHCI_EM_PRESENT);
+ 
+         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+         ahci_ctlp->ahcictl_em_flags |= AHCI_EM_QUIESCE;
+         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ 
+         ddi_taskq_wait(ahci_ctlp->ahcictl_em_taskq);
+ }
+ 
+ static void
+ ahci_em_suspend(ahci_ctl_t *ahci_ctlp)
+ {
+         ahci_em_quiesce(ahci_ctlp);
+ 
+         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+         ahci_ctlp->ahcictl_em_flags &= ~AHCI_EM_READY;
+         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ }
+ 
+ static void
+ ahci_em_resume(ahci_ctl_t *ahci_ctlp)
+ {
+         mutex_enter(&ahci_ctlp->ahcictl_mutex);
+         ahci_ctlp->ahcictl_em_flags |= AHCI_EM_RESETTING;
+         mutex_exit(&ahci_ctlp->ahcictl_mutex);
+ 
+         (void) ddi_taskq_dispatch(ahci_ctlp->ahcictl_em_taskq, ahci_em_reset,
+             ahci_ctlp, DDI_SLEEP);
+ }
+ 
+ static void
+ ahci_em_fini(ahci_ctl_t *ahci_ctlp)
+ {
+         if ((ahci_ctlp->ahcictl_em_flags & AHCI_EM_PRESENT) == 0) {
+                 return;
+         }
+ 
+         ahci_em_quiesce(ahci_ctlp);
+         ddi_taskq_destroy(ahci_ctlp->ahcictl_em_taskq);
+         ahci_ctlp->ahcictl_em_taskq = NULL;
  }