From d20185ac96c4b50ed4de7098a101a31f2b140b82 Mon Sep 17 00:00:00 2001 From: Cástor Muñoz Date: Tue, 9 Dec 2014 19:38:47 +0100 Subject: iPod Classic: reads HDD S.M.A.R.T. data Adds ata_read_smart() function to storage ATA driver, current SMART data can be displayed and optionally written to hard disk using System->Debug menu. Change-Id: Ie8817bb311d5d956df2f0fbfaf554e2d53e89a93 --- apps/debug_menu.c | 202 +++++++++++++++++++++ apps/gui/list.h | 4 + firmware/export/ata.h | 106 +++++++++++ firmware/export/config/ipod6g.h | 2 + .../target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c | 59 ++++++ 5 files changed, 373 insertions(+) diff --git a/apps/debug_menu.c b/apps/debug_menu.c index e602b713c3..2b9702299b 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -1507,6 +1507,205 @@ static int disk_callback(int btn, struct gui_synclist *lists) #endif /* HAVE_ATA_DMA */ return btn; } + +#ifdef HAVE_ATA_SMART +static struct ata_smart_values *smart_data = NULL; + +static const char * ata_smart_get_attr_name(unsigned char id) +{ + switch (id) + { + case 1: return "Raw Read Error Rate"; + case 2: return "Throughput Performance"; + case 3: return "Spin-Up Time"; + case 4: return "Start/Stop Count"; + case 5: return "Reallocated Sector Count"; + case 7: return "Seek Error Rate"; + case 8: return "Seek Time Performance"; + case 9: return "Power-On Hours Count"; + case 10: return "Spin-Up Retry Count"; + case 12: return "Power Cycle Count"; + case 192: return "Power-Off Retract Count"; + case 193: return "Load/Unload Cycle Count"; + case 194: return "HDA Temperature"; + case 196: return "Reallocated Event Count"; + case 197: return "Current Pending Sector Count"; + case 198: return "Uncorrectable Sector Count"; + case 199: return "UltraDMA CRC Error Count"; + case 220: return "Disk Shift"; + case 222: return "Loaded Hours"; + case 223: return "Load/Unload Retry Count"; + case 224: return "Load Friction"; + case 226: return "Load-In Time"; + case 240: return "Transfer Error Rate"; /* Fujitsu */ + /*case 240: return "Head Flying Hours";*/ + default: return "Unknown Attribute"; + } +}; + +static int ata_smart_get_attr_rawfmt(unsigned char id) +{ + switch (id) + { + case 3: /* Spin-up time */ + return RAWFMT_RAW16_OPT_AVG16; + + case 5: /* Reallocated sector count */ + case 196: /* Reallocated event count */ + return RAWFMT_RAW16_OPT_RAW16; + + case 190: /* Airflow Temperature */ + case 194: /* HDA Temperature */ + return RAWFMT_TEMPMINMAX; + + default: + return RAWFMT_RAW48; + } +}; + +static int ata_smart_attr_to_string( + struct ata_smart_attribute *attr, char *str, int size) +{ + uint16_t w[3]; /* 3 words to store 6 bytes of raw data */ + char buf[size]; /* temp string to store attribute data */ + int len, slen; + int id = attr->id; + + if (id == 0) + return 0; /* null attribute */ + + /* align and convert raw data */ + memcpy(w, attr->raw, 6); + w[0] = letoh16(w[0]); + w[1] = letoh16(w[1]); + w[2] = letoh16(w[2]); + + len = snprintf(buf, size, ": %u,%u ", attr->current, attr->worst); + + switch (ata_smart_get_attr_rawfmt(id)) + { + case RAWFMT_RAW16_OPT_RAW16: + len += snprintf(buf+len, size-len, "%u", w[0]); + if ((w[1] || w[2]) && (len < size)) + len += snprintf(buf+len, size-len, " %u %u", w[1],w[2]); + break; + + case RAWFMT_RAW16_OPT_AVG16: + len += snprintf(buf+len, size-len, "%u", w[0]); + if (w[1] && (len < size)) + len += snprintf(buf+len, size-len, " Avg: %u", w[1]); + break; + + case RAWFMT_TEMPMINMAX: + len += snprintf(buf+len, size-len, "%u -/+: %u/%u", w[0],w[1],w[2]); + break; + + case RAWFMT_RAW48: + default: + /* shows first 4 bytes of raw data as uint32 LE, + and the ramaining 2 bytes as uint16 LE */ + len += snprintf(buf+len, size-len, "%lu", letoh32(*((uint32_t*)w))); + if (w[2] && (len < size)) + len += snprintf(buf+len, size-len, " %u", w[2]); + break; + } + /* ignore trailing \0 when truncated */ + if (len >= size) len = size-1; + + /* fill return string; when max. size is exceded: first truncate + attribute name, then attribute data and finally attribute id */ + slen = snprintf(str, size, "%d ", id); + if (slen < size) { + /* maximum space disponible for attribute name, + including initial space separator */ + int name_sz = size - (slen + len); + if (name_sz > 1) { + len = snprintf(str+slen, name_sz, " %s", + ata_smart_get_attr_name(id)); + if (len >= name_sz) len = name_sz-1; + slen += len; + } + snprintf(str+slen, size-slen, "%s", buf); + } + + return 1; /* ok */ +} + +static bool ata_smart_dump(void) +{ + int fd; + + fd = creat("/smart_data.bin", 0666); + if(fd >= 0) + { + write(fd, smart_data, sizeof(struct ata_smart_values)); + close(fd); + } + + fd = creat("/smart_data.txt", 0666); + if(fd >= 0) + { + int i; + char buf[128]; + for (i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++) + { + if (ata_smart_attr_to_string( + &smart_data->vendor_attributes[i], buf, sizeof(buf))) + { + write(fd, buf, strlen(buf)); + write(fd, "\n", 1); + } + } + close(fd); + } + + return false; +} + +static int ata_smart_callback(int btn, struct gui_synclist *lists) +{ + (void)lists; + + if (btn == ACTION_STD_CANCEL) + { + smart_data = NULL; + return btn; + } + + /* read S.M.A.R.T. data only on first redraw */ + if (!smart_data) + { + int i; + char buf[SIMPLELIST_MAX_LINELENGTH]; + smart_data = ata_read_smart(); + simplelist_set_line_count(0); + simplelist_addline("Id Name: Current,Worst Raw"); + for (i = 0; i < NUMBER_ATA_SMART_ATTRIBUTES; i++) { + if (ata_smart_attr_to_string( + &smart_data->vendor_attributes[i], buf, sizeof(buf))) + simplelist_addline(buf); + } + } + + if (btn == ACTION_STD_CONTEXT) + { + ata_smart_dump(); + splashf(HZ, "S.M.A.R.T. data dumped"); + } + + return btn; +} + +static bool dbg_ata_smart(void) +{ + struct simplelist_info info; + simplelist_info_init(&info, "S.M.A.R.T. Data [CONTEXT to dump]", 1, NULL); + info.action_callback = ata_smart_callback; + info.hide_selection = true; + info.scroll_all = true; + return simplelist_show_list(&info); +} +#endif /* HAVE_ATA_SMART */ #else /* No SD, MMC or ATA */ static int disk_callback(int btn, struct gui_synclist *lists) { @@ -2383,6 +2582,9 @@ static const struct { { "View disk info", dbg_disk_info }, #if (CONFIG_STORAGE & STORAGE_ATA) { "Dump ATA identify info", dbg_identify_info}, +#ifdef HAVE_ATA_SMART + { "View/Dump S.M.A.R.T. data", dbg_ata_smart}, +#endif #endif #endif { "Metadata log", dbg_metadatalog }, diff --git a/apps/gui/list.h b/apps/gui/list.h index 0f2f51a424..ef08a9e220 100644 --- a/apps/gui/list.h +++ b/apps/gui/list.h @@ -257,7 +257,11 @@ struct simplelist_info { }; #define SIMPLELIST_MAX_LINES 32 +#ifdef HAVE_ATA_SMART +#define SIMPLELIST_MAX_LINELENGTH 48 +#else #define SIMPLELIST_MAX_LINELENGTH 32 +#endif /** The next three functions are used if the text is mostly static. These should be called in the action callback for the list. diff --git a/firmware/export/ata.h b/firmware/export/ata.h index 0bcb144e63..6f0d0b699f 100644 --- a/firmware/export/ata.h +++ b/firmware/export/ata.h @@ -25,6 +25,107 @@ #include "config.h" /* for HAVE_MULTIVOLUME or not */ #include "mv.h" /* for IF_MV() and friends */ +#ifdef HAVE_ATA_SMART +/* S.M.A.R.T. headers from smartmontools-5.42 */ +#define NUMBER_ATA_SMART_ATTRIBUTES 30 + +struct ata_smart_attribute { + unsigned char id; + /* meaning of flag bits: see MACROS just below */ + /* WARNING: MISALIGNED! */ + unsigned short flags; + unsigned char current; + unsigned char worst; + unsigned char raw[6]; + unsigned char reserv; +} __attribute__((packed)); + +/* MACROS to interpret the flags bits in the previous structure. */ +/* These have not been implemented using bitflags and a union, to make */ +/* it portable across bit/little endian and different platforms. */ + +/* 0: Prefailure bit */ + +/* From SFF 8035i Revision 2 page 19: Bit 0 (pre-failure/advisory bit) */ +/* - If the value of this bit equals zero, an attribute value less */ +/* than or equal to its corresponding attribute threshold indicates an */ +/* advisory condition where the usage or age of the device has */ +/* exceeded its intended design life period. If the value of this bit */ +/* equals one, an attribute value less than or equal to its */ +/* corresponding attribute threshold indicates a prefailure condition */ +/* where imminent loss of data is being predicted. */ +#define ATTRIBUTE_FLAGS_PREFAILURE(x) (x & 0x01) + +/* 1: Online bit */ + +/* From SFF 8035i Revision 2 page 19: Bit 1 (on-line data collection */ +/* bit) - If the value of this bit equals zero, then the attribute */ +/* value is updated only during off-line data collection */ +/* activities. If the value of this bit equals one, then the attribute */ +/* value is updated during normal operation of the device or during */ +/* both normal operation and off-line testing. */ +#define ATTRIBUTE_FLAGS_ONLINE(x) (x & 0x02) + +/* The following are (probably) IBM's, Maxtors and Quantum's definitions for the */ +/* vendor-specific bits: */ +/* 2: Performance type bit */ +#define ATTRIBUTE_FLAGS_PERFORMANCE(x) (x & 0x04) + +/* 3: Errorrate type bit */ +#define ATTRIBUTE_FLAGS_ERRORRATE(x) (x & 0x08) + +/* 4: Eventcount bit */ +#define ATTRIBUTE_FLAGS_EVENTCOUNT(x) (x & 0x10) + +/* 5: Selfpereserving bit */ +#define ATTRIBUTE_FLAGS_SELFPRESERVING(x) (x & 0x20) + +/* 6-15: Reserved for future use */ +#define ATTRIBUTE_FLAGS_OTHER(x) ((x) & 0xffc0) + +struct ata_smart_values +{ + unsigned short int revnumber; + struct ata_smart_attribute vendor_attributes [NUMBER_ATA_SMART_ATTRIBUTES]; + unsigned char offline_data_collection_status; + unsigned char self_test_exec_status; + unsigned short int total_time_to_complete_off_line; + unsigned char vendor_specific_366; + unsigned char offline_data_collection_capability; + unsigned short int smart_capability; + unsigned char errorlog_capability; + unsigned char vendor_specific_371; + unsigned char short_test_completion_time; + unsigned char extend_test_completion_time; + unsigned char conveyance_test_completion_time; + unsigned char reserved_375_385[11]; + unsigned char vendor_specific_386_510[125]; + unsigned char chksum; +} __attribute__((packed)); + +/* Raw attribute value print formats */ +enum ata_attr_raw_format +{ + RAWFMT_DEFAULT, + RAWFMT_RAW8, + RAWFMT_RAW16, + RAWFMT_RAW48, + RAWFMT_HEX48, + RAWFMT_RAW64, + RAWFMT_HEX64, + RAWFMT_RAW16_OPT_RAW16, + RAWFMT_RAW16_OPT_AVG16, + RAWFMT_RAW24_DIV_RAW24, + RAWFMT_RAW24_DIV_RAW32, + RAWFMT_SEC2HOUR, + RAWFMT_MIN2HOUR, + RAWFMT_HALFMIN2HOUR, + RAWFMT_MSEC24_HOUR32, + RAWFMT_TEMPMINMAX, + RAWFMT_TEMP10X, +}; +#endif /* HAVE_ATA_SMART */ + struct storage_info; void ata_enable(bool on); @@ -69,4 +170,9 @@ int ata_spinup_time(void); /* ticks */ int ata_get_dma_mode(void); #endif /* HAVE_ATA_DMA */ +#ifdef HAVE_ATA_SMART +/* Returns current S.M.A.R.T. data */ +void* ata_read_smart(void); +#endif + #endif /* __ATA_H__ */ diff --git a/firmware/export/config/ipod6g.h b/firmware/export/config/ipod6g.h index 7e7025e157..0a40108699 100644 --- a/firmware/export/config/ipod6g.h +++ b/firmware/export/config/ipod6g.h @@ -201,6 +201,8 @@ #define STORAGE_NEEDS_ALIGN +#define HAVE_ATA_SMART + /* define this if the device has larger sectors when accessed via USB */ /* (only relevant in disk.c, fat.c now always supports large virtual sectors) */ //#define MAX_LOG_SECTOR_SIZE 4096 diff --git a/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c b/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c index c629fd583a..53ec45ec6d 100644 --- a/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c +++ b/firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c @@ -50,6 +50,9 @@ /** static, private data **/ static uint8_t ceata_taskfile[16] STORAGE_ALIGN_ATTR; static uint16_t ata_identify_data[0x100] STORAGE_ALIGN_ATTR; +#ifdef HAVE_ATA_SMART +static uint16_t ata_smart_data[0x100] STORAGE_ALIGN_ATTR; +#endif static bool ceata; static bool ata_swap; static bool ata_lba48; @@ -1211,6 +1214,62 @@ int ata_init(void) return 0; } +#ifdef HAVE_ATA_SMART +static int ata_smart(uint16_t* buf) +{ + mutex_lock(&ata_mutex); + ata_power_up(); + + if (ceata) + { + memset(ceata_taskfile, 0, 16); + ceata_taskfile[0xc] = 0x4f; + ceata_taskfile[0xd] = 0xc2; + ceata_taskfile[0xe] = 0x40; /* Device/Head Register, bit6: 0->CHS, 1->LBA */ + ceata_taskfile[0xf] = 0xb0; + PASS_RC(ceata_wait_idle(), 3, 0); + if (((uint8_t*)ata_identify_data)[54] != 'A') /* Model != aAmsung */ + { + ceata_taskfile[0x9] = 0xd8; /* SMART enable operations */ + PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 3, 1); + PASS_RC(ceata_check_error(), 3, 2); + } + ceata_taskfile[0x9] = 0xd0; /* SMART read data */ + PASS_RC(ceata_write_multiple_register(0, ceata_taskfile, 16), 3, 3); + PASS_RC(ceata_rw_multiple_block(false, buf, 1, CEATA_COMMAND_TIMEOUT * HZ / 1000000), 3, 4); + } + else + { + int i; + uint32_t old = ATA_CFG; + ATA_CFG |= BIT(6); /* 16bit big-endian */ + PASS_RC(ata_wait_for_not_bsy(10000000), 3, 5); + ata_write_cbr(&ATA_PIO_DAD, 0); + ata_write_cbr(&ATA_PIO_FED, 0xd0); + ata_write_cbr(&ATA_PIO_SCR, 0); + ata_write_cbr(&ATA_PIO_LLR, 0); + ata_write_cbr(&ATA_PIO_LMR, 0x4f); + ata_write_cbr(&ATA_PIO_LHR, 0xc2); + ata_write_cbr(&ATA_PIO_DVR, BIT(6)); + ata_write_cbr(&ATA_PIO_CSD, 0xb0); + PASS_RC(ata_wait_for_start_of_transfer(10000000), 3, 6); + for (i = 0; i < 0x100; i++) buf[i] = ata_read_cbr(&ATA_PIO_DTR); + ATA_CFG = old; + } + + ata_set_active(); + mutex_unlock(&ata_mutex); + + return 0; +} + +void* ata_read_smart(void) +{ + ata_smart(ata_smart_data); + return ata_smart_data; +} +#endif /* HAVE_ATA_SMART */ + #ifdef CONFIG_STORAGE_MULTI static int ata_num_drives(int first_drive) { -- cgit v1.2.3