kernel - AHCI - enable AHCI device initiated power management
authorMatthew Dillon <dillon@apollo.backplane.com>
Fri, 12 Mar 2010 23:07:58 +0000 (15:07 -0800)
committerMatthew Dillon <dillon@apollo.backplane.com>
Fri, 12 Mar 2010 23:07:58 +0000 (15:07 -0800)
* Add hw.ahci sysctls to allow the power management state to be set and
  monitored.

* Issue device features command when aggressive power management is
  enabled to turn on device-initiated power management, and turn it off
  when power management is set to none.

* Currently does not try do power management for devices behind a
  PM.

Submitted-by: Johannes Hofmann <johannes.hofmann@gmx.de>
sys/dev/disk/ahci/ahci.c
sys/dev/disk/ahci/ahci.h
sys/dev/disk/ahci/ahci_dragonfly.c
sys/dev/disk/ahci/ahci_pm.c

index 54e097d..1b8bf29 100644 (file)
@@ -533,6 +533,8 @@ ahci_port_link_pwr_mgmt(struct ahci_port *ap, int link_pwr_mgmt)
                kprintf("%s: enabling aggressive link power management.\n",
                        PORTNAME(ap));
 
+               ap->link_pwr_mgmt = link_pwr_mgmt;
+
                ap->ap_intmask &= ~AHCI_PREG_IE_PRCE;
                ahci_port_interrupt_enable(ap);
 
@@ -540,23 +542,36 @@ ahci_port_link_pwr_mgmt(struct ahci_port *ap, int link_pwr_mgmt)
                sctl &= ~(AHCI_PREG_SCTL_IPM_DISABLED);
                ahci_pwrite(ap, AHCI_PREG_SCTL, sctl);
 
+               /*
+                * Enable device initiated link power management for
+                * directly attached devices that support it.
+                */
+               if (ap->ap_type != ATA_PORT_T_PM &&
+                   ap->ap_ata[0]->at_identify.satafsup & (1 << 3)) {
+                       if (ahci_set_feature(ap, NULL, ATA_SATAFT_DEVIPS, 1))
+                               kprintf("%s: Could not enable device initiated "
+                                   "link power management.\n",
+                                   PORTNAME(ap));
+               }
+
                cmd = ahci_pread(ap, AHCI_PREG_CMD);
                cmd |= AHCI_PREG_CMD_ASP;
                cmd |= AHCI_PREG_CMD_ALPE;
                ahci_pwrite(ap, AHCI_PREG_CMD, cmd);
 
-               ap->link_pwr_mgmt = link_pwr_mgmt;
-
        } else if (link_pwr_mgmt == AHCI_LINK_PWR_MGMT_MEDIUM &&
                   (ap->ap_sc->sc_cap & AHCI_REG_CAP_PSC)) {
                kprintf("%s: enabling medium link power management.\n",
                        PORTNAME(ap));
 
+               ap->link_pwr_mgmt = link_pwr_mgmt;
+
                ap->ap_intmask &= ~AHCI_PREG_IE_PRCE;
                ahci_port_interrupt_enable(ap);
 
                sctl = ahci_pread(ap, AHCI_PREG_SCTL);
-               sctl &= ~(AHCI_PREG_SCTL_IPM_DISABLED);
+               sctl |= AHCI_PREG_SCTL_IPM_DISABLED;
+               sctl &= ~AHCI_PREG_SCTL_IPM_NOPARTIAL;
                ahci_pwrite(ap, AHCI_PREG_SCTL, sctl);
 
                cmd = ahci_pread(ap, AHCI_PREG_CMD);
@@ -564,14 +579,16 @@ ahci_port_link_pwr_mgmt(struct ahci_port *ap, int link_pwr_mgmt)
                cmd |= AHCI_PREG_CMD_ALPE;
                ahci_pwrite(ap, AHCI_PREG_CMD, cmd);
 
-               ap->link_pwr_mgmt = link_pwr_mgmt;
-
        } else if (link_pwr_mgmt == AHCI_LINK_PWR_MGMT_NONE) {
                kprintf("%s: disabling link power management.\n",
                        PORTNAME(ap));
 
+               /* Disable device initiated link power management */
+               if (ap->ap_type != ATA_PORT_T_PM &&
+                   ap->ap_ata[0]->at_identify.satafsup & (1 << 3))
+                       ahci_set_feature(ap, NULL, ATA_SATAFT_DEVIPS, 0);
+
                cmd = ahci_pread(ap, AHCI_PREG_CMD);
-               cmd |= AHCI_PREG_CMD_ASP;
                cmd &= ~(AHCI_PREG_CMD_ALPE | AHCI_PREG_CMD_ASP);
                ahci_pwrite(ap, AHCI_PREG_CMD, cmd);
 
@@ -584,7 +601,8 @@ ahci_port_link_pwr_mgmt(struct ahci_port *ap, int link_pwr_mgmt)
                ahci_os_sleep(1000);
                ahci_os_lock_port(ap);
 
-               ahci_pwrite(ap, AHCI_PREG_SERR, AHCI_PREG_SERR_DIAG_N);
+               ahci_pwrite(ap, AHCI_PREG_SERR,
+                   AHCI_PREG_SERR_DIAG_N | AHCI_PREG_SERR_DIAG_W);
                ahci_pwrite(ap, AHCI_PREG_IS, AHCI_PREG_IS_PRCS);
 
                ap->ap_intmask |= AHCI_PREG_IE_PRCE;
@@ -599,6 +617,26 @@ ahci_port_link_pwr_mgmt(struct ahci_port *ap, int link_pwr_mgmt)
        ahci_os_unlock_port(ap);
 }
 
