/* These three will be autotested for: */
SK_DISK_TYPE_ATA_PASSTHROUGH_12, /* ATA passthrough over SCSI transport, 12-byte version */
SK_DISK_TYPE_ATA_PASSTHROUGH_16, /* ATA passthrough over SCSI transport, 16-byte version */
- SK_DISK_TYPE_ATA, /* Classic Linux /dev/hda ioctls */
+ SK_DISK_TYPE_LINUX_IDE, /* Classic Linux /dev/hda ioctls */
/* These three will not be autotested for */
SK_DISK_TYPE_SUNPLUS, /* SunPlus USB/ATA bridges */
- SK_DISK_TYPE_BLOB,
- SK_DISK_TYPE_UNKNOWN,
+ SK_DISK_TYPE_JMICRON, /* JMicron USB/ATA bridges */
+ SK_DISK_TYPE_BLOB, /* From a file */
+ SK_DISK_TYPE_NONE, /* No access method */
+ SK_DISK_TYPE_AUTO, /* We don't know yet */
_SK_DISK_TYPE_MAX,
_SK_DISK_TYPE_TEST_MAX = SK_DISK_TYPE_SUNPLUS /* only auto test until here */
} SkDiskType;
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;
SkBool blob_smart_status:1;
SkBool blob_smart_status_valid:1;
+ SkBool attribute_verification_bad:1;
+
SkIdentifyParsedData identify_parsed_data;
SkSmartParsedData smart_parsed_data;
SK_SMART_COMMAND_RETURN_STATUS = 0xDA
} SkSmartCommand;
-static const char *disk_type_to_string(SkDiskType type) {
+/* Hmm, if the data we parse is out of a certain range just consider it misparsed */
+#define SK_MKELVIN_VALID_MIN ((uint64_t) ((-15LL*1000LL) + 273150LL))
+#define SK_MKELVIN_VALID_MAX ((uint64_t) ((100LL*1000LL) + 273150LL))
+
+#define SK_MSECOND_VALID_MIN 1ULL
+#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% */
static const char* const map[_SK_DISK_TYPE_MAX] = {
[SK_DISK_TYPE_ATA_PASSTHROUGH_16] = "16 Byte SCSI ATA SAT Passthru",
[SK_DISK_TYPE_ATA_PASSTHROUGH_12] = "12 Byte SCSI ATA SAT Passthru",
- [SK_DISK_TYPE_ATA] = "Native Linux ATA",
+ [SK_DISK_TYPE_LINUX_IDE] = "Native Linux IDE",
[SK_DISK_TYPE_SUNPLUS] = "Sunplus SCSI ATA Passthru",
+ [SK_DISK_TYPE_JMICRON] = "JMicron SCSI ATA Passthru",
[SK_DISK_TYPE_BLOB] = "Blob",
- [SK_DISK_TYPE_UNKNOWN] = "Unknown"
+ [SK_DISK_TYPE_AUTO] = "Automatic",
+ [SK_DISK_TYPE_NONE] = "None"
};
/* %STRINGPOOLSTOP% */
return _P(map[type]);
}
+static const char *disk_type_to_prefix_string(SkDiskType type) {
+
+ /* %STRINGPOOLSTART% */
+ static const char* const map[_SK_DISK_TYPE_MAX] = {
+ [SK_DISK_TYPE_ATA_PASSTHROUGH_16] = "sat16",
+ [SK_DISK_TYPE_ATA_PASSTHROUGH_12] = "sat12",
+ [SK_DISK_TYPE_LINUX_IDE] = "linux-ide",
+ [SK_DISK_TYPE_SUNPLUS] = "sunplus",
+ [SK_DISK_TYPE_JMICRON] = "jmicron",
+ [SK_DISK_TYPE_NONE] = "none",
+ [SK_DISK_TYPE_AUTO] = "auto",
+ };
+ /* %STRINGPOOLSTOP% */
+
+ if (type >= _SK_DISK_TYPE_MAX)
+ return NULL;
+
+ return _P(map[type]);
+}
+
+static const char *disk_type_from_string(const char *s, SkDiskType *type) {
+ unsigned u;
+
+ assert(s);
+ assert(type);
+
+ for (u = 0; u < _SK_DISK_TYPE_MAX; u++) {
+ const char *t;
+ size_t l;
+
+ if (!(t = disk_type_to_prefix_string(u)))
+ continue;
+
+ l = strlen(t);
+
+ if (strncmp(s, t, l))
+ continue;
+
+ if (s[l] != ':')
+ continue;
+
+ *type = u;
+
+ return s + l + 1;
+ }
+
+ return NULL;
+}
+
static SkBool disk_smart_is_available(SkDisk *d) {
return d->identify_valid && !!(d->identify[164] & 1);
}
return !!(d->smart_data[367] & 41);
}
-static int disk_ata_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
+static int disk_linux_ide_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
uint8_t *bytes = cmd_data;
int ret;
- assert(d->type == SK_DISK_TYPE_ATA);
+ assert(d->type == SK_DISK_TYPE_LINUX_IDE);
switch (direction) {
memset(sense, 0, sizeof(sense));
- if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
+ if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, len ? *len : 0, sense, sizeof(sense))) < 0)
return ret;
if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
memset(sense, 0, sizeof(sense));
- if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[4] * 512, sense, sizeof(sense))) < 0)
+ if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, len ? *len : 0, sense, sizeof(sense))) < 0)
return ret;
if (sense[0] != 0x72 || desc[0] != 0x9 || desc[1] != 0x0c) {
assert(d->type == SK_DISK_TYPE_SUNPLUS);
- /* SunplusIT specific SCSI ATA pass-thru */
+ /* SunplusIT specific SCSI ATA pass-thru. Inspired by smartmonutils' support for these bridges */
memset(cdb, 0, sizeof(cdb));
memset(sense, 0, sizeof(sense));
/* Issue request */
- if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, (size_t) cdb[6] * 512, sense, sizeof(sense))) < 0)
+ if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, len ? *len : 0, sense, sizeof(sense))) < 0)
return ret;
memset(cdb, 0, sizeof(cdb));
return ret;
}
+static int disk_jmicron_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* _data, size_t *_len) {
+ uint8_t *bytes = cmd_data;
+ uint8_t cdb[12];
+ uint8_t sense[32];
+ uint8_t port;
+ int ret;
+ SkBool is_smart_status = FALSE;
+ void *data = _data;
+ size_t len = _len ? *_len : 0;
+ uint8_t smart_status = 0;
+
+ static const int direction_map[] = {
+ [SK_DIRECTION_NONE] = SG_DXFER_NONE,
+ [SK_DIRECTION_IN] = SG_DXFER_FROM_DEV,
+ [SK_DIRECTION_OUT] = SG_DXFER_TO_DEV
+ };
+
+ assert(d->type == SK_DISK_TYPE_JMICRON);
+
+ /* JMicron specific SCSI ATA pass-thru. Inspired by smartmonutils' support for these bridges */
+
+ memset(cdb, 0, sizeof(cdb));
+
+ cdb[0] = 0xdf; /* operation code */
+ cdb[1] = 0x10;
+ cdb[2] = 0x00;
+ cdb[3] = 0x00; /* size HI */
+ cdb[4] = sizeof(port); /* size LO */
+ cdb[5] = 0x00;
+ cdb[6] = 0x72; /* register address HI */
+ cdb[7] = 0x0f; /* register address LO */
+ cdb[8] = 0x00;
+ cdb[9] = 0x00;
+ cdb[10] = 0x00;
+ cdb[11] = 0xfd;
+
+ memset(sense, 0, sizeof(sense));
+
+ if ((ret = sg_io(d->fd, SG_DXFER_FROM_DEV, cdb, sizeof(cdb), &port, sizeof(port), sense, sizeof(sense))) < 0)
+ return ret;
+
+ /* Port & 0x04 is port #0, Port & 0x40 is port #1 */
+ if (!(port & 0x44))
+ return -EIO;
+
+ cdb[0] = 0xdf; /* OPERATION CODE: 12 byte pass through */
+
+ if (command == SK_ATA_COMMAND_SMART && bytes[1] == SK_SMART_COMMAND_RETURN_STATUS) {
+ /* We need to rewrite the SMART status request */
+ is_smart_status = TRUE;
+ direction = SK_DIRECTION_IN;
+ data = &smart_status;
+ len = sizeof(smart_status);
+ cdb[1] = 0x10;
+ } else if (direction == SK_DIRECTION_NONE)
+ cdb[1] = 0x10;
+ else if (direction == SK_DIRECTION_IN)
+ cdb[1] = 0x10;
+ else if (direction == SK_DIRECTION_OUT)
+ cdb[1] = 0x00;
+
+ cdb[2] = 0x00;
+
+ cdb[3] = (uint8_t) (len >> 8);
+ cdb[4] = (uint8_t) (len & 0xFF);
+
+ cdb[5] = bytes[1]; /* FEATURES */
+ cdb[6] = bytes[3]; /* SECTORS */
+
+ cdb[7] = bytes[9]; /* LBA LOW */
+ cdb[8] = bytes[8]; /* LBA MID */
+ cdb[9] = bytes[7]; /* LBA HIGH */
+
+ cdb[10] = bytes[10] | ((port & 0x04) ? 0xA0 : 0xB0); /* SELECT */
+ cdb[11] = (uint8_t) command;
+
+ memset(sense, 0, sizeof(sense));
+
+ if ((ret = sg_io(d->fd, direction_map[direction], cdb, sizeof(cdb), data, len, sense, sizeof(sense))) < 0)
+ return ret;
+
+ memset(bytes, 0, 12);
+
+ if (is_smart_status) {
+ if (smart_status == 0x01 || smart_status == 0xc2) {
+ bytes[7] = 0xc2; /* LBA HIGH */
+ bytes[8] = 0x4f; /* LBA MID */
+ } else if (smart_status == 0x00 || smart_status == 0x2c) {
+ bytes[7] = 0x2c; /* LBA HIGH */
+ bytes[8] = 0xf4; /* LBA MID */
+ } else
+ return -EIO;
+ } else {
+ uint8_t regbuf[16];
+
+ cdb[0] = 0xdf; /* operation code */
+ cdb[1] = 0x10;
+ cdb[2] = 0x00;
+ cdb[3] = 0x00; /* size HI */
+ cdb[4] = sizeof(regbuf); /* size LO */
+ cdb[5] = 0x00;
+ cdb[6] = (port & 0x04) ? 0x80 : 0x90; /* register address HI */
+ cdb[7] = 0x00; /* register address LO */
+ cdb[8] = 0x00;
+ cdb[9] = 0x00;
+ cdb[10] = 0x00;
+ cdb[11] = 0xfd;
+
+ if ((ret = sg_io(d->fd, SG_DXFER_FROM_DEV, cdb, sizeof(cdb), regbuf, sizeof(regbuf), sense, sizeof(sense))) < 0)
+ return ret;
+
+ bytes[2] = regbuf[14]; /* STATUS */
+ bytes[3] = regbuf[0]; /* SECTORS */
+ bytes[9] = regbuf[6]; /* LBA LOW */
+ bytes[8] = regbuf[4]; /* LBA MID */
+ bytes[7] = regbuf[10]; /* LBA HIGH */
+ bytes[10] = regbuf[9]; /* SELECT */
+ bytes[11] = regbuf[13]; /* ERROR */
+ }
+
+ return ret;
+}
+
static int disk_command(SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) {
static int (* const disk_command_table[_SK_DISK_TYPE_MAX]) (SkDisk *d, SkAtaCommand command, SkDirection direction, void* cmd_data, void* data, size_t *len) = {
- [SK_DISK_TYPE_ATA] = disk_ata_command,
+ [SK_DISK_TYPE_LINUX_IDE] = disk_linux_ide_command,
[SK_DISK_TYPE_ATA_PASSTHROUGH_12] = disk_passthrough_12_command,
[SK_DISK_TYPE_ATA_PASSTHROUGH_16] = disk_passthrough_16_command,
[SK_DISK_TYPE_SUNPLUS] = disk_sunplus_command,
- [SK_DISK_TYPE_BLOB] = NULL,
- [SK_DISK_TYPE_UNKNOWN] = NULL
+ [SK_DISK_TYPE_JMICRON] = disk_jmicron_command,
+ [SK_DISK_TYPE_BLOB] = NULL,
+ [SK_DISK_TYPE_AUTO] = NULL,
+ [SK_DISK_TYPE_NONE] = NULL
};
assert(d);
assert(direction == SK_DIRECTION_NONE || (data && len && *len > 0));
assert(direction != SK_DIRECTION_NONE || (!data && !len));
- if (!disk_command_table[d->type]) {
- errno = -ENOTSUP;
- return -1;
- }
+ if (!disk_command_table[d->type]) {
+ errno = -ENOTSUP;
+ return -1;
+ }
return disk_command_table[d->type](d, command, direction, cmd_data, data, len);
}
int ret;
size_t len = 512;
+ if (init_smart(d) < 0)
+ return -1;
+
if (!disk_smart_is_available(d)) {
errno = ENOTSUP;
return -1;
uint16_t cmd[6];
int ret;
+ if (init_smart(d) < 0)
+ return -1;
+
if (!disk_smart_is_available(d)) {
errno = ENOTSUP;
return -1;
uint16_t cmd[6];
int ret;
+ if (init_smart(d) < 0)
+ return -1;
+
if (!disk_smart_is_available(d)) {
errno = ENOTSUP;
return -1;
else if (!strcmp(a->name, "temperature-centi-celsius"))
a->pretty_value = (fourtyeight & 0xFFFF)*100 + 273150;
else if (!strcmp(a->name, "power-on-minutes"))
- a->pretty_value = (((uint64_t) a->raw[0]) | (uint64_t) a->raw[1]) * 60 * 1000;
- else if (!strcmp(a->name, "power-on-seconds"))
+ a->pretty_value = fourtyeight * 60 * 1000;
+ 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;
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;
+ a->pretty_value = (fourtyeight & 0xFFFFFFFFU) * 60 * 60 * 1000;
+ 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;
}
+typedef void (*SkSmartAttributeVerify)(SkDisk *d, SkSmartAttributeParsedData *a);
+
typedef struct SkSmartAttributeInfo {
const char *name;
SkSmartAttributeUnit unit;
+ SkSmartAttributeVerify verify;
} SkSmartAttributeInfo;
+static void verify_temperature(SkDisk *d, SkSmartAttributeParsedData *a) {
+ assert(a);
+ assert(a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_MKELVIN);
+
+ if (a->pretty_value < SK_MKELVIN_VALID_MIN ||
+ a->pretty_value > SK_MKELVIN_VALID_MAX) {
+ a->pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
+ d->attribute_verification_bad = TRUE;
+ }
+}
+
+static void verify_short_time(SkDisk *d, SkSmartAttributeParsedData *a) {
+ assert(a);
+ assert(a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_MSECONDS);
+
+ if (a->pretty_value < SK_MSECOND_VALID_MIN ||
+ a->pretty_value > SK_MSECOND_VALID_SHORT_MAX) {
+ a->pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
+ d->attribute_verification_bad = TRUE;
+ }
+}
+
+static void verify_long_time(SkDisk *d, SkSmartAttributeParsedData *a) {
+ assert(a);
+ assert(a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_MSECONDS);
+
+ if (a->pretty_value < SK_MSECOND_VALID_MIN ||
+ a->pretty_value > SK_MSECOND_VALID_LONG_MAX) {
+ a->pretty_unit = SK_SMART_ATTRIBUTE_UNIT_UNKNOWN;
+ d->attribute_verification_bad = TRUE;
+ }
+}
+
+static void verify_sectors(SkDisk *d, SkSmartAttributeParsedData *a) {
+ uint64_t max_sectors;
+
+ assert(d);
+ assert(a);
+ assert(a->pretty_unit == SK_SMART_ATTRIBUTE_UNIT_SECTORS);
+
+ max_sectors = d->size / 512ULL;
+
+ 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 {
+ if ((!strcmp(a->name, "reallocated-sector-count") ||
+ !strcmp(a->name, "current-pending-sector")) &&
+ a->pretty_value > 0)
+ a->warn = TRUE;
+ }
+}
+
/* This data is stolen from smartmontools */
/* %STRINGPOOLSTART% */
static const SkSmartAttributeInfo const attribute_info[256] = {
- [1] = { "raw-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
- [2] = { "throughput-performance", 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_SECTORS },
- [6] = { "read-channel-margin", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
- [7] = { "seek-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE },
- [8] = { "seek-time-performance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
- [9] = { "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
- [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_MKELVIN },
- [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_MKELVIN },
- [195] = { "hardware-ecc-recovered", SK_SMART_ATTRIBUTE_UNIT_NONE },
- [196] = { "reallocated-event-count", SK_SMART_ATTRIBUTE_UNIT_SECTORS },
- [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-open", SK_SMART_ATTRIBUTE_UNIT_NONE },
- [205] = { "shock-rate-write-open", 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-performance", 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-2", 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-2", SK_SMART_ATTRIBUTE_UNIT_NONE },
- [230] = { "head-amplitude", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN },
- [231] = { "temperature-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN },
- [240] = { "head-flying-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS },
- [250] = { "read-error-retry-rate", SK_SMART_ATTRIBUTE_UNIT_NONE }
+ [1] = { "raw-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [2] = { "throughput-performance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [3] = { "spin-up-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_short_time },
+ [4] = { "start-stop-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [5] = { "reallocated-sector-count", SK_SMART_ATTRIBUTE_UNIT_SECTORS, verify_sectors },
+ [6] = { "read-channel-margin", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [7] = { "seek-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [8] = { "seek-time-performance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [9] = { "power-on-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time },
+ [10] = { "spin-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [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 },
+ [192] = { "power-off-retract-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [193] = { "load-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [194] = { "temperature-celsius-2", SK_SMART_ATTRIBUTE_UNIT_MKELVIN, verify_temperature },
+ [195] = { "hardware-ecc-recovered", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [196] = { "reallocated-event-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [197] = { "current-pending-sector", SK_SMART_ATTRIBUTE_UNIT_SECTORS, verify_sectors },
+ [198] = { "offline-uncorrectable", SK_SMART_ATTRIBUTE_UNIT_SECTORS, verify_sectors },
+ [199] = { "udma-crc-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [200] = { "multi-zone-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [201] = { "soft-read-error-rate", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [202] = { "ta-increase-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [203] = { "run-out-cancel", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [204] = { "shock-count-write-open", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [205] = { "shock-rate-write-open", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [206] = { "flying-height", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [207] = { "spin-high-current", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [208] = { "spin-buzz", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [209] = { "offline-seek-performance", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [220] = { "disk-shift", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [221] = { "g-sense-error-rate-2", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [222] = { "loaded-hours", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time },
+ [223] = { "load-retry-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [224] = { "load-friction", SK_SMART_ATTRIBUTE_UNIT_UNKNOWN, NULL },
+ [225] = { "load-cycle-count-2", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [226] = { "load-in-time", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_short_time },
+ [227] = { "torq-amp-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL },
+ [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 = 1,
- SK_SMART_QUIRK_9_POWERONSECONDS = 2,
- SK_SMART_QUIRK_9_POWERONHALFMINUTES = 4,
- SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT = 8,
- SK_SMART_QUIRK_193_LOADUNLOAD = 16,
- SK_SMART_QUIRK_194_10XCELSIUS = 32,
- SK_SMART_QUIRK_194_UNKNOWN = 64,
- SK_SMART_QUIRK_200_WRITEERRORCOUNT = 128,
- SK_SMART_QUIRK_201_DETECTEDTACOUNT = 256,
+ 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% */
"194_UNKNOWN",
"200_WRITEERRORCOUNT",
"201_DETECTEDTACOUNT",
+ "5_UNKNOWN",
+ "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% */
static const SkSmartQuirkDatabase quirk_database[] = { {
/*** Fujitsu */
+ "^("
+ "FUJITSU MHY2120BH|"
+ "FUJITSU MHY2250BH"
+ ")$",
+ "^0085000B$", /* seems to be specific to this firmware */
+ SK_SMART_QUIRK_9_POWERONMINUTES|
+ SK_SMART_QUIRK_197_UNKNOWN|
+ SK_SMART_QUIRK_198_UNKNOWN
+ }, {
"^FUJITSU MHR2040AT$",
NULL,
SK_SMART_QUIRK_9_POWERONSECONDS|
SK_SMART_QUIRK_9_POWERONMINUTES|
SK_SMART_QUIRK_193_LOADUNLOAD
}, {
+ "^HTS541010G9SA00$",
+ "^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
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)
+ return NULL;
+
+ break;
case 9:
/* %STRINGPOOLSTART% */
if (quirk & SK_SMART_QUIRK_9_POWERONMINUTES) {
static const SkSmartAttributeInfo a = {
- "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+ "power-on-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time
};
return &a;
} else if (quirk & SK_SMART_QUIRK_9_POWERONSECONDS) {
static const SkSmartAttributeInfo a = {
- "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+ "power-on-seconds", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time
};
return &a;
} else if (quirk & SK_SMART_QUIRK_9_POWERONHALFMINUTES) {
static const SkSmartAttributeInfo a = {
- "power-on-half-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS
+ "power-on-half-minutes", SK_SMART_ATTRIBUTE_UNIT_MSECONDS, verify_long_time
};
return &a;
- }
+ } else if (quirk & SK_SMART_QUIRK_9_UNKNOWN)
+ return NULL;
/* %STRINGPOOLSTOP% */
break;
+ case 190:
+ if (quirk & SK_SMART_QUIRK_190_UNKNOWN)
+ return NULL;
+
+ break;
+
case 192:
/* %STRINGPOOLSTART% */
if (quirk & SK_SMART_QUIRK_192_EMERGENCYRETRACTCYCLECT) {
static const SkSmartAttributeInfo a = {
- "emergency-retract-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE
+ "emergency-retract-cycle-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL
};
return &a;
}
/* %STRINGPOOLSTART% */
if (quirk & SK_SMART_QUIRK_194_10XCELSIUS) {
static const SkSmartAttributeInfo a = {
- "temperature-centi-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN
+ "temperature-centi-celsius", SK_SMART_ATTRIBUTE_UNIT_MKELVIN, verify_temperature
};
return &a;
} else if (quirk & SK_SMART_QUIRK_194_UNKNOWN)
break;
+ case 197:
+ if (quirk & SK_SMART_QUIRK_197_UNKNOWN)
+ return NULL;
+
+ break;
+
+ case 198:
+ if (quirk & SK_SMART_QUIRK_198_UNKNOWN)
+ return NULL;
+
+ break;
+
case 200:
/* %STRINGPOOLSTART% */
if (quirk & SK_SMART_QUIRK_200_WRITEERRORCOUNT) {
static const SkSmartAttributeInfo a = {
- "write-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE
+ "write-error-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL
};
return &a;
}
/* %STRINGPOOLSTART% */
if (quirk & SK_SMART_QUIRK_201_DETECTEDTACOUNT) {
static const SkSmartAttributeInfo a = {
- "detected-ta-count", SK_SMART_ATTRIBUTE_UNIT_NONE
+ "detected-ta-count", SK_SMART_ATTRIBUTE_UNIT_NONE, NULL
+ };
+ return &a;
+ }
+ /* %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;
+
}
}
uint8_t *p;
unsigned n;
- if (!d->smart_thresholds_valid) {
- a->threshold_valid = FALSE;
- return;
- }
+ if (!d->smart_thresholds_valid)
+ goto fail;
for (n = 0, p = d->smart_thresholds+2; n < 30; n++, p+=12)
if (p[0] == a->id)
break;
- if (n >= 30) {
- a->threshold_valid = FALSE;
- a->good_valid = FALSE;
- return;
- }
+ if (n >= 30)
+ goto fail;
a->threshold = p[1];
a->threshold_valid = p[1] != 0xFE;
- a->good_valid = FALSE;
- a->good = TRUE;
+ a->good_now_valid = FALSE;
+ a->good_now = TRUE;
+ a->good_in_the_past_valid = FALSE;
+ a->good_in_the_past = TRUE;
/* Always-Fail and Always-Passing thresholds are not relevant
* for our assessment. */
if (p[1] >= 1 && p[1] <= 0xFD) {
if (a->worst_value_valid) {
- a->good = a->good && (a->worst_value > a->threshold);
- a->good_valid = TRUE;
+ a->good_in_the_past = a->good_in_the_past && (a->worst_value > a->threshold);
+ a->good_in_the_past_valid = TRUE;
}
if (a->current_value_valid) {
- a->good = a->good && (a->current_value > a->threshold);
- a->good_valid = TRUE;
+ a->good_now = a->good_now && (a->current_value > a->threshold);
+ a->good_now_valid = TRUE;
}
}
+
+ a->warn =
+ (a->good_now_valid && !a->good_now) ||
+ (a->good_in_the_past_valid && !a->good_in_the_past);
+
+ return;
+
+fail:
+ a->threshold_valid = FALSE;
+ a->good_now_valid = FALSE;
+ a->good_in_the_past_valid = FALSE;
+ a->warn = FALSE;
}
int sk_disk_smart_parse_attributes(SkDisk *d, SkSmartAttributeParseCallback cb, void* userdata) {
find_threshold(d, &a);
- /* Handle a few fields specially */
- if ((!strcmp(a.name, "reallocated-sector-count") ||
- !strcmp(a.name, "reallocated-event-count") ||
- !strcmp(a.name, "current-pending-sector")) &&
- a.pretty_unit == SK_SMART_ATTRIBUTE_UNIT_SECTORS &&
- a.pretty_value > 0) {
- a.good = FALSE;
- a.good_valid = TRUE;
- }
+ if (i && i->verify)
+ i->verify(d, &a);
cb(d, &a, userdata);
free(an);
[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% */
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")) {
/* %STRINGPOOLSTART% */
const char * const map[] = {
[SK_SMART_OVERALL_GOOD] = "GOOD",
+ [SK_SMART_OVERALL_BAD_ATTRIBUTE_IN_THE_PAST] = "BAD_ATTRIBUTE_IN_THE_PAST",
+ [SK_SMART_OVERALL_BAD_SECTOR] = "BAD_SECTOR",
+ [SK_SMART_OVERALL_BAD_ATTRIBUTE_NOW] = "BAD_ATTRIBUTE_NOW",
+ [SK_SMART_OVERALL_BAD_SECTOR_MANY] = "BAD_SECTOR_MANY",
[SK_SMART_OVERALL_BAD_STATUS] = "BAD_STATUS",
- [SK_SMART_OVERALL_BAD_ATTRIBUTE] = "BAD_ATTRIBUTE",
- [SK_SMART_OVERALL_BAD_SECTOR] = "BAD_SECTOR"
};
/* %STRINGPOOLSTOP% */
return _P(map[overall]);
}
-static void bad_attribute_cb(SkDisk *d, const SkSmartAttributeParsedData *a, SkBool *good) {
- if (a->prefailure && a->good_valid && !a->good)
+static void bad_attribute_now_cb(SkDisk *d, const SkSmartAttributeParsedData *a, SkBool *good) {
+ if (a->prefailure && a->good_now_valid && !a->good_now)
+ *good = FALSE;
+}
+
+static void bad_attribute_in_the_past_cb(SkDisk *d, const SkSmartAttributeParsedData *a, SkBool *good) {
+ if (a->prefailure && a->good_in_the_past_valid && !a->good_in_the_past)
*good = FALSE;
}
+static uint64_t u64log2(uint64_t n) {
+ unsigned r;
+
+ if (n <= 1)
+ return 0;
+
+ r = 0;
+ for (;;) {
+ n = n >> 1;
+ if (!n)
+ return r;
+ r++;
+ }
+}
+
int sk_disk_smart_get_overall(SkDisk *d, SkSmartOverall *overall) {
SkBool good;
- uint64_t sectors;
+ uint64_t sectors, sector_threshold;
assert(d);
assert(overall);
+ /* First, check SMART self-assesment */
if (sk_disk_smart_status(d, &good) < 0)
return -1;
return 0;
}
+ /* Second, check if the number of bad sectors is greater than
+ * a certain threshold */
if (sk_disk_smart_get_bad(d, §ors) < 0) {
if (errno != ENOENT)
return -1;
- } else if (sectors > 0) {
+ sectors = 0;
+ } else {
+
+ /* We use log2(n_sectors) as a threshold here. We had to pick
+ * something, and this makes a bit of sense, or doesn't it? */
+ sector_threshold = u64log2(d->size/512);
+
+ if (sectors >= sector_threshold) {
+ *overall = SK_SMART_OVERALL_BAD_SECTOR_MANY;
+ return 0;
+ }
+ }
+
+ /* Third, check if any of the SMART attributes is bad */
+ good = TRUE;
+ if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) bad_attribute_now_cb, &good) < 0)
+ return -1;
+
+ if (!good) {
+ *overall = SK_SMART_OVERALL_BAD_ATTRIBUTE_NOW;
+ return 0;
+ }
+
+ /* Fourth, check if there are any bad sectors at all */
+ if (sectors > 0) {
*overall = SK_SMART_OVERALL_BAD_SECTOR;
return 0;
}
+ /* Fifth, check if any of the SMART attributes ever was bad */
good = TRUE;
- if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) bad_attribute_cb, &good) < 0)
+ if (sk_disk_smart_parse_attributes(d, (SkSmartAttributeParseCallback) bad_attribute_in_the_past_cb, &good) < 0)
return -1;
if (!good) {
- *overall = SK_SMART_OVERALL_BAD_ATTRIBUTE;
+ *overall = SK_SMART_OVERALL_BAD_ATTRIBUTE_IN_THE_PAST;
return 0;
}
+ /* Sixth, there's really nothing to complain about, so give it a pass */
*overall = SK_SMART_OVERALL_GOOD;
return 0;
}
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;
snprintf(tc, sizeof(tc), "%3u", a->current_value);
tc[sizeof(tc)-1] = 0;
- highlight = a->good_valid && !a->good && isatty(1);
+ highlight = a->warn && isatty(1);
if (highlight)
fprintf(stderr, HIGHLIGHT);
- printf("%3u %-27s %-3s %-3s %-3s %-11s 0x%02x%02x%02x%02x%02x%02x %-7s %-7s %-3s\n",
+ printf("%3u %-27s %-3s %-3s %-3s %-11s 0x%02x%02x%02x%02x%02x%02x %-7s %-7s %-4s %-4s\n",
a->id,
print_name(name, sizeof(name), a->id, a->name),
a->current_value_valid ? tc : "n/a",
a->raw[0], a->raw[1], a->raw[2], a->raw[3], a->raw[4], a->raw[5],
a->prefailure ? "prefail" : "old-age",
a->online ? "online" : "offline",
- a->good_valid ? yes_no(a->good) : "n/a");
+ a->good_now_valid ? yes_no(a->good_now) : "n/a",
+ a->good_in_the_past_valid ? yes_no(a->good_in_the_past) : "n/a");
if (highlight)
fprintf(stderr, ENDHIGHLIGHT);
assert(d);
- printf("Device: %s\n"
+ printf("Device: %s%s%s\n"
"Type: %s\n",
+ d->name ? disk_type_to_prefix_string(d->type) : "",
+ d->name ? ":" : "",
d->name ? d->name : "n/a",
- disk_type_to_string(d->type));
+ disk_type_to_human_string(d->type));
ret = sk_disk_get_size(d, &size);
if (ret >= 0)
printf(" %s", _P(quirk_name[i]));
printf("\n");
-
}
ret = sk_disk_check_sleep_mode(d, &awake);
uint64_t value, power_on;
ret = sk_disk_smart_status(d, &good);
- printf("SMART Disk Health Good: %s\n",
- ret >= 0 ? yes_no(good) : strerror(errno));
-
+ printf("%sSMART Disk Health Good: %s%s\n",
+ ret >= 0 && !good ? HIGHLIGHT : "",
+ ret >= 0 ? yes_no(good) : strerror(errno),
+ ret >= 0 && !good ? ENDHIGHLIGHT : "");
if ((ret = sk_disk_smart_read_data(d)) < 0)
return ret;
else
printf("Temperature: %s\n", print_value(pretty, sizeof(pretty), value, SK_SMART_ATTRIBUTE_UNIT_MKELVIN));
+ printf("Attribute Parsing Verification: %s\n",
+ d->attribute_verification_bad ? "Bad" : "Good");
+
if (sk_disk_smart_get_overall(d, &overall) < 0)
printf("Overall Status: %s\n", strerror(errno));
else
sk_smart_overall_to_string(overall),
overall != SK_SMART_OVERALL_GOOD ? ENDHIGHLIGHT : "");
- printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-3s\n",
+ printf("%3s %-27s %5s %5s %5s %-11s %-14s %-7s %-7s %-4s %-4s\n",
"ID#",
"Name",
"Value",
"Raw",
"Type",
"Updates",
- "Good");
+ "Good",
+ "Good/Past");
if ((ret = sk_disk_smart_parse_attributes(d, disk_dump_attributes, NULL)) < 0)
return ret;
} else
- printf("ATA SMART not supported.\n");
+ printf("ATA SMART not supported.\n");
return 0;
}
struct udev *udev;
struct udev_device *dev = NULL, *usb;
int r = -1;
+ const char *a;
assert(d);
goto finish;
}
+ if ((a = udev_device_get_property_value(dev, "ID_ATA_SMART_ACCESS"))) {
+ unsigned u;
+
+ for (u = 0; u < _SK_DISK_TYPE_MAX; u++) {
+ const char *t;
+
+ if (!(t = disk_type_to_prefix_string(u)))
+ continue;
+
+ if (!strcmp(a, t)) {
+ d->type = u;
+ r = 0;
+ goto finish;
+ }
+ }
+
+ d->type = SK_DISK_TYPE_NONE;
+ r = 0;
+ goto finish;
+ }
+
if ((usb = udev_device_get_parent_with_subsystem_devtype(dev, "usb", "usb_device"))) {
const char *product, *vendor;
uint32_t pid, vid;
goto finish;
}
- if ((vid == 0x0c0b && pid == 0xb159) ||
- (vid == 0x04fc && pid == 0x0c25))
+ 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
d->type = SK_DISK_TYPE_ATA_PASSTHROUGH_12;
} else if (udev_device_get_parent_with_subsystem_devtype(dev, "ide", NULL))
- d->type = SK_DISK_TYPE_ATA;
+ d->type = SK_DISK_TYPE_LINUX_IDE;
else if (udev_device_get_parent_with_subsystem_devtype(dev, "scsi", NULL))
d->type = SK_DISK_TYPE_ATA_PASSTHROUGH_16;
else
- d->type = SK_DISK_TYPE_UNKNOWN;
+ d->type = SK_DISK_TYPE_AUTO;
r = 0;
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;
goto fail;
}
- if (!name) {
- d->fd = -1;
+ d->fd = -1;
+ d->size = (uint64_t) -1;
+
+ if (!name)
d->type = SK_DISK_TYPE_BLOB;
- d->size = (uint64_t) -1;
- } else {
+ else {
+ const char *dn;
+
+ d->type = SK_DISK_TYPE_AUTO;
+
+ if (!(dn = disk_type_from_string(name, &d->type)))
+ dn = name;
- if (!(d->name = strdup(name))) {
+ if (!(d->name = strdup(dn))) {
errno = ENOMEM;
goto fail;
}
- if ((d->fd = open(name,
+ if ((d->fd = open(d->name,
O_RDONLY|O_NOCTTY|O_NONBLOCK
#ifdef O_CLOEXEC
|O_CLOEXEC
}
/* OK, it's a real block device with a size. Now let's find the suitable API */
- if ((ret = disk_find_type(d, st.st_rdev)) < 0)
- goto fail;
+ if (d->type == SK_DISK_TYPE_AUTO)
+ if ((ret = disk_find_type(d, st.st_rdev)) < 0)
+ goto fail;
- if (d->type == SK_DISK_TYPE_UNKNOWN) {
+ if (d->type == SK_DISK_TYPE_AUTO) {
/* We have no clue, so let's autotest for a working API */
for (d->type = 0; d->type < _SK_DISK_TYPE_TEST_MAX; d->type++)
if (disk_identify_device(d) >= 0)
break;
- if (d->type >= _SK_DISK_TYPE_TEST_MAX)
- d->type = SK_DISK_TYPE_UNKNOWN;
+ if (d->type >= _SK_DISK_TYPE_TEST_MAX)
+ 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;
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;