On some systems, attaching ahci(4) is one of the most noticeably slow parts of the boot process, since each port with no device attached takes a whole second to probe. I've made a few noises about fixing that over the years, and here's a new one.
This rearranges the device detection phase so that we poll all the ports in parallel until either all ports have detected a device or one second has elapsed. Devices are still attached in the same order, just faster. The diff doesn't really show what's going on, since it's mostly splitting up ahci_port_portreset into a few smaller pieces. VMware's emulated ahci always has 32 ports, so this cuts about 30 seconds off boot time if you use that for some reason. Qemu's has fewer ports, so it only cuts 4s or so off in my testing. Similar improvements can be expected on desktop systems since they tend to have some extra sata ports, but laptops are unlikely to see any difference. ok? diff --git a/sys/dev/ic/ahci.c b/sys/dev/ic/ahci.c index 9057d44ff78..00a162cf340 100644 --- a/sys/dev/ic/ahci.c +++ b/sys/dev/ic/ahci.c @@ -72,6 +72,7 @@ void ahci_enable_interrupts(struct ahci_port *); int ahci_init(struct ahci_softc *); int ahci_port_alloc(struct ahci_softc *, u_int); +void ahci_port_detect(struct ahci_softc *, u_int); void ahci_port_free(struct ahci_softc *, u_int); int ahci_port_init(struct ahci_softc *, u_int); @@ -80,6 +81,9 @@ int ahci_port_stop(struct ahci_port *, int); int ahci_port_clo(struct ahci_port *); int ahci_port_softreset(struct ahci_port *); int ahci_port_portreset(struct ahci_port *, int); +void ahci_port_portreset_start(struct ahci_port *); +int ahci_port_portreset_poll(struct ahci_port *); +int ahci_port_portreset_finish(struct ahci_port *, int); int ahci_port_signature(struct ahci_port *); int ahci_pmp_port_softreset(struct ahci_port *, int); int ahci_pmp_port_portreset(struct ahci_port *, int); @@ -173,7 +177,7 @@ ahci_attach(struct ahci_softc *sc) { struct atascsi_attach_args aaa; u_int32_t pi; - int i; + int i, j, done; if (sc->sc_port_start == NULL) sc->sc_port_start = ahci_default_port_start; @@ -268,6 +272,37 @@ noccc: if (ahci_port_alloc(sc, i) == ENOMEM) goto freeports; + + if (sc->sc_ports[i] != NULL) + ahci_port_portreset_start(sc->sc_ports[i]); + } + + /* + * Poll for device detection until all ports report a device, or one + * second has elapsed. + */ + for (i = 0; i < 1000; i++) { + done = 1; + for (j = 0; j < AHCI_MAX_PORTS; j++) { + if (sc->sc_ports[j] == NULL) + continue; + + if (ahci_port_portreset_poll(sc->sc_ports[j])) + done = 0; + } + + if (done) + break; + + delay(1000); + } + + /* + * Finish device detection on all ports that initialized. + */ + for (i = 0; i < AHCI_MAX_PORTS; i++) { + if (sc->sc_ports[i] != NULL) + ahci_port_detect(sc, i); } memset(&aaa, 0, sizeof(aaa)); @@ -446,7 +481,6 @@ ahci_port_alloc(struct ahci_softc *sc, u_int port) u_int32_t cmd; struct ahci_cmd_hdr *hdr; struct ahci_cmd_table *table; - const char *speed; int i, rc = ENOMEM; ap = malloc(sizeof(*ap), M_DEVBUF, M_NOWAIT | M_ZERO); @@ -594,10 +628,25 @@ nomem: /* Wait for ICC change to complete */ ahci_pwait_clr(ap, AHCI_PREG_CMD, AHCI_PREG_CMD_ICC, 1); + rc = 0; - /* Reset port */ - rc = ahci_port_portreset(ap, 1); +freeport: + if (rc != 0) + ahci_port_free(sc, port); +reterr: + return (rc); +} + +void +ahci_port_detect(struct ahci_softc *sc, u_int port) +{ + struct ahci_port *ap; + const char *speed; + int rc; + + ap = sc->sc_ports[port]; + rc = ahci_port_portreset_finish(ap, 1); switch (rc) { case ENODEV: switch (ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_DET) { @@ -667,12 +716,9 @@ nomem: ahci_write(sc, AHCI_REG_IS, 1 << port); ahci_enable_interrupts(ap); - freeport: if (rc != 0) ahci_port_free(sc, port); -reterr: - return (rc); } void @@ -1372,25 +1418,12 @@ err: } /* AHCI port reset, Section 10.4.2 */ -int -ahci_port_portreset(struct ahci_port *ap, int pmp) -{ - u_int32_t cmd, r; - int rc, s, retries = 0; - - s = splbio(); - DPRINTF(AHCI_D_VERBOSE, "%s: port reset\n", PORTNAME(ap)); - /* Save previous command register state */ - cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC; - - /* Clear ST, ignoring failure */ - ahci_port_stop(ap, 0); +void +ahci_port_comreset(struct ahci_port *ap) +{ + u_int32_t r; - /* Perform device detection */ - ahci_pwrite(ap, AHCI_PREG_SCTL, 0); -retry: - delay(10000); r = AHCI_PREG_SCTL_IPM_DISABLED | AHCI_PREG_SCTL_DET_INIT; if ((ap->ap_sc->sc_dev.dv_cfdata->cf_flags & 0x01) != 0) { DPRINTF(AHCI_D_VERBOSE, "%s: forcing GEN1\n", PORTNAME(ap)); @@ -1403,10 +1436,48 @@ retry: r |= AHCI_PREG_SCTL_DET_NONE; ahci_pwrite(ap, AHCI_PREG_SCTL, r); delay(10000); +} + +void +ahci_port_portreset_start(struct ahci_port *ap) +{ + u_int32_t cmd; + int s; + + s = splbio(); + DPRINTF(AHCI_D_VERBOSE, "%s: port reset\n", PORTNAME(ap)); + + /* Save previous command register state */ + cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC; + + /* Clear ST, ignoring failure */ + ahci_port_stop(ap, 0); - /* Wait for device to be detected and communications established */ - if (ahci_pwait_eq(ap, AHCI_PREG_SSTS, AHCI_PREG_SSTS_DET, - AHCI_PREG_SSTS_DET_DEV, 1)) { + /* Perform device detection */ + ahci_pwrite(ap, AHCI_PREG_SCTL, 0); + delay(10000); + ahci_port_comreset(ap); + splx(s); +} + +int +ahci_port_portreset_poll(struct ahci_port *ap) +{ + if ((ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_DET) != + AHCI_PREG_SSTS_DET_DEV) + return (EAGAIN); + return (0); +} + +int +ahci_port_portreset_finish(struct ahci_port *ap, int pmp) +{ + u_int32_t cmd; + int rc, s, retries = 0; + + s = splbio(); +retry: + if (ahci_port_portreset_poll(ap)) { rc = ENODEV; if (ahci_pread(ap, AHCI_PREG_SSTS) & AHCI_PREG_SSTS_DET) { /* this may be a port multiplier with no device @@ -1427,6 +1498,8 @@ retry: */ if (retries == 0) { retries = 1; + delay(10000); + ahci_port_comreset(ap); goto retry; } rc = EBUSY; @@ -1450,6 +1523,13 @@ err: } int +ahci_port_portreset(struct ahci_port *ap, int pmp) +{ + ahci_port_portreset_start(ap); + return (ahci_port_portreset_finish(ap, pmp)); +} + +int ahci_port_detect_pmp(struct ahci_port *ap) { int count, pmp_rc, rc;