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
This commit is contained in:
Cástor Muñoz 2014-12-09 19:38:47 +01:00
parent 32b4558511
commit d20185ac96
5 changed files with 373 additions and 0 deletions

View file

@ -1507,6 +1507,205 @@ static int disk_callback(int btn, struct gui_synclist *lists)
#endif /* HAVE_ATA_DMA */ #endif /* HAVE_ATA_DMA */
return btn; 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 */ #else /* No SD, MMC or ATA */
static int disk_callback(int btn, struct gui_synclist *lists) static int disk_callback(int btn, struct gui_synclist *lists)
{ {
@ -2383,6 +2582,9 @@ static const struct {
{ "View disk info", dbg_disk_info }, { "View disk info", dbg_disk_info },
#if (CONFIG_STORAGE & STORAGE_ATA) #if (CONFIG_STORAGE & STORAGE_ATA)
{ "Dump ATA identify info", dbg_identify_info}, { "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
#endif #endif
{ "Metadata log", dbg_metadatalog }, { "Metadata log", dbg_metadatalog },

View file

@ -257,7 +257,11 @@ struct simplelist_info {
}; };
#define SIMPLELIST_MAX_LINES 32 #define SIMPLELIST_MAX_LINES 32
#ifdef HAVE_ATA_SMART
#define SIMPLELIST_MAX_LINELENGTH 48
#else
#define SIMPLELIST_MAX_LINELENGTH 32 #define SIMPLELIST_MAX_LINELENGTH 32
#endif
/** The next three functions are used if the text is mostly static. /** The next three functions are used if the text is mostly static.
These should be called in the action callback for the list. These should be called in the action callback for the list.

View file

@ -25,6 +25,107 @@
#include "config.h" /* for HAVE_MULTIVOLUME or not */ #include "config.h" /* for HAVE_MULTIVOLUME or not */
#include "mv.h" /* for IF_MV() and friends */ #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; struct storage_info;
void ata_enable(bool on); void ata_enable(bool on);
@ -69,4 +170,9 @@ int ata_spinup_time(void); /* ticks */
int ata_get_dma_mode(void); int ata_get_dma_mode(void);
#endif /* HAVE_ATA_DMA */ #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__ */ #endif /* __ATA_H__ */

View file

@ -201,6 +201,8 @@
#define STORAGE_NEEDS_ALIGN #define STORAGE_NEEDS_ALIGN
#define HAVE_ATA_SMART
/* define this if the device has larger sectors when accessed via USB */ /* 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) */ /* (only relevant in disk.c, fat.c now always supports large virtual sectors) */
//#define MAX_LOG_SECTOR_SIZE 4096 //#define MAX_LOG_SECTOR_SIZE 4096

View file

@ -50,6 +50,9 @@
/** static, private data **/ /** static, private data **/
static uint8_t ceata_taskfile[16] STORAGE_ALIGN_ATTR; static uint8_t ceata_taskfile[16] STORAGE_ALIGN_ATTR;
static uint16_t ata_identify_data[0x100] 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 ceata;
static bool ata_swap; static bool ata_swap;
static bool ata_lba48; static bool ata_lba48;
@ -1211,6 +1214,62 @@ int ata_init(void)
return 0; 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 #ifdef CONFIG_STORAGE_MULTI
static int ata_num_drives(int first_drive) static int ata_num_drives(int first_drive)
{ {