From 0b14149c8ef296ac0e13ee26e7fd546515905a31 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Miguel?= <38981464+jmbass@users.noreply.github.com> Date: Fri, 28 Aug 2026 16:48:33 +0200 Subject: [PATCH] Add option to disable HDD power saving Some USB hard disks enter aggressive power-saving states while a game is idle. Their multi-second wake latency can exceed the Wii USB stack timeout and leave games frozen or on a black screen at the next load transition. Add an opt-in setting under Hard Drive Settings. During the next IOS58 startup, probe the mounted USB disk with ATA CHECK POWER MODE. If the bridge supports SAT pass-through, independently disable ATA Advanced Power Management and the ATA standby timer before USB Loader GX reloads into cIOS. Keep the option off by default. Target only the disk already mounted by the loader, report full, partial, unsupported, and transport-error outcomes, and never write sectors, partitions, bridge EEPROM, or firmware. The ATA settings are volatile and reset when the HDD loses power. This is intended for rotating SATA HDDs behind compatible USB-to-SATA bridges. Disabling power saving may keep a mechanical disk spinning continuously, increasing power use, temperature, noise, and accumulated running time. Tested after a full Wii and HDD cold boot with: - Maxone USB 3.0 1 TB enclosure - JMicron JMS578 bridge (152d:0578, firmware 0215) - HGST HTS541010A9E680 1 TB 5400 RPM HDD (firmware JA0OA560) The drive restores ATA APM level 1 after power loss. With this setting enabled, previously reproducible idle/load freezes did not recur in The Legend of Zelda: Twilight Princess or Guitar Hero III. A similar Maxone 2.5-inch Wii drive freeze at load transitions was reported here: https://www.reddit.com/r/WiiHacking/comments/1rj17al/game_freezes_at_transition_help/ --- source/StartUpProcess.cpp | 39 ++++++++++++++++++++ source/settings/CSettings.cpp | 7 ++++ source/settings/CSettings.h | 1 + source/settings/menus/HardDriveSM.cpp | 18 ++++++++++ source/usbloader/usbstorage_libogc.c | 52 ++++++++++++++++++++++++++- source/usbloader/usbstorage_libogc.h | 13 +++++++ 6 files changed, 129 insertions(+), 1 deletion(-) diff --git a/source/StartUpProcess.cpp b/source/StartUpProcess.cpp index 9fdde6f7..26590c2e 100644 --- a/source/StartUpProcess.cpp +++ b/source/StartUpProcess.cpp @@ -19,6 +19,7 @@ #include "settings/CGameCategories.hpp" #include "settings/GameTitles.h" #include "usbloader/usbstorage2.h" +#include "usbloader/usbstorage_libogc.h" #include "usbloader/MountGamePartition.h" #include "usbloader/GameBooter.hpp" #include "usbloader/GameList.h" @@ -366,6 +367,44 @@ int StartUpProcess::Execute(bool quickGameBoot, bool isBadBoot) gprintf("\tLoading game categories...%s\n", GameCategories.Load(Settings.ConfigPath) ? "done" : "failed"); gprintf("\tLoading cached titles...%s\n", GameTitles.ReadCachedTitles(Settings.titlestxt_path) ? "done" : "failed (using default)"); + // Apply volatile ATA power settings while the IOS58/libogc USB backend is + // still mounted. The later cIOS backend does not expose raw SCSI commands. + if (Settings.DisableHDDPowerSaving && USBSuccess) + { + if (IOS_GetVersion() == 58) + { + SetTextf("Disabling HDD power saving\n"); + s32 result = USBStorage_OGC_DisablePowerSaving(); + if (result == USBSTORAGE_POWER_ALL_DISABLED) + { + SetTextf("HDD power saving disabled\n"); + gprintf("Disable HDD power saving: APM and standby timer disabled\n"); + } + else if (result > 0) + { + SetTextf("HDD power saving partially disabled\n"); + gprintf("Disable HDD power saving: APM %s, standby timer %s\n", + (result & USBSTORAGE_POWER_APM_DISABLED) ? "disabled" : "failed", + (result & USBSTORAGE_POWER_STANDBY_DISABLED) ? "disabled" : "failed"); + } + else if (result == 0) + { + SetTextf("HDD power saving not supported\n"); + gprintf("Disable HDD power saving: ATA power commands not supported\n"); + } + else + { + SetTextf("HDD power saving command failed\n"); + gprintf("Disable HDD power saving: transport error (%i)\n", result); + } + } + else + { + SetTextf("HDD power saving requires IOS58\n"); + gprintf("Disable HDD power saving: skipped (requires IOS58 USB backend)\n"); + } + } + // Some settings need to be enabled to boot directly into games gprintf("Quick game boot: %s\n", quickGameBoot ? "yes" : "no"); if (quickGameBoot) diff --git a/source/settings/CSettings.cpp b/source/settings/CSettings.cpp index f1fffef7..d25bd4d3 100644 --- a/source/settings/CSettings.cpp +++ b/source/settings/CSettings.cpp @@ -156,6 +156,7 @@ void CSettings::SetDefault() MultiplePartitions = OFF; BlockIOSReload = AUTO; USBPort = 0; + DisableHDDPowerSaving = OFF; WSFactor = 0.8f; //actually should be 0.75 for real widescreen FontScaleFactor = 0.8f; //it's a work around to not have to change ALL fonts now ClockFontScaleFactor = 1.0f; // Scale of 1 to prevent misaligned clock. @@ -414,6 +415,7 @@ bool CSettings::Save() fprintf(file, "SilentHomeMenu = %d\n", SilentHomeMenu); fprintf(file, "MultiplePartitions = %d\n", MultiplePartitions); fprintf(file, "USBPort = %d\n", USBPort); + fprintf(file, "DisableHDDPowerSaving = %d\n", DisableHDDPowerSaving); fprintf(file, "BlockIOSReload = %d\n", BlockIOSReload); fprintf(file, "WSFactor = %0.3f\n", WSFactor); fprintf(file, "FontScaleFactor = %0.3f\n", FontScaleFactor); @@ -829,6 +831,11 @@ bool CSettings::SetSetting(char *name, char *value) USBPort = atoi(value); return true; } + else if (strcmp(name, "DisableHDDPowerSaving") == 0) + { + DisableHDDPowerSaving = atoi(value); + return true; + } else if (strcmp(name, "patchcountrystrings") == 0) { patchcountrystrings = atoi(value); diff --git a/source/settings/CSettings.h b/source/settings/CSettings.h index 17004b1c..fa06f6d7 100644 --- a/source/settings/CSettings.h +++ b/source/settings/CSettings.h @@ -159,6 +159,7 @@ class CSettings short SilentHomeMenu; short MultiplePartitions; short USBPort; + short DisableHDDPowerSaving; short BlockIOSReload; u32 InstallPartitions; u32 ParentalBlocks; diff --git a/source/settings/menus/HardDriveSM.cpp b/source/settings/menus/HardDriveSM.cpp index 37b5336c..05f83b4a 100644 --- a/source/settings/menus/HardDriveSM.cpp +++ b/source/settings/menus/HardDriveSM.cpp @@ -88,6 +88,7 @@ HardDriveSM::HardDriveSM() if (strncmp(Settings.ConfigPath, "sd", 2) == 0) Options->SetName(Idx++, "%s", tr( "SD Card Mode" )); Options->SetName(Idx++, "%s", tr( "USB Port" )); + Options->SetName(Idx++, "%s", tr( "Disable HDD Power Saving" )); Options->SetName(Idx++, "%s", tr( "Install Directories" )); Options->SetName(Idx++, "%s", tr( "Game Split Size" )); Options->SetName(Idx++, "%s", tr( "Install Partitions" )); @@ -185,6 +186,9 @@ void HardDriveSM::SetOptionValues() else Options->SetValue(Idx++, "%i", NewSettingsUSBPort); + //! Settings: Disable HDD power saving + Options->SetValue(Idx++, "%s", tr( OnOffText[Settings.DisableHDDPowerSaving] )); + //! Settings: Install directories Options->SetValue(Idx++, "%s", tr( InstallToText[Settings.InstallToDir] )); @@ -274,6 +278,20 @@ int HardDriveSM::GetMenuInternal() NewSettingsUSBPort = 0; } + //! Settings: Disable HDD power saving + else if (ret == ++Idx) + { + if (++Settings.DisableHDDPowerSaving >= MAX_ON_OFF) + Settings.DisableHDDPowerSaving = 0; + + if (Settings.DisableHDDPowerSaving == ON) + { + WindowPrompt(0, + tr("Applied on the next launch after testing ATA support. This may keep a mechanical HDD spinning, increasing power use, heat, noise, and running time."), + tr("OK")); + } + } + //! Settings: Install directories else if (ret == ++Idx) { diff --git a/source/usbloader/usbstorage_libogc.c b/source/usbloader/usbstorage_libogc.c index 977f99df..0847e457 100644 --- a/source/usbloader/usbstorage_libogc.c +++ b/source/usbloader/usbstorage_libogc.c @@ -234,7 +234,7 @@ static s32 __send_cbw(usbstorage_handle *dev, u8 lun, u32 len, u8 flags, const u __stwbrx(cbw_buffer, 8, len); cbw_buffer[12] = flags; cbw_buffer[13] = lun; - cbw_buffer[14] = (cbLen > 6 ? 10 : 6); + cbw_buffer[14] = (cbLen > 6 ? cbLen : 6); memcpy(cbw_buffer + 15, cb, cbLen); @@ -1013,6 +1013,56 @@ int USBStorage_OGC_ioctl(int request, ...) return retval; } +static s32 __usbstorage_ogc_ata_nodata(u8 ata_command, u8 feature) +{ + raw_device_command rdc; + s32 retval; + + memset(&rdc, 0, sizeof(rdc)); + rdc.command[0] = 0x85; /* ATA PASS-THROUGH (16) */ + rdc.command[1] = 0x06; /* Non-data protocol */ + rdc.command[2] = 0x0c; + rdc.command[4] = feature; + rdc.command[14] = ata_command; + rdc.command_length = sizeof(rdc.command); + rdc.flags = B_RAW_DEVICE_DATA_IN; + + retval = USBStorage_OGC_ioctl(B_RAW_DEVICE_COMMAND, &rdc); + if (retval < 0) + return retval; + + return rdc.scsi_status == 0 ? USBSTORAGE_OK : USBSTORAGE_ESTATUS; +} + +s32 USBStorage_OGC_DisablePowerSaving() +{ + /* CHECK POWER MODE does not change drive state. */ + s32 retval = __usbstorage_ogc_ata_nodata(0xe5, 0x00); + if (retval == USBSTORAGE_ESTATUS) + return 0; + if (retval < 0) + return retval; + + s32 result = 0; + s32 transport_error = 0; + + /* SET FEATURES, DISABLE APM. */ + retval = __usbstorage_ogc_ata_nodata(0xef, 0x85); + if (retval == USBSTORAGE_OK) + result |= USBSTORAGE_POWER_APM_DISABLED; + else if (retval != USBSTORAGE_ESTATUS) + transport_error = retval; + + /* IDLE with sector count zero disables the standby timer. */ + retval = __usbstorage_ogc_ata_nodata(0xe3, 0x00); + if (retval == USBSTORAGE_OK) + result |= USBSTORAGE_POWER_STANDBY_DISABLED; + else if (retval != USBSTORAGE_ESTATUS && transport_error == 0) + transport_error = retval; + + return result != 0 ? result : transport_error; +} + DISC_INTERFACE __io_usbstorage_ogc = { DEVICE_TYPE_WII_USB, FEATURE_MEDIUM_CANREAD | FEATURE_MEDIUM_CANWRITE | FEATURE_WII_USB, diff --git a/source/usbloader/usbstorage_libogc.h b/source/usbloader/usbstorage_libogc.h index a9fa67b1..c0760b50 100644 --- a/source/usbloader/usbstorage_libogc.h +++ b/source/usbloader/usbstorage_libogc.h @@ -28,6 +28,19 @@ s32 USBStorage_OGC_Read(usbstorage_handle *dev, u8 lun, u32 sector, u16 n_sector s32 USBStorage_OGC_Write(usbstorage_handle *dev, u8 lun, u32 sector, u16 n_sectors, const u8 *buffer); s32 USBStorage_OGC_StartStop(usbstorage_handle *dev, u8 lun, u8 lo_ej, u8 start, u8 imm); +#define USBSTORAGE_POWER_APM_DISABLED (1 << 0) +#define USBSTORAGE_POWER_STANDBY_DISABLED (1 << 1) +#define USBSTORAGE_POWER_ALL_DISABLED (USBSTORAGE_POWER_APM_DISABLED | USBSTORAGE_POWER_STANDBY_DISABLED) + +/** + * Disable ATA Advanced Power Management and the ATA standby timer on the + * currently mounted USB/SAT disk. A non-mutating ATA command is used to check + * pass-through support first. Returns a USBSTORAGE_POWER_* bitmask, zero when + * unsupported, or a negative transport error. The settings are volatile and + * do not write sectors or bridge firmware. + */ +s32 USBStorage_OGC_DisablePowerSaving(); + extern DISC_INTERFACE __io_usbstorage_ogc; #ifdef __cplusplus