While looking at implementing disk hotplug for ahci, I saw that the ahci spec
suggests putting disconnected ports in listen mode, which allows the controller
to put the phy in a low power state while still allowing it to generate 
interrupts
when a device is attached.  As a first step towards hotplug, the diff below
implements this, and also stops freeing disconnected ports, since they may 
become
active later on.

In listen mode, ahci_ata_probe will always return ATA_T_PORT_NONE, so no 
commands
will be issued to the port.  Resetting the port exits listen mode.

Some rough testing here shows that placing an otherwise active port in listen
mode reduces power draw by around 0.1W (assuming we can believe acpibat 
sensors),
so if your laptop has an extra sata phy for some reason, it might get you a 
couple
more minutes of battery life, for the cost of a few kb of memory.  I've tested 
this
on intel, amd and asmedia ahcis and haven't seen any ill effects.


Index: ahci.c
===================================================================
RCS file: /cvs/src/sys/dev/ic/ahci.c,v
retrieving revision 1.29
diff -u -p -r1.29 ahci.c
--- ahci.c      5 Mar 2017 09:55:16 -0000       1.29
+++ ahci.c      27 May 2017 06:42:36 -0000
@@ -81,6 +81,7 @@ int                   ahci_port_clo(struct ahci_port *);
 int                    ahci_port_softreset(struct ahci_port *);
 int                    ahci_port_portreset(struct ahci_port *, int);
 int                    ahci_port_signature(struct ahci_port *);
+int                    ahci_port_listenmode(struct ahci_port *);
 int                    ahci_pmp_port_softreset(struct ahci_port *, int);
 int                    ahci_pmp_port_portreset(struct ahci_port *, int);
 int                    ahci_pmp_port_probe(struct ahci_port *ap, int pmp_port);
@@ -186,6 +187,7 @@ ahci_attach(struct ahci_softc *sc)
        printf("\n");
 
        sc->sc_cap = ahci_read(sc, AHCI_REG_CAP);
+       sc->sc_cap2 = ahci_read(sc, AHCI_REG_CAP2);
        sc->sc_ncmds = AHCI_REG_CAP_NCS(sc->sc_cap);
 #ifdef AHCI_DEBUG
        if (ahcidebug & AHCI_D_VERBOSE) {
@@ -210,7 +212,7 @@ ahci_attach(struct ahci_softc *sc)
                    DEVNAME(sc), sc->sc_cap, AHCI_FMT_CAP,
                    AHCI_REG_CAP_NP(sc->sc_cap), sc->sc_ncmds, gen);
                printf("%s: extended capabilities 0x%b\n", DEVNAME(sc),
-                   ahci_read(sc, AHCI_REG_CAP2), AHCI_FMT_CAP2);
+                   sc->sc_cap2, AHCI_FMT_CAP2);
        }
 #endif
 
@@ -628,7 +631,11 @@ nomem:
                            "on port %d\n", DEVNAME(sc), port);
                        break;
                }
-               goto freeport;
+
+               rc = ahci_port_listenmode(ap);
+               if (rc != 0)
+                       goto freeport;
+               goto flushport;
 
        case EBUSY:
                printf("%s: device on port %d didn't come ready, "
@@ -676,6 +689,7 @@ nomem:
                rc = ENXIO;     /* couldn't start port */
        }
 
+flushport:
        /* Flush interrupts for port */
        ahci_pwrite(ap, AHCI_PREG_IS, ahci_pread(ap, AHCI_PREG_IS));
        ahci_write(sc, AHCI_REG_IS, 1 << port);
@@ -807,7 +821,10 @@ ahci_port_init(struct ahci_softc *sc, u_
                            "on port %d\n", DEVNAME(sc), port);
                        break;
                }
