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;

Reply via email to