summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--apps/debug_menu.c202
-rw-r--r--apps/gui/list.h4
-rw-r--r--firmware/export/ata.h106
-rw-r--r--firmware/export/config/ipod6g.h2
-rw-r--r--firmware/target/arm/s5l8702/ipod6g/storage_ata-ipod6g.c59
5 files changed, 373 insertions, 0 deletions
diff --git a/apps/debug_menu.c b/apps/debug_menu.c
index e602b71..2b97022 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 0f2f51a..ef08a9e 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 0bcb144..6f0d0b6 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 7e7025e..0a40108 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 c629fd5..53ec45e 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)
{