-               goto reterr;
+               rc = ahci_port_listenmode(ap);
+               if (rc == 0)
+                       goto reterr;
+               goto flushport;
 
        case EBUSY:
                printf("%s: device on port %d didn't come ready, "
@@ -857,6 +874,7 @@ ahci_port_init(struct ahci_softc *sc, u_
                rc = ENXIO;     /* couldn't start port */
        }
 
+flushport:
        /* Flush interrupts for port */
        ahci_pwrite(ap, AHCI_PREG_IS, ahci_pread(ap, AHCI_PREG_IS));
        ahci_write(sc, AHCI_REG_IS, 1 << port);
@@ -933,6 +951,39 @@ ahci_port_stop(struct ahci_port *ap, int
        return (0);
 }
 
+/* place port in listen mode (10.10.1) */
+int
+ahci_port_listenmode(struct ahci_port *ap)
+{
+       int rc;
+       uint32_t cmd, ipm;
+
+       rc = ahci_port_stop(ap, 1);
+       if (rc != 0)
+               return (rc);
+
+       cmd = ahci_pread(ap, AHCI_PREG_CMD);
+       cmd &= ~(AHCI_PREG_CMD_CLO | AHCI_PREG_CMD_PMA | AHCI_PREG_CMD_ICC);
+       ahci_pwrite(ap, AHCI_PREG_CMD, cmd);
+
+       /* disable IPM and DET */
+       ipm = AHCI_PREG_SCTL_IPM_DISABLED;
+       if (ap->ap_sc->sc_cap2 & AHCI_REG_CAP2_SDS)
+               ipm |= AHCI_PREG_SCTL_IPM_NODEVSLP;
+       ahci_pwrite(ap, AHCI_PREG_SCTL, ipm);
+
+       delay(10000);
+
+       /* disable SUD */
+       cmd = ahci_pread(ap, AHCI_PREG_CMD);
+       cmd &= ~AHCI_PREG_CMD_SUD;
+       ahci_pwrite(ap, AHCI_PREG_CMD, cmd);
+
+       ap->ap_state = AP_S_LISTEN;
+
+       return (0);
+}
+
 /* AHCI command list override -> forcibly clear TFD.STS.{BSY,DRQ} */
 int
 ahci_port_clo(struct ahci_port *ap)
@@ -1407,6 +1458,18 @@ ahci_port_portreset(struct ahci_port *ap
        s = splbio();
        DPRINTF(AHCI_D_VERBOSE, "%s: port reset\n", PORTNAME(ap));
 
+       /* exit listen mode if required */
+       if (ap->ap_state == AP_S_LISTEN) {
+               cmd = ahci_pread(ap, AHCI_PREG_CMD);
+               cmd |= AHCI_PREG_CMD_SUD | AHCI_PREG_CMD_POD |
+                   AHCI_PREG_CMD_ICC_ACTIVE;
+               ahci_pwrite(ap, AHCI_PREG_CMD, cmd);
+
+               ahci_pwait_clr(ap, AHCI_PREG_CMD, AHCI_PREG_CMD_ICC, 1);
+               ahci_flush_tfd(ap);
+               ap->ap_state = AP_S_NORMAL;
+       }
+
        /* Save previous command register state */
        cmd = ahci_pread(ap, AHCI_PREG_CMD) & ~AHCI_PREG_CMD_ICC;
 
@@ -2726,7 +2872,7 @@ ahci_ata_probe(void *xsc, int port, int 
        struct ahci_softc               *sc = xsc;
        struct ahci_port                *ap = sc->sc_ports[port];
 
-       if (ap == NULL)
+       if (ap == NULL || ap->ap_state == AP_S_LISTEN)
                return (ATA_PORT_T_NONE);
 
        if (lun != 0) {
Index: ahcireg.h
===================================================================
RCS file: /cvs/src/sys/dev/ic/ahcireg.h,v
retrieving revision 1.5
diff -u -p -r1.5 ahcireg.h
--- ahcireg.h   11 Feb 2015 07:13:44 -0000      1.5
+++ ahcireg.h   27 May 2017 06:42:36 -0000
@@ -201,6 +201,7 @@
 #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_IPM_NODEVSLP   0x400
 #define AHCI_PREG_SERR         0x30 /* SATA Error */
 #define  AHCI_PREG_SERR_ERR(_r)                ((_r) & 0xffff)
 #define  AHCI_PREG_SERR_ERR_I          (1<<0) /* Recovered Data Integrity */
Index: ahcivar.h
===================================================================
RCS file: /cvs/src/sys/dev/ic/ahcivar.h,v
retrieving revision 1.9
diff -u -p -r1.9 ahcivar.h
--- ahcivar.h   3 Dec 2014 04:33:06 -0000       1.9
+++ ahcivar.h   27 May 2017 06:42:36 -0000
@@ -85,6 +86,7 @@ struct ahci_port {
 #define AP_S_PMP_PORT_PROBE            2
 #define AP_S_ERROR_RECOVERY            3
 #define AP_S_FATAL_ERROR               4
+#define AP_S_LISTEN                    5
 
        int                     ap_pmp_ports;
        int                     ap_port;
@@ -131,6 +133,7 @@ struct ahci_softc {
        struct atascsi          *sc_atascsi;
 
        u_int32_t               sc_cap;
+       u_int32_t               sc_cap2;
 
 #ifdef AHCI_COALESCE
        u_int32_t               sc_ccc_mask;

Reply via email to