atasmart: ignore sector count if it is -1
[platform/upstream/libatasmart.git] / atasmart.c
index bce315b..f3c8e32 100644 (file)
@@ -108,6 +108,8 @@ struct SkDisk {
         uint8_t smart_data[512];
         uint8_t smart_thresholds[512];
 
+        SkBool smart_initialized:1;
+
         SkBool identify_valid:1;
         SkBool smart_data_valid:1;
         SkBool smart_thresholds_valid:1;
@@ -149,6 +151,8 @@ typedef enum SkSmartCommand {
 #define SK_MSECOND_VALID_SHORT_MAX (60ULL * 60ULL * 1000ULL)
 #define SK_MSECOND_VALID_LONG_MAX (30ULL * 365ULL * 24ULL * 60ULL * 60ULL * 1000ULL)
 
+static int init_smart(SkDisk *d);
+
 static const char *disk_type_to_human_string(SkDiskType type) {
 
         /* %STRINGPOOLSTART% */
@@ -818,6 +822,9 @@ int sk_disk_smart_read_data(SkDisk *d) {
         int ret;
         size_t len = 512;
 
+        if (init_smart(d) < 0)
+                return -1;
+
         if (!disk_smart_is_available(d)) {
                 errno = ENOTSUP;
                 return -1;
@@ -875,6 +882,9 @@ int sk_disk_smart_status(SkDisk *d, SkBool *good) {
         uint16_t cmd[6];
         int ret;
 
+        if (init_smart(d) < 0)
+                return -1;
+
         if (!disk_smart_is_available(d)) {
                 errno = ENOTSUP;
                 return -1;
@@ -921,6 +931,9 @@ int sk_disk_smart_self_test(SkDisk *d, SkSmartSelfTest test) {
         uint16_t cmd[6];
         int ret;
 
+        if (init_smart(d) < 0)
+                return -1;
+
         if (!disk_smart_is_available(d)) {
                 errno = ENOTSUP;
                 return -1;
@@ -1183,7 +1196,8 @@ static void make_pretty(SkSmartAttributeParsedData *a) {
                 a->pretty_value = (fourtyeight & 0xFFFF)*100 + 273150;
         else if (!strcmp(a->name, "power-on-minutes"))
                 a->pretty_value = fourtyeight * 60 * 1000;
-        else if (!strcmp(a->name, "power-on-seconds"))
+        else if (!strcmp(a->name, "power-on-seconds") ||
+                 !strcmp(a->name, "power-on-seconds-2"))
                 a->pretty_value = fourtyeight * 1000;
         else if (!strcmp(a->name, "power-on-half-minutes"))
                 a->pretty_value = fourtyeight * 30 * 1000;
@@ -1194,6 +1208,17 @@ static void make_pretty(SkSmartAttributeParsedData *a) {
         else if (!strcmp(a->name, "reallocated-sector-count") ||
                  !strcmp(a->name, "current-pending-sector"))
                 a->pretty_value = fourtyeight & 0xFFFFFFFFU;
+        else if (!strcmp(a->name, "endurance-remaining") ||
+                 !strcmp(a->name, "available-reserved-space"))
+                a->pretty_value = a->current_value;
+        else if (!strcmp(a->name, "total-lbas-written") ||
+                 !strcmp(a->name, "total-lbas-read"))
+                a->pretty_value = fourtyeight * 65536LLU * 512LLU / 1000000LLU;
+        else if (!strcmp(a->name, "timed-workload-media-wear") ||
+                 !strcmp(a->name, "timed-workload-host-reads"))
+                a->pretty_value = (double)fourtyeight / 1024LLU;
+        else if (!strcmp(a->name, "workload-timer"))
+                a->pretty_value = fourtyeight * 60 * 1000;
         else
                 a->pretty_value = fourtyeight;
 }
@@ -1248,7 +1273,9 @@ static void verify_sectors(SkDisk *d, SkSmartAttributeParsedData *a) {
 
         max_sectors = d->size / 512ULL;
 
-        if (max_sectors > 0 && a->pretty_value > max_sectors) {
+        if (a->pretty_value == 0xffffffffULL ||
+            a->pretty_value == 0xffffffffffffffffULL ||
+            (max_sectors > 0 && a->pretty_value > max_sectors)) {
                 a->pretty_value = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
                 d->attribute_verification_bad = TRUE;
         } else {
@@ -1276,7 +1303,12 @@ static const SkSmartAttributeInfo const attribute_info[256] = {
         [11]  = { "calibration-retry-count",     SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
         [12]  = { "power-cycle-count",           SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
         [13]  = { "read-soft-error-rate",        SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [170] = { "available-reserved-space",    SK_SMART_ATTRIBUTE_UNIT_PERCENT,  NULL },
+        [171] = { "program-fail-count",          SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [172] = { "erase-fail-count",            SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
+        [184] = { "end-to-end-error",            SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
         [187] = { "reported-uncorrect",          SK_SMART_ATTRIBUTE_UNIT_SECTORS,  verify_sectors },
+        [188] = { "command-timeout",             SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
         [189] = { "high-fly-writes",             SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
         [190] = { "airflow-temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN,  verify_temperature },
         [191] = { "g-sense-error-rate",          SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
@@ -1309,25 +1341,43 @@ static const SkSmartAttributeInfo const attribute_info[256] = {
         [228] = { "power-off-retract-count-2",   SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL },
         [230] = { "head-amplitude",              SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
         [231] = { "temperature-celsius",         SK_SMART_ATTRIBUTE_UNIT_MKELVIN,  verify_temperature },
+
+        /* http://www.adtron.com/pdf/SMART_for_XceedLite_SATA_RevA.pdf */
+        [232] = { "endurance-remaining",         SK_SMART_ATTRIBUTE_UNIT_PERCENT,  NULL },
+        [233] = { "power-on-seconds-2",          SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+        [234] = { "uncorrectable-ecc-count",     SK_SMART_ATTRIBUTE_UNIT_SECTORS,  NULL },
+        [235] = { "good-block-rate",             SK_SMART_ATTRIBUTE_UNIT_UNKNOWN,  NULL },
+
         [240] = { "head-flying-hours",           SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time },
+        [241] = { "total-lbas-written",          SK_SMART_ATTRIBUTE_UNIT_MB,  NULL },
+        [242] = { "total-lbas-read",             SK_SMART_ATTRIBUTE_UNIT_MB,  NULL },
         [250] = { "read-error-retry-rate",       SK_SMART_ATTRIBUTE_UNIT_NONE,     NULL }
 };
 /* %STRINGPOOLSTOP% */
 
 typedef enum SkSmartQuirk {
-        SK_SMART_QUIRK_9_POWERONMINUTES            = 0x0001,
-        SK_SMART_QUIRK_9_POWERONSECONDS            = 0x0002,
-        SK_SMART_QUIRK_9_POWERONHALFMINUTES        = 0x0004,
-        SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT = 0x0008,
-        SK_SMART_QUIRK_193_LOADUNLOAD              = 0x0010,
-        SK_SMART_QUIRK_194_10XCELSIUS              = 0x0020,
-        SK_SMART_QUIRK_194_UNKNOWN                 = 0x0040,
-        SK_SMART_QUIRK_200_WRITEERRORCOUNT         = 0x0080,
-        SK_SMART_QUIRK_201_DETECTEDTACOUNT         = 0x0100,
-        SK_SMART_QUIRK_5_UNKNOWN                   = 0x0200,
-        SK_SMART_QUIRK_9_UNKNOWN                   = 0x0400,
-        SK_SMART_QUIRK_197_UNKNOWN                 = 0x0800,
-        SK_SMART_QUIRK_198_UNKNOWN                 = 0x1000,
+        SK_SMART_QUIRK_9_POWERONMINUTES            = 0x000001,
+        SK_SMART_QUIRK_9_POWERONSECONDS            = 0x000002,
+        SK_SMART_QUIRK_9_POWERONHALFMINUTES        = 0x000004,
+        SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT = 0x000008,
+        SK_SMART_QUIRK_193_LOADUNLOAD              = 0x000010,
+        SK_SMART_QUIRK_194_10XCELSIUS              = 0x000020,
+        SK_SMART_QUIRK_194_UNKNOWN                 = 0x000040,
+        SK_SMART_QUIRK_200_WRITEERRORCOUNT         = 0x000080,
+        SK_SMART_QUIRK_201_DETECTEDTACOUNT         = 0x000100,
+        SK_SMART_QUIRK_5_UNKNOWN                   = 0x000200,
+        SK_SMART_QUIRK_9_UNKNOWN                   = 0x000400,
+        SK_SMART_QUIRK_197_UNKNOWN                 = 0x000800,
+        SK_SMART_QUIRK_198_UNKNOWN                 = 0x001000,
+        SK_SMART_QUIRK_190_UNKNOWN                 = 0x002000,
+        SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE  = 0x004000,
+        SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR   = 0x008000,
+        SK_SMART_QUIRK_225_TOTALLBASWRITTEN        = 0x010000,
+        SK_SMART_QUIRK_4_UNUSED                    = 0x020000,
+        SK_SMART_QUIRK_226_TIMEWORKLOADMEDIAWEAR   = 0x040000,
+        SK_SMART_QUIRK_227_TIMEWORKLOADHOSTREADS   = 0x080000,
+        SK_SMART_QUIRK_228_WORKLOADTIMER           = 0x100000,
+        SK_SMART_QUIRK_3_UNUSED                    = 0x200000
 } SkSmartQuirk;
 
 /* %STRINGPOOLSTART% */
@@ -1345,6 +1395,15 @@ static const char *quirk_name[] = {
         "9_UNKNOWN",
         "197_UNKNOWN",
         "198_UNKNOWN",
+        "190_UNKNOWN",
+        "232_AVAILABLERESERVEDSPACE",
+        "233_MEDIAWEAROUTINDICATOR",
+        "225_TOTALLBASWRITTEN",
+        "4_UNUSED",
+        "226_TIMEWORKLOADMEDIAWEAR",
+        "227_TIMEWORKLOADHOSTREADS",
+        "228_WORKLOADTIMER",
+        "3_UNUSED",
         NULL
 };
 /* %STRINGPOOLSTOP% */
@@ -1489,6 +1548,27 @@ static const SkSmartQuirkDatabase quirk_database[] = { {
                 "^MBZOC60P$",
                 SK_SMART_QUIRK_5_UNKNOWN
         }, {
+
+        /*** Apple SSD (?) http://bugs.freedesktop.org/show_bug.cgi?id=24700
+                          https://bugs.launchpad.net/ubuntu/+source/gnome-disk-utility/+bug/438136/comments/4 */
+                "^MCCOE64GEMPP$",
+                "^2.9.0[3-9]$",
+                SK_SMART_QUIRK_5_UNKNOWN|
+                SK_SMART_QUIRK_190_UNKNOWN
+        }, {
+
+        /*** Intel */
+                "^INTEL SSDSA2CW[0-9]{3}G3$",
+                NULL,
+                SK_SMART_QUIRK_3_UNUSED|
+                SK_SMART_QUIRK_4_UNUSED|
+                SK_SMART_QUIRK_225_TOTALLBASWRITTEN|
+                SK_SMART_QUIRK_226_TIMEWORKLOADMEDIAWEAR|
+                SK_SMART_QUIRK_227_TIMEWORKLOADHOSTREADS|
+                SK_SMART_QUIRK_228_WORKLOADTIMER|
+                SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE|
+                SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR
+        }, {
                 NULL,
                 NULL,
                 0
@@ -1570,6 +1650,29 @@ static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
 
         if (quirk) {
                 switch (id) {
+                        case 3:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_3_UNUSED) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "spin-up-time", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+
+                                break;
+
+                        case 4:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_4_UNUSED) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "start-stop-count", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+
+                                break;
 
                         case 5:
                                 if (quirk & SK_SMART_QUIRK_5_UNKNOWN)
@@ -1602,6 +1705,12 @@ static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
 
                                 break;
 
+                        case 190:
+                                if (quirk & SK_SMART_QUIRK_190_UNKNOWN)
+                                        return NULL;
+
+                                break;
+
                         case 192:
                                 /* %STRINGPOOLSTART% */
                                 if (quirk & SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT) {
@@ -1662,6 +1771,77 @@ static const SkSmartAttributeInfo *lookup_attribute(SkDisk *d, uint8_t id) {
                                 /* %STRINGPOOLSTOP% */
 
                                 break;
+
+                        case 225:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_225_TOTALLBASWRITTEN) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "total-lbas-written", SK_SMART_ATTRIBUTE_UNIT_MB, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+
+                                break;
+
+                        case 226:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_226_TIMEWORKLOADMEDIAWEAR) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "timed-workload-media-wear", SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+
+                                break;
+
+                        case 227:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_227_TIMEWORKLOADHOSTREADS) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "timed-workload-host-reads", SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+
+                                break;
+
+                        case 228:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_228_WORKLOADTIMER) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "workload-timer", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+
+                                break;
+
+                        case 232:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_232_AVAILABLERESERVEDSPACE) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "available-reserved-space", SK_SMART_ATTRIBUTE_UNIT_PERCENT, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+                                break;
+
+                        case 233:
+                                /* %STRINGPOOLSTART% */
+                                if (quirk & SK_SMART_QUIRK_233_MEDIAWEAROUTINDICATOR) {
+                                        static const SkSmartAttributeInfo a = {
+                                                "media-wearout-indicator", SK_SMART_ATTRIBUTE_UNIT_PERCENT, NULL
+                                        };
+                                        return &a;
+                                }
+                                /* %STRINGPOOLSTOP% */
+                                break;
+
                 }
         }
 
@@ -1852,7 +2032,10 @@ const char* sk_smart_attribute_unit_to_string(SkSmartAttributeUnit unit) {
                 [SK_SMART_ATTRIBUTE_UNIT_NONE] = "",
                 [SK_SMART_ATTRIBUTE_UNIT_MSECONDS] = "ms",
                 [SK_SMART_ATTRIBUTE_UNIT_SECTORS] = "sectors",
-                [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK"
+                [SK_SMART_ATTRIBUTE_UNIT_MKELVIN] = "mK",
+                [SK_SMART_ATTRIBUTE_UNIT_PERCENT] = "%",
+                [SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT] = "%",
+                [SK_SMART_ATTRIBUTE_UNIT_MB] = "MB"
         };
         /* %STRINGPOOLSTOP% */
 
@@ -1911,6 +2094,7 @@ static void power_on_cb(SkDisk *d, const SkSmartAttributeParsedData *a, struct a
 
         if (!strcmp(a->name, "power-on-minutes") ||
             !strcmp(a->name, "power-on-seconds") ||
+            !strcmp(a->name, "power-on-seconds-2") ||
             !strcmp(a->name, "power-on-half-minutes") ||
             !strcmp(a->name, "power-on-hours")) {
 
@@ -2188,6 +2372,23 @@ static char *print_value(char *s, size_t len, uint64_t pretty_value, SkSmartAttr
                         snprintf(s, len, "%llu sectors", (unsigned long long) pretty_value);
                         break;
 
+                case SK_SMART_ATTRIBUTE_UNIT_PERCENT:
+                        snprintf(s, len, "%llu%%", (unsigned long long) pretty_value);
+                        break;
+
+                case SK_SMART_ATTRIBUTE_UNIT_SMALL_PERCENT:
+                        snprintf(s, len, "%0.3f%%", (double) pretty_value);
+                        break;
+
+                case SK_SMART_ATTRIBUTE_UNIT_MB:
+                        if (pretty_value >= 1000000LLU)
+                          snprintf(s, len, "%0.3f TB",  (double) pretty_value / 1000000LLU);
+                        else if (pretty_value >= 1000LLU)
+                          snprintf(s, len, "%0.3f GB",  (double) pretty_value / 1000LLU);
+                        else
+                          snprintf(s, len, "%llu MB", (unsigned long long) pretty_value);
+                        break;
+
                 case SK_SMART_ATTRIBUTE_UNIT_NONE:
                         snprintf(s, len, "%llu", (unsigned long long) pretty_value);
                         break;
@@ -2465,15 +2666,37 @@ static int disk_find_type(SkDisk *d, dev_t devnum) {
                         goto finish;
                 }
 
-                if ((vid == 0x0c0b && pid == 0xb159) ||
+                if ((vid == 0x0928 && pid == 0x0000))
+                        /* This Oxford Semiconductor bridge seems to
+                         * choke on SAT commands. Let's explicitly
+                         * black list it here.
+                         *
+                         * http://bugs.freedesktop.org/show_bug.cgi?id=24951 */
+                        d->type = SK_DISK_TYPE_NONE;
+                else if ((vid == 0x152d && pid == 0x2329) ||
+                         (vid == 0x152d && pid == 0x2338) ||
+                         (vid == 0x152d && pid == 0x2339))
+                        /* Some JMicron bridges seem to choke on SMART
+                         * commands, so let's explicitly black list
+                         * them here.
+                         *
+                         * https://bugzilla.redhat.com/show_bug.cgi?id=515881
+                         *
+                         * At least some of the JMicron bridges with
+                         * these vids/pids choke on the jmicron access
+                         * mode. To make sure we don't break things
+                         * for people we now disable this by
+                         * default. */
+                        d->type = SK_DISK_TYPE_NONE;
+                else if ((vid == 0x152d && pid == 0x2336))
+                        /* This JMicron bridge seems to always work
+                         * with SMART commands send with the jmicron
+                         * access mode. */
+                        d->type = SK_DISK_TYPE_JMICRON;
+                else if ((vid == 0x0c0b && pid == 0xb159) ||
                     (vid == 0x04fc && pid == 0x0c25) ||
                     (vid == 0x04fc && pid == 0x0c15))
                         d->type = SK_DISK_TYPE_SUNPLUS;
-                else if ((vid == 0x152d && pid == 0x2329) ||
-                    (vid == 0x152d && pid == 0x2336) ||
-                    (vid == 0x152d && pid == 0x2338) ||
-                    (vid == 0x152d && pid == 0x2339))
-                        d->type = SK_DISK_TYPE_JMICRON;
                 else
                         d->type = SK_DISK_TYPE_ATA_PASSTHROUGH_12;
 
@@ -2496,6 +2719,42 @@ finish:
         return r;
 }
 
+static int init_smart(SkDisk *d) {
+        /* We don't do the SMART initialization right-away, since some
+         * drivers spin up when we do that */
+
+        int ret;
+
+        if (d->smart_initialized)
+                return 0;
+
+        d->smart_initialized = TRUE;
+
+        /* Check if driver can do SMART, and enable if necessary */
+        if (!disk_smart_is_available(d))
+                return 0;
+
+        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);
+        ret = 0;
+
+fail:
+        return ret;
+}
+
 int sk_disk_open(const char *name, SkDisk **_d) {
         SkDisk *d;
         int ret = -1;
@@ -2570,26 +2829,6 @@ int sk_disk_open(const char *name, SkDisk **_d) {
                                 d->type = SK_DISK_TYPE_NONE;
                 } else
                         disk_identify_device(d);
-
-                /* Check if driver can do SMART, and enable if necessary */
-                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;
@@ -2627,7 +2866,7 @@ int sk_disk_get_blob(SkDisk *d, const void **blob, size_t *rsize) {
         size =
                 (d->identify_valid ? 8 + sizeof(d->identify) : 0) +
                 (d->smart_data_valid ? 8 + sizeof(d->smart_data) : 0) +
-                (d->smart_thresholds ? 8 + sizeof(d->smart_thresholds) : 0);
+                (d->smart_thresholds_valid ? 8 + sizeof(d->smart_thresholds) : 0);
 
         if (sk_disk_smart_status(d, &good) >= 0) {
                 size += 12;