Mercurial > illumos > illumos-gate
changeset 10391:12b08c516444
6838603 Under heavy I/O load, all Seagate Dragonfly drives in an x4x40 go offline at the same time
6857563 nv_sata poorly handles port reset, signature detection and hot-plugging events for Dragonfly drives
6869467 nv_sata: race between reset/abort/timeout and command completion interrupt causing panic
6679403 nv_sata: mcp55 interrupt handling can loop and delay other devices interrupt servicing
6854127 sata: device under reset may appear as gone
author | Pawel Wojcik <Pawel.Wojcik@Sun.COM> |
---|---|
date | Thu, 27 Aug 2009 11:15:07 -0700 |
parents | fe2d41eef35a |
children | 952e1ebcd49b |
files | usr/src/uts/common/io/sata/adapters/nv_sata/nv_sata.c usr/src/uts/common/io/sata/impl/sata.c usr/src/uts/common/sys/sata/adapters/nv_sata/nv_sata.h usr/src/uts/common/sys/sata/impl/sata.h |
diffstat | 4 files changed, 1215 insertions(+), 530 deletions(-) [+] |
line wrap: on
line diff
--- a/usr/src/uts/common/io/sata/adapters/nv_sata/nv_sata.c Thu Aug 27 12:59:34 2009 -0400 +++ b/usr/src/uts/common/io/sata/adapters/nv_sata/nv_sata.c Thu Aug 27 11:15:07 2009 -0700 @@ -134,7 +134,6 @@ static void nv_start_dma_engine(nv_port_t *nvp, int slot); static void nv_port_state_change(nv_port_t *nvp, int event, uint8_t addr_type, int state); -static boolean_t nv_check_link(uint32_t sstatus); static void nv_common_reg_init(nv_ctl_t *nvc); static void ck804_intr_process(nv_ctl_t *nvc, uint8_t intr_status); static void nv_reset(nv_port_t *nvp); @@ -148,7 +147,8 @@ static void nv_resume(nv_port_t *nvp); static void nv_suspend(nv_port_t *nvp); static int nv_start_sync(nv_port_t *nvp, sata_pkt_t *spkt); -static int nv_abort_active(nv_port_t *nvp, sata_pkt_t *spkt, int abort_reason); +static int nv_abort_active(nv_port_t *nvp, sata_pkt_t *spkt, int abort_reason, + int flag); static void nv_copy_registers(nv_port_t *nvp, sata_device_t *sd, sata_pkt_t *spkt); static void nv_report_add_remove(nv_port_t *nvp, int flags); @@ -160,6 +160,10 @@ static int nv_wait(nv_port_t *nvp, uchar_t onbits, uchar_t offbits, uint_t timeout_usec, int type_wait); static int nv_start_rqsense_pio(nv_port_t *nvp, nv_slot_t *nv_slotp); +static void nv_init_port_link_processing(nv_ctl_t *nvc); +static void nv_setup_timeout(nv_port_t *nvp, int time); +static void nv_monitor_reset(nv_port_t *nvp); +static int nv_bm_status_clear(nv_port_t *nvp); #ifdef SGPIO_SUPPORT static int nv_open(dev_t *devp, int flag, int otyp, cred_t *credp); @@ -322,6 +326,69 @@ /* + * Wait for a signature. + * If this variable is non-zero, the driver will wait for a device signature + * before reporting a device reset to the sata module. + * Some (most?) drives will not process commands sent to them before D2H FIS + * is sent to a host. + */ +int nv_wait_for_signature = 1; + +/* + * Check for a signature availability. + * If this variable is non-zero, the driver will check task file error register + * for indication of a signature availability before reading a signature. + * Task file error register bit 0 set to 1 indicates that the drive + * is ready and it has sent the D2H FIS with a signature. + * This behavior of the error register is not reliable in the mcp5x controller. + */ +int nv_check_tfr_error = 0; + +/* + * Max signature acquisition time, in milliseconds. + * The driver will try to acquire a device signature within specified time and + * quit acquisition operation if signature was not acquired. + */ +long nv_sig_acquisition_time = NV_SIG_ACQUISITION_TIME; + +/* + * If this variable is non-zero, the driver will wait for a signature in the + * nv_monitor_reset function without any time limit. + * Used for debugging and drive evaluation. + */ +int nv_wait_here_forever = 0; + +/* + * Reset after hotplug. + * If this variable is non-zero, driver will reset device after hotplug + * (device attached) interrupt. + * If the variable is zero, driver will not reset the new device nor will it + * try to read device signature. + * Chipset is generating a hotplug (device attached) interrupt with a delay, so + * the device should have already sent the D2H FIS with the signature. + */ +int nv_reset_after_hotplug = 1; + +/* + * Delay after device hotplug. + * It specifies the time between detecting a hotplugged device and sending + * a notification to the SATA module. + * It is used when device is not reset after hotpugging and acquiring signature + * may be unreliable. The delay should be long enough for a device to become + * ready to accept commands. + */ +int nv_hotplug_delay = NV_HOTPLUG_DELAY; + + +/* + * Maximum number of consecutive interrupts processed in the loop in the + * single invocation of the port interrupt routine. + */ +int nv_max_intr_loops = NV_MAX_INTR_PER_DEV; + + + +/* * wait between checks of reg status */ int nv_usec_delay = NV_WAIT_REG_CHECK; @@ -618,7 +685,7 @@ attach_state |= ATTACH_PROGRESS_BARS; /* - * initialize controller and driver core + * initialize controller structures */ status = nv_init_ctl(nvc, pci_conf_handle); @@ -723,6 +790,10 @@ #endif /* SGPIO_SUPPORT */ /* + * Initiate link processing and device identification + */ + nv_init_port_link_processing(nvc); + /* * attach to sata module */ if (sata_hba_attach(nvc->nvc_dip, @@ -877,7 +948,7 @@ mutex_destroy(&nvc->nvc_mutex); /* - * Uninitialize the controller + * Uninitialize the controller structures */ nv_uninit_ctl(nvc); @@ -1137,10 +1208,6 @@ * Called by sata module to probe a port. Port and device state * are not changed here... only reported back to the sata module. * - * If probe confirms a device is present for the first time, it will - * initiate a device reset, then probe will be called again and the - * signature will be check. If the signature is valid, data structures - * will be initialized. */ static int nv_sata_probe(dev_info_t *dip, sata_device_t *sd) @@ -1149,7 +1216,6 @@ uint8_t cport = sd->satadev_addr.cport; uint8_t pmport = sd->satadev_addr.pmport; uint8_t qual = sd->satadev_addr.qual; - clock_t nv_lbolt = ddi_get_lbolt(); nv_port_t *nvp; if (cport >= NV_MAX_PORTS(nvc)) { @@ -1163,7 +1229,7 @@ nvp = &(nvc->nvc_port[cport]); ASSERT(nvp != NULL); - NVLOG((NVDBG_PROBE, nvc, nvp, + NVLOG((NVDBG_RESET, nvc, nvp, "nv_sata_probe: enter cport: 0x%x, pmport: 0x%x, " "qual: 0x%x", cport, pmport, qual)); @@ -1180,7 +1246,17 @@ sd->satadev_state = SATA_PSTATE_SHUTDOWN; mutex_exit(&nvp->nvp_mutex); - return (SATA_FAILURE); + return (SATA_SUCCESS); + } + + if (nvp->nvp_state & NV_PORT_FAILED) { + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "probe: port failed")); + sd->satadev_type = SATA_DTYPE_NONE; + sd->satadev_state = SATA_PSTATE_FAILED; + mutex_exit(&nvp->nvp_mutex); + + return (SATA_SUCCESS); } if (qual == SATA_ADDR_PMPORT) { @@ -1190,182 +1266,50 @@ nv_cmn_err(CE_WARN, nvc, nvp, "controller does not support port multiplier"); - return (SATA_FAILURE); + return (SATA_SUCCESS); } sd->satadev_state = SATA_PSTATE_PWRON; nv_copy_registers(nvp, sd, NULL); - /* - * determine link status - */ - if (nv_check_link(sd->satadev_scr.sstatus) == B_FALSE) { - uint8_t det; - + if (nvp->nvp_state & (NV_PORT_RESET | NV_PORT_RESET_RETRY)) { /* + * We are waiting for reset to complete and to fetch + * a signature. * Reset will cause the link to go down for a short period of - * time. If link is lost for less than 2 seconds ignore it - * so that the reset can progress. + * time. If reset processing continues for less than + * NV_LINK_DOWN_TIMEOUT, fake the status of the link so that + * we will not report intermittent link down. + * Maybe we should report previous link state? */ - if (nvp->nvp_state & NV_PORT_RESET_PROBE) { - - if (nvp->nvp_link_lost_time == 0) { - nvp->nvp_link_lost_time = nv_lbolt; - } - - if (TICK_TO_SEC(nv_lbolt - - nvp->nvp_link_lost_time) < NV_LINK_LOST_OK) { - NVLOG((NVDBG_ALWAYS, nvp->nvp_ctlp, nvp, - "probe: intermittent link lost while" - " resetting")); - /* - * fake status of link so that probe continues - */ - SSTATUS_SET_IPM(sd->satadev_scr.sstatus, - SSTATUS_IPM_ACTIVE); - SSTATUS_SET_DET(sd->satadev_scr.sstatus, - SSTATUS_DET_DEVPRE_PHYCOM); - sd->satadev_type = SATA_DTYPE_UNKNOWN; - mutex_exit(&nvp->nvp_mutex); - - return (SATA_SUCCESS); - } else { - nvp->nvp_state &= - ~(NV_PORT_RESET_PROBE|NV_PORT_RESET); - } + if (TICK_TO_MSEC(ddi_get_lbolt() - nvp->nvp_reset_time) < + NV_LINK_DOWN_TIMEOUT) { + SSTATUS_SET_IPM(sd->satadev_scr.sstatus, + SSTATUS_IPM_ACTIVE); + SSTATUS_SET_DET(sd->satadev_scr.sstatus, + SSTATUS_DET_DEVPRE_PHYCOM); + sd->satadev_type = nvp->nvp_type; + mutex_exit(&nvp->nvp_mutex); + + return (SATA_SUCCESS); } - - /* - * no link, so tear down port and abort all active packets - */ - - det = (sd->satadev_scr.sstatus & SSTATUS_DET) >> - SSTATUS_DET_SHIFT; - - switch (det) { - case SSTATUS_DET_NODEV: - case SSTATUS_DET_PHYOFFLINE: - sd->satadev_type = SATA_DTYPE_NONE; - break; - default: - sd->satadev_type = SATA_DTYPE_UNKNOWN; - break; - } - - NVLOG((NVDBG_PROBE, nvp->nvp_ctlp, nvp, - "probe: link lost invoking nv_abort_active")); - - (void) nv_abort_active(nvp, NULL, SATA_PKT_TIMEOUT); - nv_uninit_port(nvp); - - mutex_exit(&nvp->nvp_mutex); - - return (SATA_SUCCESS); - } else { - nvp->nvp_link_lost_time = 0; - } - + } /* - * A device is present so clear hotremoved flag + * Just report the current port state */ - nvp->nvp_state &= ~NV_PORT_HOTREMOVED; + sd->satadev_type = nvp->nvp_type; + sd->satadev_state = nvp->nvp_state | SATA_PSTATE_PWRON; + mutex_exit(&nvp->nvp_mutex); #ifdef SGPIO_SUPPORT - nv_sgp_drive_connect(nvp->nvp_ctlp, SGP_CTLR_PORT_TO_DRV( - nvp->nvp_ctlp->nvc_ctlr_num, nvp->nvp_port_num)); + if (nvp->nvp_type != SATA_DTYPE_NONE) { + nv_sgp_drive_connect(nvp->nvp_ctlp, SGP_CTLR_PORT_TO_DRV( + nvp->nvp_ctlp->nvc_ctlr_num, nvp->nvp_port_num)); + } #endif - /* - * If the signature was acquired previously there is no need to - * do it again. - */ - if (nvp->nvp_signature != 0) { - NVLOG((NVDBG_PROBE, nvp->nvp_ctlp, nvp, - "probe: signature acquired previously")); - sd->satadev_type = nvp->nvp_type; - mutex_exit(&nvp->nvp_mutex); - - return (SATA_SUCCESS); - } - - /* - * If NV_PORT_RESET is not set, this is the first time through - * so perform reset and return. - */ - if ((nvp->nvp_state & NV_PORT_RESET) == 0) { - NVLOG((NVDBG_PROBE, nvp->nvp_ctlp, nvp, - "probe: first reset to get sig")); - nvp->nvp_state |= NV_PORT_RESET_PROBE; - nv_reset(nvp); - sd->satadev_type = nvp->nvp_type = SATA_DTYPE_UNKNOWN; - nvp->nvp_probe_time = nv_lbolt; - mutex_exit(&nvp->nvp_mutex); - - return (SATA_SUCCESS); - } - - /* - * Reset was done previously. see if the signature is - * available. - */ - nv_read_signature(nvp); - sd->satadev_type = nvp->nvp_type; - - /* - * Some drives may require additional resets to get a - * valid signature. If a drive was not just powered up, the signature - * should arrive within half a second of reset. Therefore if more - * than 5 seconds has elapsed while waiting for a signature, reset - * again. These extra resets do not appear to create problems when - * the drive is spinning up for more than this reset period. - */ - if (nvp->nvp_signature == 0) { - if (TICK_TO_SEC(nv_lbolt - nvp->nvp_reset_time) > 5) { - NVLOG((NVDBG_PROBE, nvc, nvp, "additional reset" - " during signature acquisition")); - nv_reset(nvp); - } - - mutex_exit(&nvp->nvp_mutex); - - return (SATA_SUCCESS); - } - - NVLOG((NVDBG_PROBE, nvc, nvp, "signature acquired after %d ms", - TICK_TO_MSEC(nv_lbolt - nvp->nvp_probe_time))); - - /* - * nv_sata only deals with ATA disks and ATAPI CD/DVDs so far. If - * it is not either of those, then just return. - */ - if ((nvp->nvp_type != SATA_DTYPE_ATADISK) && - (nvp->nvp_type != SATA_DTYPE_ATAPICD)) { - NVLOG((NVDBG_PROBE, nvc, nvp, "Driver currently handles only" - " disks/CDs/DVDs. Signature acquired was %X", - nvp->nvp_signature)); - mutex_exit(&nvp->nvp_mutex); - - return (SATA_SUCCESS); - } - - /* - * make sure structures are initialized - */ - if (nv_init_port(nvp) == NV_SUCCESS) { - NVLOG((NVDBG_PROBE, nvc, nvp, - "device detected and set up at port %d", cport)); - mutex_exit(&nvp->nvp_mutex); - - return (SATA_SUCCESS); - } else { - nv_cmn_err(CE_WARN, nvc, nvp, "failed to set up data " - "structures for port %d", cport); - mutex_exit(&nvp->nvp_mutex); - - return (SATA_FAILURE); - } - /*NOTREACHED*/ + return (SATA_SUCCESS); } @@ -1385,16 +1329,30 @@ mutex_enter(&nvp->nvp_mutex); - /* - * hotremoved is an intermediate state where the link was lost, - * but the hotplug event has not yet been processed by the sata - * module. Fail the request. - */ - if (nvp->nvp_state & NV_PORT_HOTREMOVED) { + if ((nvp->nvp_state & NV_PORT_INIT) == 0) { + spkt->satapkt_reason = SATA_PKT_PORT_ERROR; + NVLOG((NVDBG_ERRS, nvc, nvp, + "nv_sata_start: port not yet initialized")); + nv_copy_registers(nvp, &spkt->satapkt_device, NULL); + mutex_exit(&nvp->nvp_mutex); + + return (SATA_TRAN_PORT_ERROR); + } + + if (nvp->nvp_state & NV_PORT_INACTIVE) { spkt->satapkt_reason = SATA_PKT_PORT_ERROR; - spkt->satapkt_device.satadev_state = SATA_STATE_UNKNOWN; NVLOG((NVDBG_ERRS, nvc, nvp, - "nv_sata_start: NV_PORT_HOTREMOVED")); + "nv_sata_start: NV_PORT_INACTIVE")); + nv_copy_registers(nvp, &spkt->satapkt_device, NULL); + mutex_exit(&nvp->nvp_mutex); + + return (SATA_TRAN_PORT_ERROR); + } + + if (nvp->nvp_state & NV_PORT_FAILED) { + spkt->satapkt_reason = SATA_PKT_PORT_ERROR; + NVLOG((NVDBG_ERRS, nvc, nvp, + "nv_sata_start: NV_PORT_FAILED state")); nv_copy_registers(nvp, &spkt->satapkt_device, NULL); mutex_exit(&nvp->nvp_mutex); @@ -1402,7 +1360,7 @@ } if (nvp->nvp_state & NV_PORT_RESET) { - NVLOG((NVDBG_ERRS, nvc, nvp, + NVLOG((NVDBG_VERBOSE, nvc, nvp, "still waiting for reset completion")); spkt->satapkt_reason = SATA_PKT_BUSY; mutex_exit(&nvp->nvp_mutex); @@ -1439,36 +1397,6 @@ return (SATA_TRAN_CMD_UNSUPPORTED); } - if ((nvp->nvp_state & NV_PORT_INIT) == 0) { - spkt->satapkt_reason = SATA_PKT_PORT_ERROR; - NVLOG((NVDBG_ERRS, nvc, nvp, - "nv_sata_start: port not yet initialized")); - nv_copy_registers(nvp, &spkt->satapkt_device, NULL); - mutex_exit(&nvp->nvp_mutex); - - return (SATA_TRAN_PORT_ERROR); - } - - if (nvp->nvp_state & NV_PORT_INACTIVE) { - spkt->satapkt_reason = SATA_PKT_PORT_ERROR; - NVLOG((NVDBG_ERRS, nvc, nvp, - "nv_sata_start: NV_PORT_INACTIVE")); - nv_copy_registers(nvp, &spkt->satapkt_device, NULL); - mutex_exit(&nvp->nvp_mutex); - - return (SATA_TRAN_PORT_ERROR); - } - - if (nvp->nvp_state & NV_PORT_FAILED) { - spkt->satapkt_reason = SATA_PKT_PORT_ERROR; - NVLOG((NVDBG_ERRS, nvc, nvp, - "nv_sata_start: NV_PORT_FAILED state")); - nv_copy_registers(nvp, &spkt->satapkt_device, NULL); - mutex_exit(&nvp->nvp_mutex); - - return (SATA_TRAN_PORT_ERROR); - } - /* * after a device reset, and then when sata module restore processing * is complete, the sata module will set sata_clear_dev_reset which @@ -1477,7 +1405,7 @@ */ if (spkt->satapkt_cmd.satacmd_flags.sata_clear_dev_reset) { nvp->nvp_state &= ~NV_PORT_RESTORE; - NVLOG((NVDBG_ENTRY, nvc, nvp, + NVLOG((NVDBG_RESET, nvc, nvp, "nv_sata_start: clearing NV_PORT_RESTORE")); } @@ -1494,7 +1422,7 @@ !(spkt->satapkt_cmd.satacmd_flags.sata_ignore_dev_reset) && (ddi_in_panic() == 0)) { spkt->satapkt_reason = SATA_PKT_BUSY; - NVLOG((NVDBG_ENTRY, nvc, nvp, + NVLOG((NVDBG_VERBOSE, nvc, nvp, "nv_sata_start: waiting for restore ")); mutex_exit(&nvp->nvp_mutex); @@ -1510,6 +1438,9 @@ return (SATA_TRAN_BUSY); } + /* Clear SError to be able to check errors after the command failure */ + nv_put32(nvp->nvp_ctlp->nvc_bar_hdl[5], nvp->nvp_serror, 0xffffffff); + if (spkt->satapkt_op_mode & (SATA_OPMODE_POLLING|SATA_OPMODE_SYNCH)) { @@ -1643,6 +1574,9 @@ mutex_enter(&nvp->nvp_mutex); spkt->satapkt_reason = SATA_PKT_TIMEOUT; nv_copy_registers(nvp, &spkt->satapkt_device, spkt); + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | + NV_PORT_RESET_RETRY); nv_reset(nvp); nv_complete_io(nvp, spkt, 0); mutex_exit(&nvp->nvp_mutex); @@ -1665,6 +1599,9 @@ " unclaimed -- resetting")); mutex_enter(&nvp->nvp_mutex); nv_copy_registers(nvp, &spkt->satapkt_device, spkt); + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | + NV_PORT_RESET_RETRY); nv_reset(nvp); spkt->satapkt_reason = SATA_PKT_TIMEOUT; nv_complete_io(nvp, spkt, 0); @@ -1714,7 +1651,7 @@ /* * spkt == NULL then abort all commands */ - c_a = nv_abort_active(nvp, spkt, SATA_PKT_ABORTED); + c_a = nv_abort_active(nvp, spkt, SATA_PKT_ABORTED, B_TRUE); if (c_a) { NVLOG((NVDBG_ENTRY, nvc, nvp, @@ -1742,7 +1679,7 @@ * held and returns with it held. Not NCQ aware. */ static int -nv_abort_active(nv_port_t *nvp, sata_pkt_t *spkt, int abort_reason) +nv_abort_active(nv_port_t *nvp, sata_pkt_t *spkt, int abort_reason, int flag) { int aborted = 0, i, reset_once = B_FALSE; struct nv_slot *nv_slotp; @@ -1760,7 +1697,7 @@ return (0); } - NVLOG((NVDBG_ENTRY, nvp->nvp_ctlp, nvp, "nv_abort_active")); + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, "nv_abort_active")); nvp->nvp_state |= NV_PORT_ABORTING; @@ -1796,8 +1733,16 @@ */ nv_put8(bmhdl, nvp->nvp_bmicx, 0); - nv_reset(nvp); - reset_once = B_TRUE; + /* + * Reset only if explicitly specified by the arg flag + */ + if (flag == B_TRUE) { + reset_once = B_TRUE; + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | + NV_PORT_RESET_RETRY); + nv_reset(nvp); + } } spkt_slot->satapkt_reason = abort_reason; @@ -1833,8 +1778,10 @@ case SATA_ADDR_CPORT: /*FALLTHROUGH*/ case SATA_ADDR_DCPORT: + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~NV_PORT_RESTORE; nv_reset(nvp); - (void) nv_abort_active(nvp, NULL, SATA_PKT_RESET); + (void) nv_abort_active(nvp, NULL, SATA_PKT_RESET, B_FALSE); break; case SATA_ADDR_CNTRL: @@ -1898,7 +1845,13 @@ (*(nvc->nvc_set_intr))(nvp, NV_INTR_ENABLE); - nvp->nvp_state = 0; + nvp->nvp_state &= ~NV_PORT_INACTIVE; + /* Initiate link probing and device signature acquisition */ + nvp->nvp_type = SATA_DTYPE_NONE; + nvp->nvp_signature = 0; + nvp->nvp_state |= NV_PORT_RESET; /* | NV_PORT_PROBE; */ + nvp->nvp_state &= ~(NV_PORT_RESTORE | NV_PORT_RESET_RETRY); + nv_reset(nvp); mutex_exit(&nvp->nvp_mutex); @@ -1921,20 +1874,18 @@ mutex_enter(&nvp->nvp_mutex); - (void) nv_abort_active(nvp, NULL, SATA_PKT_RESET); + (void) nv_abort_active(nvp, NULL, SATA_PKT_ABORTED, B_FALSE); /* - * mark the device as inaccessible + * make the device inaccessible */ - nvp->nvp_state &= ~NV_PORT_INACTIVE; + nvp->nvp_state |= NV_PORT_INACTIVE; /* * disable the interrupts on port */ (*(nvc->nvc_set_intr))(nvp, NV_INTR_DISABLE); - nv_uninit_port(nvp); - sd->satadev_state = SATA_PSTATE_SHUTDOWN; nv_copy_registers(nvp, sd, NULL); @@ -2102,10 +2053,12 @@ */ if ((nvp->nvp_timeout_id == 0) && ((spkt->satapkt_op_mode & SATA_OPMODE_POLLING) == 0)) { - nvp->nvp_timeout_id = timeout(nv_timeout, (void *)nvp, - drv_usectohz(NV_ONE_SEC)); + nv_setup_timeout(nvp, NV_ONE_SEC); } + nvp->nvp_previous_cmd = nvp->nvp_last_cmd; + nvp->nvp_last_cmd = spkt->satapkt_cmd.satacmd_cmd_reg; + return (SATA_TRAN_ACCEPTED); } @@ -2135,11 +2088,27 @@ { ddi_acc_handle_t cmdhdl = nvp->nvp_cmd_hdl; + /* + * Task file error register bit 0 set to 1 indicate that drive + * is ready and have sent D2H FIS with a signature. + */ + if (nv_check_tfr_error != 0) { + uint8_t tfr_error = nv_get8(cmdhdl, nvp->nvp_error); + if (!(tfr_error & SATA_ERROR_ILI)) { + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_read_signature: signature not ready")); + return; + } + } + nvp->nvp_signature = nv_get8(cmdhdl, nvp->nvp_count); nvp->nvp_signature |= (nv_get8(cmdhdl, nvp->nvp_sect) << 8); nvp->nvp_signature |= (nv_get8(cmdhdl, nvp->nvp_lcyl) << 16); nvp->nvp_signature |= (nv_get8(cmdhdl, nvp->nvp_hcyl) << 24); + NVLOG((NVDBG_ENTRY, nvp->nvp_ctlp, nvp, + "nv_read_signature: 0x%x ", nvp->nvp_signature)); + switch (nvp->nvp_signature) { case NV_SIG_DISK: @@ -2169,13 +2138,66 @@ } if (nvp->nvp_signature) { - nvp->nvp_state &= ~(NV_PORT_RESET_PROBE|NV_PORT_RESET); + nvp->nvp_state &= ~(NV_PORT_RESET_RETRY | NV_PORT_RESET); } } /* + * Set up a new timeout or complete a timeout. + * Timeout value has to be specified in microseconds. If time is zero, no new + * timeout is scheduled. + * Must be called at the end of the timeout routine. + */ +static void +nv_setup_timeout(nv_port_t *nvp, int time) +{ + clock_t old_duration = nvp->nvp_timeout_duration; + + ASSERT(time != 0); + + if (nvp->nvp_timeout_id != 0 && nvp->nvp_timeout_duration == 0) { + /* + * Since we are dropping the mutex for untimeout, + * the timeout may be executed while we are trying to + * untimeout and setting up a new timeout. + * If nvp_timeout_duration is 0, then this function + * was re-entered. Just exit. + */ + cmn_err(CE_WARN, "nv_setup_timeout re-entered"); + return; + } + nvp->nvp_timeout_duration = 0; + if (nvp->nvp_timeout_id == 0) { + /* Start new timer */ + nvp->nvp_timeout_id = timeout(nv_timeout, (void *)nvp, + drv_usectohz(time)); + } else { + /* + * If the currently running timeout is due later than the + * requested one, restart it with a new expiration. + * Our timeouts do not need to be accurate - we would be just + * checking that the specified time was exceeded. + */ + if (old_duration > time) { + mutex_exit(&nvp->nvp_mutex); + untimeout(nvp->nvp_timeout_id); + mutex_enter(&nvp->nvp_mutex); + nvp->nvp_timeout_id = timeout(nv_timeout, (void *)nvp, + drv_usectohz(time)); + } + } + nvp->nvp_timeout_duration = time; +} + + + +int nv_reset_length = NV_RESET_LENGTH; + +/* * Reset the port + * + * Entered with nvp mutex held */ static void nv_reset(nv_port_t *nvp) @@ -2183,49 +2205,94 @@ ddi_acc_handle_t bar5_hdl = nvp->nvp_ctlp->nvc_bar_hdl[5]; ddi_acc_handle_t cmdhdl = nvp->nvp_cmd_hdl; nv_ctl_t *nvc = nvp->nvp_ctlp; - uint32_t sctrl; - - NVLOG((NVDBG_ENTRY, nvc, nvp, "nv_reset()")); + uint32_t sctrl, serr, sstatus; + uint8_t bmicx; + int i, j, reset = 0; ASSERT(mutex_owned(&nvp->nvp_mutex)); + NVLOG((NVDBG_RESET, nvc, nvp, "nv_reset()")); + serr = nv_get32(bar5_hdl, nvp->nvp_serror); + NVLOG((NVDBG_RESET, nvc, nvp, "nv_reset: serr 0x%x", serr)); + /* - * clear signature registers + * stop DMA engine. */ - nv_put8(cmdhdl, nvp->nvp_sect, 0); - nv_put8(cmdhdl, nvp->nvp_lcyl, 0); - nv_put8(cmdhdl, nvp->nvp_hcyl, 0); - nv_put8(cmdhdl, nvp->nvp_count, 0); - - nvp->nvp_signature = 0; - nvp->nvp_type = 0; + bmicx = nv_get8(nvp->nvp_bm_hdl, nvp->nvp_bmicx); + nv_put8(nvp->nvp_bm_hdl, nvp->nvp_bmicx, bmicx & ~BMICX_SSBM); + nvp->nvp_state |= NV_PORT_RESET; nvp->nvp_reset_time = ddi_get_lbolt(); - nvp->nvp_link_lost_time = 0; /* - * assert reset in PHY by writing a 1 to bit 0 scontrol - */ - sctrl = nv_get32(bar5_hdl, nvp->nvp_sctrl); - - nv_put32(bar5_hdl, nvp->nvp_sctrl, sctrl | SCONTROL_DET_COMRESET); - - /* - * wait 1ms + * Issue hardware reset; retry if necessary. */ - drv_usecwait(1000); - - /* - * de-assert reset in PHY - */ - nv_put32(bar5_hdl, nvp->nvp_sctrl, sctrl); - - /* - * make sure timer is running - */ - if (nvp->nvp_timeout_id == 0) { - nvp->nvp_timeout_id = timeout(nv_timeout, (void *)nvp, - drv_usectohz(NV_ONE_SEC)); + for (i = 0; i < NV_RESET_ATTEMPTS; i++) { + /* + * Clear signature registers + */ + nv_put8(cmdhdl, nvp->nvp_sect, 0); + nv_put8(cmdhdl, nvp->nvp_lcyl, 0); + nv_put8(cmdhdl, nvp->nvp_hcyl, 0); + nv_put8(cmdhdl, nvp->nvp_count, 0); + + /* Clear task file error register */ + nv_put8(nvp->nvp_cmd_hdl, nvp->nvp_error, 0); + + /* + * assert reset in PHY by writing a 1 to bit 0 scontrol + */ + sctrl = nv_get32(bar5_hdl, nvp->nvp_sctrl); + nv_put32(bar5_hdl, nvp->nvp_sctrl, + sctrl | SCONTROL_DET_COMRESET); + + /* Wait at least 1ms, as required by the spec */ + drv_usecwait(nv_reset_length); + + /* Reset all accumulated error bits */ + nv_put32(bar5_hdl, nvp->nvp_serror, 0xffffffff); + + sstatus = nv_get32(bar5_hdl, nvp->nvp_sstatus); + sctrl = nv_get32(bar5_hdl, nvp->nvp_sctrl); + NVLOG((NVDBG_RESET, nvc, nvp, "nv_reset: applied (%d); " + "sctrl 0x%x, sstatus 0x%x", i, sctrl, sstatus)); + + /* de-assert reset in PHY */ + nv_put32(bar5_hdl, nvp->nvp_sctrl, + sctrl & ~SCONTROL_DET_COMRESET); + + /* + * Wait up to 10ms for COMINIT to arrive, indicating that + * the device recognized COMRESET. + */ + for (j = 0; j < 10; j++) { + drv_usecwait(NV_ONE_MSEC); + sstatus = nv_get32(bar5_hdl, nvp->nvp_sstatus); + if ((SSTATUS_GET_IPM(sstatus) == SSTATUS_IPM_ACTIVE) && + (SSTATUS_GET_DET(sstatus) == + SSTATUS_DET_DEVPRE_PHYCOM)) { + reset = 1; + break; + } + } + if (reset == 1) + break; + } + serr = nv_get32(bar5_hdl, nvp->nvp_serror); + if (reset == 0) { + NVLOG((NVDBG_RESET, nvc, nvp, "nv_reset not succeeded " + "(serr 0x%x) after %d attempts", serr, i)); + } else { + NVLOG((NVDBG_RESET, nvc, nvp, "nv_reset succeeded (serr 0x%x)" + "after %dms", serr, TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + } + nvp->nvp_reset_time = ddi_get_lbolt(); + + if (servicing_interrupt()) { + nv_setup_timeout(nvp, NV_ONE_MSEC); + } else if (!(nvp->nvp_state & NV_PORT_RESET_RETRY)) { + nv_monitor_reset(nvp); } } @@ -2484,6 +2551,12 @@ nvp->nvp_bmidtpx = (uint32_t *)(bm_addr + BMIDTPX_REG); nvp->nvp_state = 0; + + /* + * Initialize dma handles, etc. + * If it fails, the port is in inactive state. + */ + (void) nv_init_port(nvp); } /* @@ -2602,6 +2675,14 @@ */ nvp->nvp_queue_depth = 1; + /* + * Port is initialized whether the device is attached or not. + * Link processing and device identification will be started later, + * after interrupts are initialized. + */ + nvp->nvp_type = SATA_DTYPE_NONE; + nvp->nvp_signature = 0; + nvp->nvp_state |= NV_PORT_INIT; return (NV_SUCCESS); @@ -2609,6 +2690,112 @@ /* + * Establish initial link & device type + * Called only from nv_attach + * Loops up to approximately 210ms; can exit earlier. + * The time includes wait for the link up and completion of the initial + * signature gathering operation. + */ +static void +nv_init_port_link_processing(nv_ctl_t *nvc) +{ + ddi_acc_handle_t bar5_hdl; + nv_port_t *nvp; + volatile uint32_t sstatus; + int port, links_up, ready_ports, i; + + + for (port = 0; port < NV_MAX_PORTS(nvc); port++) { + nvp = &(nvc->nvc_port[port]); + if (nvp != NULL && (nvp->nvp_state & NV_PORT_INIT)) { + /* + * Initiate device identification, if any is attached + * and reset was not already applied by hot-plug + * event processing. + */ + mutex_enter(&nvp->nvp_mutex); + if (!(nvp->nvp_state & NV_PORT_RESET)) { + nvp->nvp_state |= NV_PORT_RESET | NV_PORT_PROBE; + nv_reset(nvp); + } + mutex_exit(&nvp->nvp_mutex); + } + } + /* + * Wait up to 10ms for links up. + * Spec says that link should be up in 1ms. + */ + for (i = 0; i < 10; i++) { + drv_usecwait(NV_ONE_MSEC); + links_up = 0; + for (port = 0; port < NV_MAX_PORTS(nvc); port++) { + nvp = &(nvc->nvc_port[port]); + mutex_enter(&nvp->nvp_mutex); + bar5_hdl = nvp->nvp_ctlp->nvc_bar_hdl[5]; + sstatus = nv_get32(bar5_hdl, nvp->nvp_sstatus); + if ((SSTATUS_GET_IPM(sstatus) == SSTATUS_IPM_ACTIVE) && + (SSTATUS_GET_DET(sstatus) == + SSTATUS_DET_DEVPRE_PHYCOM)) { + if ((nvp->nvp_state & NV_PORT_RESET) && + nvp->nvp_type == SATA_DTYPE_NONE) { + nvp->nvp_type = SATA_DTYPE_UNKNOWN; + } + NVLOG((NVDBG_INIT, nvc, nvp, + "nv_init_port_link_processing()" + "link up; time from reset %dms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + links_up++; + } + mutex_exit(&nvp->nvp_mutex); + } + if (links_up == NV_MAX_PORTS(nvc)) { + break; + } + } + NVLOG((NVDBG_RESET, nvc, nvp, "nv_init_port_link_processing():" + "%d links up", links_up)); + /* + * At this point, if any device is attached, the link is established. + * Wait till devices are ready to be accessed, no more than 200ms. + * 200ms is empirical time in which a signature should be available. + */ + for (i = 0; i < 200; i++) { + ready_ports = 0; + for (port = 0; port < NV_MAX_PORTS(nvc); port++) { + nvp = &(nvc->nvc_port[port]); + mutex_enter(&nvp->nvp_mutex); + bar5_hdl = nvp->nvp_ctlp->nvc_bar_hdl[5]; + sstatus = nv_get32(bar5_hdl, nvp->nvp_sstatus); + if ((SSTATUS_GET_IPM(sstatus) == SSTATUS_IPM_ACTIVE) && + (SSTATUS_GET_DET(sstatus) == + SSTATUS_DET_DEVPRE_PHYCOM) && + !(nvp->nvp_state & (NV_PORT_RESET | + NV_PORT_RESET_RETRY))) { + /* + * Reset already processed + */ + NVLOG((NVDBG_RESET, nvc, nvp, + "nv_init_port_link_processing()" + "device ready; port state %x; " + "time from reset %dms", nvp->nvp_state, + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + + ready_ports++; + } + mutex_exit(&nvp->nvp_mutex); + } + if (ready_ports == links_up) { + break; + } + drv_usecwait(NV_ONE_MSEC); + } + NVLOG((NVDBG_RESET, nvc, nvp, "nv_init_port_link_processing():" + "%d devices ready", ready_ports)); +} + +/* * Free dynamically allocated structures for port. */ static void @@ -2624,6 +2811,10 @@ return; } + /* + * Mark port unusable now. + */ + nvp->nvp_state &= ~NV_PORT_INIT; NVLOG((NVDBG_INIT, nvp->nvp_ctlp, nvp, "nv_uninit_port uninitializing")); @@ -2660,9 +2851,6 @@ kmem_free(nvp->nvp_sg_paddr, sizeof (uint32_t) * NV_QUEUE_SLOTS); nvp->nvp_sg_paddr = NULL; - - nvp->nvp_state &= ~NV_PORT_INIT; - nvp->nvp_signature = 0; } @@ -2880,16 +3068,7 @@ nv_copy_registers(nvp, &spkt->satapkt_device, spkt); - /* - * If there is no link cannot be certain about the completion - * of the packet, so abort it. - */ - if (nv_check_link((&spkt->satapkt_device)-> - satadev_scr.sstatus) == B_FALSE) { - - (void) nv_abort_active(nvp, NULL, SATA_PKT_PORT_ERROR); - - } else if (nv_slotp->nvslot_flags == NVSLOT_COMPLETE) { + if (nv_slotp->nvslot_flags == NVSLOT_COMPLETE) { nv_complete_io(nvp, spkt, 0); } @@ -2979,7 +3158,8 @@ nv_port_state_change(nvp, SATA_EVNT_PORT_FAILED, SATA_ADDR_CPORT, SATA_PSTATE_FAILED); nvp->nvp_state |= NV_PORT_FAILED; - (void) nv_abort_active(nvp, NULL, SATA_PKT_DEV_ERROR); + (void) nv_abort_active(nvp, NULL, SATA_PKT_DEV_ERROR, + B_TRUE); nv_cmn_err(CE_WARN, nvc, nvp, "unable to clear " "interrupt. disabling port intr_status=%X", intr_status); @@ -3003,10 +3183,14 @@ uint8_t clear = 0, intr_cycles = 0; int ret = DDI_INTR_UNCLAIMED; uint16_t int_status; - - NVLOG((NVDBG_INTR, nvc, nvp, "mcp5x_intr_port entered")); - - for (;;) { + clock_t intr_time; + int loop_cnt = 0; + + nvp->intr_start_time = ddi_get_lbolt(); + + NVLOG((NVDBG_INTR, nvc, nvp, "mcp55_intr_port entered")); + + do { /* * read current interrupt status */ @@ -3042,7 +3226,7 @@ ret = DDI_INTR_CLAIMED; if (mcp5x_packet_complete_intr(nvc, nvp) == NV_FAILURE) { - clear = MCP5X_INT_COMPLETE; + clear |= MCP5X_INT_COMPLETE; } else { intr_cycles = 0; } @@ -3063,8 +3247,8 @@ } if (int_status & MCP5X_INT_REM) { - NVLOG((NVDBG_INTR, nvc, nvp, "mcp5x device removed")); - clear = MCP5X_INT_REM; + NVLOG((NVDBG_HOT, nvc, nvp, "mcp5x device removed")); + clear |= MCP5X_INT_REM; ret = DDI_INTR_CLAIMED; mutex_enter(&nvp->nvp_mutex); @@ -3073,19 +3257,18 @@ } else if (int_status & MCP5X_INT_ADD) { NVLOG((NVDBG_HOT, nvc, nvp, "mcp5x device added")); - clear = MCP5X_INT_ADD; + clear |= MCP5X_INT_ADD; ret = DDI_INTR_CLAIMED; mutex_enter(&nvp->nvp_mutex); nv_report_add_remove(nvp, 0); mutex_exit(&nvp->nvp_mutex); } - if (clear) { nv_put16(bar5_hdl, nvp->nvp_mcp5x_int_status, clear); clear = 0; } - + /* Protect against a stuck interrupt */ if (intr_cycles++ == NV_MAX_INTR_LOOP) { nv_cmn_err(CE_WARN, nvc, nvp, "excessive interrupt " "processing. Disabling port int_status=%X" @@ -3095,12 +3278,46 @@ nv_port_state_change(nvp, SATA_EVNT_PORT_FAILED, SATA_ADDR_CPORT, SATA_PSTATE_FAILED); nvp->nvp_state |= NV_PORT_FAILED; - (void) nv_abort_active(nvp, NULL, SATA_PKT_DEV_ERROR); + (void) nv_abort_active(nvp, NULL, SATA_PKT_DEV_ERROR, + B_TRUE); mutex_exit(&nvp->nvp_mutex); } - } - - NVLOG((NVDBG_INTR, nvc, nvp, "mcp5x_intr_port: finished ret=%d", ret)); + + } while (loop_cnt++ < nv_max_intr_loops); + + if (loop_cnt > nvp->intr_loop_cnt) { + NVLOG((NVDBG_INTR, nvp->nvp_ctlp, nvp, + "Exiting with multiple intr loop count %d", loop_cnt)); + nvp->intr_loop_cnt = loop_cnt; + } + + if ((nv_debug_flags & (NVDBG_INTR | NVDBG_VERBOSE)) == + (NVDBG_INTR | NVDBG_VERBOSE)) { + uint8_t status, bmstatus; + uint16_t int_status2; + + if (int_status & MCP5X_INT_COMPLETE) { + status = nv_get8(nvp->nvp_ctl_hdl, nvp->nvp_altstatus); + bmstatus = nv_get8(nvp->nvp_bm_hdl, nvp->nvp_bmisx); + int_status2 = nv_get16(nvp->nvp_ctlp->nvc_bar_hdl[5], + nvp->nvp_mcp5x_int_status); + NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, + "mcp55_intr_port: Exiting with altstatus %x, " + "bmicx %x, int_status2 %X, int_status %X, ret %x," + " loop_cnt %d ", status, bmstatus, int_status2, + int_status, ret, loop_cnt)); + } + } + + NVLOG((NVDBG_INTR, nvc, nvp, "mcp55_intr_port: finished ret=%d", ret)); + + /* + * To facilitate debugging, keep track of the length of time spent in + * the port interrupt routine. + */ + intr_time = ddi_get_lbolt() - nvp->intr_start_time; + if (intr_time > nvp->intr_duration) + nvp->intr_duration = intr_time; return (ret); } @@ -3167,13 +3384,11 @@ */ bmicx = nv_get8(bmhdl, nvp->nvp_bmicx); -#if 0 if (bmicx & BMICX_SSBM) { NVLOG((NVDBG_INTR, nvc, nvp, "BM was already enabled for " "another packet. Cancelling and reprogramming")); nv_put8(bmhdl, nvp->nvp_bmicx, bmicx & ~BMICX_SSBM); } -#endif nv_put8(bmhdl, nvp->nvp_bmicx, bmicx & ~BMICX_SSBM); nv_start_dma_engine(nvp, slot); @@ -3203,7 +3418,7 @@ bmstatus = nv_get8(bmhdl, nvp->nvp_bmisx); - if (!(bmstatus & BMISX_IDEINTS)) { + if (!(bmstatus & (BMISX_IDEINTS | BMISX_IDERR))) { NVLOG((NVDBG_INTR, nvc, nvp, "BMISX_IDEINTS not set")); mutex_exit(&nvp->nvp_mutex); @@ -3211,10 +3426,17 @@ } /* - * If the just completed item is a non-ncq command, the busy - * bit should not be set + * Commands may have been processed by abort or timeout before + * interrupt processing acquired the mutex. So we may be processing + * an interrupt for packets that were already removed. + * For functionning NCQ processing all slots may be checked, but + * with NCQ disabled (current code), relying on *_run flags is OK. */ if (nvp->nvp_non_ncq_run) { + /* + * If the just completed item is a non-ncq command, the busy + * bit should not be set + */ status = nv_get8(nvp->nvp_ctl_hdl, nvp->nvp_altstatus); if (status & SATA_STATUS_BSY) { nv_cmn_err(CE_WARN, nvc, nvp, @@ -3228,24 +3450,27 @@ */ return (NV_FAILURE); } - + ASSERT(nvp->nvp_ncq_run == 0); } else { + ASSERT(nvp->nvp_non_ncq_run == 0); + /* + * Pre-NCQ code! + * Nothing to do. The packet for the command that just + * completed is already gone. Just clear the interrupt. + */ + (void) nv_bm_status_clear(nvp); + (void) nv_get8(nvp->nvp_cmd_hdl, nvp->nvp_status); + mutex_exit(&nvp->nvp_mutex); + return (NV_SUCCESS); + /* * NCQ check for BSY here and wait if still bsy before * continuing. Rather than wait for it to be cleared * when starting a packet and wasting CPU time, the starting * thread can exit immediate, but might have to spin here * for a bit possibly. Needs more work and experimentation. + * */ - ASSERT(nvp->nvp_ncq_run); - } - - - if (nvp->nvp_ncq_run) { - ncq_command = B_TRUE; - ASSERT(nvp->nvp_non_ncq_run == 0); - } else { - ASSERT(nvp->nvp_non_ncq_run != 0); } /* @@ -3308,15 +3533,7 @@ nv_copy_registers(nvp, &spkt->satapkt_device, spkt); - /* - * If there is no link cannot be certain about the completion - * of the packet, so abort it. - */ - if (nv_check_link((&spkt->satapkt_device)-> - satadev_scr.sstatus) == B_FALSE) { - (void) nv_abort_active(nvp, NULL, SATA_PKT_PORT_ERROR); - - } else if (nv_slotp->nvslot_flags == NVSLOT_COMPLETE) { + if (nv_slotp->nvslot_flags == NVSLOT_COMPLETE) { nv_complete_io(nvp, spkt, active_pkt); } @@ -3964,7 +4181,7 @@ } -int +static int nv_bm_status_clear(nv_port_t *nvp) { ddi_acc_handle_t bmhdl = nvp->nvp_bm_hdl; @@ -4009,11 +4226,13 @@ NVLOG((NVDBG_DELIVER, nvp->nvp_ctlp, nvp, "nv_start_dma_engine entered")); +#if NOT_USED /* - * reset the controller's interrupt and error status bits + * NOT NEEDED. Left here of historical reason. + * Reset the controller's interrupt and error status bits. */ (void) nv_bm_status_clear(nvp); - +#endif /* * program the PRD table physical start address */ @@ -4217,6 +4436,8 @@ */ nv_copy_registers(nvp, &spkt->satapkt_device, spkt); nv_complete_io(nvp, spkt, 0); + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | NV_PORT_RESET_RETRY); nv_reset(nvp); return (SATA_TRAN_PORT_ERROR); @@ -4305,6 +4526,8 @@ nv_copy_registers(nvp, &spkt->satapkt_device, spkt); nv_complete_io(nvp, spkt, 0); + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | NV_PORT_RESET_RETRY); nv_reset(nvp); return (SATA_TRAN_PORT_ERROR); @@ -4384,6 +4607,8 @@ sata_cmdp->satacmd_status_reg = nv_get8(ctlhdl, nvp->nvp_altstatus); sata_cmdp->satacmd_error_reg = nv_get8(cmdhdl, nvp->nvp_error); + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | NV_PORT_RESET_RETRY); nv_reset(nvp); return; @@ -4557,7 +4782,9 @@ } else { nv_slotp->nvslot_flags = NVSLOT_COMPLETE; spkt->satapkt_reason = SATA_PKT_TIMEOUT; - + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | + NV_PORT_RESET_RETRY); nv_reset(nvp); } @@ -4819,10 +5046,12 @@ * check for bus master errors */ if (bm_status & BMISX_IDERR) { - spkt->satapkt_reason = SATA_PKT_RESET; + spkt->satapkt_reason = SATA_PKT_RESET; /* ? */ sata_cmdp->satacmd_status_reg = nv_get8(ctlhdl, nvp->nvp_altstatus); sata_cmdp->satacmd_error_reg = nv_get8(cmdhdl, nvp->nvp_error); + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | NV_PORT_RESET_RETRY); nv_reset(nvp); return; @@ -4998,21 +5227,6 @@ /* - * nv_check_link() checks if a specified link is active device present - * and communicating. - */ -static boolean_t -nv_check_link(uint32_t sstatus) -{ - uint8_t det; - - det = (sstatus & SSTATUS_DET) >> SSTATUS_DET_SHIFT; - - return (det == SSTATUS_DET_DEVPRE_PHYCOM); -} - - -/* * nv_port_state_change() reports the state of the port to the * sata module by calling sata_hba_event_notify(). This * function is called any time the state of the port is changed @@ -5022,6 +5236,10 @@ { sata_device_t sd; + NVLOG((NVDBG_EVENT, nvp->nvp_ctlp, nvp, + "nv_port_state_change: event 0x%x type 0x%x state 0x%x " + "time %ld (ticks)", event, addr_type, state, ddi_get_lbolt())); + bzero((void *)&sd, sizeof (sata_device_t)); sd.satadev_rev = SATA_DEVICE_REV; nv_copy_registers(nvp, &sd, NULL); @@ -5038,14 +5256,281 @@ } + +/* + * Monitor reset progress and signature gathering. + * This function may loop, so it should not be called from interrupt + * context. + * + * Entered with nvp mutex held. + */ +static void +nv_monitor_reset(nv_port_t *nvp) +{ + ddi_acc_handle_t bar5_hdl = nvp->nvp_ctlp->nvc_bar_hdl[5]; + uint32_t sstatus; + int send_notification = B_FALSE; + uint8_t dev_type; + + sstatus = nv_get32(bar5_hdl, nvp->nvp_sstatus); + + /* + * We do not know here the reason for port reset. + * Check the link status. The link needs to be active before + * we can check the link's status. + */ + if ((SSTATUS_GET_IPM(sstatus) != SSTATUS_IPM_ACTIVE) || + (SSTATUS_GET_DET(sstatus) != SSTATUS_DET_DEVPRE_PHYCOM)) { + /* + * Either link is not active or there is no device + * If the link remains down for more than NV_LINK_DOWN_TIMEOUT + * (milliseconds), abort signature acquisition and complete + * reset processing. + * The link will go down when COMRESET is sent by nv_reset(), + * so it is practically nvp_reset_time milliseconds. + */ + + if (TICK_TO_MSEC(ddi_get_lbolt() - nvp->nvp_reset_time) >= + NV_LINK_DOWN_TIMEOUT) { + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_monitor_reset: no link - ending signature " + "acquisition; time after reset %ldms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + } + nvp->nvp_state &= ~(NV_PORT_RESET | NV_PORT_RESET_RETRY | + NV_PORT_PROBE | NV_PORT_HOTPLUG_DELAY); + /* + * Else, if the link was lost (i.e. was present before) + * the controller should generate a 'remove' interrupt + * that will cause the appropriate event notification. + */ + return; + } + + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_monitor_reset: link up after reset; time %ldms", + TICK_TO_MSEC(ddi_get_lbolt() - nvp->nvp_reset_time))); + +sig_read: + if (nvp->nvp_signature != 0) { + /* + * The link is up. The signature was acquired before (device + * was present). + * But we may need to wait for the signature (D2H FIS) before + * accessing the drive. + */ + if (nv_wait_for_signature != 0) { + uint32_t old_signature; + uint8_t old_type; + + old_signature = nvp->nvp_signature; + old_type = nvp->nvp_type; + nvp->nvp_signature = 0; + nv_read_signature(nvp); + if (nvp->nvp_signature == 0) { + nvp->nvp_signature = old_signature; + nvp->nvp_type = old_type; + +#ifdef NV_DEBUG + /* FOR DEBUGGING */ + if (nv_wait_here_forever) { + drv_usecwait(1000); + goto sig_read; + } +#endif + /* + * Wait, but not endlessly. + */ + if (TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time) < + nv_sig_acquisition_time) { + drv_usecwait(1000); + goto sig_read; + } else if (!(nvp->nvp_state & + NV_PORT_RESET_RETRY)) { + /* + * Retry reset. + */ + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_monitor_reset: retrying reset " + "time after first reset: %ldms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + nvp->nvp_state |= NV_PORT_RESET_RETRY; + nv_reset(nvp); + goto sig_read; + } + + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_monitor_reset: terminating signature " + "acquisition (1); time after reset: %ldms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + } else { + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_monitor_reset: signature acquired; " + "time after reset: %ldms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + } + } + /* + * Clear reset state, set device reset recovery state + */ + nvp->nvp_state &= ~(NV_PORT_RESET | NV_PORT_RESET_RETRY | + NV_PORT_PROBE); + nvp->nvp_state |= NV_PORT_RESTORE; + + /* + * Need to send reset event notification + */ + send_notification = B_TRUE; + } else { + /* + * The link is up. The signature was not acquired before. + * We can try to fetch a device signature. + */ + dev_type = nvp->nvp_type; + +acquire_signature: + nv_read_signature(nvp); + if (nvp->nvp_signature != 0) { + /* + * Got device signature. + */ + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_monitor_reset: signature acquired; " + "time after reset: %ldms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + + /* Clear internal reset state */ + nvp->nvp_state &= + ~(NV_PORT_RESET | NV_PORT_RESET_RETRY); + + if (dev_type != SATA_DTYPE_NONE) { + /* + * We acquired the signature for a + * pre-existing device that was not identified + * before and and was reset. + * Need to enter the device reset recovery + * state and to send the reset notification. + */ + nvp->nvp_state |= NV_PORT_RESTORE; + send_notification = B_TRUE; + } else { + /* + * Else, We acquired the signature because a new + * device was attached (the driver attach or + * a hot-plugged device). There is no need to + * enter the device reset recovery state or to + * send the reset notification, but we may need + * to send a device attached notification. + */ + if (nvp->nvp_state & NV_PORT_PROBE) { + nv_port_state_change(nvp, + SATA_EVNT_DEVICE_ATTACHED, + SATA_ADDR_CPORT, 0); + nvp->nvp_state &= ~NV_PORT_PROBE; + } + } + } else { + if (TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time) < nv_sig_acquisition_time) { + drv_usecwait(1000); + goto acquire_signature; + } else if (!(nvp->nvp_state & NV_PORT_RESET_RETRY)) { + /* + * Some drives may require additional + * reset(s) to get a valid signature + * (indicating that the drive is ready). + * If a drive was not just powered + * up, the signature should be available + * within few hundred milliseconds + * after reset. Therefore, if more than + * NV_SIG_ACQUISITION_TIME has elapsed + * while waiting for a signature, reset + * device again. + */ + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_monitor_reset: retrying reset " + "time after first reset: %ldms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + nvp->nvp_state |= NV_PORT_RESET_RETRY; + nv_reset(nvp); + drv_usecwait(1000); + goto acquire_signature; + } + /* + * Terminating signature acquisition. + * Hopefully, the drive is ready. + * The SATA module can deal with this as long as it + * knows that some device is attached and a device + * responds to commands. + */ + if (!(nvp->nvp_state & NV_PORT_PROBE)) { + send_notification = B_TRUE; + } + nvp->nvp_state &= ~(NV_PORT_RESET | + NV_PORT_RESET_RETRY); + nvp->nvp_type = SATA_DTYPE_UNKNOWN; + if (nvp->nvp_state & NV_PORT_PROBE) { + nv_port_state_change(nvp, + SATA_EVNT_DEVICE_ATTACHED, + SATA_ADDR_CPORT, 0); + nvp->nvp_state &= ~NV_PORT_PROBE; + } + nvp->nvp_type = dev_type; + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_monitor_reset: terminating signature " + "acquisition (2); time after reset: %ldms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + } + } + if (send_notification) { + nv_port_state_change(nvp, SATA_EVNT_DEVICE_RESET, + SATA_ADDR_DCPORT, + SATA_DSTATE_RESET | SATA_DSTATE_PWR_ACTIVE); + } +} + + +/* + * Send a hotplug (add device) notification at the appropriate time after + * hotplug detection. + * Relies on nvp_reset_time set at a hotplug detection time. + * Called only from nv_timeout when NV_PORT_HOTPLUG_DELAY flag is set in + * the nvp_state. + */ +static void +nv_delay_hotplug_notification(nv_port_t *nvp) +{ + + if (TICK_TO_MSEC(ddi_get_lbolt() - nvp->nvp_reset_time) >= + nv_hotplug_delay) { + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, nvp, + "nv_delay_hotplug_notification: notifying framework after " + "%dms delay", TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + nvp->nvp_state &= ~NV_PORT_HOTPLUG_DELAY; + nv_port_state_change(nvp, SATA_EVNT_DEVICE_ATTACHED, + SATA_ADDR_CPORT, 0); + } +} + /* * timeout processing: * - * Check if any packets have crossed a timeout threshold. If so, then - * abort the packet. This function is not NCQ aware. + * Check if any packets have crossed a timeout threshold. If so, + * abort the packet. This function is not NCQ-aware. * - * If reset was invoked in any other place than nv_sata_probe(), then - * monitor for reset completion here. + * If reset was invoked, call reset monitoring function. + * + * Timeout frequency may be lower for checking packet timeout (1s) + * and higher for reset monitoring (1ms) * */ static void @@ -5053,123 +5538,51 @@ { nv_port_t *nvp = arg; nv_slot_t *nv_slotp; - int restart_timeout = B_FALSE; + int next_timeout = NV_ONE_SEC; /* Default */ + uint16_t int_status; + uint8_t status, bmstatus; + static int intr_warn_once = 0; + + ASSERT(nvp != NULL); mutex_enter(&nvp->nvp_mutex); + nvp->nvp_timeout_id = 0; /* - * If the probe entry point is driving the reset and signature - * acquisition, just return. - */ - if (nvp->nvp_state & NV_PORT_RESET_PROBE) { - goto finished; - } - - /* - * If the port is not in the init state, it likely - * means the link was lost while a timeout was active. + * If the port is not in the init state, ignore it. */ if ((nvp->nvp_state & NV_PORT_INIT) == 0) { NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, "nv_timeout: port uninitialized")); + next_timeout = 0; goto finished; } - if (nvp->nvp_state & NV_PORT_RESET) { - ddi_acc_handle_t bar5_hdl = nvp->nvp_ctlp->nvc_bar_hdl[5]; - uint32_t sstatus; - - NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, - "nv_timeout(): port waiting for signature")); - - sstatus = nv_get32(bar5_hdl, nvp->nvp_sstatus); - - /* - * check for link presence. If the link remains - * missing for more than 2 seconds, send a remove - * event and abort signature acquisition. - */ - if (nv_check_link(sstatus) == B_FALSE) { - clock_t e_link_lost = ddi_get_lbolt(); - - if (nvp->nvp_link_lost_time == 0) { - nvp->nvp_link_lost_time = e_link_lost; - } - if (TICK_TO_SEC(e_link_lost - - nvp->nvp_link_lost_time) < NV_LINK_LOST_OK) { - NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, - "probe: intermittent link lost while" - " resetting")); - restart_timeout = B_TRUE; - } else { - NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, - "link lost during signature acquisition." - " Giving up")); - nv_port_state_change(nvp, - SATA_EVNT_DEVICE_DETACHED| - SATA_EVNT_LINK_LOST, - SATA_ADDR_CPORT, 0); - nvp->nvp_state |= NV_PORT_HOTREMOVED; - nvp->nvp_state &= ~NV_PORT_RESET; - } - - goto finished; - } else { - - nvp->nvp_link_lost_time = 0; - } - - nv_read_signature(nvp); - - if (nvp->nvp_signature != 0) { - if ((nvp->nvp_type == SATA_DTYPE_ATADISK) || - (nvp->nvp_type == SATA_DTYPE_ATAPICD)) { - nvp->nvp_state |= NV_PORT_RESTORE; - nv_port_state_change(nvp, - SATA_EVNT_DEVICE_RESET, - SATA_ADDR_DCPORT, - SATA_DSTATE_RESET|SATA_DSTATE_PWR_ACTIVE); - } - - goto finished; - } - - /* - * Reset if more than 5 seconds has passed without - * acquiring a signature. - */ - if (TICK_TO_SEC(ddi_get_lbolt() - nvp->nvp_reset_time) > 5) { - nv_reset(nvp); - } - - restart_timeout = B_TRUE; + if (nvp->nvp_state & (NV_PORT_RESET | NV_PORT_RESET_RETRY)) { + nv_monitor_reset(nvp); + next_timeout = NV_ONE_MSEC; /* at least 1ms */ + goto finished; } - - /* - * not yet NCQ aware - */ - nv_slotp = &(nvp->nvp_slot[0]); - - /* - * this happens early on before nv_slotp is set - * up OR when a device was unexpectedly removed and - * there was an active packet. - */ - if (nv_slotp == NULL) { - NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, - "nv_timeout: nv_slotp == NULL")); + if ((nvp->nvp_state & NV_PORT_HOTPLUG_DELAY) != 0) { + nv_delay_hotplug_notification(nvp); + next_timeout = NV_ONE_MSEC; /* at least 1ms */ goto finished; } /* + * Not yet NCQ-aware - there is only one command active. + */ + nv_slotp = &(nvp->nvp_slot[0]); + + /* * perform timeout checking and processing only if there is an * active packet on the port */ - if (nv_slotp->nvslot_spkt != NULL) { + if (nv_slotp != NULL && nv_slotp->nvslot_spkt != NULL) { sata_pkt_t *spkt = nv_slotp->nvslot_spkt; sata_cmd_t *satacmd = &spkt->satapkt_cmd; uint8_t cmd = satacmd->satacmd_cmd_reg; @@ -5189,42 +5602,104 @@ * timeout not needed if there is a polling thread */ if (spkt->satapkt_op_mode & SATA_OPMODE_POLLING) { + next_timeout = 0; goto finished; } if (TICK_TO_SEC(ddi_get_lbolt() - nv_slotp->nvslot_stime) > spkt->satapkt_time) { + + uint32_t serr = nv_get32(nvp->nvp_ctlp->nvc_bar_hdl[5], + nvp->nvp_serror); + NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, - "abort timeout: " + "nv_timeout: aborting: " "nvslot_stime: %ld max ticks till timeout: " "%ld cur_time: %ld cmd=%x lba=%d", - nv_slotp->nvslot_stime, drv_usectohz(MICROSEC * - spkt->satapkt_time), ddi_get_lbolt(), cmd, lba)); - - (void) nv_abort_active(nvp, spkt, SATA_PKT_TIMEOUT); + nv_slotp->nvslot_stime, + drv_usectohz(MICROSEC * + spkt->satapkt_time), ddi_get_lbolt(), + cmd, lba)); + + NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, + "nv_timeout: SError at timeout: 0x%x", serr)); + + NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, + "nv_timeout: previous cmd=%x", + nvp->nvp_previous_cmd)); + + if (nvp->nvp_mcp5x_int_status != NULL) { + status = nv_get8(nvp->nvp_ctl_hdl, + nvp->nvp_altstatus); + bmstatus = nv_get8(nvp->nvp_bm_hdl, + nvp->nvp_bmisx); + int_status = nv_get16( + nvp->nvp_ctlp->nvc_bar_hdl[5], + nvp->nvp_mcp5x_int_status); + NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, + "nv_timeout: altstatus %x, bmicx %x, " + "int_status %X", status, bmstatus, + int_status)); + + if (int_status & MCP5X_INT_COMPLETE) { + /* + * Completion interrupt was missed! + * Issue warning message once + */ + if (!intr_warn_once) { + cmn_err(CE_WARN, + "nv_sata: missing command " + "completion interrupt(s)!"); + intr_warn_once = 1; + } + NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, + nvp, "timeout detected with " + "interrupt ready - calling " + "int directly")); + mutex_exit(&nvp->nvp_mutex); + mcp5x_intr_port(nvp); + mutex_enter(&nvp->nvp_mutex); + } else { + /* + * True timeout and not a missing + * interrupt. + */ + (void) nv_abort_active(nvp, spkt, + SATA_PKT_TIMEOUT, B_TRUE); + } + } else { + (void) nv_abort_active(nvp, spkt, + SATA_PKT_TIMEOUT, B_TRUE); + } } else { - NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, "nv_timeout:" - " still in use so restarting timeout")); +#ifdef NV_DEBUG + if (nv_debug_flags & NVDBG_VERBOSE) { + NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, + "nv_timeout:" + " still in use so restarting timeout")); + } +#endif + next_timeout = NV_ONE_SEC; } - restart_timeout = B_TRUE; - } else { /* * there was no active packet, so do not re-enable timeout */ - NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, - "nv_timeout: no active packet so not re-arming timeout")); - } - - finished: - - if (restart_timeout == B_TRUE) { - nvp->nvp_timeout_id = timeout(nv_timeout, (void *)nvp, - drv_usectohz(NV_ONE_SEC)); - } else { - nvp->nvp_timeout_id = 0; + next_timeout = 0; +#ifdef NV_DEBUG + if (nv_debug_flags & NVDBG_VERBOSE) { + NVLOG((NVDBG_TIMEOUT, nvp->nvp_ctlp, nvp, + "nv_timeout: no active packet so not re-arming " + "timeout")); + } +#endif + } + +finished: + if (next_timeout != 0) { + nv_setup_timeout(nvp, next_timeout); } mutex_exit(&nvp->nvp_mutex); } @@ -5360,25 +5835,28 @@ if (nvp->nvp_state & NV_PORT_INACTIVE) { mutex_exit(&nvp->nvp_mutex); + return; } + /* Enable interrupt */ + (*(nvp->nvp_ctlp->nvc_set_intr))(nvp, NV_INTR_CLEAR_ALL|NV_INTR_ENABLE); + + /* + * Power may have been removed to the port and the + * drive, and/or a drive may have been added or removed. + * Force a reset which will cause a probe and re-establish + * any state needed on the drive. + */ + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | NV_PORT_RESET_RETRY); + nv_reset(nvp); + #ifdef SGPIO_SUPPORT nv_sgp_drive_connect(nvp->nvp_ctlp, SGP_CTLR_PORT_TO_DRV( nvp->nvp_ctlp->nvc_ctlr_num, nvp->nvp_port_num)); #endif - /* Enable interrupt */ - (*(nvp->nvp_ctlp->nvc_set_intr))(nvp, NV_INTR_CLEAR_ALL|NV_INTR_ENABLE); - - /* - * power may have been removed to the port and the - * drive, and/or a drive may have been added or removed. - * Force a reset which will cause a probe and re-establish - * any state needed on the drive. - */ - nv_reset(nvp); - mutex_exit(&nvp->nvp_mutex); } @@ -5397,6 +5875,7 @@ if (nvp->nvp_state & NV_PORT_INACTIVE) { mutex_exit(&nvp->nvp_mutex); + return; } @@ -5427,8 +5906,6 @@ uchar_t status; struct sata_cmd_flags flags; - NVLOG((NVDBG_INIT, nvp->nvp_ctlp, nvp, "nv_copy_registers()")); - sd->satadev_scr.sstatus = nv_get32(bar5_hdl, nvp->nvp_sstatus); sd->satadev_scr.serror = nv_get32(bar5_hdl, nvp->nvp_serror); sd->satadev_scr.scontrol = nv_get32(bar5_hdl, nvp->nvp_sctrl); @@ -5538,27 +6015,20 @@ * end up checking the state of the other port and discover the hot * interrupt flag is set even though it was masked. Checking for recent * reset activity and then ignoring turns out to be the easiest way. + * + * Entered with nvp mutex held. */ static void nv_report_add_remove(nv_port_t *nvp, int flags) { ddi_acc_handle_t bar5_hdl = nvp->nvp_ctlp->nvc_bar_hdl[5]; - clock_t time_diff = ddi_get_lbolt() - nvp->nvp_reset_time; uint32_t sstatus; int i; - - /* - * If reset within last 1 second ignore. This should be - * reworked and improved instead of having this somewhat - * heavy handed clamping job. - */ - if (time_diff < drv_usectohz(NV_ONE_SEC)) { - NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, "nv_report_add_remove()" - "ignoring plug interrupt was %dms ago", - TICK_TO_MSEC(time_diff))); - - return; - } + clock_t nv_lbolt = ddi_get_lbolt(); + + + NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, "nv_report_add_remove() - " + "time (ticks) %d", nv_lbolt)); /* * wait up to 1ms for sstatus to settle and reflect the true @@ -5584,22 +6054,163 @@ } NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, - "sstatus took %i us for DEVPRE_PHYCOM to settle", i)); + "sstatus took %d us for DEVPRE_PHYCOM to settle", i)); if (flags == NV_PORT_HOTREMOVED) { + + (void) nv_abort_active(nvp, NULL, SATA_PKT_PORT_ERROR, + B_FALSE); + + /* + * No device, no point of bothering with device reset + */ + nvp->nvp_type = SATA_DTYPE_NONE; + nvp->nvp_signature = 0; + nvp->nvp_state &= ~(NV_PORT_RESET | NV_PORT_RESET_RETRY | + NV_PORT_RESTORE); NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, "nv_report_add_remove() hot removed")); nv_port_state_change(nvp, SATA_EVNT_DEVICE_DETACHED, SATA_ADDR_CPORT, 0); - nvp->nvp_state |= NV_PORT_HOTREMOVED; } else { - NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, - "nv_report_add_remove() hot plugged")); - nv_port_state_change(nvp, SATA_EVNT_DEVICE_ATTACHED, - SATA_ADDR_CPORT, 0); - } + /* + * This is a hot plug or link up indication + * Now, re-check the link state - no link, no device + */ + if ((SSTATUS_GET_IPM(sstatus) == SSTATUS_IPM_ACTIVE) && + (SSTATUS_GET_DET(sstatus) == SSTATUS_DET_DEVPRE_PHYCOM)) { + + if (nvp->nvp_type == SATA_DTYPE_NONE) { + /* + * Real device attach - there was no device + * attached to this port before this report + */ + NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, + "nv_report_add_remove() new device hot" + "plugged")); + nvp->nvp_reset_time = ddi_get_lbolt(); + if (!(nvp->nvp_state & + (NV_PORT_RESET_RETRY | NV_PORT_RESET))) { + + nvp->nvp_signature = 0; + if (nv_reset_after_hotplug != 0) { + + /* + * Send reset to obtain a device + * signature + */ + nvp->nvp_state |= + NV_PORT_RESET | + NV_PORT_PROBE; + nv_reset(nvp); + NVLOG((NVDBG_HOT, + nvp->nvp_ctlp, nvp, + "nv_report_add_remove() " + "resetting device")); + } else { + nvp->nvp_type = + SATA_DTYPE_UNKNOWN; + } + } + + if (!(nvp->nvp_state & NV_PORT_PROBE)) { + if (nv_reset_after_hotplug == 0) { + /* + * In case a hotplug interrupt + * is generated right after a + * link is up, delay reporting + * a hotplug event to let the + * drive to initialize and to + * send a D2H FIS with a + * signature. + * The timeout will issue an + * event notification after + * the NV_HOTPLUG_DELAY + * milliseconds delay. + */ + nvp->nvp_state |= + NV_PORT_HOTPLUG_DELAY; + nvp->nvp_type = + SATA_DTYPE_UNKNOWN; + /* + * Make sure timer is running. + */ + nv_setup_timeout(nvp, + NV_ONE_MSEC); + } else { + nv_port_state_change(nvp, + SATA_EVNT_DEVICE_ATTACHED, + SATA_ADDR_CPORT, 0); + } + } + return; + } + /* + * Othervise it is a bogus attach, indicating recovered + * link loss. No real need to report it after-the-fact. + * But we may keep some statistics, or notify the + * sata module by reporting LINK_LOST/LINK_ESTABLISHED + * events to keep track of such occurrences. + * Anyhow, we may want to terminate signature + * acquisition. + */ + NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, + "nv_report_add_remove() ignoring plug interrupt " + "- recovered link?")); + + if (nvp->nvp_state & + (NV_PORT_RESET_RETRY | NV_PORT_RESET)) { + NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, + "nv_report_add_remove() - " + "time since last reset %dms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + /* + * If the driver does not have to wait for + * a signature, then terminate reset processing + * now. + */ + if (nv_wait_for_signature == 0) { + NVLOG((NVDBG_RESET, nvp->nvp_ctlp, + nvp, "nv_report_add_remove() - ", + "terminating signature acquisition", + ", time after reset: %dms", + TICK_TO_MSEC(ddi_get_lbolt() - + nvp->nvp_reset_time))); + + nvp->nvp_state &= ~(NV_PORT_RESET | + NV_PORT_RESET_RETRY); + + if (!(nvp->nvp_state & NV_PORT_PROBE)) { + nvp->nvp_state |= + NV_PORT_RESTORE; + nvp->nvp_state &= + ~NV_PORT_PROBE; + + /* + * It is not the initial device + * probing, so notify sata + * module that device was + * reset + */ + nv_port_state_change(nvp, + SATA_EVNT_DEVICE_RESET, + SATA_ADDR_DCPORT, + SATA_DSTATE_RESET | + SATA_DSTATE_PWR_ACTIVE); + } + + } + } + return; + } + NVLOG((NVDBG_HOT, nvp->nvp_ctlp, nvp, "nv_report_add_remove()" + "ignoring add dev interrupt - " + "link is down or no device!")); + } + } /* @@ -5679,6 +6290,8 @@ nv_copy_registers(nvp, &spkt->satapkt_device, spkt); nv_complete_io(nvp, spkt, 0); + nvp->nvp_state |= NV_PORT_RESET; + nvp->nvp_state &= ~(NV_PORT_RESTORE | NV_PORT_RESET_RETRY); nv_reset(nvp); return (NV_FAILURE); @@ -5741,7 +6354,6 @@ nvp->nvp_type = 0; nvp->nvp_state |= NV_PORT_RESET; nvp->nvp_reset_time = ddi_get_lbolt(); - nvp->nvp_link_lost_time = 0; /* * assert reset in PHY by writing a 1 to bit 0 scontrol
--- a/usr/src/uts/common/io/sata/impl/sata.c Thu Aug 27 12:59:34 2009 -0400 +++ b/usr/src/uts/common/io/sata/impl/sata.c Thu Aug 27 11:15:07 2009 -0700 @@ -131,7 +131,7 @@ #define LEGACY_HWID_LEN 64 /* Model (40) + Serial (20) + pad */ -static char sata_rev_tag[] = {"1.44"}; +static char sata_rev_tag[] = {"1.45"}; /* * SATA cb_ops functions @@ -2069,8 +2069,17 @@ sata_hba_inst = (sata_hba_inst_t *)(hba_tran->tran_hba_private); /* Validate scsi device address */ + /* + * Note: tgt_free relates to the SCSA view of a device. If called, there + * was a device at this address, so even if the sata framework internal + * resources were alredy released because a device was detached, + * this function should be executed as long as its actions do + * not require the internal sata view of a device and the address + * refers to a valid sata address. + * Validating the address here means that we do not trust SCSA... + */ if (sata_validate_scsi_address(sata_hba_inst, &sd->sd_address, - &sata_device) != 0) + &sata_device) == -1) return; mutex_enter(&(SATA_CPORT_MUTEX(sata_hba_inst, @@ -3053,16 +3062,30 @@ case -1: /* Invalid address or invalid device type */ - SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst, - "sata_scsi_start: reject command because " - "dev type or address is invalid\n", NULL); return (TRAN_BADPKT); + case 2: + /* + * Valid address but device type is unknown - Chack if it is + * in the reset state and therefore in an indeterminate state. + */ + sdinfo = sata_get_device_info(spx->txlt_sata_hba_inst, + &spx->txlt_sata_pkt->satapkt_device); + if (sdinfo != NULL && (sdinfo->satadrv_event_flags & + (SATA_EVNT_DEVICE_RESET | + SATA_EVNT_INPROC_DEVICE_RESET)) != 0) { + if (!ddi_in_panic()) { + spx->txlt_scsi_pkt->pkt_reason = CMD_INCOMPLETE; + *reason = CMD_INCOMPLETE; + SATADBG1(SATA_DBG_SCSI_IF, + spx->txlt_sata_hba_inst, + "sata_scsi_start: rejecting command " + "because of device reset state\n", NULL); + return (TRAN_BUSY); + } + } + /* FALLTHROUGH */ case 1: - /* valid address but no device - it has disappeared ? */ - SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst, - "sata_scsi_start: reject command because " - "device is gone\n", NULL); - + /* valid address but no valid device - it has disappeared */ spx->txlt_scsi_pkt->pkt_reason = CMD_DEV_GONE; *reason = CMD_DEV_GONE; /* @@ -3743,8 +3766,8 @@ /* * SATA translate command: Test Unit Ready - * At the moment this is an emulated command (ATA version for SATA hard disks). - * May be translated into Check Power Mode command in the future + * (ATA version for SATA hard disks). + * It is translated into the Check Power Mode command. * * Returns TRAN_ACCEPT and appropriate values in scsi_pkt fields. */ @@ -4145,8 +4168,8 @@ } /* - * since it was synchronous commands, - * a callback function will be called directely. + * Since it was a synchronous command, + * a callback function will be called directly. */ mutex_exit(&SATA_CPORT_MUTEX(shi, cport)); SATADBG1(SATA_DBG_SCSI_IF, spx->txlt_sata_hba_inst, @@ -7891,7 +7914,7 @@ /* * sata_build_lsense_page_0e() is used to create the - * SCSI LOG SENSE page 0e (supported log pages) + * SCSI LOG SENSE page 0e (start-stop cycle counter page) * * Date of Manufacture (0x0001) * YEAR = "0000" @@ -10883,7 +10906,8 @@ * SCSI target address is translated into SATA cport/pmport and compared * with a controller port/device configuration. LUN has to be 0. * Returns 0 if a scsi target refers to an attached device, - * returns 1 if address is valid but device is not attached, + * returns 1 if address is valid but no valid device is attached, + * returns 2 if address is valid but device type is unknown (not valid device), * returns -1 if bad address or device is of an unsupported type. * Upon return sata_device argument is set. * @@ -10913,6 +10937,11 @@ sata_pmult_info_t *pmultinfo; sata_drive_info_t *sdinfo = NULL; + sata_device->satadev_addr.qual = qual; + sata_device->satadev_addr.cport = cport; + sata_device->satadev_addr.pmport = pmport; + sata_device->satadev_rev = SATA_DEVICE_REV_1; + rval = 1; /* Valid sata address */ cportinfo = SATA_CPORT_INFO(sata_hba_inst, cport); @@ -10921,12 +10950,18 @@ cportinfo->cport_dev_type == SATA_DTYPE_NONE) goto out; + sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo); + if (cportinfo->cport_dev_type == SATA_DTYPE_UNKNOWN && + sdinfo != NULL) { + rval = 2; + goto out; + } + if ((cportinfo->cport_dev_type & SATA_VALID_DEV_TYPE) == 0) { rval = -1; goto out; } - sdinfo = SATA_CPORTINFO_DRV_INFO(cportinfo); } else if (qual == SATA_ADDR_DPMPORT) { pmultinfo = SATA_CPORTINFO_PMULT_INFO(cportinfo); @@ -10942,6 +10977,18 @@ sdinfo = SATA_PMPORT_DRV_INFO(sata_hba_inst, cport, pmport); + if (SATA_PMPORT_DEV_TYPE(sata_hba_inst, cport, + pmport) == SATA_DTYPE_UNKNOWN && sdinfo != NULL) { + rval = 2; + goto out; + } + + if ((SATA_PMPORT_DEV_TYPE(sata_hba_inst, cport, + pmport) && SATA_VALID_DEV_TYPE) == 0) { + rval = -1; + goto out; + } + } else { rval = -1; goto out; @@ -10951,14 +10998,11 @@ goto out; sata_device->satadev_type = sdinfo->satadrv_type; - sata_device->satadev_addr.qual = qual; - sata_device->satadev_addr.cport = cport; - sata_device->satadev_addr.pmport = pmport; - sata_device->satadev_rev = SATA_DEVICE_REV_1; + return (0); } out: - if (rval == 1) { + if (rval > 0) { SATADBG2(SATA_DBG_SCSI_IF, sata_hba_inst, "sata_validate_scsi_address: no valid target %x lun %x", ap->a_target, ap->a_lun); @@ -11159,6 +11203,7 @@ return (rval); } + /* * Get pointer to sata_drive_info structure. * @@ -12259,6 +12304,7 @@ sata_common_free_dma_rsrcs(spx); } + /* * Fetch Device Identify data. * Send DEVICE IDENTIFY or IDENTIFY PACKET DEVICE (depending on a device type) @@ -12993,7 +13039,7 @@ sata_cport_info_t *cportinfo = NULL; sata_pmport_info_t *pmportinfo = NULL; sata_pmult_info_t *pmultinfo = NULL; - sata_device_t subsdevice; + sata_device_t subsdevice; int cport, pmport, qual; int rval = SATA_SUCCESS; int npmport = 0;
--- a/usr/src/uts/common/sys/sata/adapters/nv_sata/nv_sata.h Thu Aug 27 12:59:34 2009 -0400 +++ b/usr/src/uts/common/sys/sata/adapters/nv_sata/nv_sata.h Thu Aug 27 11:15:07 2009 -0700 @@ -172,8 +172,6 @@ timeout_id_t nvp_timeout_id; clock_t nvp_reset_time; /* time of last reset */ - clock_t nvp_probe_time; /* time when probe began */ - clock_t nvp_link_lost_time; /* time link lost was noticed */ int nvp_state; /* state of port. flags defined below */ @@ -183,6 +181,14 @@ #ifdef SGPIO_SUPPORT uint8_t nvp_sgp_ioctl_mod; /* LEDs modified by ioctl */ #endif + int nvp_timeout_duration; + + uint8_t nvp_last_cmd; + uint8_t nvp_previous_cmd; + int nvp_reset_count; + clock_t intr_duration; /* max length of port intr (ticks) */ + clock_t intr_start_time; + int intr_loop_cnt; }; @@ -272,7 +278,7 @@ #define NVDBG_ERRS 0x0800 #define NVDBG_COOKIES 0x1000 #define NVDBG_HOT 0x2000 -#define NVDBG_PROBE 0x4000 +#define NVDBG_RESET 0x4000 #define NVDBG_ATAPI 0x8000 #ifdef DEBUG @@ -318,7 +324,9 @@ #define ATDC_D3 0x08 /* mysterious bit */ #define ATDC_HOB 0x80 /* high order byte to read 48-bit values */ - +/* + * MCP5x NCQ and INTR control registers + */ #define MCP5X_CTL 0x400 /* queuing control */ #define MCP5X_INT_STATUS 0x440 /* status bits for interrupt */ #define MCP5X_INT_CTL 0x444 /* enable bits for interrupt */ @@ -576,34 +584,51 @@ #define NV_QUEUE_SLOTS 1 #endif -/* - * wait 30 seconds for signature - */ -#define NV_SIG_TIMEOUT 45 - #define NV_BM_64K_BOUNDARY 0x10000ull +#define NV_MAX_INTR_PER_DEV 20 /* Empirical value */ + /* - * every 1 second + * 1 second (in microseconds) */ -#define NV_ONE_SEC 1000000 +#define NV_ONE_SEC 1000000 + +/* + * 1 millisecond (in microseconds) + */ +#define NV_ONE_MSEC 1000 +/* + * Length of port reset (microseconds) - SControl bit 0 set to 1 + */ +#define NV_RESET_LENGTH 1000 + +#define NV_RESET_ATTEMPTS 3 /* - * the amount of time link can be down during - * reset without taking action. + * The maximum amount of time (milliseconds) a link can be down during + * reset without assuming that there is no device attached. */ -#define NV_LINK_LOST_OK 2 +#define NV_LINK_DOWN_TIMEOUT 10 /* - * nv_reset() flags + * The maximum amount of time (milliseconds) the signature acquisition can + * drag on before it is terminated. + * Some disks have very long acquisition times after hotplug, related to + * to spinning-up and reading some data from a media. + * The value below is empirical (20s) + * */ -#define NV_RESET_SEND_EVENT 0x1 /* send reset event to sata module */ -#define NV_RESET_WAIT 0x2 /* OK to block waiting for reset */ +#define NV_SIG_ACQUISITION_TIME 20000 - - -#define NV_RESET_ATTEMPTS 3 +/* + * Minimum amount of time (milliseconds) to delay reporting hotplug + * (device added) event. + * It is the time allowed for a drive to initialize and to send a D2H FIS with + * a device signature. + * It varies between drives from a few milliseconds up to 20s. + */ +#define NV_HOTPLUG_DELAY 20000 /* * nvp_state flags @@ -614,8 +639,10 @@ #define NV_PORT_INIT 0x008 #define NV_PORT_FAILED 0x010 #define NV_PORT_RESET 0x020 -#define NV_PORT_RESET_PROBE 0x040 +#define NV_PORT_RESET_RETRY 0x040 #define NV_PORT_RESTORE 0x080 +#define NV_PORT_PROBE 0x100 +#define NV_PORT_HOTPLUG_DELAY 0x200 /* * nvc_state flags