+/*
+ * Return current link power state.
+ */
+int
+ahci_port_link_pwr_state(struct ahci_port *ap)
+{
+       uint32_t r;
+
+       r = ahci_pread(ap, AHCI_PREG_SSTS);
+       switch (r & SATA_PM_SSTS_IPM) {
+       case SATA_PM_SSTS_IPM_ACTIVE:
+               return 1;
+       case SATA_PM_SSTS_IPM_PARTIAL:
+               return 2;
+       case SATA_PM_SSTS_IPM_SLUMBER:
+               return 3;
+       default:
+               return 0;
+       }
+}
 
 /*
  * Run the port / target state machine from a main context.
@@ -2273,6 +2311,13 @@ ahci_port_intr(struct ahci_port *ap, int blockable)
                ap->ap_sactive, ahci_pread(ap, AHCI_PREG_SACT));
 #endif
 
+       /* ignore AHCI_PREG_IS_PRCS when link power management is on */
+       if (ap->link_pwr_mgmt != AHCI_LINK_PWR_MGMT_NONE) {
+               is &= ~AHCI_PREG_IS_PRCS;
+               ahci_pwrite(ap, AHCI_PREG_SERR,
+                   AHCI_PREG_SERR_DIAG_N | AHCI_PREG_SERR_DIAG_W);
+       }
+
        if (is & AHCI_PREG_IS_TFES) {
                /*
                 * Command failed (blockable).
@@ -2526,11 +2571,6 @@ finish_error:
         *           and restarted.
         */
 
-       /* ignore AHCI_PREG_IS_PRCS when link power management is on */
-       if (ap->link_pwr_mgmt != AHCI_LINK_PWR_MGMT_NONE) {
-               is &= ~AHCI_PREG_IS_PRCS;
-       }
-
        if (is & (AHCI_PREG_IS_PCS | AHCI_PREG_IS_PRCS)) {
                kprintf("%s: Transient Errors: %b\n",
                        PORTNAME(ap), is, AHCI_PFMT_IS);
@@ -3431,3 +3471,35 @@ static void
 ahci_empty_done(struct ahci_ccb *ccb)
 {
 }
+
+int
+ahci_set_feature(struct ahci_port *ap, struct ata_port *atx, int feature, int enable)
+{
+       struct ata_port *at;
+       struct ata_xfer *xa;
+       int error;
+
+       at = atx ? atx : ap->ap_ata[0];
+
+       xa = ahci_ata_get_xfer(ap, atx);
+
+       xa->fis->type = ATA_FIS_TYPE_H2D;
+       xa->fis->flags = ATA_H2D_FLAGS_CMD | at->at_target;
+       xa->fis->command = ATA_C_SET_FEATURES;
+       xa->fis->features = 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_dummy_done;
+       xa->datalen = 0;
+       xa->flags = ATA_F_POLL;
+       xa->timeout = 1000;
+
+       if (ahci_ata_cmd(xa) == ATA_S_COMPLETE)
+               error = 0;
+       else
+               error = EIO;
+       ahci_ata_put_xfer(xa);
+       return(error);
+}
index 7d56db7..e1f7c0c 100644 (file)
@@ -507,6 +507,7 @@ void        ahci_port_state_machine(struct ahci_port *ap, int initial);
 void   ahci_port_free(struct ahci_softc *, u_int);
 int    ahci_port_reset(struct ahci_port *, struct ata_port *at, int);
 void   ahci_port_link_pwr_mgmt(struct ahci_port *, int link_pwr_mgmt);
+int    ahci_port_link_pwr_state(struct ahci_port *);
 
 u_int32_t ahci_read(struct ahci_softc *, bus_size_t);
 void   ahci_write(struct ahci_softc *, bus_size_t, u_int32_t);
@@ -522,6 +523,8 @@ int ahci_port_start(struct ahci_port *ap);
 int    ahci_port_stop(struct ahci_port *ap, int stop_fis_rx);
 int    ahci_port_clo(struct ahci_port *ap);
 void   ahci_flush_tfd(struct ahci_port *ap);
+int    ahci_set_feature(struct ahci_port *ap, struct ata_port *atx,
+                       int feature, int enable);
 
 int    ahci_cam_attach(struct ahci_port *ap);
 void   ahci_cam_changed(struct ahci_port *ap, struct ata_port *at, int found);
@@ -535,7 +538,6 @@ int ahci_ata_cmd(struct ata_xfer *xa);
 int     ahci_pm_port_probe(struct ahci_port *ap, int);
 int    ahci_pm_port_init(struct ahci_port *ap, struct ata_port *at);
 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);
index 633fdb7..407827c 100644 (file)
@@ -47,7 +47,7 @@ u_int32_t AhciNoFeatures = 0;
 static int     ahci_probe (device_t dev);
 static int     ahci_attach (device_t dev);
 static int     ahci_detach (device_t dev);
-static int     ahci_systcl_link_pwr_mgmt (SYSCTL_HANDLER_ARGS);
+static int     ahci_sysctl_link_pwr_mgmt (SYSCTL_HANDLER_ARGS);
 #if 0
 static int     ahci_shutdown (device_t dev);
 static int     ahci_suspend (device_t dev);
@@ -149,7 +149,7 @@ ahci_detach (device_t dev)
 }
 
 static int
