From 60c555bb050ba862f27f8ec36b6ee07c8053ee88 Mon Sep 17 00:00:00 2001 From: Lennart Poettering Date: Sun, 29 Jun 2008 21:14:39 +0200 Subject: implement attribute parsing properly --- smart.c | 333 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- smart.h | 19 +++- 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 -- cgit