From: Matthew Dillon Date: Fri, 12 Jun 2009 06:10:06 +0000 (-0700) Subject: AHCI - Add Port Multiplier HOTPLUG support X-Git-Tag: v2.3.2~189 X-Git-Url: https://gitweb.dragonflybsd.org/dragonfly.git/commitdiff_plain/3209f581df87eb2723810cc4f0dcb8be697198e9 AHCI - Add Port Multiplier HOTPLUG support * Add a ton of infrastructure to the port multiplier module. We now probe the PM capabilities and turn on asynchronous notification if it supports it. Upon reception of an async notification event we access the PM to determine which targets may have changed state, then rescan those targets. * Add code to handle transient IFS failures when issuing a PM softreset command. * Significantly rework the CAM infrastructure such that most of the hard work now occurs from a main context instead of from an interrupt context. * Replace all DELAY()'s with ahci_os_sleep(), and use tsleep(). * Use milliseconds as a universal time-base. * Do not initiate a timeout until a CCB is actually sent to the chip. * Improve the device probe and state machine (it still needs work though). --- diff --git a/sys/dev/disk/ahci/TODO b/sys/dev/disk/ahci/TODO index 55261f95ef..c799a86a33 100644 --- a/sys/dev/disk/ahci/TODO +++ b/sys/dev/disk/ahci/TODO @@ -9,6 +9,15 @@ Port multiplier support (basics are now in) Simulate various mode pages (serial number access and so forth). +NOTE RACE: When stopping a port explicitly which has not self stopped, +i.e. CR is still on, we can race command completion and not have a good +idea what bits to reload into CI etc to restart the commands that +were running. This should only be done if we intend to reset the port. + +NOTE RACE: A transient IFS interrupt (fatal phy/protocol error) can occur +when soft-resetting through a port multiplier, between the first and second +FISes. We need to be able to lock access to the port. + ------ Misc probe info -------- port @@ -23,3 +32,73 @@ Chipsets supporting FBSS (FIS-Based Switching): SB800 S5000 (w/ ESB2) (add more) +--------------------------- + +Set device bits FIS: + + EEEEEEEE HHHHLLLL NIRxxxxx FFFFFFFF + rrrrrrrr rrrrrrrr rrrrrrrr rrrrrrrr (reserved) + + F8 FIS TYPE (0xA1) + N Notification bit + I Interrupt bit + R Reset bit + H4 Status hi (bit 3 is 'r' bit?) + L4 Status Lo (bit 3 is 'r' bit?) + E8 Error code + + ATAPI/DISK notification: Word78 of IDENTIFY, + Use SET FEATURES to set. + +IDENTIFY DEVICE Changed in SATA 2: + + Word 75 4:0 Max Queue depth + + Word 76 9 Supports IPM requests + 8 supports NCQ + 7-4 reservedr + 3 reserved + 2 supports GEN2 + 1 supports GEN1 + 0 reserved (set to 0) + + Word 78 4 supports in-order data delivery + 3 supports IPMfrom device + 2 supports DMA setup AA opt + 1 supports non-zero buffer offssets in DMA setup + 0 reserved (set to 0 + + Word 79 (sata features enabled) + + + Device configuration overlay + Word 0-7 Defined by ATA + Word 8 3 suports async notification + 2 supports IPM + 1 supports nz buffer offsets in DMA setup FIS + 0 supports NCQ + Word 9 reserved for SATA + 10-255 as defined by ATA + +SET FEATURES DEF + + Feature 10h Enable use of SATA feature + feature 90h Disable use of SATA feature + + sector count register contains specific feature to enable + + 01 No zero buffer offset in DMA setup fis + 02 DMA setup fis AA opt + 03 device initated power state transitions + 04 guaranteed in-order data delivery + 05 Asynchronous notification + + +SCR REGISTERS + + 0 SStatus + 1 SError + 2 SControl + 3 SActive + 4 SNotification <---- + 5-15 reserved diff --git a/sys/dev/disk/ahci/ahci.c b/sys/dev/disk/ahci/ahci.c index 119d4e8452..aa72bf3aee 100644 --- a/sys/dev/disk/ahci/ahci.c +++ b/sys/dev/disk/ahci/ahci.c @@ -183,7 +183,7 @@ ahci_port_alloc(struct ahci_softc *sc, u_int port) at = &ap->ap_ata[i]; at->at_ahci_port = ap; at->at_target = i; - at->at_probe = ATA_PROBE_NEED_HARD_RESET; + at->at_probe = ATA_PROBE_NEED_INIT; ksnprintf(at->at_name, sizeof(at->at_name), "%s.%d", ap->ap_name, i); } @@ -198,7 +198,7 @@ ahci_port_alloc(struct ahci_softc *sc, u_int port) ap->ap_sc = sc; ap->ap_num = port; - ap->ap_probe = ATA_PROBE_NEED_HARD_RESET; + ap->ap_probe = ATA_PROBE_NEED_INIT; TAILQ_INIT(&ap->ap_ccb_free); TAILQ_INIT(&ap->ap_ccb_pending); lockinit(&ap->ap_ccb_lock, "ahcipo", 0, 0); @@ -445,7 +445,7 @@ ahci_port_init(struct ahci_port *ap, struct ata_port *at) } /* - * Flush interupts on the port. XXX + * Flush interrupts on the port. XXX * * Enable interrupts on the port whether a device is sitting on * it or not, to handle hot-plug events. @@ -470,6 +470,138 @@ ahci_port_init(struct ahci_port *ap, struct ata_port *at) return(rc); } +/* + * Run the port / target state machine from a main context. + * + * The state machine for the port is always run. + * + * If atx is non-NULL run the state machine for a particular target. + * If atx is NULL run the state machine for all targets. + */ +void +ahci_port_state_machine(struct ahci_port *ap) +{ + struct ata_port *at; + u_int32_t data; + int target; + int didsleep; + + if (ap->ap_type == ATA_PORT_T_NONE) { + if (ap->ap_probe == ATA_PROBE_NEED_INIT) { + for (target = 0; target < AHCI_MAX_PMPORTS; ++target) { + at = &ap->ap_ata[target]; + at->at_probe = ATA_PROBE_NEED_INIT; + } + ahci_port_init(ap, NULL); + } + if (ap->ap_probe == ATA_PROBE_NEED_HARD_RESET) + ahci_port_reset(ap, NULL, 1); + if (ap->ap_probe == ATA_PROBE_NEED_SOFT_RESET) + ahci_port_reset(ap, NULL, 0); + if (ap->ap_probe == ATA_PROBE_NEED_IDENT) + ahci_cam_probe(ap, NULL); + } + if (ap->ap_type != ATA_PORT_T_PM) { + if (ap->ap_probe == ATA_PROBE_FAILED) { + ahci_cam_changed(ap, NULL, 0); + } else if (ap->ap_probe == ATA_PROBE_GOOD) { + ahci_cam_changed(ap, NULL, 1); + } + return; + } + + for (;;) { + if (ahci_pm_read(ap, 15, AHCI_PMREG_EINFO, &data)) { + kprintf("%s: PM unable to read hot-plug bitmap\n", + PORTNAME(ap)); + break; + } + data &= (1 << ap->ap_pmcount) - 1; + + /* + * Stop if no ports on the target have indicated a state + * change. + */ + if (data == 0) + break; + + /* + * New devices showing up in the bitmap require some spin-up + * time before we start probing them. Reset didsleep. The + * first new device we detect will sleep before probing. + */ + didsleep = 0; + + for (target = 0; target < ap->ap_pmcount; ++target) { + at = &ap->ap_ata[target]; + + /* + * Check the target state for targets behind the PM + * which have changed state. This will adjust + * at_probe and set ATA_PORT_F_RESCAN + * + * We want to wait at least 4 seconds before probing + * a newly inserted device. If the check status + * indicates a device is present and in need of a + * hard reset, we make sure we have slept before + * continuing. + */ + if (data & (1 << target)) { + ahci_pm_check_good(ap, target); + if (at->at_probe == ATA_PROBE_NEED_HARD_RESET) { + if (didsleep == 0) { + didsleep = 1; + ahci_os_sleep(4000); + } + } + } + + /* + * Run through the state machine as necessary. + */ + if (at->at_type == ATA_PORT_T_NONE && + at->at_probe != ATA_PROBE_FAILED) { + if (at->at_probe == ATA_PROBE_NEED_INIT) + ahci_port_init(ap, at); + if (at->at_probe == ATA_PROBE_NEED_HARD_RESET) + ahci_port_reset(ap, at, 1); + if (at->at_probe == ATA_PROBE_NEED_SOFT_RESET) + ahci_port_reset(ap, at, 0); + if (at->at_probe == ATA_PROBE_NEED_IDENT) + ahci_cam_probe(ap, at); + } + + if (data & (1 << target)) { + kprintf("%s: HOTPLUG event, ", + ATANAME(ap, at)); + if (at->at_probe == ATA_PROBE_GOOD) + kprintf("device inserted\n"); + else + kprintf("device removed\n"); + } + + /* + * Initial conditions set automatic add/rem + */ + if (at->at_probe <= ATA_PROBE_NEED_HARD_RESET) + at->at_features |= ATA_PORT_F_RESCAN; + + /* + * add or remove from CAM + */ + if (at->at_features & ATA_PORT_F_RESCAN) { + at->at_features &= ~ATA_PORT_F_RESCAN; + if (at->at_probe == ATA_PROBE_FAILED) { + ahci_cam_changed(ap, at, 0); + } else if (at->at_probe == ATA_PROBE_GOOD) { + ahci_cam_changed(ap, at, 1); + } + } + } + } +} + + /* * De-initialize and detach a port. */ @@ -729,11 +861,10 @@ ahci_port_softreset(struct ahci_port *ap) struct ahci_ccb *ccb = NULL; struct ahci_cmd_hdr *cmd_slot; u_int8_t *fis; - int rc, count; + int error; u_int32_t cmd; - rc = EIO; - count = 10; /* device reset delay x 100ms */ + error = EIO; kprintf("%s: START SOFTRESET %b\n", PORTNAME(ap), ahci_pread(ap, AHCI_PREG_CMD), AHCI_PFMT_CMD); @@ -785,7 +916,7 @@ ahci_port_softreset(struct ahci_port *ap) PORTNAME(ap), (ahci_read(ap->ap_sc, AHCI_REG_CAP) & AHCI_REG_CAP_SCLO) ? "failed" : "unsupported"); - rc = EBUSY; + error = EBUSY; goto err; } @@ -811,7 +942,7 @@ ahci_port_softreset(struct ahci_port *ap) ccb->ccb_xa.state = ATA_S_PENDING; ccb->ccb_xa.flags = 0; - if (ahci_poll(ccb, hz, NULL) != 0 || + if (ahci_poll(ccb, 1000, NULL) != 0 || ccb->ccb_xa.state != ATA_S_COMPLETE) { kprintf("%s: First FIS failed\n", PORTNAME(ap)); goto err; @@ -820,7 +951,7 @@ ahci_port_softreset(struct ahci_port *ap) /* * The device may muff the PHY up. */ - DELAY(10000); /* XXX 3000 */ + ahci_os_sleep(10); /* 3ms min, use 10 */ /* * Prep second D2H command to read status and complete reset sequence @@ -842,7 +973,7 @@ ahci_port_softreset(struct ahci_port *ap) ccb->ccb_xa.state = ATA_S_PENDING; ccb->ccb_xa.flags = 0; - if (ahci_poll(ccb, hz, NULL) != 0 || + if (ahci_poll(ccb, 1000, NULL) != 0 || ccb->ccb_xa.state != ATA_S_COMPLETE) { kprintf("%s: Second FIS failed\n", PORTNAME(ap)); goto err; @@ -853,10 +984,10 @@ ahci_port_softreset(struct ahci_port *ap) kprintf("%s: device didn't come ready after reset, TFD: 0x%b\n", PORTNAME(ap), ahci_pread(ap, AHCI_PREG_TFD), AHCI_PFMT_TFD_STS); - rc = EBUSY; + error = EBUSY; goto err; } - DELAY(10000); + ahci_os_sleep(10); /* * If the softreset is trying to clear a BSY condition after a @@ -872,18 +1003,18 @@ ahci_port_softreset(struct ahci_port *ap) if (ahci_port_signature_detect(ap, NULL) != ap->ap_type) { kprintf("%s: device signature unexpectedly " "changed\n", PORTNAME(ap)); - rc = EBUSY; /* XXX */ + error = EBUSY; /* XXX */ } } - rc = 0; + error = 0; - DELAY(3000); + ahci_os_sleep(3); err: if (ccb != NULL) { /* * Abort our command, if it failed, by stopping command DMA. */ - if (rc && (ap->ap_active & (1 << ccb->ccb_slot))) { + if (error && (ap->ap_active & (1 << ccb->ccb_slot))) { kprintf("%s: stopping the port, softreset slot " "%d was still active.\n", PORTNAME(ap), @@ -913,26 +1044,24 @@ err: * Don't kill the port if the softreset is on a port multiplier * target, that would kill all the targets! */ - if (rc) { + if (error) { ahci_port_hardstop(ap); + /* ap_probe set to failed */ } else if (cmd & AHCI_PREG_CMD_ST) { + ap->ap_probe = ATA_PROBE_NEED_IDENT; kprintf("%s: STARTING PORT\n", PORTNAME(ap)); ahci_port_start(ap); } else { + ap->ap_probe = ATA_PROBE_NEED_IDENT; kprintf("%s: STOPPING PORT\n", PORTNAME(ap)); ahci_port_stop(ap, !(cmd & AHCI_PREG_CMD_FRE)); } - if (rc) - ap->ap_probe = ATA_PROBE_FAILED; - else - ap->ap_probe = ATA_PROBE_NEED_IDENT; - + ap->ap_flags &= ~AP_F_IN_RESET; crit_exit(); kprintf("%s: END SOFTRESET\n", PORTNAME(ap)); - ap->ap_flags &= ~AP_F_IN_RESET; - return (rc); + return (error); } /* @@ -945,7 +1074,7 @@ int ahci_port_hardreset(struct ahci_port *ap, int hard) { u_int32_t cmd, r; - int rc; + int error; int loop; int type; @@ -958,8 +1087,7 @@ ahci_port_hardreset(struct ahci_port *ap, int hard) */ ahci_port_stop(ap, 0); ap->ap_state = AP_S_NORMAL; - ap->ap_probe = ATA_PROBE_FAILED; - rc = 0; + error = 0; /* * The port may have been quiescent with its SUD bit cleared, so @@ -979,7 +1107,7 @@ ahci_port_hardreset(struct ahci_port *ap, int hard) if (hard == 2) r |= AHCI_PREG_SCTL_DET_DISABLE; ahci_pwrite(ap, AHCI_PREG_SCTL, r); - DELAY(10000); + ahci_os_sleep(10); /* * Start transmitting COMRESET. COMRESET must be sent for at @@ -993,7 +1121,7 @@ ahci_port_hardreset(struct ahci_port *ap, int hard) r |= AHCI_PREG_SCTL_SPD_ANY; } ahci_pwrite(ap, AHCI_PREG_SCTL, r); - DELAY(1000); + ahci_os_sleep(1); /* * Only SERR_DIAG_X needs to be cleared for TFD updates, but @@ -1017,25 +1145,25 @@ ahci_port_hardreset(struct ahci_port *ap, int hard) r = ahci_pread(ap, AHCI_PREG_SSTS); if (r & AHCI_PREG_SSTS_DET) break; - DELAY(10000); + ahci_os_sleep(10); } if (loop == 0) { ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_PRCS); kprintf("%s: Port appears to be unplugged\n", PORTNAME(ap)); - rc = ENODEV; + error = ENODEV; } /* * There is something on the port. Give the device 3 seconds * to fully negotiate. */ - if (rc == 0 && + if (error == 0 && ahci_pwait_eq(ap, 3000, AHCI_PREG_SSTS, AHCI_PREG_SSTS_DET, AHCI_PREG_SSTS_DET_DEV)) { kprintf("%s: Device may be powered down\n", PORTNAME(ap)); - rc = ENODEV; + error = ENODEV; } /* @@ -1044,32 +1172,29 @@ ahci_port_hardreset(struct ahci_port *ap, int hard) * This can take more then a second, give it 3 seconds. If we * succeed give the device another 3ms after that. * - * NOTE: Port Multipliers can do two things here. First they can + * NOTE: Port multipliers can do two things here. First they can * return device-ready if a device is on target 0 and also * return the signature for that device. If there is no * device on target 0 then BSY/DRQ is never cleared and * it never comes ready. */ - if (rc == 0 && + if (error == 0 && ahci_pwait_clr_to(ap, 3000, AHCI_PREG_TFD, AHCI_PREG_TFD_STS_BSY | AHCI_PREG_TFD_STS_DRQ)) { /* * The device is bricked or its a port multiplier and will * not unbusy until we do the pmprobe CLO softreset sequence. */ - rc = ahci_port_pmprobe(ap); - if (rc) { + error = ahci_port_pmprobe(ap); + if (error) { kprintf("%s: Device will not come ready 0x%b\n", PORTNAME(ap), ahci_pread(ap, AHCI_PREG_TFD), AHCI_PFMT_TFD_STS); } else { ap->ap_type = ATA_PORT_T_PM; - ap->ap_probe = ATA_PROBE_NEED_SOFT_RESET; - kprintf("%s: Port Multiplier detected\n", - PORTNAME(ap)); } - } else if (rc == 0) { + } else if (error == 0) { /* * We generally will not get a port multiplier signature in * this case even if this is a port multiplier, because of @@ -1081,15 +1206,14 @@ ahci_port_hardreset(struct ahci_port *ap, int hard) * success. */ type = ahci_port_signature_detect(ap, NULL); - rc = ahci_port_pmprobe(ap); - if (rc) { + error = ahci_port_pmprobe(ap); + if (error) { ap->ap_type = type; ap->ap_probe = ATA_PROBE_NEED_SOFT_RESET; - rc = 0; + error = 0; } else { ap->ap_type = ATA_PORT_T_PM; - ap->ap_probe = ATA_PROBE_NEED_SOFT_RESET; - kprintf("%s: Port Multiplier detected\n", + kprintf("%s: Port multiplier detected\n", PORTNAME(ap)); } } @@ -1098,10 +1222,15 @@ ahci_port_hardreset(struct ahci_port *ap, int hard) * hard-stop the port if we failed. This will set ap_probe * to FAILED. */ - if (rc) - ahci_port_hardstop(ap); ap->ap_flags &= ~AP_F_IN_RESET; - return (rc); + if (error) { + ahci_port_hardstop(ap); + /* ap_probe set to failed */ + } else { + ap->ap_probe = (ap->ap_type == ATA_PORT_T_PM) ? + ATA_PROBE_GOOD : ATA_PROBE_NEED_SOFT_RESET; + } + return (error); } /* @@ -1196,7 +1325,7 @@ retry: ccb->ccb_xa.state = ATA_S_PENDING; ccb->ccb_xa.flags = 0; - if (ahci_poll(ccb, hz, NULL) != 0 || + if (ahci_poll(ccb, 1000, NULL) != 0 || ccb->ccb_xa.state != ATA_S_COMPLETE) { kprintf("%s: PMPROBE First FIS failed\n", PORTNAME(ap)); if (--count) { @@ -1214,7 +1343,7 @@ retry: /* * The device may have muffed up the PHY when it reset. */ - DELAY(10000); + ahci_os_sleep(10); ahci_flush_tfd(ap); ahci_pwrite(ap, AHCI_PREG_SERR, -1); /* ahci_pm_phy_status(ap, 15, &cmd); */ @@ -1242,7 +1371,7 @@ retry: ccb->ccb_xa.state = ATA_S_PENDING; ccb->ccb_xa.flags = 0; - if (ahci_poll(ccb, hz, NULL) != 0 || + if (ahci_poll(ccb, 1000, NULL) != 0 || ccb->ccb_xa.state != ATA_S_COMPLETE) { kprintf("%s: PMPROBE Second FIS failed\n", PORTNAME(ap)); if (--count) { @@ -1299,6 +1428,26 @@ err: ahci_put_err_ccb(ccb); } + if (rc == 0 && ahci_pm_identify(ap)) { + kprintf("%s: PM - cannot identify port multiplier\n", + PORTNAME(ap)); + rc = EBUSY; + } +#if 0 + if (rc == 0 && ahci_pm_set_feature(ap, ATA_SATAFT_ASYNCNOTIFY, 1)) { + kprintf("%s: PM - Warning, cannot enable async notify\n", + PORTNAME(ap)); + /* ignore error */ + } + if (rc == 0) { + u_int32_t data; + if (ahci_pm_read(ap, 2, 4, &data)) + kprintf("Cannot read snotify\n"); + else + kprintf("Read snotify %08x\n", data); + } +#endif + /* * If we failed turn off PMA, otherwise identify the port multiplier. * CAM will iterate the devices. @@ -1308,15 +1457,6 @@ err: cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC; cmd &= ~AHCI_PREG_CMD_PMA; ahci_pwrite(ap, AHCI_PREG_CMD, cmd); - } else { - ahci_pm_identify(ap); -#if 0 - ahci_pm_hardreset(ap, 0); - ahci_pm_hardreset(ap, 1); - ahci_pm_hardreset(ap, 2); - ahci_pm_hardreset(ap, 3); - ahci_pm_hardreset(ap, 4); -#endif } ahci_port_stop(ap, 0); @@ -1359,7 +1499,7 @@ ahci_port_hardstop(struct ahci_port *ap) for (i = 0; ap->ap_ata && i < AHCI_MAX_PMPORTS; ++i) { at = &ap->ap_ata[i]; at->at_type = ATA_PORT_T_NONE; - at->at_probe = ATA_PORT_T_NONE; + at->at_probe = ATA_PROBE_FAILED; } /* @@ -1388,12 +1528,12 @@ ahci_port_hardstop(struct ahci_port *ap) * Deactivating SUD only applies if the controller supports SUD. */ ahci_pwrite(ap, AHCI_PREG_SCTL, AHCI_PREG_SCTL_IPM_DISABLED); - DELAY(1000); + ahci_os_sleep(1); if (cmd & AHCI_PREG_CMD_SUD) { cmd &= ~AHCI_PREG_CMD_SUD; ahci_pwrite(ap, AHCI_PREG_CMD, cmd); } - DELAY(1000); + ahci_os_sleep(1); /* * Transition su to the spin-up state. HVA shall send COMRESET and @@ -1403,7 +1543,7 @@ ahci_port_hardstop(struct ahci_port *ap) */ cmd |= AHCI_PREG_CMD_SUD; ahci_pwrite(ap, AHCI_PREG_CMD, cmd); - DELAY(1000); + ahci_os_sleep(1); /* * Transition us to the Reset state. Theoretically we send a @@ -1417,7 +1557,7 @@ ahci_port_hardstop(struct ahci_port *ap) r |= AHCI_PREG_SCTL_SPD_ANY; } ahci_pwrite(ap, AHCI_PREG_SCTL, r); - DELAY(1000); + ahci_os_sleep(1); /* * Flush SERR_DIAG_X so the TFD can update. @@ -1454,7 +1594,7 @@ ahci_flush_tfd(struct ahci_port *ap) r = ahci_pread(ap, AHCI_PREG_SERR); while (r & AHCI_PREG_SERR_DIAG_X) { ahci_pwrite(ap, AHCI_PREG_SERR, AHCI_PREG_SERR_DIAG_X); - DELAY(1000); + ahci_os_sleep(1); r = ahci_pread(ap, AHCI_PREG_SERR); } } @@ -1474,7 +1614,6 @@ ahci_port_signature_detect(struct ahci_port *ap, struct ata_port *at) return(ATA_PORT_T_ATAPI); } else if ((sig & 0xffff0000) == (SATA_SIGNATURE_PORT_MULTIPLIER & 0xffff0000)) { - kprintf("found PM\n"); return(ATA_PORT_T_PM); } else { return(ATA_PORT_T_DISK); @@ -1585,6 +1724,8 @@ ahci_unload_prdt(struct ahci_ccb *ccb) /* * Start a command and poll for completion. * + * timeout is in ms and only counts once the command gets on-chip. + * * NOTE: If the caller specifies a NULL timeout function the caller is * responsible for clearing hardware state on failure, but we will * deal with removing the ccb from any pending queue. @@ -1598,7 +1739,6 @@ int ahci_poll(struct ahci_ccb *ccb, int timeout, void (*timeout_fn)(void *)) { struct ahci_port *ap = ccb->ccb_port; - u_int32_t slot_mask = 1 << ccb->ccb_slot; if (ccb->ccb_port->ap_state == AP_S_FATAL_ERROR) { ccb->ccb_xa.state = ATA_S_ERROR; @@ -1608,24 +1748,16 @@ ahci_poll(struct ahci_ccb *ccb, int timeout, void (*timeout_fn)(void *)) ahci_start(ccb); do { - if (ahci_port_intr(ap, AHCI_PREG_CI_ALL_SLOTS) & slot_mask) { - crit_exit(); - return (0); - } + ahci_port_intr(ap); if (ccb->ccb_xa.state != ATA_S_ONCHIP && ccb->ccb_xa.state != ATA_S_PENDING) { - break; + crit_exit(); + return (0); } - DELAY(1000000 / hz); - } while (--timeout > 0); - - if (ccb->ccb_xa.state != ATA_S_ONCHIP && - ccb->ccb_xa.state != ATA_S_PENDING) { - kprintf("%s: Warning poll completed unexpectedly for slot %d\n", - PORTNAME(ap), ccb->ccb_slot); - crit_exit(); - return (0); - } + ahci_os_sleep(100); + if (ccb->ccb_xa.state == ATA_S_ONCHIP) + timeout -= 100; + } while (timeout > 0); kprintf("%s: Poll timed-out for slot %d state %d\n", ATANAME(ap, ccb->ccb_xa.at), ccb->ccb_slot, ccb->ccb_xa.state); @@ -1642,6 +1774,19 @@ ahci_poll(struct ahci_ccb *ccb, int timeout, void (*timeout_fn)(void *)) return (1); } +static +__inline +void +ahci_start_timeout(struct ahci_ccb *ccb) +{ + if (ccb->ccb_xa.flags & ATA_F_TIMEOUT_DESIRED) { + ccb->ccb_xa.flags |= ATA_F_TIMEOUT_RUNNING; + callout_reset(&ccb->ccb_timeout, + (ccb->ccb_xa.timeout * hz + 999) / 1000, + ahci_ata_cmd_timeout_unserialized, ccb); + } +} + void ahci_start(struct ahci_ccb *ccb) { @@ -1674,6 +1819,7 @@ ahci_start(struct ahci_ccb *ccb) if (ap->ap_active || TAILQ_FIRST(&ap->ap_ccb_pending)) { TAILQ_INSERT_TAIL(&ap->ap_ccb_pending, ccb, ccb_entry); } else { + ahci_start_timeout(ccb); KKASSERT(ap->ap_active_cnt == 0); ap->ap_sactive |= (1 << ccb->ccb_slot); ccb->ccb_xa.state = ATA_S_ONCHIP; @@ -1696,6 +1842,7 @@ ahci_start(struct ahci_ccb *ccb) if (ap->ap_sactive || ap->ap_active_cnt >= limit) { TAILQ_INSERT_TAIL(&ap->ap_ccb_pending, ccb, ccb_entry); } else { + ahci_start_timeout(ccb); ap->ap_active |= 1 << ccb->ccb_slot; ccb->ccb_xa.state = ATA_S_ONCHIP; ahci_pwrite(ap, AHCI_PREG_CI, 1 << ccb->ccb_slot); @@ -1719,6 +1866,7 @@ ahci_issue_pending_ncq_commands(struct ahci_port *ap) /* Start all the NCQ commands at the head of the pending list. */ do { TAILQ_REMOVE(&ap->ap_ccb_pending, nextccb, ccb_entry); + ahci_start_timeout(nextccb); sact_change |= 1 << nextccb->ccb_slot; nextccb->ccb_xa.state = ATA_S_ONCHIP; nextccb = TAILQ_FIRST(&ap->ap_ccb_pending); @@ -1764,6 +1912,7 @@ ahci_issue_pending_commands(struct ahci_port *ap, int last_was_ncq) while (ap->ap_active_cnt < 2 && nextccb && (nextccb->ccb_xa.flags & ATA_F_NCQ) == 0) { TAILQ_REMOVE(&ap->ap_ccb_pending, nextccb, ccb_entry); + ahci_start_timeout(nextccb); ap->ap_active |= 1 << nextccb->ccb_slot; nextccb->ccb_xa.state = ATA_S_ONCHIP; ahci_pwrite(ap, AHCI_PREG_CI, 1 << nextccb->ccb_slot); @@ -1799,10 +1948,8 @@ ahci_intr(void *arg) /* Process interrupts for each port */ while (is) { port = ffs(is) - 1; - if (sc->sc_ports[port]) { - ahci_port_intr(sc->sc_ports[port], - AHCI_PREG_CI_ALL_SLOTS); - } + if (sc->sc_ports[port]) + ahci_port_intr(sc->sc_ports[port]); is &= ~(1 << port); } @@ -1810,11 +1957,11 @@ ahci_intr(void *arg) ahci_write(sc, AHCI_REG_IS, ack); } -u_int32_t -ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) +void +ahci_port_intr(struct ahci_port *ap) { struct ahci_softc *sc = ap->ap_sc; - u_int32_t is, ci_saved, ci_masked, processed = 0; + u_int32_t is, ci_saved, ci_masked; int slot; struct ahci_ccb *ccb = NULL; struct ata_port *ccb_at = NULL; @@ -1832,13 +1979,10 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) is, AHCI_PFMT_IS); #endif - /* Ack port interrupt only if checking all command slots. */ - if (ci_mask == AHCI_PREG_CI_ALL_SLOTS) - ahci_pwrite(ap, AHCI_PREG_IS, is); - - if (is) - DPRINTF(AHCI_D_INTR, "%s: interrupt: %b\n", PORTNAME(ap), - is, AHCI_PFMT_IS); + /* + * Ack the port interrupt + */ + ahci_pwrite(ap, AHCI_PREG_IS, is); if (ap->ap_sactive) { /* Active NCQ commands - use SActive instead of CI */ @@ -2037,18 +2181,28 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) data = ahci_pread(ap, AHCI_PREG_SNTF); if (data) { kprintf("%s: NOTIFY %08x\n", PORTNAME(ap), data); - ahci_pwrite(ap, AHCI_PREG_SNTF, data); + ahci_pwrite(ap, AHCI_PREG_SERR, AHCI_PREG_SERR_DIAG_N); ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_SDBS); + ahci_pwrite(ap, AHCI_PREG_SNTF, data); + ahci_cam_changed(ap, NULL, -1); } } -#if 0 - /* XXX future IFS recovery code? or just scrap it */ - if (is & AHCI_PREG_IS_IFS) { + + /* + * Spurious IFS errors can occur while we are doing a reset + * sequence through a PM. Try to recover if we are being asked + * to ignore IFS errors during these periods. + */ + if ((is & AHCI_PREG_IS_IFS) && (ap->ap_flags & AP_F_IGNORE_IFS)) { u_int32_t serr = ahci_pread(ap, AHCI_PREG_SERR); - kprintf("%s: Ignoring IFS (XXX) (IS: %b, SERR: %b)\n", - PORTNAME(ap), - is, AHCI_PFMT_IS, - serr, AHCI_PFMT_SERR); + if ((ap->ap_flags & AP_F_IFS_IGNORED) == 0) { + kprintf("%s: Ignoring IFS (XXX) (IS: %b, SERR: %b)\n", + PORTNAME(ap), + is, AHCI_PFMT_IS, + serr, AHCI_PFMT_SERR); + ap->ap_flags |= AP_F_IFS_IGNORED; + } + ap->ap_flags |= AP_F_IFS_OCCURED; ahci_pwrite(ap, AHCI_PREG_SERR, -1); ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_IFS); is &= ~AHCI_PREG_IS_IFS; @@ -2056,7 +2210,6 @@ ahci_port_intr(struct ahci_port *ap, u_int32_t ci_mask) ahci_port_start(ap); need = NEED_RESTART; } -#endif /* * Port change (hot-plug). @@ -2173,7 +2326,7 @@ failall: * Any active bits not saved are completed within the restrictions * imposed by the caller. */ - ci_masked = ~ci_saved & *active & ci_mask; + ci_masked = ~ci_saved & *active; while (ci_masked) { slot = ffs(ci_masked) - 1; ccb = &ap->ap_ccbs[slot]; @@ -2201,8 +2354,6 @@ failall: --ap->ap_active_cnt; } ccb->ccb_done(ccb); - - processed |= 1 << ccb->ccb_slot; } switch(need) { @@ -2245,8 +2396,8 @@ failall: if ((ap->ap_flags & AP_F_IN_RESET) == 0) { kprintf("%s: HOTPLUG - Device inserted\n", PORTNAME(ap)); - if (ahci_port_init(ap, NULL) == 0) - ahci_cam_changed(ap, 1); + ap->ap_probe = ATA_PROBE_NEED_INIT; + ahci_cam_changed(ap, NULL, -1); } break; case NEED_HOTPLUG_REMOVE: @@ -2261,13 +2412,13 @@ failall: kprintf("%s: HOTPLUG - Device removed\n", PORTNAME(ap)); ahci_port_hardstop(ap); - ahci_cam_changed(ap, 0); + /* ap_probe set to failed */ + ahci_cam_changed(ap, NULL, -1); } break; default: break; } - return (processed); } struct ahci_ccb * @@ -2438,7 +2589,7 @@ ahci_port_read_ncq_error(struct ahci_port *ap, int *err_slotp) } ccb->ccb_xa.state = ATA_S_PENDING; - if (ahci_poll(ccb, hz, NULL) != 0) + if (ahci_poll(ccb, 1000, NULL) != 0) goto err; rc = 0; @@ -2569,7 +2720,7 @@ ahci_wait_ne(struct ahci_softc *sc, bus_size_t r, u_int32_t mask, for (i = 0; i < 1000; i++) { if ((ahci_read(sc, r) & mask) != target) return (0); - DELAY(1000); + ahci_os_sleep(1); } return (1); @@ -2600,7 +2751,7 @@ ahci_pwait_eq(struct ahci_port *ap, int timeout, for (i = 0; i < timeout; i++) { if ((ahci_pread(ap, r) & mask) == target) return (0); - DELAY(1000); + ahci_os_sleep(1); } return (1); @@ -2688,9 +2839,7 @@ ahci_ata_cmd(struct ata_xfer *xa) } crit_enter(); - xa->flags |= ATA_F_TIMEOUT_RUNNING; - callout_reset(&ccb->ccb_timeout, xa->timeout, - ahci_ata_cmd_timeout_unserialized, ccb); + xa->flags |= ATA_F_TIMEOUT_DESIRED; ahci_start(ccb); crit_exit(); return (ATA_QUEUED); @@ -2712,6 +2861,7 @@ ahci_ata_cmd_done(struct ahci_ccb *ccb) xa->flags &= ~ATA_F_TIMEOUT_RUNNING; callout_stop(&ccb->ccb_timeout); } + xa->flags &= ~ATA_F_TIMEOUT_DESIRED; if (xa->state == ATA_S_ONCHIP || xa->state == ATA_S_ERROR) ahci_issue_pending_commands(ccb->ccb_port, @@ -2764,8 +2914,10 @@ ahci_ata_cmd_timeout(void *arg) /* * NOTE: Timeout will not be running if the command was polled. + * If we got here at least one of these flags should be set. */ - KKASSERT(xa->flags & (ATA_F_POLL|ATA_F_TIMEOUT_RUNNING)); + KKASSERT(xa->flags & (ATA_F_POLL | ATA_F_TIMEOUT_DESIRED | + ATA_F_TIMEOUT_RUNNING)); xa->flags &= ~ATA_F_TIMEOUT_RUNNING; ncq_cmd = (xa->flags & ATA_F_NCQ); active = ncq_cmd ? &ap->ap_sactive : &ap->ap_active; @@ -2775,11 +2927,6 @@ ahci_ata_cmd_timeout(void *arg) "before it got on chip\n", PORTNAME(ap), ccb->ccb_slot); TAILQ_REMOVE(&ap->ap_ccb_pending, ccb, ccb_entry); ccb_was_started = 0; - } else if (ccb->ccb_xa.state == ATA_S_ONCHIP && - ahci_port_intr(ap, 1 << ccb->ccb_slot)) { - DPRINTF(AHCI_D_TIMEOUT, "%s: final poll of port completed " - "command in slot %d\n", PORTNAME(ap), ccb->ccb_slot); - goto ret; } else if (ccb->ccb_xa.state != ATA_S_ONCHIP) { DPRINTF(AHCI_D_TIMEOUT, "%s: command slot %d already " "handled%s\n", PORTNAME(ap), ccb->ccb_slot, @@ -2820,14 +2967,14 @@ ahci_ata_cmd_timeout(void *arg) "in slot %d, active %08x\n", PORTNAME(ap), ncq_cmd ? " NCQ" : "", ccb->ccb_slot, *active); /* XXX */ - if (ccb->ccb_xa.at) { + if (ccb->ccb_xa.at && ap->ap_type == ATA_PORT_T_PM) { /* XXX how do we unbrick a PM target? */ kprintf("%s: PM target bricked and timed-out, " "disabling PM target but trying to " "leave the port intact\n", ATANAME(ap, ccb->ccb_xa.at)); ccb->ccb_xa.at->at_probe = ATA_PROBE_FAILED; - ahci_port_intr(ap, AHCI_PREG_CI_ALL_SLOTS); + ahci_port_intr(ap); ahci_port_stop(ap, 0); ahci_port_clo(ap); ahci_port_start(ap); @@ -2842,7 +2989,7 @@ ahci_ata_cmd_timeout(void *arg) "bricked on us\n", PORTNAME(ap)); ap->ap_state = AP_S_FATAL_ERROR; - ahci_port_intr(ap, AHCI_PREG_CI_ALL_SLOTS); + ahci_port_intr(ap); status = 1; } else { status = 0; diff --git a/sys/dev/disk/ahci/ahci.h b/sys/dev/disk/ahci/ahci.h index 88eabbfefd..8c627a5ed9 100644 --- a/sys/dev/disk/ahci/ahci.h +++ b/sys/dev/disk/ahci/ahci.h @@ -223,6 +223,13 @@ int ahcidebug = AHCI_D_VERBOSE; #define AHCI_PREG_SCTL_IPM_NOPARTIAL 0x100 #define AHCI_PREG_SCTL_IPM_NOSLUMBER 0x200 #define AHCI_PREG_SCTL_IPM_DISABLED 0x300 +#define AHCI_PREG_SCTL_SPM 0xf000 /* Select Power Management */ +#define AHCI_PREG_SCTL_SPM_NONE 0x0000 +#define AHCI_PREG_SCTL_SPM_NOPARTIAL 0x1000 +#define AHCI_PREG_SCTL_SPM_NOSLUMBER 0x2000 +#define AHCI_PREG_SCTL_SPM_DISABLED 0x3000 +#define AHCI_PREG_SCTL_PMP 0xf0000 /* Set PM port for xmit FISes */ +#define AHCI_PREG_SCTL_PMP_SHIFT 16 #define AHCI_PREG_SERR 0x30 /* SATA Error */ #define AHCI_PREG_SERR_ERR_I (1<<0) /* Recovered Data Integrity */ @@ -261,6 +268,42 @@ int ahcidebug = AHCI_D_VERBOSE; #define AHCI_PMREG_SSTS 0 /* use AHCI_PREG_SSTS_ bit defs */ #define AHCI_PMREG_SERR 1 /* use AHCI_PREG_SERR_ bit defs */ #define AHCI_PMREG_SCTL 2 /* use AHCI_PREG_SCTL_ bit defs */ +#define AHCI_PMREG_SACT 3 /* (not implemented on PM) */ + +/* + * AHCI port multiplier revision information SCR[1] (see ahci_pm_read) + * + * Rev 1.1 is the one that should support async notification. + */ +#define AHCI_PMREV_PM1_0 0x00000002 +#define AHCI_PMREV_PM1_1 0x00000004 +#define AHCI_PFMT_PM_REV "\20" "\003PM1.1" "\002PM1.0" + +/* + * GSCR[64] and GSCR[96] - Port Multiplier features available and features + * enabled. + */ +#define AHCI_PMREG_FEA 64 +#define AHCI_PMREG_FEAEN 96 /* (features enabled) */ +#define AHCI_PMFEA_BIST 0x00000001 /* BIST Support */ +#define AHCI_PMFEA_PMREQ 0x00000002 /* Can issue PMREQp to host */ +#define AHCI_PMFEA_DYNSSC 0x00000004 /* Dynamic SSC transmit enab */ +#define AHCI_PMFEA_ASYNCNOTIFY 0x00000008 /* Async notification */ + +#define AHCI_PFMT_PM_FEA "\20" \ + "\004AsyncNotify" \ + "\003DynamicSSC" \ + "\002PMREQ" \ + "\001BIST" + +/* + * Enable generation of async notify events for individual targets + * via the PMEENA register. Each bit in PMEINFO is a wire-or of all + * SERROR bits for that target. To enable a new notification event + * the SERROR bits in PMSERROR_REGNO must be cleared. + */ +#define AHCI_PMREG_EINFO 32 /* error info 16 ports */ +#define AHCI_PMREG_EEENA 33 /* error info enable 16 ports */ /* * AHCI mapped structures @@ -373,8 +416,12 @@ struct ahci_port { #define AP_F_BUS_REGISTERED 0x0001 #define AP_F_CAM_ATTACHED 0x0002 #define AP_F_IN_RESET 0x0004 +#define AP_F_SCAN_RUNNING 0x0008 +#define AP_F_SCAN_REQUESTED 0x0010 +#define AP_F_IGNORE_IFS 0x0020 +#define AP_F_IFS_IGNORED 0x0040 +#define AP_F_IFS_OCCURED 0x0080 struct cam_sim *ap_sim; - struct cam_path *ap_path; struct ahci_rfis *ap_rfis; struct ahci_dmamem *ap_dmamem_rfis; @@ -463,6 +510,7 @@ struct ahci_device { const struct ahci_device *ahci_lookup_device(device_t dev); int ahci_init(struct ahci_softc *); int ahci_port_alloc(struct ahci_softc *, u_int); +void ahci_port_state_machine(struct ahci_port *ap); void ahci_port_free(struct ahci_softc *, u_int); int ahci_port_reset(struct ahci_port *, struct ata_port *at, int); @@ -474,17 +522,19 @@ void ahci_pwrite(struct ahci_port *, bus_size_t, u_int32_t); int ahci_pwait_eq(struct ahci_port *, int, bus_size_t, u_int32_t, u_int32_t); void ahci_intr(void *); -u_int32_t ahci_port_intr(struct ahci_port *, u_int32_t); +void ahci_port_intr(struct ahci_port *); int ahci_cam_attach(struct ahci_port *ap); -void ahci_cam_changed(struct ahci_port *ap, int found); +void ahci_cam_changed(struct ahci_port *ap, struct ata_port *at, int found); void ahci_cam_detach(struct ahci_port *ap); +int ahci_cam_probe(struct ahci_port *ap, struct ata_port *at); struct ata_xfer *ahci_ata_get_xfer(struct ahci_port *ap, struct ata_port *at); void ahci_ata_put_xfer(struct ata_xfer *xa); int ahci_ata_cmd(struct ata_xfer *xa); int ahci_pm_identify(struct ahci_port *ap); +int ahci_pm_set_feature(struct ahci_port *ap, int feature, int enable); int ahci_pm_hardreset(struct ahci_port *ap, int target, int hard); int ahci_pm_softreset(struct ahci_port *ap, int target); int ahci_pm_phy_status(struct ahci_port *ap, int target, u_int32_t *datap); @@ -492,6 +542,7 @@ int ahci_pm_read(struct ahci_port *ap, int target, int which, u_int32_t *res); int ahci_pm_write(struct ahci_port *ap, int target, int which, u_int32_t data); +void ahci_pm_check_good(struct ahci_port *ap, int target); void ahci_ata_cmd_timeout(void *arg); struct ahci_ccb *ahci_get_ccb(struct ahci_port *ap); void ahci_put_ccb(struct ahci_ccb *ccb); @@ -499,5 +550,6 @@ int ahci_poll(struct ahci_ccb *ccb, int timeout, void (*timeout_fn)(void *)); int ahci_port_signature_detect(struct ahci_port *ap, struct ata_port *at); +void ahci_os_sleep(int ticks); extern u_int32_t AhciForceGen1; diff --git a/sys/dev/disk/ahci/ahci_attach.c b/sys/dev/disk/ahci/ahci_attach.c index 4bc1aba98f..e3e42369bd 100644 --- a/sys/dev/disk/ahci/ahci_attach.c +++ b/sys/dev/disk/ahci/ahci_attach.c @@ -384,7 +384,8 @@ noccc: } error = ahci_port_alloc(sc, i); if (error == 0) { - ahci_cam_attach(sc->sc_ports[i]); + if (ahci_cam_attach(sc->sc_ports[i]) == 0) + ahci_cam_changed(sc->sc_ports[i], NULL, -1); } if (error == ENODEV) error = 0; diff --git a/sys/dev/disk/ahci/ahci_cam.c b/sys/dev/disk/ahci/ahci_cam.c index 02257643e3..d622e2b1d3 100644 --- a/sys/dev/disk/ahci/ahci_cam.c +++ b/sys/dev/disk/ahci/ahci_cam.c @@ -109,7 +109,6 @@ static void ahci_ata_dummy_sense(struct scsi_sense_data *sense_data); static void ahci_ata_atapi_sense(struct ata_fis_d2h *rfis, struct scsi_sense_data *sense_data); -static int ahci_cam_probe(struct ahci_port *ap, struct ata_port *at); static int ahci_cam_probe_disk(struct ahci_port *ap, struct ata_port *at); static int ahci_cam_probe_atapi(struct ahci_port *ap, struct ata_port *at); static void ahci_ata_dummy_done(struct ata_xfer *xa); @@ -149,12 +148,6 @@ ahci_cam_attach(struct ahci_port *ap) return (EINVAL); } ap->ap_flags |= AP_F_BUS_REGISTERED; - error = xpt_create_path(&ap->ap_path, NULL, cam_sim_path(sim), - CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); - if (error != CAM_REQ_CMP) { - ahci_cam_detach(ap); - return (ENOMEM); - } error = ahci_cam_probe(ap, NULL); if (error) { @@ -163,34 +156,55 @@ ahci_cam_attach(struct ahci_port *ap) } ap->ap_flags |= AP_F_CAM_ATTACHED; - ahci_cam_rescan(ap); - return(0); } /* - * Use a wildcard path to indicate that all the devices on the bus - * were found, or lost. + * The state of the port has changed. + * + * If at is NULL the physical port has changed state. + * If at is non-NULL a particular target behind a PM has changed state. + * + * If found is -1 the target state must be queued to a non-interrupt context. + * (only works with at == NULL). + * + * If found is 0 the target was removed. + * If found is 1 the target was inserted. */ void -ahci_cam_changed(struct ahci_port *ap, int found) +ahci_cam_changed(struct ahci_port *ap, struct ata_port *atx, int found) { struct cam_path *tmppath; + int status; + int target; + + target = atx ? atx->at_target : CAM_TARGET_WILDCARD; if (ap->ap_sim == NULL) return; - if (xpt_create_path(&tmppath, NULL, cam_sim_path(ap->ap_sim), - CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD) != CAM_REQ_CMP) { - return; - } - - if (found) { - kprintf("%s: CAM RESCAN\n", PORTNAME(ap)); - ahci_cam_probe(ap, NULL); - /*xpt_async(AC_FOUND_DEVICE, tmppath, NULL);*/ + if (found == CAM_TARGET_WILDCARD) { + status = xpt_create_path(&tmppath, NULL, + cam_sim_path(ap->ap_sim), + target, CAM_LUN_WILDCARD); + if (status != CAM_REQ_CMP) + return; ahci_cam_rescan(ap); } else { - xpt_async(AC_LOST_DEVICE, tmppath, NULL); + status = xpt_create_path(&tmppath, NULL, + cam_sim_path(ap->ap_sim), + target, + CAM_LUN_WILDCARD); + if (status != CAM_REQ_CMP) + return; +#if 0 + /* + * This confuses CAM + */ + if (found) + xpt_async(AC_FOUND_DEVICE, tmppath, NULL); + else + xpt_async(AC_LOST_DEVICE, tmppath, NULL); +#endif } xpt_free_path(tmppath); } @@ -206,10 +220,6 @@ ahci_cam_detach(struct ahci_port *ap) if (ap->ap_sim) { xpt_freeze_simq(ap->ap_sim, 1); } - if (ap->ap_path) { - xpt_free_path(ap->ap_path); - ap->ap_path = NULL; - } if (ap->ap_flags & AP_F_BUS_REGISTERED) { error = xpt_bus_deregister(cam_sim_path(ap->ap_sim)); KKASSERT(error == CAM_REQ_CMP); @@ -230,7 +240,7 @@ ahci_cam_detach(struct ahci_port *ap) * If at is NULL we are probing the direct-attached device on the port, * which may or may not be a port multiplier. */ -static int +int ahci_cam_probe(struct ahci_port *ap, struct ata_port *atx) { struct ata_port *at; @@ -247,12 +257,7 @@ ahci_cam_probe(struct ahci_port *ap, struct ata_port *atx) const char *scstr; const char *type; - /* - * If nothing is probed on the port then there is nothing for us - * to do. In this state ap->ap_ata will also probably be NULL. - */ - if (ap->ap_type == ATA_PORT_T_NONE) - return (EIO); + error = EIO; /* * A NULL atx indicates a probe of the directly connected device. @@ -263,26 +268,28 @@ ahci_cam_probe(struct ahci_port *ap, struct ata_port *atx) * an (at) pointing to target 0. */ if (atx == NULL) { + at = ap->ap_ata; /* direct attached - device 0 */ if (ap->ap_type == ATA_PORT_T_PM) { kprintf("%s: Found Port Multiplier\n", ATANAME(ap, atx)); ap->ap_probe = ATA_PROBE_GOOD; return (0); } - at = ap->ap_ata; /* direct attached - device 0 */ at->at_type = ap->ap_type; } else { + at = atx; if (atx->at_type == ATA_PORT_T_PM) { kprintf("%s: Bogus device, reducing port count to %d\n", ATANAME(ap, atx), atx->at_target); if (ap->ap_pmcount > atx->at_target) ap->ap_pmcount = atx->at_target; - return (EIO); + goto err; } - at = atx; } + if (ap->ap_type == ATA_PORT_T_NONE) + goto err; if (at->at_type == ATA_PORT_T_NONE) - return (EIO); + goto err; /* * Issue identify, saving the result @@ -310,21 +317,21 @@ ahci_cam_probe(struct ahci_port *ap, struct ata_port *atx) xa->fis->features = 0; xa->fis->device = 0; xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; - xa->timeout = hz; + xa->timeout = 1000; status = ahci_ata_cmd(xa); if (status != ATA_COMPLETE) { kprintf("%s: Detected %s device but unable to IDENTIFY\n", ATANAME(ap, atx), type); ahci_ata_put_xfer(xa); - return(EIO); + goto err; } if (xa->state != ATA_S_COMPLETE) { kprintf("%s: Detected %s device but unable to IDENTIFY " " xa->state=%d\n", ATANAME(ap, atx), type, xa->state); ahci_ata_put_xfer(xa); - return(EIO); + goto err; } ahci_ata_put_xfer(xa); @@ -349,7 +356,6 @@ ahci_cam_probe(struct ahci_port *ap, struct ata_port *atx) at->at_capacity = capacity; if (atx == NULL) ap->ap_probe = ATA_PROBE_GOOD; - at->at_probe = ATA_PROBE_GOOD; capacity_bytes = capacity * 512; @@ -483,7 +489,17 @@ ahci_cam_probe(struct ahci_port *ap, struct ata_port *atx) error = EIO; break; } - return (0); +err: + if (error) { + at->at_probe = ATA_PROBE_FAILED; + if (atx == NULL) + ap->ap_probe = at->at_probe; + } else { + at->at_probe = ATA_PROBE_GOOD; + if (atx == NULL) + ap->ap_probe = at->at_probe; + } + return (error); } /* @@ -518,7 +534,7 @@ ahci_cam_probe_disk(struct ahci_port *ap, struct ata_port *atx) xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target; xa->fis->device = 0; xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; - xa->timeout = hz; + xa->timeout = 1000; xa->datalen = 0; status = ahci_ata_cmd(xa); if (status == ATA_COMPLETE) @@ -538,7 +554,7 @@ ahci_cam_probe_disk(struct ahci_port *ap, struct ata_port *atx) xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target; xa->fis->device = 0; xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; - xa->timeout = hz; + xa->timeout = 1000; xa->datalen = 0; status = ahci_ata_cmd(xa); if (status == ATA_COMPLETE) @@ -560,7 +576,7 @@ ahci_cam_probe_disk(struct ahci_port *ap, struct ata_port *atx) xa->fis->command = ATA_C_SEC_FREEZE_LOCK; xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target; xa->flags = ATA_F_READ | ATA_F_PIO | ATA_F_POLL; - xa->timeout = hz; + xa->timeout = 1000; xa->datalen = 0; status = ahci_ata_cmd(xa); if (status == ATA_COMPLETE) @@ -612,13 +628,21 @@ ahci_ata_dummy_done(struct ata_xfer *xa) } /* - * Initiate a bus scan. + * Use an engineering request to initiate a target scan for devices + * behind a port multiplier. * - * An asynchronous bus scan is used to avoid reentrancy issues + * An asynchronous bus scan is used to avoid reentrancy issues. */ static void ahci_cam_rescan_callback(struct cam_periph *periph, union ccb *ccb) { + struct ahci_port *ap = ccb->ccb_h.sim_priv.entries[0].ptr; + + ap->ap_flags &= ~AP_F_SCAN_RUNNING; + if (ap->ap_flags & AP_F_SCAN_REQUESTED) { + ap->ap_flags &= ~AP_F_SCAN_REQUESTED; + ahci_cam_rescan(ap); + } kfree(ccb, M_TEMP); } @@ -628,6 +652,16 @@ ahci_cam_rescan(struct ahci_port *ap) struct cam_path *path; union ccb *ccb; int status; + int i; + + if (ap->ap_flags & AP_F_SCAN_RUNNING) { + ap->ap_flags |= AP_F_SCAN_REQUESTED; + return; + } + ap->ap_flags |= AP_F_SCAN_RUNNING; + for (i = 0; i < AHCI_MAX_PMPORTS; ++i) { + ap->ap_ata[i].at_features |= ATA_PORT_F_RESCAN; + } ccb = kmalloc(sizeof(*ccb), M_TEMP, M_WAITOK | M_ZERO); status = xpt_create_path(&path, xpt_periph, cam_sim_path(ap->ap_sim), @@ -636,14 +670,49 @@ ahci_cam_rescan(struct ahci_port *ap) return; xpt_setup_ccb(&ccb->ccb_h, path, 5); /* 5 = low priority */ - ccb->ccb_h.func_code = XPT_SCAN_BUS | XPT_FC_QUEUED; + ccb->ccb_h.func_code = XPT_ENG_EXEC | XPT_FC_QUEUED; ccb->ccb_h.cbfcnp = ahci_cam_rescan_callback; + ccb->ccb_h.sim_priv.entries[0].ptr = ap; ccb->crcn.flags = CAM_FLAG_NONE; xpt_action(ccb); /* scan is now underway */ } +static void +ahci_xpt_rescan(struct ahci_port *ap) +{ + struct cam_path *path; + union ccb *ccb; + int status; + + status = xpt_create_path(&path, xpt_periph, cam_sim_path(ap->ap_sim), + CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); + if (status != CAM_REQ_CMP) + return; + ccb = kmalloc(sizeof(*ccb), M_TEMP, M_WAITOK | M_ZERO); + xpt_setup_ccb(&ccb->ccb_h, path, 5); /* 5 = low priority */ + ccb->ccb_h.func_code = XPT_SCAN_BUS | XPT_FC_QUEUED; + ccb->ccb_h.cbfcnp = ahci_cam_rescan_callback; + ccb->ccb_h.sim_priv.entries[0].ptr = ap; + ccb->crcn.flags = CAM_FLAG_NONE; + xpt_action(ccb); +} + +#if 0 + ccb = xpt_alloc_ccb(); + status = xpt_create_path(&ccb->ccb_h.path, xpt_periph, + cam_sim_path(ap->ap_sim), + CAM_TARGET_WILDCARD, CAM_LUN_WILDCARD); + if (status == CAM_REQ_CMP) { + kprintf("RESCAN SCSI BUS %d\n", ccb->ccb_h.timeout); + ccb->crcn.flags = CAM_FLAG_NONE; + xpt_rescan(ccb); + } else { + xpt_free_ccb(ccb); + } +#endif + /* * Action function - dispatch command */ @@ -665,6 +734,10 @@ ahci_xpt_action(struct cam_sim *sim, union ccb *ccb) unit = cam_sim_unit(sim); /* + * Early failure checks. These checks do not apply to XPT_PATH_INQ, + * otherwise the bus rescan will not remove the dead devices when + * unplugging a PM. + * * For non-wildcards we have one target (0) and one lun (0), * unless we have a port multiplier. * @@ -677,9 +750,10 @@ ahci_xpt_action(struct cam_sim *sim, union ccb *ccb) * * XXX What do we do with a LUN wildcard? */ - if (ccbh->target_id != CAM_TARGET_WILDCARD) { + if (ccbh->target_id != CAM_TARGET_WILDCARD && + ccbh->func_code != XPT_PATH_INQ) { if (ap->ap_type == ATA_PORT_T_NONE) { - ccbh->status = CAM_REQ_INVALID; + ccbh->status = CAM_DEV_NOT_THERE; xpt_done(ccb); return; } @@ -703,7 +777,26 @@ ahci_xpt_action(struct cam_sim *sim, union ccb *ccb) * Switch on the meta XPT command */ switch(ccbh->func_code) { + case XPT_ENG_EXEC: + /* + * This routine is called after a port multiplier has been + * probed. + */ + ccbh->status = CAM_REQ_CMP; + ahci_port_state_machine(ap); + xpt_done(ccb); + + /* + * Rescanning the scsi bus should clean up the peripheral + * associations. + */ + ahci_xpt_rescan(ap); + break; case XPT_PATH_INQ: + /* + * This command always succeeds, otherwise the bus scan + * will not detach dead devices. + */ ccb->cpi.version_num = 1; ccb->cpi.hba_inquiry = 0; ccb->cpi.target_sprt = 0; @@ -723,13 +816,7 @@ ahci_xpt_action(struct cam_sim *sim, union ccb *ccb) ccb->cpi.protocol = PROTO_SCSI; ccb->cpi.protocol_version = SCSI_REV_2; - /* - * Non-zero target and lun ids will be used for future - * port multiplication(?). A target wildcard indicates only - * the general bus is being probed. - * - * XXX What do we do with a LUN wildcard? - */ + ccbh->status = CAM_REQ_CMP; if (ccbh->target_id != CAM_TARGET_WILDCARD) { switch(ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_SPD) { @@ -744,46 +831,17 @@ ahci_xpt_action(struct cam_sim *sim, union ccb *ccb) ccb->cpi.base_transfer_speed = 1000; break; } - ccbh->status = CAM_REQ_CMP; - - /* - * Initialize the port as needed - */ - if (ap->ap_type == ATA_PORT_T_NONE) { - if (ap->ap_probe == ATA_PROBE_NEED_HARD_RESET) - ahci_port_reset(ap, NULL, 1); - if (ap->ap_probe == ATA_PROBE_NEED_SOFT_RESET) - ahci_port_reset(ap, NULL, 0); - if (ap->ap_probe == ATA_PROBE_NEED_IDENT) - ahci_cam_probe(ap, atx); - if (ap->ap_type == ATA_PORT_T_NONE) - ccbh->status = CAM_DEV_NOT_THERE; - } - - /* - * Initialize the target (if behind a port multiplier) - * as needed. - */ - if (ap->ap_type != ATA_PORT_T_NONE && - atx && atx->at_type == ATA_PORT_T_NONE) { - if (atx->at_probe == ATA_PROBE_NEED_HARD_RESET) - ahci_port_reset(ap, atx, 1); - if (atx->at_probe == ATA_PROBE_NEED_SOFT_RESET) - ahci_port_reset(ap, atx, 0); - if (atx->at_probe == ATA_PROBE_NEED_IDENT) - ahci_cam_probe(ap, atx); - if (atx->at_type == ATA_PORT_T_NONE) - ccbh->status = CAM_DEV_NOT_THERE; - } - } else { - ccbh->status = CAM_REQ_CMP; +#if 0 + if (ap->ap_type == ATA_PORT_T_NONE) + ccbh->status = CAM_DEV_NOT_THERE; +#endif } xpt_done(ccb); break; case XPT_RESET_DEV: lwkt_serialize_enter(&ap->ap_sc->sc_serializer); if (ap->ap_type == ATA_PORT_T_NONE) { - ccbh->status = CAM_REQ_INVALID; + ccbh->status = CAM_DEV_NOT_THERE; } else { ahci_port_reset(ap, atx, 0); ccbh->status = CAM_REQ_CMP; @@ -795,9 +853,6 @@ ahci_xpt_action(struct cam_sim *sim, union ccb *ccb) lwkt_serialize_enter(&ap->ap_sc->sc_serializer); ahci_port_reset(ap, NULL, 1); lwkt_serialize_exit(&ap->ap_sc->sc_serializer); - - xpt_async(AC_BUS_RESET, ap->ap_path, NULL); - ccbh->status = CAM_REQ_CMP; xpt_done(ccb); break; @@ -855,7 +910,7 @@ ahci_xpt_poll(struct cam_sim *sim) ap = cam_sim_softc(sim); crit_enter(); lwkt_serialize_enter(&ap->ap_sc->sc_serializer); - ahci_port_intr(ap, AHCI_PREG_CI_ALL_SLOTS); + ahci_port_intr(ap); lwkt_serialize_exit(&ap->ap_sc->sc_serializer); crit_exit(); } @@ -996,8 +1051,8 @@ ahci_xpt_scsi_disk_io(struct ahci_port *ap, struct ata_port *atx, fis->flags = ATA_H2D_FLAGS_CMD; fis->command = ATA_C_FLUSH_CACHE; fis->device = 0; - if (xa->timeout < 45 * hz) - xa->timeout = 45 * hz; + if (xa->timeout < 45000) + xa->timeout = 45000; xa->datalen = 0; xa->flags = ATA_F_READ; xa->complete = ahci_ata_complete_disk_synchronize_cache; @@ -1120,7 +1175,7 @@ ahci_xpt_scsi_disk_io(struct ahci_port *ap, struct ata_port *atx, xa->data = csio->data_ptr; xa->datalen = csio->dxfer_len; xa->complete = ahci_ata_complete_disk_rw; - xa->timeout = ccbh->timeout * hz / 1000; + xa->timeout = ccbh->timeout; /* milliseconds */ if (ccbh->flags & CAM_POLLED) xa->flags |= ATA_F_POLL; break; @@ -1216,7 +1271,7 @@ ahci_xpt_scsi_atapi_io(struct ahci_port *ap, struct ata_port *atx, xa->flags = flags; xa->data = csio->data_ptr; xa->datalen = csio->dxfer_len; - xa->timeout = ccbh->timeout * hz / 1000; + xa->timeout = ccbh->timeout; /* milliseconds */ if (ccbh->flags & CAM_POLLED) xa->flags |= ATA_F_POLL; diff --git a/sys/dev/disk/ahci/ahci_dragonfly.c b/sys/dev/disk/ahci/ahci_dragonfly.c index ae4ee856a0..f2e7bb6131 100644 --- a/sys/dev/disk/ahci/ahci_dragonfly.c +++ b/sys/dev/disk/ahci/ahci_dragonfly.c @@ -139,3 +139,19 @@ ahci_resume (device_t dev) } #endif + +void +ahci_os_sleep(int ms) +{ + int ticks; + +#if 0 + if (mygd->gd_intr_nesting_level) { + DELAY(ms * 1000); + } else { +#endif + { + ticks = hz * ms / 1000 + 1; + tsleep(&ticks, 0, "ahslp", ticks); + } +} diff --git a/sys/dev/disk/ahci/ahci_dragonfly.h b/sys/dev/disk/ahci/ahci_dragonfly.h index 5058bb2f04..2937303978 100644 --- a/sys/dev/disk/ahci/ahci_dragonfly.h +++ b/sys/dev/disk/ahci/ahci_dragonfly.h @@ -54,6 +54,7 @@ #include #include #include +#include #include #include diff --git a/sys/dev/disk/ahci/ahci_pm.c b/sys/dev/disk/ahci/ahci_pm.c index 29ea8a86c0..fe091aae9e 100644 --- a/sys/dev/disk/ahci/ahci_pm.c +++ b/sys/dev/disk/ahci/ahci_pm.c @@ -46,22 +46,69 @@ ahci_pm_identify(struct ahci_port *ap) u_int32_t chipid; u_int32_t rev; u_int32_t nports; - - kprintf("%s: pm_identify\n", PORTNAME(ap)); + u_int32_t data1; + u_int32_t data2; ap->ap_pmcount = 0; ap->ap_probe = ATA_PROBE_FAILED; if (ahci_pm_read(ap, 15, 0, &chipid)) - return(EIO); + goto err; if (ahci_pm_read(ap, 15, 1, &rev)) - return(EIO); + goto err; if (ahci_pm_read(ap, 15, 2, &nports)) - return(EIO); + goto err; + nports &= 0x0000000F; /* only the low 4 bits */ ap->ap_probe = ATA_PROBE_GOOD; - kprintf("%s: port multiplier chip=%08x rev=%08x nports=%d\n", - PORTNAME(ap), chipid, rev, nports); + kprintf("%s: Port multiplier: chip=%08x rev=0x%b nports=%d\n", + PORTNAME(ap), + chipid, + rev, AHCI_PFMT_PM_REV, + nports); ap->ap_pmcount = nports; + + if (ahci_pm_read(ap, 15, AHCI_PMREG_FEA, &data1)) { + kprintf("%s: Port multiplier: Warning, " + "cannot read feature register\n", PORTNAME(ap)); + } else { + kprintf("%s: Port multiplier features: 0x%b\n", + PORTNAME(ap), + data1, + AHCI_PFMT_PM_FEA); + } + if (ahci_pm_read(ap, 15, AHCI_PMREG_FEAEN, &data2) == 0) { + kprintf("%s: Port multiplier defaults: 0x%b\n", + PORTNAME(ap), + data2, + AHCI_PFMT_PM_FEA); + } + + /* + * Turn on async notification if we support and the PM supports it. + * This allows the PM to forward async notification events to us and + * it will also generate an event for target 15 for hot-plug events + * (or is supposed to anyway). + */ + if ((ap->ap_sc->sc_cap & AHCI_REG_CAP_SSNTF) && + (data1 & AHCI_PMFEA_ASYNCNOTIFY)) { + u_int32_t serr_bits = AHCI_PREG_SERR_DIAG_N | + AHCI_PREG_SERR_DIAG_X; + data2 |= AHCI_PMFEA_ASYNCNOTIFY; + if (ahci_pm_write(ap, 15, AHCI_PMREG_FEAEN, data2)) { + kprintf("%s: Port multiplier: AsyncNotify cannot be " + "enabled\n", PORTNAME(ap)); + } else if (ahci_pm_write(ap, 15, AHCI_PMREG_EEENA, serr_bits)) { + kprintf("%s: Port mulltiplier: AsyncNotify unable " + "to enable error info bits\n", PORTNAME(ap)); + } else { + kprintf("%s: Port multiplier: AsyncNotify enabled\n", + PORTNAME(ap)); + } + } + return (0); +err: + kprintf("%s: Port multiplier cannot be identified\n", PORTNAME(ap)); + return (EIO); } /* @@ -80,8 +127,10 @@ ahci_pm_hardreset(struct ahci_port *ap, int target, int hard) struct ata_port *at; u_int32_t data; int loop; + int error = EIO; - kprintf("%s.%d: ahci_pm_hardreset\n", PORTNAME(ap), target); + kprintf("%s.%d: ahci_pm_hardreset hard=%d\n", + PORTNAME(ap), target, hard); at = &ap->ap_ata[target]; @@ -92,9 +141,11 @@ ahci_pm_hardreset(struct ahci_port *ap, int target, int hard) data = AHCI_PREG_SCTL_IPM_DISABLED; if (hard == 2) data |= AHCI_PREG_SCTL_DET_DISABLE; + if (ahci_pm_write(ap, target, AHCI_PMREG_SERR, -1)) + goto err; if (ahci_pm_write(ap, target, AHCI_PMREG_SCTL, data)) - return(EIO); - DELAY(10000); + goto err; + ahci_os_sleep(10); /* * Start transmitting COMRESET. COMRESET must be sent for at @@ -110,8 +161,15 @@ ahci_pm_hardreset(struct ahci_port *ap, int target, int hard) data |= AHCI_PREG_SCTL_SPD_ANY; } if (ahci_pm_write(ap, target, AHCI_PMREG_SCTL, data)) - return(EIO); - DELAY(1000); + goto err; + ahci_os_sleep(2); + +#if 0 + if (ahci_pm_phy_status(ap, target, &data)) { + kprintf("%s: Cannot clear phy status\n", + ATANAME(ap ,at)); + } +#endif /* * Flush any status, then clear DET to initiate negotiation. @@ -119,7 +177,7 @@ ahci_pm_hardreset(struct ahci_port *ap, int target, int hard) ahci_pm_write(ap, target, AHCI_PMREG_SERR, -1); data = AHCI_PREG_SCTL_IPM_DISABLED | AHCI_PREG_SCTL_DET_NONE; if (ahci_pm_write(ap, target, AHCI_PMREG_SCTL, data)) - return(EIO); + goto err; /* * Try to determine if there is a device on the port. @@ -128,43 +186,58 @@ ahci_pm_hardreset(struct ahci_port *ap, int target, int hard) * If we fail clear any pending status since we may have * cycled the phy and probably caused another PRCS interrupt. */ - for (loop = 30; loop; --loop) { + for (loop = 3; loop; --loop) { if (ahci_pm_read(ap, target, AHCI_PMREG_SSTS, &data)) - return(EIO); + goto err; if (data & AHCI_PREG_SSTS_DET) break; + ahci_os_sleep(100); } if (loop == 0) { kprintf("%s.%d: Port appears to be unplugged\n", PORTNAME(ap), target); - return (ENODEV); + error = ENODEV; + goto err; } /* * There is something on the port. Give the device 3 seconds * to fully negotiate. */ - for (loop = 300; loop; --loop) { + for (loop = 30; loop; --loop) { if (ahci_pm_read(ap, target, AHCI_PMREG_SSTS, &data)) - return(EIO); + goto err; if ((data & AHCI_PREG_SSTS_DET) == AHCI_PREG_SSTS_DET_DEV) break; - DELAY(10000); + ahci_os_sleep(100); } + + /* + * Clear SERR on the target so we get a new NOTIFY event if a hot-plug + * or hot-unplug occurs. + */ + ahci_pm_write(ap, target, AHCI_PMREG_SERR, -1); + + /* + * Device not detected + */ if (loop == 0) { kprintf("%s: Device may be powered down\n", PORTNAME(ap)); - return (ENODEV); + error = ENODEV; + goto err; } /* - * Device detected! Wait for it to become ready? XXX + * Device detected */ - ahci_pm_write(ap, target, AHCI_PMREG_SERR, -1); kprintf("%s.%d: Device detected data=%08x\n", PORTNAME(ap), target, data); - at->at_probe = ATA_PROBE_NEED_SOFT_RESET; - return (0); + ahci_os_sleep(100); + error = 0; +err: + at->at_probe = error ? ATA_PROBE_FAILED : ATA_PROBE_NEED_SOFT_RESET; + return (EIO); } /* @@ -183,17 +256,19 @@ ahci_pm_softreset(struct ahci_port *ap, int target) struct ahci_ccb *ccb = NULL; struct ahci_cmd_hdr *cmd_slot; u_int8_t *fis; - int rc, count; + int count; + int error; u_int32_t data; + int tried_longer; - rc = EIO; + error = EIO; at = &ap->ap_ata[target]; DPRINTF(AHCI_D_VERBOSE, "%s: soft reset\n", PORTNAME(ap)); kprintf("%s: ahci_pm_softreset\n", ATANAME(ap, at)); count = 2; - + tried_longer = 0; retry: /* * Try to clear the phy so we get a good signature, otherwise @@ -242,9 +317,23 @@ retry: ccb->ccb_xa.state = ATA_S_PENDING; ccb->ccb_xa.flags = 0; - if (ahci_poll(ccb, hz, ahci_ata_cmd_timeout) != 0 || + /* + * XXX hack to ignore IFS errors which can occur during the target + * device's reset. + * + * If an IFS error occurs the target is probably powering up, + * so we try for a longer period of time. + */ + ap->ap_flags |= AP_F_IGNORE_IFS; + ap->ap_flags &= ~(AP_F_IFS_IGNORED | AP_F_IFS_OCCURED); + + if (ahci_poll(ccb, 1000, ahci_ata_cmd_timeout) != 0 || ccb->ccb_xa.state != ATA_S_COMPLETE) { kprintf("%s: First FIS failed\n", ATANAME(ap, at)); + if (tried_longer == 0 && (ap->ap_flags & AP_F_IFS_OCCURED)) { + tried_longer = 1; + count += 4; + } if (--count) { fis[15] = 0; ahci_put_ccb(ccb); @@ -256,7 +345,7 @@ retry: /* * The device may muff the PHY up. */ - DELAY(100000); /* XXX 3000 */ + ahci_os_sleep(100); /* XXX 3ms should do it? */ /* * Prep second D2H command to read status and complete reset sequence @@ -282,7 +371,7 @@ retry: ccb->ccb_xa.state = ATA_S_PENDING; ccb->ccb_xa.flags = 0; - if (ahci_poll(ccb, hz, ahci_ata_cmd_timeout) != 0 || + if (ahci_poll(ccb, 1000, ahci_ata_cmd_timeout) != 0 || ccb->ccb_xa.state != ATA_S_COMPLETE) { kprintf("%s: Second FIS failed\n", ATANAME(ap, at)); if (--count) { @@ -293,7 +382,7 @@ retry: goto err; } - DELAY(10000); + ahci_os_sleep(10); /* * Do it again, even if we think we got a good result @@ -318,12 +407,12 @@ retry: if (ahci_port_signature_detect(ap, at) != at->at_type) { kprintf("%s: device signature unexpectedly " "changed\n", ATANAME(ap, at)); - rc = EBUSY; /* XXX */ + error = EBUSY; /* XXX */ } } - rc = 0; + error = 0; - DELAY(3000); + ahci_os_sleep(100); err: /* * Clean up the CCB. If the command failed it already went through @@ -343,14 +432,21 @@ err: * Don't kill the port if the softreset is on a port multiplier * target, that would kill all the targets! */ - if (rc) - at->at_probe = ATA_PROBE_FAILED; - else - at->at_probe = ATA_PROBE_NEED_IDENT; - kprintf("%s: ahci_pm_softreset done %d\n", ATANAME(ap, at), rc); + ap->ap_flags &= ~AP_F_IGNORE_IFS; + + /* + * Clear error status so we can detect removal + */ + if (ahci_pm_write(ap, target, AHCI_PMREG_SERR, -1)) { + kprintf("%s: ahci_pm_softreset unable to clear SERR\n", + ATANAME(ap, at)); + } + + kprintf("%s: ahci_pm_softreset done %d\n", ATANAME(ap, at), error); - return (rc); + at->at_probe = error ? ATA_PROBE_FAILED : ATA_PROBE_NEED_IDENT; + return (error); } @@ -377,6 +473,84 @@ ahci_pm_phy_status(struct ahci_port *ap, int target, u_int32_t *datap) return(error); } +int +ahci_pm_set_feature(struct ahci_port *ap, int feature, int enable) +{ + struct ata_xfer *xa; + int status; + int error; + + xa = ahci_ata_get_xfer(ap, &ap->ap_ata[15]); + + bzero(xa->fis, sizeof(*xa->fis)); + xa->fis->type = ATA_FIS_TYPE_H2D; + xa->fis->flags = ATA_H2D_FLAGS_CMD | 15; + xa->fis->command = enable ? ATA_C_SATA_FEATURE_ENA : + ATA_C_SATA_FEATURE_DIS; + xa->fis->sector_count = feature; + xa->fis->control = ATA_FIS_CONTROL_4BIT; + + xa->complete = ahci_pm_dummy_done; + xa->datalen = 0; + xa->flags = ATA_F_READ | ATA_F_POLL; + xa->timeout = 1000; + + status = ahci_ata_cmd(xa); + error = (status == ATA_COMPLETE && xa->state == ATA_S_COMPLETE) ? + 0 : EIO; + ahci_ata_put_xfer(xa); + return(error); +} + +/* + * Check that a target is still good. + */ +void +ahci_pm_check_good(struct ahci_port *ap, int target) +{ + struct ata_port *at; + u_int32_t data; + + /* + * It looks like we might have to read the EINFO register + * to allow the PM to generate a new event. + */ + if (ahci_pm_read(ap, 15, AHCI_PMREG_EINFO, &data)) { + kprintf("%s: Port multiplier EINFO could not be read\n", + PORTNAME(ap)); + } + if (ahci_pm_write(ap, target, AHCI_PMREG_SERR, -1)) { + kprintf("%s: Port multiplier: SERR could not be cleared\n", + PORTNAME(ap)); + } + + if (target == CAM_TARGET_WILDCARD) + return; + + at = &ap->ap_ata[target]; + if (ahci_pm_read(ap, target, AHCI_PMREG_SSTS, &data)) { + kprintf("%s: Unable to access PM SSTS register target %d\n", + PORTNAME(ap), target); + return; + } + if ((data & AHCI_PREG_SSTS_DET) != AHCI_PREG_SSTS_DET_DEV) { + if (at->at_probe != ATA_PROBE_FAILED) { + at->at_probe = ATA_PROBE_FAILED; + at->at_type = ATA_PORT_T_NONE; + at->at_features |= ATA_PORT_F_RESCAN; + kprintf("%s: HOTPLUG - Device removed\n", + ATANAME(ap, at)); + } + } else { + if (at->at_probe == ATA_PROBE_FAILED) { + at->at_probe = ATA_PROBE_NEED_HARD_RESET; + at->at_features |= ATA_PORT_F_RESCAN; + kprintf("%s: HOTPLUG - Device inserted\n", + ATANAME(ap, at)); + } + } +} + /* * Read a PM register */ @@ -400,7 +574,7 @@ ahci_pm_read(struct ahci_port *ap, int target, int which, u_int32_t *datap) xa->complete = ahci_pm_dummy_done; xa->datalen = 0; xa->flags = ATA_F_READ | ATA_F_POLL; - xa->timeout = hz; + xa->timeout = 1000; status = ahci_ata_cmd(xa); if (status == ATA_COMPLETE && xa->state == ATA_S_COMPLETE) { @@ -408,6 +582,7 @@ ahci_pm_read(struct ahci_port *ap, int target, int which, u_int32_t *datap) (xa->rfis.lba_mid << 16) | (xa->rfis.lba_high << 24); error = 0; } else { + kprintf("pm_read status %d xa->state %d\n", status, xa->state); *datap = 0; error = EIO; } @@ -442,7 +617,7 @@ ahci_pm_write(struct ahci_port *ap, int target, int which, u_int32_t data) xa->complete = ahci_pm_dummy_done; xa->datalen = 0; xa->flags = ATA_F_READ | ATA_F_POLL; - xa->timeout = hz; + xa->timeout = 1000; status = ahci_ata_cmd(xa); error = (status == ATA_COMPLETE && xa->state == ATA_S_COMPLETE) ? diff --git a/sys/dev/disk/ahci/atascsi.h b/sys/dev/disk/ahci/atascsi.h index e53d2423d8..10b770020b 100644 --- a/sys/dev/disk/ahci/atascsi.h +++ b/sys/dev/disk/ahci/atascsi.h @@ -23,11 +23,13 @@ struct scsi_link; * ATA commands */ +#define ATA_C_SATA_FEATURE_ENA 0x10 #define ATA_C_READDMA_EXT 0x25 #define ATA_C_READ_LOG_EXT 0x2f #define ATA_C_WRITEDMA_EXT 0x35 #define ATA_C_READ_FPDMA 0x60 #define ATA_C_WRITE_FPDMA 0x61 +#define ATA_C_SATA_FEATURE_DIS 0x90 #define ATA_C_PACKET 0xa0 #define ATA_C_ATAPI_IDENTIFY 0xa1 #define ATA_C_READDMA 0xc8 @@ -40,6 +42,15 @@ struct scsi_link; #define ATA_C_SET_FEATURES 0xef #define ATA_C_SEC_FREEZE_LOCK 0xf5 +/* + * ATA SATA FEATURES subcommands + */ +#define ATA_SATAFT_NONZDMA 0x01 /* DMA non-zero buffer offset */ +#define ATA_SATAFT_DMAAAOPT 0x02 /* DMA AA optimization */ +#define ATA_SATAFT_DEVIPS 0x03 /* Device-initiated pwr state*/ +#define ATA_SATAFT_INORDER 0x04 /* in-order data delivery */ +#define ATA_SATAFT_ASYNCNOTIFY 0x05 /* Async notification */ + /* * ATA SET FEATURES subcommands */ @@ -270,11 +281,13 @@ struct ata_port { #define ATA_PORT_F_WCACHE (1 << 0) #define ATA_PORT_F_RAHEAD (1 << 1) #define ATA_PORT_F_FRZLCK (1 << 2) +#define ATA_PORT_F_RESCAN (1 << 3) /* re-check on bus scan */ int at_probe; -#define ATA_PROBE_NEED_HARD_RESET 0 -#define ATA_PROBE_NEED_SOFT_RESET 1 -#define ATA_PROBE_NEED_IDENT 2 -#define ATA_PROBE_GOOD 3 +#define ATA_PROBE_NEED_INIT 0 +#define ATA_PROBE_NEED_HARD_RESET 1 +#define ATA_PROBE_NEED_SOFT_RESET 2 +#define ATA_PROBE_NEED_IDENT 3 +#define ATA_PROBE_GOOD 4 #define ATA_PROBE_FAILED 7 int at_ncqdepth; u_int64_t at_capacity; /* only if ATA_PORT_T_DISK */ @@ -304,6 +317,7 @@ struct ata_xfer { #define ATA_F_PACKET (1<<5) #define ATA_F_NCQ (1<<6) #define ATA_F_TIMEOUT_RUNNING (1<<7) +#define ATA_F_TIMEOUT_DESIRED (1<<8) #define ATA_FMT_FLAGS "\020" "\010TRUNNING" \ "\007NCQ" "\006PACKET" \ "\005PIO" "\004POLL" "\003NOWAIT" \