-ahci_systcl_link_pwr_mgmt (SYSCTL_HANDLER_ARGS)
+ahci_sysctl_link_pwr_mgmt (SYSCTL_HANDLER_ARGS)
 {
        struct ahci_port *ap = arg1;
        int error, link_pwr_mgmt;
@@ -163,6 +163,22 @@ ahci_systcl_link_pwr_mgmt (SYSCTL_HANDLER_ARGS)
        return 0;
 }
 
+static int
+ahci_sysctl_link_pwr_state (SYSCTL_HANDLER_ARGS)
+{
+       struct ahci_port *ap = arg1;
+       const char *state_names[] = {"unknown", "active", "partial", "slumber"};
+       char buf[16];
+       int state;
+
+       state = ahci_port_link_pwr_state(ap);
+       if (state < 0 || state >= sizeof(state_names) / sizeof(state_names[0]))
+               state = 0;
+
+       ksnprintf(buf, sizeof(buf), "%s", state_names[state]);
+       return sysctl_handle_string(oidp, buf, sizeof(buf), req);
+}
+
 #if 0
 
 static int
@@ -240,9 +256,14 @@ ahci_os_start_port(struct ahci_port *ap)
                SYSCTL_ADD_PROC(&ap->sysctl_ctx,
                        SYSCTL_CHILDREN(ap->sysctl_tree), OID_AUTO,
                        "link_pwr_mgmt", CTLTYPE_INT | CTLFLAG_RW, ap, 0,
-                       ahci_systcl_link_pwr_mgmt, "I",
-                       "Link power management "
+                       ahci_sysctl_link_pwr_mgmt, "I",
+                       "Link power management policy "
                        "(0 = disabled, 1 = medium, 2 = aggressive)");
+               SYSCTL_ADD_PROC(&ap->sysctl_ctx,
+                       SYSCTL_CHILDREN(ap->sysctl_tree), OID_AUTO,
+                       "link_pwr_state", CTLTYPE_STRING | CTLFLAG_RD, ap, 0,
+                       ahci_sysctl_link_pwr_state, "A",
+                       "Link power management state");
 
        }
 
index 296d8bd..31a62a3 100644 (file)
@@ -738,34 +738,6 @@ 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 error;
-
-       xa = ahci_ata_get_xfer(ap, ap->ap_ata[15]);
-
-       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_POLL;
-       xa->timeout = 1000;
-
-       if (ahci_ata_cmd(xa) == ATA_S_COMPLETE)
-               error = 0;
-       else
-               error = EIO;
-       ahci_ata_put_xfer(xa);
-       return(error);
-}
-
 /*
  * Check that a target is still good.
  */