summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--smart.c333
-rw-r--r--smart.h19
2 files changed, 327 insertions, 25 deletions
diff --git a/smart.c b/smart.c
index 1d981ae..7b6e292 100644
--- a/smart.c
+++ b/smart.c
@@ -28,7 +28,6 @@ typedef enum SkDirection {
typedef enum SkDeviceType {
SK_DEVICE_TYPE_ATA_PASSTHROUGH, /* ATA passthrough over SCSI transport */
SK_DEVICE_TYPE_ATA,
- SK_DEVICE_TYPE_SCSI,
SK_DEVICE_TYPE_UNKNOWN,
_SK_DEVICE_TYPE_MAX
} SkDeviceType;
@@ -63,6 +62,7 @@ typedef enum SkAtaCommand {
/* ATA SMART subcommands (ATA8 7.52.1) */
typedef enum SkSmartCommand {
SK_SMART_COMMAND_READ_DATA = 0xD0,
+ SK_SMART_COMMAND_READ_THRESHOLDS = 0xD1,
SK_SMART_COMMAND_EXECUTE_OFFLINE_IMMEDIATE = 0xD4,
SK_SMART_COMMAND_ENABLE_OPERATIONS = 0xD8,
SK_SMART_COMMAND_DISABLE_OPERATIONS = 0xD9,
@@ -201,15 +201,6 @@ static int sg_io(int fd, int direction,
return ioctl(fd, SG_IO, &io_hdr);
}
-static int disk_scsi_command(SkDevice *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) {
- g_assert(d->type == SK_DEVICE_TYPE_SCSI);
-
- g_warning("SCSI disks not yet supported because Lennart doesn't have any to test this with.");
-
- errno = ENOTSUP;
- return -1;
-}
-
static int disk_passthrough_command(SkDevice *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) {
guint8 *bytes = cmd_data;
guint8 cdb[16];
@@ -285,7 +276,6 @@ static int disk_command(SkDevice *d, SkAtaCommand command, SkDirection direction
static int (* const disk_command_table[_SK_DEVICE_TYPE_MAX]) (SkDevice *d, SkAtaCommand command, SkDirection direction, gpointer cmd_data, gpointer data, size_t *len) = {
[SK_DEVICE_TYPE_ATA] = disk_ata_command,
- [SK_DEVICE_TYPE_SCSI] = disk_scsi_command,
[SK_DEVICE_TYPE_ATA_PASSTHROUGH] = disk_passthrough_command,
};
@@ -389,6 +379,32 @@ int sk_disk_smart_read_data(SkDevice *d) {
return ret;
}
+static int disk_smart_read_thresholds(SkDevice *d) {
+ guint16 cmd[6];
+ int ret;
+ size_t len = 512;
+
+ if (!disk_smart_is_available(d)) {
+ errno = ENOTSUP;
+ return -1;
+ }
+
+ memset(cmd, 0, sizeof(cmd));
+
+ cmd[0] = GUINT16_TO_BE(SK_SMART_COMMAND_READ_THRESHOLDS);
+ cmd[1] = GUINT16_TO_BE(1);
+ cmd[2] = GUINT16_TO_BE(0x0000U);
+ cmd[3] = GUINT16_TO_BE(0x00C2U);
+ cmd[4] = GUINT16_TO_BE(0x4F00U);
+
+ if ((ret = disk_command(d, SK_ATA_COMMAND_SMART, SK_DIRECTION_IN, cmd, d->smart_threshold_data, &len)) < 0)
+ return ret;
+
+ d->smart_threshold_data_valid = TRUE;
+
+ return ret;
+}
+
/* int disk_smart_status(SkDevice *d, SmartLogAddress a, gboolean *b) { */
/* guint16 cmd[6]; */
@@ -519,9 +535,125 @@ const char *sk_offline_data_collection_status_to_string(SkOfflineDataCollectionS
return map[status];
}
+typedef struct SkSmartAttributeInfo {
+ const char *name;
+ SkSmartAttributeUnit unit;
+} SkSmartAttributeInfo;
+
+/* This data is stolen from smartmontools */
+static const SkSmartAttributeInfo const attribute_info[255] = {
+ [1] = { "raw-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [2] = { "throughput-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [3] = { "spin-up-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
+ [4] = { "start-stop-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [5] = { "reallocated-sector-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [6] = { "read-channel-margin", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [7] = { "seek-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [8] = { "seek-time-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [10] = { "spin-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [11] = { "calibration-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [12] = { "power-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [13] = { "read-soft-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [187] = { "reported-uncorrect", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
+ [189] = { "high-fly-writes", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_KELVIN },
+ [191] = { "g-sense-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [192] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [193] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [194] = { "temperature-celsius-2", SK_SMART_ATTRIBUTE_UNIT_KELVIN },
+ [195] = { "hardware-ecc-recovered", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [196] = { "reallocated-event-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [197] = { "current-pending-sector", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
+ [198] = { "offline-uncorrectable", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
+ [199] = { "udma-crc-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [200] = { "multi-zone-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [201] = { "soft-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [202] = { "ta-increase-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [203] = { "run-out-cancel", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [204] = { "shock-count-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [205] = { "shock-rate-write-opern", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [206] = { "flying-height", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [207] = { "spin-high-current", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [208] = { "spin-buzz", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN},
+ [209] = { "offline-seek-perfomance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [220] = { "disk-shift", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [221] = { "g-sense-error-rate-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [222] = { "loaded-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
+ [223] = { "load-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [224] = { "load-friction", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [225] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [226] = { "load-in-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
+ [227] = { "torq-amp-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [228] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE },
+ [230] = { "head-amplitude", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
+ [231] = { "temperature-celsius-1", SK_SMART_ATTRIBUTE_UNIT_KELVIN },
+ [240] = { "head-flying-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
+ [250] = { "read-error-retry-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
+};
+
+static void make_pretty(SkSmartAttribute *a) {
+ guint64 fourtyeight;
+
+ if (!a->name)
+ return;
+
+ if (a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_UNKNOWN)
+ return;
+
+ fourtyeight =
+ ((guint64) a->raw[0]) |
+ (((guint64) a->raw[1]) << 8) |
+ (((guint64) a->raw[2]) << 16) |
+ (((guint64) a->raw[3]) << 24) |
+ (((guint64) a->raw[4]) << 32) |
+ (((guint64) a->raw[5]) << 40);
+
+ if (!strcmp(a->name, "spin-up-time"))
+ a->pretty_value = fourtyeight & 0xFFFF;
+ else if (!strcmp(a->name, "airflow-temperature-celsius") ||
+ !strcmp(a->name, "temperature-celsius-1") ||
+ !strcmp(a->name, "temperature-celsius-2")) {
+ a->pretty_value = (fourtyeight & 0xFFFF) + 273;
+ } else if (!strcmp(a->name, "power-on-minutes"))
+ a->pretty_value = fourtyeight * 60 * 1000;
+ else if (!strcmp(a->name, "power-on-seconds"))
+ a->pretty_value = fourtyeight * 1000;
+ else if (!strcmp(a->name, "power-on-hours") ||
+ !strcmp(a->name, "loaded-hours") ||
+ !strcmp(a->name, "head-flying-hours"))
+ a->pretty_value = fourtyeight * 60 * 60 * 1000;
+ else
+ a->pretty_value = fourtyeight;
-static const char *lookup_attribute_name(SkDevice *d, guint8 id) {
- return "blah";
+}
+
+static const SkSmartAttributeInfo *lookup_attribute(SkDevice *d, guint8 id, SkSmartAttributeInfo *space) {
+ const SkIdentifyParsedData *ipd;
+
+ /* These are the simple cases */
+ if (attribute_info[id].name)
+ return &attribute_info[id];
+
+ /* These are the complex ones */
+ if (sk_disk_identify_parse(d, &ipd) < 0)
+ return NULL;
+
+ switch (id) {
+ case 9:
+
+ if (strstr(ipd->model, "Maxtor"))
+ space->name = "power-on-minutes";
+ else if (strstr(ipd->model, "Fujitsu") || strstr(ipd->model, "FUJITSU"))
+ space->name = "power-on-seconds";
+ else
+ space->name = "power-on-hours";
+
+ space->unit = SK_SMART_ATTRIBUTE_UNIT_MSECONDS;
+
+ return space;
+ }
+
+ return NULL;
}
int sk_disk_smart_parse(SkDevice *d, const SkSmartParsedData **spd) {
@@ -584,9 +716,35 @@ int sk_disk_smart_parse(SkDevice *d, const SkSmartParsedData **spd) {
return 0;
}
+static void find_threshold(SkDevice *d, SkSmartAttribute *a) {
+ guint8 *p;
+ unsigned n;
+
+ if (!d->smart_threshold_data_valid) {
+ a->threshold_valid = FALSE;
+ return;
+ }
+
+ for (n = 0, p = d->smart_threshold_data+2; n < 30; n++, p+=12)
+ if (p[0] == a->id)
+ break;
+
+ if (n >= 30) {
+ a->threshold_valid = FALSE;
+ return;
+ }
+
+ a->threshold = p[1];
+ a->threshold_valid = TRUE;
+
+ a->bad =
+ a->worst_value <= a->threshold ||
+ a->current_value <= a->threshold;
+}
+
int sk_disk_smart_parse_attributes(SkDevice *d, SkSmartAttributeCallback cb, gpointer userdata) {
guint8 *p;
- unsigned n = 0;
+ unsigned n;
if (!d->smart_data_valid) {
errno = ENOENT;
@@ -595,15 +753,31 @@ int sk_disk_smart_parse_attributes(SkDevice *d, SkSmartAttributeCallback cb, gpo
for (n = 0, p = d->smart_data + 2; n < 30; n++, p+=12) {
SkSmartAttribute a;
+ SkSmartAttributeInfo space;
+ const SkSmartAttributeInfo *i;
if (p[0] == 0)
continue;
- g_printerr("attr(%i)\t = %i\t (0x02%x)\n", p[0], p[3], p[3]);
-
+ memset(&a, 0, sizeof(a));
a.id = p[0];
- a.name = lookup_attribute_name(d, p[0]);
- a.value = p[3];
+ a.current_value = p[3];
+ a.worst_value = p[4];
+
+ a.flag = p[2];
+ a.prefailure = !!(p[1] & 1);
+ a.online = !!(p[1] & 2);
+
+ memcpy(a.raw, p+5, 6);
+
+ if ((i = lookup_attribute(d, p[0], &space))) {
+ a.name = i->name;
+ a.pretty_unit = i->unit;
+ }
+
+ make_pretty(&a);
+
+ find_threshold(d, &a);
if (cb)
cb(d, &a, userdata);
@@ -616,6 +790,98 @@ static const char *yes_no(gboolean b) {
return b ? "yes" : "no";
}
+const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
+
+ const char * const map[] = {
+ [SK_SMART_ATTRIBUTE_UNIT_UNKNOWN] = NULL,
+ [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
+ [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
+ [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
+ [SK_SMART_ATTRIBUTE_UNIT_KELVIN] = "K"
+ };
+
+ if (unit >= _SK_SMART_ATTRIBUTE_UNIT_MAX)
+ return NULL;
+
+ return map[unit];
+}
+
+static char* print_name(char *s, size_t len, guint8 id, const char *k) {
+
+ if (k)
+ g_strlcpy(s, k, len);
+ else
+ g_snprintf(s, len, "%u", id);
+
+ return s;
+
+}
+
+static char *print_value(char *s, size_t len, const SkSmartAttribute *a) {
+
+ switch (a->pretty_unit) {
+ case SK_SMART_ATTRIBUTE_UNIT_MSECONDS:
+
+ if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*365LLU)
+ g_snprintf(s, len, "%0.1f years", ((double) a->pretty_value)/(1000.0*60*60*24*365));
+ else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU*30LLU)
+ g_snprintf(s, len, "%0.1f months", ((double) a->pretty_value)/(1000.0*60*60*24*30));
+ else if (a->pretty_value >= 1000LLU*60LLU*60LLU*24LLU)
+ g_snprintf(s, len, "%0.1f days", ((double) a->pretty_value)/(1000.0*60*60*24));
+ else if (a->pretty_value >= 1000LLU*60LLU*60LLU)
+ g_snprintf(s, len, "%0.1f h", ((double) a->pretty_value)/(1000.0*60*60));
+ else if (a->pretty_value >= 1000LLU*60LLU)
+ g_snprintf(s, len, "%0.1f min", ((double) a->pretty_value)/(1000.0*60));
+ else if (a->pretty_value >= 1000LLU)
+ g_snprintf(s, len, "%0.1f s", ((double) a->pretty_value)/(1000.0));
+ else
+ g_snprintf(s, len, "%llu ms", (unsigned long long) a->pretty_value);
+
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_KELVIN:
+
+ g_snprintf(s, len, "%lli C", (long long) a->pretty_value - 273);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_SECTORS:
+ g_snprintf(s, len, "%llu sectors", (unsigned long long) a->pretty_value);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_NONE:
+ g_snprintf(s, len, "%llu", (unsigned long long) a->pretty_value);
+ break;
+
+ case SK_SMART_ATTRIBUTE_UNIT_UNKNOWN:
+ g_snprintf(s, len, "n/a");
+ break;
+
+ case _SK_SMART_ATTRIBUTE_UNIT_MAX:
+ g_assert_not_reached();
+ }
+
+ return s;
+};
+
+static void disk_dump_attributes(SkDevice *d, const SkSmartAttribute *a, gpointer userdata) {
+ char name[32];
+ char pretty[32];
+ char t[32];
+
+ g_snprintf(t, sizeof(t), "%3u", a->threshold);
+
+ g_print("%3u %-27s %3u %3u %-3s %-11s %-7s %-7s %-3s\n",
+ a->id,
+ print_name(name, sizeof(name), a->id, a->name),
+ a->current_value,
+ a->worst_value,
+ a->threshold_valid ? t : "n/a",
+ print_value(pretty, sizeof(pretty), a),
+ a->prefailure ? "prefail" : "old-age",
+ a->online ? "online" : "offline",
+ yes_no(!a->bad));
+}
+
int sk_disk_dump(SkDevice *d) {
int ret;
gboolean powered = FALSE;
@@ -679,6 +945,19 @@ int sk_disk_dump(SkDevice *d) {
g_print("Conveyance Self-Test Polling Time: %u min\n",
spd->conveyance_test_polling_minutes);
+ g_print("%3s %-27s %5s %5s %5s %-11s %-7s %-7s %-3s\n",
+ "ID#",
+ "Name",
+ "Value",
+ "Worst",
+ "Thres",
+ "Pretty",
+ "Type",
+ "Updates",
+ "Good");
+
+ if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
+ return ret;
}
return 0;
@@ -719,11 +998,25 @@ int sk_disk_open(const gchar *name, SkDevice **_d) {
break;
/* Check if driver can do SMART, and enable if necessary */
- if (disk_smart_is_available(d))
- if (!disk_smart_is_enabled(d))
+ if (disk_smart_is_available(d)) {
+
+ if (!disk_smart_is_enabled(d)) {
if ((ret = disk_smart_enable(d, TRUE)) < 0)
goto fail;
+ if ((ret = disk_identify_device(d)) < 0)
+ goto fail;
+
+ if (!disk_smart_is_enabled(d)) {
+ errno = EIO;
+ ret = -1;
+ goto fail;
+ }
+ }
+
+ disk_smart_read_thresholds(d);
+ }
+
*_d = d;
return 0;
diff --git a/smart.h b/smart.h
index bca06e3..f1150bf 100644
--- a/smart.h
+++ b/smart.h
@@ -41,7 +41,9 @@ typedef struct SkSmartParsedData {
} SkSmartParsedData;
typedef enum SkSmartAttributeUnit {
- SK_SMART_ATTRIBUTE_UNIT_SECONDS,
+ SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,
+ SK_SMART_ATTRIBUTE_UNIT_NONE,
+ SK_SMART_ATTRIBUTE_UNIT_MSECONDS,
SK_SMART_ATTRIBUTE_UNIT_SECTORS,
SK_SMART_ATTRIBUTE_UNIT_KELVIN,
_SK_SMART_ATTRIBUTE_UNIT_MAX
@@ -51,15 +53,21 @@ typedef struct SkSmartAttribute {
/* Static data */
guint8 id;
const char *name;
+ SkSmartAttributeUnit pretty_unit; /* for pretty value */
+
guint8 threshold;
- SkSmartAttributeUnit unit; /* for pretty_value */
+ gboolean threshold_valid:1;
+
gboolean online:1;
gboolean prefailure:1;
+ guint8 flag;
+
/* Volatile data */
- gboolean over_threshold:1;
- guint8 value;
- unsigned pretty_value;
+ gboolean bad:1;
+ guint8 current_value, worst_value;
+ guint64 pretty_value;
+ guint8 raw[6];
/* This structure may be extended at any time without being
* considered an ABI change. So take care when you copy it. */
@@ -86,5 +94,6 @@ int sk_disk_dump(SkDevice *d);
void sk_disk_free(SkDevice *d);
const char* sk_offline_data_collection_status_to_string(SkOfflineDataCollectionStatus status);
+const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit);
#endif