diff --git a/smartmontools/CHANGELOG b/smartmontools/CHANGELOG index d582fc824685e2c90819d17dac0e92d40ec0b11d..c56a114b30d57f2a56ebaaa30fde2c720b01fe04 100644 --- a/smartmontools/CHANGELOG +++ b/smartmontools/CHANGELOG @@ -43,6 +43,14 @@ NOTES FOR FUTURE RELEASES: see TODO file. <DEVELOPERS: ADDITIONS TO THE CHANGE LOG GO JUST BELOW HERE, PLEASE> + [CF] smartctl: Add option '-l scterc[,READTIME,WRITETIME]' to get/set + the SCT Error Recovery Control time limit (ticket #50). + + Patch was provided by Richard Gregory: + http://www.csc.liv.ac.uk/~greg/projects/erc/ + Modified for new ata_pass_through() interface. + Linux HPT fixes ommitted for now. + [CF] Fix SCT temperature table commands on big endian CPUs. [MS] drivedb.h updates: diff --git a/smartmontools/NEWS b/smartmontools/NEWS index b01a77a8378702192198291d22848f2fe2ec30c9..3bf0cd04be1d520bd748558b8353119079b1ec4d 100644 --- a/smartmontools/NEWS +++ b/smartmontools/NEWS @@ -13,6 +13,8 @@ Summary: smartmontools release 5.40 - Drive database is in a separate source file 'drivedb.h' which can be downloaded from SVN. - smartd libcap-ng support, option '-C, --capabilities'. +- smartctl option '-l scterc[,...]' to get/set the + SCT Error Recovery Control time limit. - Fix SCT temperature table commands on big endian CPUs. Date 2010-01-28 diff --git a/smartmontools/atacmds.cpp b/smartmontools/atacmds.cpp index 68f4c5d091c5405d110ea057023254061947f631..f4388e9699e8884b5b942c75425715d409814061 100644 --- a/smartmontools/atacmds.cpp +++ b/smartmontools/atacmds.cpp @@ -2310,6 +2310,96 @@ int ataSetSCTTempInterval(ata_device * device, unsigned interval, bool persisten return 0; } +// Get/Set SCT Error Recovery Control +static int ataGetSetSCTErrorRecoveryControltime(ata_device * device, unsigned type, + bool set, unsigned short & time_limit) +{ + // Check initial status + ata_sct_status_response sts; + if (ataReadSCTStatus(device, &sts)) + return -1; + + // Do nothing if other SCT command is executing + if (sts.ext_status_code == 0xffff) { + pout("Another SCT command is executing, abort Error Recovery Control\n" + "(SCT ext_status_code 0x%04x, action_code=%u, function_code=%u)\n", + sts.ext_status_code, sts.action_code, sts.function_code); + return -1; + } + + ata_sct_error_recovery_control_command cmd; memset(&cmd, 0, sizeof(cmd)); + // CAUTION: DO NOT CHANGE THIS VALUE (SOME ACTION CODES MAY ERASE DISK) + cmd.action_code = 3; // Error Recovery Control command + cmd.function_code = (set ? 1 : 2); // 1=Set timer, 2=Get timer + cmd.selection_code = type; // 1=Read timer, 2=Write timer + if (set) + cmd.time_limit = time_limit; + + // swap endian order if needed + if (isbigendian()) { + swapx(&cmd.action_code); + swapx(&cmd.function_code); + swapx(&cmd.selection_code); + swapx(&cmd.time_limit); + } + + // write command via SMART log page 0xe0 + // TODO: Debug output + ata_cmd_in in; + in.in_regs.command = ATA_SMART_CMD; + in.in_regs.lba_high = SMART_CYL_HI; in.in_regs.lba_mid = SMART_CYL_LOW; + in.in_regs.features = ATA_SMART_WRITE_LOG_SECTOR; + in.in_regs.lba_low = 0xe0; + in.set_data_out(&cmd, 1); + + if (!set) + // Time limit returned in ATA registers + in.out_needed.sector_count = in.out_needed.lba_low = true; + + ata_cmd_out out; + if (!device->ata_pass_through(in, out)) { + pout("Error Write SCT Error Recovery Control Command failed: %s\n", device->get_errmsg()); + return -1; + } + + // re-read and check SCT status + if (ataReadSCTStatus(device, &sts)) + return -1; + + if (!(sts.ext_status_code == 0 && sts.action_code == 3 && sts.function_code == (set ? 1 : 2))) { + pout("Error unexcepted SCT status 0x%04x (action_code=%u, function_code=%u)\n", + sts.ext_status_code, sts.action_code, sts.function_code); + return -1; + } + + if (!set) { + // Check whether registers are properly returned by ioctl() + if (!(out.out_regs.sector_count.is_set() && out.out_regs.lba_low.is_set())) { + // TODO: Output register support should be checked within each ata_pass_through() + // implementation before command is issued. + pout("Error SMART WRITE LOG does not return COUNT and LBA_LOW register\n"); + return -1; + } + // Return value to caller + time_limit = out.out_regs.sector_count | (out.out_regs.lba_low << 8); + } + + return 0; +} + +// Get SCT Error Recovery Control +int ataGetSCTErrorRecoveryControltime(ata_device * device, unsigned type, unsigned short & time_limit) +{ + return ataGetSetSCTErrorRecoveryControltime(device, type, false/*get*/, time_limit); +} + +// Set SCT Error Recovery Control +int ataSetSCTErrorRecoveryControltime(ata_device * device, unsigned type, unsigned short time_limit) +{ + return ataGetSetSCTErrorRecoveryControltime(device, type, true/*set*/, time_limit); +} + + // Print one self-test log entry. // Returns true if self-test showed an error. bool ataPrintSmartSelfTestEntry(unsigned testnum, unsigned char test_type, diff --git a/smartmontools/atacmds.h b/smartmontools/atacmds.h index f5b8becaacb27be686830c2e477e809e942e9de3..4aa06b67a6ca9beceb90bac73b4c8619b066b970 100644 --- a/smartmontools/atacmds.h +++ b/smartmontools/atacmds.h @@ -543,6 +543,20 @@ struct ata_sct_status_response #pragma pack() ASSERT_SIZEOF_STRUCT(ata_sct_status_response, 512); +// SCT Error Recovery Control command (send with SMART_WRITE_LOG page 0xe0) +// Table 88 of T13/1699-D Revision 6a +#pragma pack(1) +struct ata_sct_error_recovery_control_command +{ + unsigned short action_code; // 3 = Error Recovery Control + unsigned short function_code; // 1 = Set, 2 = Return + unsigned short selection_code; // 1 = Read Timer, 2 = Write Timer + unsigned short time_limit; // If set: Recovery time limit in 100ms units + unsigned short words004_255[252]; // reserved +} ATTR_PACKED; +#pragma pack() +ASSERT_SIZEOF_STRUCT(ata_sct_error_recovery_control_command, 512); + // SCT Feature Control command (send with SMART_WRITE_LOG page 0xe0) // Table 72 of T13/1699-D Revision 3f #pragma pack(1) @@ -552,8 +566,8 @@ struct ata_sct_feature_control_command unsigned short function_code; // 1 = Set, 2 = Return, 3 = Return options unsigned short feature_code; // 3 = Temperature logging interval unsigned short state; // Interval - unsigned short option_flags; // Bit 0: persistent, Bits 1-31: reserved - unsigned short words005_255[251]; // reserved + unsigned short option_flags; // Bit 0: persistent, Bits 1-15: reserved + unsigned short words005_255[251]; // reserved } ATTR_PACKED; #pragma pack() ASSERT_SIZEOF_STRUCT(ata_sct_feature_control_command, 512); @@ -727,6 +741,10 @@ int ataReadSCTTempHist(ata_device * device, ata_sct_temperature_history_table * // Set SCT temperature logging interval int ataSetSCTTempInterval(ata_device * device, unsigned interval, bool persistent); +// Get/Set SCT Error Recovery Control +int ataGetSCTErrorRecoveryControltime(ata_device * device, unsigned type, unsigned short & time_limit); +int ataSetSCTErrorRecoveryControltime(ata_device * device, unsigned type, unsigned short time_limit); + /* Enable/Disable SMART on device */ int ataEnableSmart (ata_device * device); @@ -789,6 +807,9 @@ int isSupportSelectiveSelfTest(const ata_smart_values * data); inline bool isSCTCapable(const ata_identify_device *drive) { return !!(drive->words088_255[206-88] & 0x01); } // 0x01 = SCT support +inline bool isSCTErrorRecoveryControlCapable(const ata_identify_device *drive) + { return ((drive->words088_255[206-88] & 0x09) == 0x09); } // 0x08 = SCT Error Recovery Control support + inline bool isSCTFeatureControlCapable(const ata_identify_device *drive) { return ((drive->words088_255[206-88] & 0x11) == 0x11); } // 0x10 = SCT Feature Control support diff --git a/smartmontools/ataprint.cpp b/smartmontools/ataprint.cpp index c578d873c267cfe799c1daaf75bb8bb497993b81..9b185acaedc06f2f261525c564ace18faa6bb0b1 100644 --- a/smartmontools/ataprint.cpp +++ b/smartmontools/ataprint.cpp @@ -883,6 +883,8 @@ static void ataPrintSCTCapability(const ata_identify_device *drive) if (!(sctcaps & 0x01)) return; pout("SCT capabilities: \t (0x%04x)\tSCT Status supported.\n", sctcaps); + if (sctcaps & 0x08) + pout("\t\t\t\t\tSCT Error Recovery Control supported.\n"); if (sctcaps & 0x10) pout("\t\t\t\t\tSCT Feature Control supported.\n"); if (sctcaps & 0x20) @@ -1698,6 +1700,20 @@ static int ataPrintSCTTempHist(const ata_sct_temperature_history_table * tmh) return 0; } +// Print SCT Error Recovery Control timers +static void ataPrintSCTErrorRecoveryControl(unsigned short read_timer, unsigned short write_timer) +{ + pout("SCT Error Recovery Control:\n"); + if (!read_timer) + pout(" Read: Disabled\n"); + else + pout(" Read: %6d (%0.1f seconds)\n", read_timer, read_timer/10.0); + if (!write_timer) + pout(" Write: Disabled\n"); + else + pout(" Write: %6d (%0.1f seconds)\n", write_timer, write_timer/10.0); +} + // Compares failure type to policy in effect, and either exits or // simply returns to the calling routine. @@ -2285,14 +2301,21 @@ int ataPrintMain (ata_device * device, const ata_print_options & options) } } + // SCT commands + bool sct_ok = false; + if ( options.sct_temp_sts || options.sct_temp_hist || options.sct_temp_int + || options.sct_erc_get || options.sct_erc_set ) { + if (!isSCTCapable(&drive)) { + pout("Warning: device does not support SCT Commands\n"); + failuretest(OPTIONAL_CMD, returnval|=FAILSMART); + } + else + sct_ok = true; + } + // Print SCT status and temperature history table - if (options.sct_temp_sts || options.sct_temp_hist || options.sct_temp_int) { + if (sct_ok && (options.sct_temp_sts || options.sct_temp_hist || options.sct_temp_int)) { for (;;) { - if (!isSCTCapable(&drive)) { - pout("Warning: device does not support SCT Commands\n"); - failuretest(OPTIONAL_CMD, returnval|=FAILSMART); - break; - } if (options.sct_temp_sts || options.sct_temp_hist) { ata_sct_status_response sts; ata_sct_temperature_history_table tmh; @@ -2340,6 +2363,44 @@ int ataPrintMain (ata_device * device, const ata_print_options & options) } } + // SCT Error Recovery Control + if (sct_ok && (options.sct_erc_get || options.sct_erc_set)) { + if (!isSCTErrorRecoveryControlCapable(&drive)) { + pout("Warning: device does not support SCT Error Recovery Control command\n"); + failuretest(OPTIONAL_CMD, returnval|=FAILSMART); + } + else { + bool sct_erc_get = options.sct_erc_get; + if (options.sct_erc_set) { + // Set SCT Error Recovery Control + if ( ataSetSCTErrorRecoveryControltime(device, 1, options.sct_erc_readtime ) + || ataSetSCTErrorRecoveryControltime(device, 2, options.sct_erc_writetime)) { + pout("Warning: device does not support SCT (Set) Error Recovery Control command\n"); + pout("Suggest common arguments: scterc,70,70 to enable ERC or sct,0,0 to disable\n"); + failuretest(OPTIONAL_CMD, returnval|=FAILSMART); + sct_erc_get = false; + } + else + sct_erc_get = true; + } + + if (sct_erc_get) { + // Print SCT Error Recovery Control + unsigned short read_timer, write_timer; + if ( ataGetSCTErrorRecoveryControltime(device, 1, read_timer ) + || ataGetSCTErrorRecoveryControltime(device, 2, write_timer)) { + pout("Warning: device does not support SCT (Get) Error Recovery Control command\n"); + if (options.sct_erc_set) + pout("The previous SCT (Set) Error Recovery Control command succeeded\n"); + failuretest(OPTIONAL_CMD, returnval|=FAILSMART); + } + else + ataPrintSCTErrorRecoveryControl(read_timer, write_timer); + } + pout("\n"); + } + } + // Print SATA Phy Event Counters if (options.sataphy) { unsigned nsectors = GetNumLogSectors(gplogdir, 0x11, true); diff --git a/smartmontools/ataprint.h b/smartmontools/ataprint.h index 6c8c5eef27bbd88143076ccd5ebf331e74015ea5..f6a461bdcecdbe80d27faea05ce05a47b3103bba 100644 --- a/smartmontools/ataprint.h +++ b/smartmontools/ataprint.h @@ -63,6 +63,9 @@ struct ata_print_options std::vector<ata_log_request> log_requests; bool sct_temp_sts, sct_temp_hist; + bool sct_erc_get; + bool sct_erc_set; + unsigned sct_erc_readtime, sct_erc_writetime; bool sataphy, sataphy_reset; bool smart_disable, smart_enable; @@ -97,6 +100,9 @@ struct ata_print_options smart_ext_selftest_log(0), retry_error_log(false), retry_selftest_log(false), sct_temp_sts(false), sct_temp_hist(false), + sct_erc_get(false), + sct_erc_set(false), + sct_erc_readtime(0), sct_erc_writetime(0), sataphy(false), sataphy_reset(false), smart_disable(false), smart_enable(false), smart_auto_offl_disable(false), smart_auto_offl_enable(false), diff --git a/smartmontools/smartctl.8.in b/smartmontools/smartctl.8.in index fc814c5c7a16b42e301ac90d98f2952495bcd6ff..a855c1cbb7278c9fec0647e66d87a0f56ec753bc 100644 --- a/smartmontools/smartctl.8.in +++ b/smartmontools/smartctl.8.in @@ -189,7 +189,7 @@ Prints all SMART and non-SMART information about the device. For ATA devices this is equivalent to .nf \'\-H \-i \-c \-A \-l xerror,error \-l xselftest,selftest \-l selective -\-l directory \-l scttemp \-l sataphy\'. +\-l directory \-l scttemp \-l scterc \-l sataphy\'. .fi and for SCSI, this is equivalent to .nf @@ -974,6 +974,16 @@ configured with the \'\-t scttempint,N[,p]\' option, see below. The SCT commands are specified in the proposed ATA\-8 Command Set (ACS), and are already implemented in some recent ATA\-7 disks. +.I scterc[,READTIME,WRITETIME] +\- [ATA only] [NEW EXPERIMENTAL SMARTCTL FEATURE] prints values +and descriptions of the SCT Error Recovery Control settings. These +are equivalent to TLER (as used by Western Digital), CCTL (as used +by Samsung and Hitachi) and ERC (as used by Seagate. READTIME and +WRITETIME arguments (deciseconds) set the specified values. Values of 0 +disable the feature, other values less than 65 are probably not +supported. For RAID configurations, this is typically set to +70,70 deciseconds. + .I sataphy[,reset] \- [SATA only] [NEW EXPERIMENTAL SMARTCTL FEATURE] prints values and descriptions of the SATA Phy Event Counters (General Purpose Log diff --git a/smartmontools/smartctl.cpp b/smartmontools/smartctl.cpp index ec701c2ed014338887be5af14e2d08706fcdada5..766224d60db3d0285f214a2f8b8d0de128f3fe39 100644 --- a/smartmontools/smartctl.cpp +++ b/smartmontools/smartctl.cpp @@ -126,7 +126,7 @@ void Usage (void){ " -l TYPE, --log=TYPE\n" " Show device log. TYPE: error, selftest, selective, directory[,g|s],\n" " background, sasphy[,reset], sataphy[,reset],\n" -" scttemp[sts,hist],\n" +" scttemp[sts,hist], scterc[,N,M],\n" " gplog,N[,RANGE], smartlog,N[,RANGE],\n" " xerror[,N][,error], xselftest[,N][,selftest]\n\n" " -v N,OPTION , --vendorattribute=N,OPTION (ATA)\n" @@ -178,7 +178,7 @@ static std::string getvalidarglist(char opt) case 'S': return "on, off"; case 'l': - return "error, selftest, selective, directory[,g|s], background, scttemp[sts|hist], " + return "error, selftest, selective, directory[,g|s], background, scttemp[sts|hist], scterc[,N,M], " "sasphy[,reset], sataphy[,reset], gplog,N[,RANGE], smartlog,N[,RANGE], " "xerror[,N][,error], xselftest[,N][,selftest]"; case 'P': @@ -430,6 +430,8 @@ const char * parse_options(int argc, char** argv, ataopts.sataphy = ataopts.sataphy_reset = true; } else if (!strcmp(optarg,"background")) { scsiopts.smart_background_log = true; + } else if (!strcmp(optarg,"scterc")) { + ataopts.sct_erc_get = true; } else if (!strcmp(optarg,"scttemp")) { ataopts.sct_temp_sts = ataopts.sct_temp_hist = true; } else if (!strcmp(optarg,"scttempsts")) { @@ -467,6 +469,18 @@ const char * parse_options(int argc, char** argv, else badarg = true; + } else if (!strncmp(optarg, "scterc,", sizeof("scterc,")-1)) { + unsigned rt = ~0, wt = ~0; int n = -1; + sscanf(optarg,"scterc,%u,%u%n", &rt, &wt, &n); + if (n == (int)strlen(optarg) && rt <= 999 && wt <= 999) { + ataopts.sct_erc_set = true; + ataopts.sct_erc_readtime = rt; + ataopts.sct_erc_writetime = wt; + } + else { + sprintf(extraerror, "Option -l scterc,[READTIME,WRITETIME] syntax error\n"); + badarg = true; + } } else if ( !strncmp(optarg, "gplog," , sizeof("gplog," )-1) || !strncmp(optarg, "smartlog,", sizeof("smartlog,")-1)) { unsigned logaddr = ~0U; unsigned page = 0, nsectors = 1; char sign = 0; @@ -526,6 +540,7 @@ const char * parse_options(int argc, char** argv, ataopts.smart_selective_selftest_log = true; ataopts.smart_logdir = ataopts.gp_logdir = true; ataopts.sct_temp_sts = ataopts.sct_temp_hist = true; + ataopts.sct_erc_get = true; ataopts.sataphy = true; scsiopts.smart_background_log = true; scsiopts.sasphy = true;