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,11 +19,13 @@
  * CDDL HEADER END
  */
 
 /*
  * Copyright (c) 2006, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2013 Nexenta Systems, Inc.  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,10 +41,46 @@
  * 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,10 +96,21 @@
 #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,11 +268,17 @@
 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,21 +368,20 @@
         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,                   /* driver operations */
         NULL,                   /* bus operations */
         NULL,                   /* power */
         ahci_quiesce,           /* quiesce */
 };
 
@@ -408,11 +462,20 @@
  * 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,10 +641,15 @@
                             "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,10 +780,20 @@
 
                 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,10 +1041,17 @@
             (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,10 +1072,14 @@
 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,10 +1150,12 @@
                         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,10 +1201,12 @@
                         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,11 +1353,11 @@
         /*
          * 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;
+        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,19 +1906,23 @@
 
                 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 */
+                        mutex_exit(&ahci_portp->ahciport_mutex);
                         ahci_port_intr(ahci_ctlp, ahci_portp, port);
+                        mutex_enter(&ahci_portp->ahciport_mutex);
 
-                        drv_usecwait(AHCI_10MS_USECS);
+                        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_10MS_TICKS;
+
+                        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,10 +1930,11 @@
                                 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,11 +9570,11 @@
 }
 
 /*
  * Used to recovery a PMULT pmport fatal error under FIS-based switching.
  *      1. device specific.PxFBS.SDE=1
- *      2. Non-Deivce specific.
+ *      2. Non Device specific.
  * Nothing will be done when Command-based switching is employed.
  *
  * Currently code is neither completed nor tested.
  */
 static void
@@ -9986,11 +10084,11 @@
                 if (tmp_slot == -1) {
                         break;
                 }
 
                 spkt = ahci_portp->ahciport_slot_pkts[tmp_slot];
-                ASSERT(spkt != NULL);
+                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,10 +10101,11 @@
                     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,11 +10301,14 @@
 /*
  * 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.
+ * 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,6 +10421,468 @@
                         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;
 }