From 795adb22603dc0509b704b8e4e5c1c82c888256a Mon Sep 17 00:00:00 2001 From: Matthew Dillon Date: Fri, 12 Mar 2010 15:07:58 -0800 Subject: [PATCH] kernel - AHCI - enable AHCI device initiated power management * 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 --- sys/dev/disk/ahci/ahci.c | 96 ++++++++++++++++++++++++++---- sys/dev/disk/ahci/ahci.h | 4 +- sys/dev/disk/ahci/ahci_dragonfly.c | 29 +++++++-- sys/dev/disk/ahci/ahci_pm.c | 28 --------- 4 files changed, 112 insertions(+), 45 deletions(-) diff --git a/sys/dev/disk/ahci/ahci.c b/sys/dev/disk/ahci/ahci.c index 54e097d64d..1b8bf294b0 100644 --- a/sys/dev/disk/ahci/ahci.c +++ b/sys/dev/disk/ahci/ahci.c @@ -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); +} diff --git a/sys/dev/disk/ahci/ahci.h b/sys/dev/disk/ahci/ahci.h index 7d56db7a94..e1f7c0cb10 100644 --- a/sys/dev/disk/ahci/ahci.h +++ b/sys/dev/disk/ahci/ahci.h @@ -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); diff --git a/sys/dev/disk/ahci/ahci_dragonfly.c b/sys/dev/disk/ahci/ahci_dragonfly.c index 633fdb7107..407827c466 100644 --- a/sys/dev/disk/ahci/ahci_dragonfly.c +++ b/sys/dev/disk/ahci/ahci_dragonfly.c @@ -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"); } diff --git a/sys/dev/disk/ahci/ahci_pm.c b/sys/dev/disk/ahci/ahci_pm.c index 296d8bd581..31a62a3b52 100644 --- a/sys/dev/disk/ahci/ahci_pm.c +++ b/sys/dev/disk/ahci/ahci_pm.c @@ -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. */ -- 2.41.0