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; }