+// Get log access permissions
+static const char * get_log_rw(unsigned logaddr)
+{
+ if ( ( logaddr <= 0x08)
+ || (0x0c <= logaddr && logaddr <= 0x0d)
+ || (0x0f <= logaddr && logaddr <= 0x14)
+ || (0x19 == logaddr)
+ || (0x20 <= logaddr && logaddr <= 0x25)
+ || (0x30 == logaddr))
+ return "R/O";
+
+ if ( (0x09 <= logaddr && logaddr <= 0x0a)
+ || (0x15 == logaddr)
+ || (0x80 <= logaddr && logaddr <= 0x9f)
+ || (0xe0 <= logaddr && logaddr <= 0xe1))
+ return "R/W";
+
+ if (0xa0 <= logaddr && logaddr <= 0xdf)
+ return "VS"; // Vendor specific
+
+ return "-"; // Unknown/Reserved
+}
+
+// Init a fake log directory, assume that standard logs are supported
+const ata_smart_log_directory * fake_logdir(ata_smart_log_directory * logdir,
+ const ata_print_options & options)
+{
+ memset(logdir, 0, sizeof(*logdir));
+ logdir->logversion = 255;
+ logdir->entry[0x01-1].numsectors = 1;
+ logdir->entry[0x03-1].numsectors = (options.smart_ext_error_log + (4-1)) / 4;
+ logdir->entry[0x04-1].numsectors = 8;
+ logdir->entry[0x06-1].numsectors = 1;
+ logdir->entry[0x07-1].numsectors = (options.smart_ext_selftest_log + (19-1)) / 19;
+ logdir->entry[0x09-1].numsectors = 1;
+ logdir->entry[0x11-1].numsectors = 1;
+ return logdir;
+}
+
+// Print SMART and/or GP Log Directory
+static void PrintLogDirectories(const ata_smart_log_directory * gplogdir,
+ const ata_smart_log_directory * smartlogdir)
+{
+ json::ref jref = jglb["ata_log_directory"];
+ if (gplogdir) {
+ jout("General Purpose Log Directory Version %u\n", gplogdir->logversion);
+ jref["gp_dir_version"] = gplogdir->logversion;
+ }
+ if (smartlogdir) {
+ jout("SMART %sLog Directory Version %u%s\n",
+ (gplogdir ? " " : ""), smartlogdir->logversion,
+ (smartlogdir->logversion==1 ? " [multi-sector log support]" : ""));
+ jref["smart_dir_version"] = smartlogdir->logversion;
+ jref["smart_dir_multi_sector"] = (smartlogdir->logversion == 1);
+ }
+
+ jout("Address Access R/W Size Description\n");
+
+ for (unsigned i = 0, ji = 0; i <= 0xff; i++) {
+ // Get number of sectors
+ unsigned smart_numsect = GetNumLogSectors(smartlogdir, i, false);
+ unsigned gp_numsect = GetNumLogSectors(gplogdir , i, true );
+
+ if (!(smart_numsect || gp_numsect))
+ continue; // Log does not exist
+
+ const char * acc; unsigned size;
+ if (smart_numsect == gp_numsect) {
+ acc = "GPL,SL"; size = gp_numsect;
+ }
+ else if (!smart_numsect) {
+ acc = "GPL"; size = gp_numsect;
+ }
+ else if (!gp_numsect) {
+ acc = " SL"; size = smart_numsect;
+ }
+ else {
+ acc = 0; size = 0;
+ }
+
+ unsigned i2 = i;
+ if (acc && ((0x80 <= i && i < 0x9f) || (0xa0 <= i && i < 0xdf))) {
+ // Find range of Host/Device vendor specific logs with same size
+ unsigned imax = (i < 0x9f ? 0x9f : 0xdf);
+ for (unsigned j = i+1; j <= imax; j++) {
+ unsigned sn = GetNumLogSectors(smartlogdir, j, false);
+ unsigned gn = GetNumLogSectors(gplogdir , j, true );
+
+ if (!(sn == smart_numsect && gn == gp_numsect))
+ break;
+ i2 = j;
+ }
+ }
+
+ const char * name = GetLogName(i);
+ const char * rw = get_log_rw(i);
+
+ if (i2 > i)
+ jout("0x%02x-0x%02x %-6s %-3s %5u %s\n", i, i2, acc, rw, size, name);
+ else if (acc)
+ jout( "0x%02x %-6s %-3s %5u %s\n", i, acc, rw, size, name);
+ else {
+ // GPL and SL support different sizes
+ jout( "0x%02x %-6s %-3s %5u %s\n", i, "GPL", rw, gp_numsect, name);
+ jout( "0x%02x %-6s %-3s %5u %s\n", i, "SL", rw, smart_numsect, name);
+ }
+
+ for (;;) {
+ json::ref jrefi = jref["table"][ji++];
+ jrefi["address"] = i;
+ jrefi["name"] = name;
+ if (rw[0] == 'R' && rw[1] && rw[2]) {
+ jrefi["read"] = true;
+ jrefi["write"] = (rw[2] == 'W');
+ }
+ if (gp_numsect)
+ jrefi["gp_sectors"] = gp_numsect;
+ if (smart_numsect)
+ jrefi["smart_sectors"] = smart_numsect;
+ if (i >= i2)
+ break;
+ i++;
+ }
+ }
+ jout("\n");
+}
+
+// Print hexdump of log pages.
+// Format is compatible with 'xxd -r'.
+static void PrintLogPages(const char * type, const unsigned char * data,
+ unsigned char logaddr, unsigned page,
+ unsigned num_pages, unsigned max_pages)
+{
+ pout("%s Log 0x%02x [%s], Page %u-%u (of %u)\n",
+ type, logaddr, GetLogName(logaddr), page, page+num_pages-1, max_pages);
+ for (unsigned i = 0; i < num_pages * 512; i += 16) {
+ const unsigned char * p = data+i;
+ pout("%07x: %02x %02x %02x %02x %02x %02x %02x %02x "
+ "%02x %02x %02x %02x %02x %02x %02x %02x ",
+ (page * 512) + i,
+ p[ 0], p[ 1], p[ 2], p[ 3], p[ 4], p[ 5], p[ 6], p[ 7],
+ p[ 8], p[ 9], p[10], p[11], p[12], p[13], p[14], p[15]);
+#define P(n) (' ' <= p[n] && p[n] <= '~' ? (int)p[n] : '.')
+ pout("|%c%c%c%c%c%c%c%c"
+ "%c%c%c%c%c%c%c%c|\n",
+ P( 0), P( 1), P( 2), P( 3), P( 4), P( 5), P( 6), P( 7),
+ P( 8), P( 9), P(10), P(11), P(12), P(13), P(14), P(15));
+#undef P
+ if ((i & 0x1ff) == 0x1f0)
+ pout("\n");
+ }
+}
+
+///////////////////////////////////////////////////////////////////////
+// Device statistics (Log 0x04)
+
+// Section A.5 of T13/2161-D (ACS-3) Revision 5, October 28, 2013
+// Section 9.5 of T13/BSR INCITS 529 (ACS-4) Revision 20, October 26, 2017
+
+struct devstat_entry_info
+{
+ short size; // #bytes of value, -1 for signed char
+ const char * name;
+};
+
+const devstat_entry_info devstat_info_0x00[] = {
+ { 2, "List of supported log pages" },
+ { 0, 0 }
+};
+
+const devstat_entry_info devstat_info_0x01[] = {
+ { 2, "General Statistics" },
+ { 4, "Lifetime Power-On Resets" },
+ { 4, "Power-on Hours" },
+ { 6, "Logical Sectors Written" },
+ { 6, "Number of Write Commands" },
+ { 6, "Logical Sectors Read" },
+ { 6, "Number of Read Commands" },
+ { 6, "Date and Time TimeStamp" }, // ACS-3
+ { 4, "Pending Error Count" }, // ACS-4
+ { 2, "Workload Utilization" }, // ACS-4
+ { 6, "Utilization Usage Rate" }, // ACS-4 (TODO: field provides 3 values)
+ { 2, "Resource Availability" }, // ACS-4
+ { 1, "Random Write Resources Used" }, // ACS-4
+ { 0, 0 }
+};
+
+const devstat_entry_info devstat_info_0x02[] = {
+ { 2, "Free-Fall Statistics" },
+ { 4, "Number of Free-Fall Events Detected" },
+ { 4, "Overlimit Shock Events" },
+ { 0, 0 }
+};
+
+const devstat_entry_info devstat_info_0x03[] = {
+ { 2, "Rotating Media Statistics" },
+ { 4, "Spindle Motor Power-on Hours" },
+ { 4, "Head Flying Hours" },
+ { 4, "Head Load Events" },
+ { 4, "Number of Reallocated Logical Sectors" },
+ { 4, "Read Recovery Attempts" },
+ { 4, "Number of Mechanical Start Failures" },
+ { 4, "Number of Realloc. Candidate Logical Sectors" }, // ACS-3
+ { 4, "Number of High Priority Unload Events" }, // ACS-3
+ { 0, 0 }
+};
+
+const devstat_entry_info devstat_info_0x04[] = {
+ { 2, "General Errors Statistics" },
+ { 4, "Number of Reported Uncorrectable Errors" },
+//{ 4, "Number of Resets Between Command Acceptance and Command Completion" },
+ { 4, "Resets Between Cmd Acceptance and Completion" },
+ { 4, "Physical Element Status Changed" }, // ACS-4
+ { 0, 0 }
+};
+
+const devstat_entry_info devstat_info_0x05[] = {
+ { 2, "Temperature Statistics" },
+ { -1, "Current Temperature" },
+ { -1, "Average Short Term Temperature" },
+ { -1, "Average Long Term Temperature" },
+ { -1, "Highest Temperature" },
+ { -1, "Lowest Temperature" },
+ { -1, "Highest Average Short Term Temperature" },
+ { -1, "Lowest Average Short Term Temperature" },
+ { -1, "Highest Average Long Term Temperature" },
+ { -1, "Lowest Average Long Term Temperature" },
+ { 4, "Time in Over-Temperature" },
+ { -1, "Specified Maximum Operating Temperature" },
+ { 4, "Time in Under-Temperature" },
+ { -1, "Specified Minimum Operating Temperature" },
+ { 0, 0 }
+};
+
+const devstat_entry_info devstat_info_0x06[] = {
+ { 2, "Transport Statistics" },
+ { 4, "Number of Hardware Resets" },
+ { 4, "Number of ASR Events" },
+ { 4, "Number of Interface CRC Errors" },
+ { 0, 0 }
+};
+
+const devstat_entry_info devstat_info_0x07[] = {
+ { 2, "Solid State Device Statistics" },
+ { 1, "Percentage Used Endurance Indicator" },
+ { 0, 0 }
+};
+
+const devstat_entry_info * devstat_infos[] = {
+ devstat_info_0x00,
+ devstat_info_0x01,
+ devstat_info_0x02,
+ devstat_info_0x03,
+ devstat_info_0x04,
+ devstat_info_0x05,
+ devstat_info_0x06,
+ devstat_info_0x07
+ // TODO: 0x08 Zoned Device Statistics (T13/f16136r7, January 2017)
+};
+
+const int num_devstat_infos = sizeof(devstat_infos)/sizeof(devstat_infos[0]);
+
+static const char * get_device_statistics_page_name(int page)
+{
+ if (page < num_devstat_infos)
+ return devstat_infos[page][0].name;
+ if (page == 0xff)
+ return "Vendor Specific Statistics"; // ACS-4
+ return "Unknown Statistics";
+}
+
+static void set_json_globals_from_device_statistics(int page, int offset, int64_t val)
+{
+ switch (page) {
+ case 1:
+ switch (offset) {
+ case 0x008: jglb["power_cycle_count"] = val; break; // ~= Lifetime Power-On Resets
+ case 0x010: jglb["power_on_time"]["hours"]= val; break;
+ }
+ break;
+ case 5:
+ switch (offset) {
+ case 0x008: jglb["temperature"]["current"] = val; break;
+ case 0x020: jglb["temperature"]["lifetime_max"] = val; break;
+ case 0x028: jglb["temperature"]["lifetime_min"] = val; break;
+ case 0x050: jglb["temperature"]["lifetime_over_limit_minutes"] = val; break;
+ case 0x058: jglb["temperature"]["op_limit_max"] = val; break;
+ case 0x060: jglb["temperature"]["lifetime_under_limit_minutes"] = val; break;
+ case 0x068: jglb["temperature"]["op_limit_min"] = val; break;
+ }
+ break;
+ }
+}
+
+static void print_device_statistics_page(const json::ref & jref, const unsigned char * data, int page)
+{
+ const devstat_entry_info * info = (page < num_devstat_infos ? devstat_infos[page] : 0);
+ const char * name = get_device_statistics_page_name(page);
+
+ // Check page number in header
+ static const char line[] = " ===== = = === == ";
+ if (!data[2]) {
+ pout("0x%02x%s%s (empty) ==\n", page, line, name);
+ return;
+ }
+ if (data[2] != page) {
+ pout("0x%02x%s%s (invalid page 0x%02x in header) ==\n", page, line, name, data[2]);
+ return;
+ }
+
+ int rev = data[0] | (data[1] << 8);
+ jout("0x%02x%s%s (rev %d) ==\n", page, line, name, rev);
+ jref["number"] = page;
+ jref["name"] = name;
+ jref["revision"] = rev;
+
+ // Print entries
+ int ji = 0;
+ for (int i = 1, offset = 8; offset < 512-7; i++, offset+=8) {
+ // Check for last known entry
+ if (info && !info[i].size)
+ info = 0;
+
+ // Skip unsupported entries
+ unsigned char flags = data[offset+7];
+ if (!(flags & 0x80))
+ continue;
+
+ // Stop if unknown entries contain garbage data due to buggy firmware
+ if (!info && (data[offset+5] || data[offset+6])) {
+ pout("0x%02x 0x%03x - - [Trailing garbage ignored]\n", page, offset);
+ break;
+ }
+
+ // Get value name
+ const char * valname = (info ? info[i].name :
+ (page == 0xff) ? "Vendor Specific" // ACS-4
+ : "Unknown" );
+
+ // Get value size, default to max if unknown
+ int size = (info ? info[i].size : 7);
+
+ // Get flags (supported flag already checked above)
+ bool valid = !!(flags & 0x40);
+ bool normalized = !!(flags & 0x20);
+ bool supports_dsn = !!(flags & 0x10); // ACS-3
+ bool monitored_condition_met = !!(flags & 0x08); // ACS-3
+ unsigned char reserved_flags = (flags & 0x07);
+
+ // Format value
+ int64_t val = 0;
+ char valstr[32];
+ if (valid) {
+ // Get value
+ if (size < 0) {
+ val = (signed char)data[offset];
+ }
+ else {
+ for (int j = 0; j < size; j++)
+ val |= (int64_t)data[offset+j] << (j*8);
+ }
+ snprintf(valstr, sizeof(valstr), "%" PRId64, val);
+ }
+ else {
+ // Value not known (yet)
+ valstr[0] = '-'; valstr[1] = 0;
+ }
+
+ char flagstr[] = {
+ (valid ? 'V' : '-'), // JSON only
+ (normalized ? 'N' : '-'),
+ (supports_dsn ? 'D' : '-'),
+ (monitored_condition_met ? 'C' : '-'),
+ (reserved_flags ? '+' : ' '),
+ 0
+ };
+
+ jout("0x%02x 0x%03x %d %15s %s %s\n",
+ page, offset, abs(size), valstr, flagstr+1, valname);
+
+ if (!jglb.is_enabled())
+ continue;
+
+ json::ref jrefi = jref["table"][ji++];
+ jrefi["offset"] = offset;
+ jrefi["name"] = valname;
+ jrefi["size"] = abs(size);
+ if (valid)
+ jrefi["value"] = val; // TODO: May be unsafe JSON int if size > 6
+
+ json::ref jreff = jrefi["flags"];
+ jreff["value"] = flags;
+ jreff["string"] = flagstr;
+ jreff["valid"] = valid;
+ jreff["normalized"] = normalized;
+ jreff["supports_dsn"] = supports_dsn;
+ jreff["monitored_condition_met"] = monitored_condition_met;
+ if (reserved_flags)
+ jreff["other"] = reserved_flags;
+
+ if (valid)
+ set_json_globals_from_device_statistics(page, offset, val);
+ }
+}
+
+static bool print_device_statistics(ata_device * device, unsigned nsectors,
+ const std::vector<int> & single_pages, bool all_pages, bool ssd_page,
+ bool use_gplog)
+{
+ // Read list of supported pages from page 0
+ unsigned char page_0[512] = {0, };
+ int rc;
+
+ if (use_gplog)
+ rc = ataReadLogExt(device, 0x04, 0, 0, page_0, 1);
+ else
+ rc = ataReadSmartLog(device, 0x04, page_0, 1);
+ if (!rc) {
+ jerr("Read Device Statistics page 0x00 failed\n\n");
+ return false;
+ }
+
+ unsigned char nentries = page_0[8];
+ if (!(page_0[2] == 0 && nentries > 0)) {
+ jerr("Device Statistics page 0x00 is invalid (page=0x%02x, nentries=%d)\n\n", page_0[2], nentries);
+ return false;
+ }
+
+ // Prepare list of pages to print
+ std::vector<int> pages;
+ unsigned i;
+ if (all_pages) {
+ // Add all supported pages
+ for (i = 0; i < nentries; i++) {
+ int page = page_0[8+1+i];
+ if (page)
+ pages.push_back(page);
+ }
+ ssd_page = false;
+ }
+ // Add manually specified pages
+ bool print_page_0 = false;
+ for (i = 0; i < single_pages.size() || ssd_page; i++) {
+ int page = (i < single_pages.size() ? single_pages[i] : 0x07);
+ if (!page)
+ print_page_0 = true;
+ else if (page >= (int)nsectors)
+ pout("Device Statistics Log has only 0x%02x pages\n", nsectors);
+ else
+ pages.push_back(page);
+ if (page == 0x07)
+ ssd_page = false;
+ }
+
+ json::ref jref = jglb["ata_device_statistics"];
+
+ // Print list of supported pages if requested
+ if (print_page_0) {
+ pout("Device Statistics (%s Log 0x04) supported pages\n",
+ use_gplog ? "GP" : "SMART");
+ jout("Page Description\n");
+ for (i = 0; i < nentries; i++) {
+ int page = page_0[8+1+i];
+ const char * name = get_device_statistics_page_name(page);
+ jout("0x%02x %s\n", page, name);
+ jref["supported_pages"][i]["number"] = page;
+ jref["supported_pages"][i]["name"] = name;
+ }
+ jout("\n");
+ }
+
+ // Read & print pages
+ if (!pages.empty()) {
+ pout("Device Statistics (%s Log 0x04)\n",
+ use_gplog ? "GP" : "SMART");
+ jout("Page Offset Size Value Flags Description\n");
+ int max_page = 0;
+
+ if (!use_gplog)
+ for (i = 0; i < pages.size(); i++) {
+ int page = pages[i];
+ if (max_page < page && page < 0xff)
+ max_page = page;
+ }
+
+ raw_buffer pages_buf((max_page+1) * 512);
+
+ if (!use_gplog && !ataReadSmartLog(device, 0x04, pages_buf.data(), max_page+1)) {
+ jerr("Read Device Statistics pages 0x00-0x%02x failed\n\n", max_page);
+ return false;
+ }
+
+ int ji = 0;
+ for (i = 0; i < pages.size(); i++) {
+ int page = pages[i];
+ if (use_gplog) {
+ if (!ataReadLogExt(device, 0x04, 0, page, pages_buf.data(), 1)) {
+ jerr("Read Device Statistics page 0x%02x failed\n\n", page);
+ return false;
+ }
+ }
+ else if (page > max_page)
+ continue;
+
+ int offset = (use_gplog ? 0 : page * 512);
+ print_device_statistics_page(jref["pages"][ji++], pages_buf.data() + offset, page);
+ }
+
+ jout("%32s|||_ C monitored condition met\n", "");
+ jout("%32s||__ D supports DSN\n", "");
+ jout("%32s|___ N normalized value\n\n", "");
+ }
+
+ return true;
+}
+
+
+///////////////////////////////////////////////////////////////////////
+// Pending Defects log (Log 0x0c)
+
+// Section 9.26 of T13/BSR INCITS 529 (ACS-4) Revision 20, October 26, 2017
+
+static bool print_pending_defects_log(ata_device * device, unsigned nsectors,
+ unsigned max_entries)
+{
+ // Read #entries from page 0
+ unsigned char page_buf[512] = {0, };
+ if (!ataReadLogExt(device, 0x0c, 0, 0, page_buf, 1)) {
+ pout("Read Pending Defects log page 0x00 failed\n\n");
+ return false;
+ }
+
+ jout("Pending Defects log (GP Log 0x0c)\n");
+ unsigned nentries = sg_get_unaligned_le32(page_buf);
+ json::ref jref = jglb["ata_pending_defects_log"];
+ jref["size"] = nsectors * 32 - 1;
+ jref["count"] = nentries;
+ if (!nentries) {
+ jout("No Defects Logged\n\n");
+ return true;
+ }
+
+ // Print entries
+ jout("Index LBA Hours\n");
+ for (unsigned i = 0, pi = 1, page = 0; i < nentries && i < max_entries; i++, pi++) {
+ // Read new page if required
+ if (pi >= 32) {
+ if (++page >= nsectors) {
+ pout("Pending Defects count %u exceeds log size (#pages=%u)\n\n",
+ nentries, nsectors);
+ return false;
+ }
+ if (!ataReadLogExt(device, 0x0c, 0, page, page_buf, 1)) {
+ pout("Read Pending Defects log page 0x%02x failed\n\n", page);
+ return false;
+ }
+ pi = 0;
+ }
+
+ const unsigned char * entry = page_buf + 16 * pi;
+ unsigned hours = sg_get_unaligned_le32(entry);
+ char hourstr[32];
+ if (hours != 0xffffffffU)
+ snprintf(hourstr, sizeof(hourstr), "%u", hours);
+ else
+ hourstr[0] = '-', hourstr[1] = 0;
+ uint64_t lba = sg_get_unaligned_le64(entry + 8);
+ jout("%5u %18" PRIu64 " %8s\n", i, lba, hourstr);
+
+ json::ref jrefi = jref["table"][i];
+ jrefi["lba"].set_unsafe_uint64(lba);
+ if (hours != 0xffffffffU)
+ jrefi["power_on_hours"] = hours;
+ }
+
+ if (nentries > max_entries)
+ pout("... (%u entries not shown)\n", nentries - max_entries);
+ jout("\n");
+ return true;
+}
+
+
+///////////////////////////////////////////////////////////////////////
+
+// Print log 0x11
+static void PrintSataPhyEventCounters(const unsigned char * data, bool reset)
+{
+ if (checksum(data))
+ checksumwarning("SATA Phy Event Counters");
+ jout("SATA Phy Event Counters (GP Log 0x11)\n");
+ if (data[0] || data[1] || data[2] || data[3])
+ pout("[Reserved: 0x%02x 0x%02x 0x%02x 0x%02x]\n",
+ data[0], data[1], data[2], data[3]);
+ jout("ID Size Value Description\n");
+
+ for (unsigned i = 4, ji = 0; ; ) {
+ // Get counter id and size (bits 14:12)
+ unsigned id = data[i] | (data[i+1] << 8);
+ unsigned size = ((id >> 12) & 0x7) << 1;
+ id &= 0x8fff;
+
+ // End of counter table ?
+ if (!id)
+ break;
+ i += 2;
+
+ if (!(2 <= size && size <= 8 && i + size < 512)) {
+ pout("0x%04x %u: Invalid entry\n", id, size);
+ break;
+ }
+
+ // Get value
+ uint64_t val = 0, max_val = 0;
+ for (unsigned j = 0; j < size; j+=2) {
+ val |= (uint64_t)(data[i+j] | (data[i+j+1] << 8)) << (j*8);
+ max_val |= (uint64_t)0xffffU << (j*8);
+ }
+ i += size;
+
+ // Get name
+ const char * name;
+ switch (id) {
+ case 0x001: name = "Command failed due to ICRC error"; break; // Mandatory
+ case 0x002: name = "R_ERR response for data FIS"; break;
+ case 0x003: name = "R_ERR response for device-to-host data FIS"; break;
+ case 0x004: name = "R_ERR response for host-to-device data FIS"; break;
+ case 0x005: name = "R_ERR response for non-data FIS"; break;
+ case 0x006: name = "R_ERR response for device-to-host non-data FIS"; break;
+ case 0x007: name = "R_ERR response for host-to-device non-data FIS"; break;
+ case 0x008: name = "Device-to-host non-data FIS retries"; break;
+ case 0x009: name = "Transition from drive PhyRdy to drive PhyNRdy"; break;
+ case 0x00A: name = "Device-to-host register FISes sent due to a COMRESET"; break; // Mandatory
+ case 0x00B: name = "CRC errors within host-to-device FIS"; break;
+ case 0x00D: name = "Non-CRC errors within host-to-device FIS"; break;
+ case 0x00F: name = "R_ERR response for host-to-device data FIS, CRC"; break;
+ case 0x010: name = "R_ERR response for host-to-device data FIS, non-CRC"; break;
+ case 0x012: name = "R_ERR response for host-to-device non-data FIS, CRC"; break;
+ case 0x013: name = "R_ERR response for host-to-device non-data FIS, non-CRC"; break;
+ default: name = ((id & 0x8000) ? "Vendor specific" : "Unknown"); break;
+ }
+
+ // Counters stop at max value, add '+' in this case
+ jout("0x%04x %u %12" PRIu64 "%c %s\n", id, size, val,
+ (val == max_val ? '+' : ' '), name);
+
+ json::ref jref = jglb["sata_phy_event_counters"]["table"][ji++];
+ jref["id"] = id;
+ jref["name"] = name;
+ jref["size"] = size;
+ jref["value"] = val;
+ jref["overflow"] = (val == max_val);
+ }
+ if (reset)
+ jout("All counters reset\n");
+ jout("\n");
+ jglb["sata_phy_event_counters"]["reset"] = reset;
+}
+
+// Format milliseconds from error log entry as "DAYS+H:M:S.MSEC"
+static std::string format_milliseconds(unsigned msec)
+{
+ unsigned days = msec / 86400000U;
+ msec -= days * 86400000U;
+ unsigned hours = msec / 3600000U;
+ msec -= hours * 3600000U;
+ unsigned min = msec / 60000U;
+ msec -= min * 60000U;
+ unsigned sec = msec / 1000U;
+ msec -= sec * 1000U;
+
+ std::string str;
+ if (days)
+ str = strprintf("%2ud+", days);
+ str += strprintf("%02u:%02u:%02u.%03u", hours, min, sec, msec);
+ return str;
+}
+
+// Get description for 'state' value from SMART Error Logs
+static const char * get_error_log_state_desc(unsigned state)
+{
+ state &= 0x0f;
+ switch (state){
+ case 0x0: return "in an unknown state";
+ case 0x1: return "sleeping";
+ case 0x2: return "in standby mode";
+ case 0x3: return "active or idle";
+ case 0x4: return "doing SMART Offline or Self-test";
+ default:
+ return (state < 0xb ? "in a reserved state"
+ : "in a vendor specific state");
+ }
+}
+
+// returns number of errors
+static int PrintSmartErrorlog(const ata_smart_errorlog *data,
+ firmwarebug_defs firmwarebugs)
+{
+ json::ref jref = jglb["ata_smart_error_log"]["summary"];
+ jout("SMART Error Log Version: %d\n", (int)data->revnumber);
+ jref["revision"] = data->revnumber;