/* * Copyright (c) 2016-2026 Chuck Tuffli * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #ifdef LIBXO #include #endif #include "libsmart.h" #include "libsmart_priv.h" #include "libsmart_dev.h" /* Default page lists */ static smart_page_list_t pg_list_ata = { .pg_count = 2, .pages = { { .id = PAGE_ID_ATA_SMART_READ_DATA, .bytes = 512 }, { .id = PAGE_ID_ATA_SMART_RET_STATUS, .bytes = 4 } } }; #define PAGE_ID_NVME_SMART_HEALTH 0x02 static smart_page_list_t pg_list_nvme = { .pg_count = 1, .pages = { { .id = PAGE_ID_NVME_SMART_HEALTH, .bytes = 512 } } }; static smart_page_list_t pg_list_scsi = { .pg_count = 8, .pages = { { .id = PAGE_ID_SCSI_WRITE_ERR, .bytes = 128 }, { .id = PAGE_ID_SCSI_READ_ERR, .bytes = 128 }, { .id = PAGE_ID_SCSI_VERIFY_ERR, .bytes = 128 }, { .id = PAGE_ID_SCSI_NON_MEDIUM_ERR, .bytes = 128 }, { .id = PAGE_ID_SCSI_LAST_N_ERR, .bytes = 128 }, { .id = PAGE_ID_SCSI_TEMPERATURE, .bytes = 64 }, { .id = PAGE_ID_SCSI_START_STOP_CYCLE, .bytes = 128 }, { .id = PAGE_ID_SCSI_INFO_EXCEPTION, .bytes = 64 }, } }; static uint32_t __smart_attribute_max(smart_buf_t *sb); static uint32_t __smart_buffer_size(smart_h h); static smart_map_t *__smart_map(smart_h h, smart_buf_t *sb); static smart_page_list_t *__smart_page_list(smart_h h); static int32_t __smart_read_pages(smart_h h, smart_buf_t *sb); static const char * smart_proto_str(smart_protocol_e p) { switch (p) { case SMART_PROTO_AUTO: return "auto"; case SMART_PROTO_ATA: return "ATA"; case SMART_PROTO_SCSI: return "SCSI"; case SMART_PROTO_NVME: return "NVME"; default: return "Unknown"; } } smart_h smart_open(smart_protocol_e protocol, char *devname) { smart_t *s; s = device_open(protocol, devname); if (s) { dprintf("protocol %s (specified %s%s)\n", smart_proto_str(s->protocol), smart_proto_str(protocol), s->info.tunneled ? ", tunneled ATA" : ""); } return s; } void smart_close(smart_h h) { device_close(h); } bool smart_supported(smart_h h) { smart_t *s = h; bool supported = false; if (s) { supported = s->info.supported; dprintf("SMART is %ssupported\n", supported ? "" : "not "); } return supported; } smart_map_t * smart_read(smart_h h) { smart_t *s = h; smart_buf_t *sb = NULL; smart_map_t *sm = NULL; sb = calloc(1, sizeof(smart_buf_t)); if (sb) { sb->protocol = s->protocol; /* * Need the page list to calculate the buffer size. If one * isn't specified, get the default based on the protocol. */ if (s->pg_list == NULL) { s->pg_list = __smart_page_list(s); if (!s->pg_list) { goto smart_read_out; } } sb->b = NULL; sb->bsize = __smart_buffer_size(s); if (sb->bsize != 0) { sb->b = malloc(sb->bsize); } if (sb->b == NULL) { goto smart_read_out; } if (__smart_read_pages(s, sb) < 0) { goto smart_read_out; } sb->attr_count = __smart_attribute_max(sb); sm = __smart_map(h, sb); if (!sm) { free(sb->b); free(sb); sb = NULL; } } smart_read_out: if (!sm) { if (sb) { if (sb->b) { free(sb->b); } free(sb); } } return sm; } void smart_free(smart_map_t *sm) { smart_buf_t *sb = NULL; uint32_t i; if (sm == NULL) return; sb = sm->sb; if (sb) { if (sb->b) { free(sb->b); sb->b = NULL; } free(sb); } for (i = 0; i < sm->count; i++) { smart_map_t *tm = sm->attr[i].thresh; if (tm) { free(tm); } if (sm->attr[i].flags & SMART_ATTR_F_ALLOC) { free((void *)(uintptr_t)sm->attr[i].description); } } free(sm); } /* * Format specifier for the various output types * Provides versions to use with libxo and without * TODO some of this is ATA specific */ #ifndef LIBXO # define __smart_print_val(fmt, ...) printf(fmt, ##__VA_ARGS__) # define VEND_STR "Vendor\t%s\n" # define DEV_STR "Device\t%s\n" # define REV_STR "Revision\t%s\n" # define SERIAL_STR "Serial\t%s\n" # define PAGE_HEX "%#01.1x\t" # define PAGE_DEC "%d\t" # define ID_HEX "%#01.1x\t" # define ID_DEC "%d\t" # define RAW_STR "%s" # define RAW_HEX "%#01.1x" # define RAW_DEC "%d" /* Long integer version of the format macro */ # define RAW_LHEX "%#01.1" PRIx64 # define RAW_LDEC "%" PRId64 # define THRESH_HEX "\t%#02.2x\t%#01.1x\t%#01.1x" # define THRESH_DEC "\t%d\t%d\t%d" # define DESC_STR "%s" #else # define __smart_print_val(fmt, ...) xo_emit(fmt, ##__VA_ARGS__) # define VEND_STR "{L:Vendor}{P:\t}{:vendor/%s}\n" # define DEV_STR "{L:Device}{P:\t}{:device/%s}\n" # define REV_STR "{L:Revision}{P:\t}{:rev/%s}\n" # define SERIAL_STR "{L:Serial}{P:\t}{:serial/%s}\n" # define PAGE_HEX "{k:page/%#01.1x}{P:\t}" # define PAGE_DEC "{k:page/%d}{P:\t}" # define ID_HEX "{k:id/%#01.1x}{P:\t}" # define ID_DEC "{k:id/%d}{P:\t}" # define RAW_STR "{k:raw/%s}" # define RAW_HEX "{k:raw/%#01.1x}" # define RAW_DEC "{k:raw/%d}" /* Long integer version of the format macro */ # define RAW_LHEX "{k:raw/%#01.1" PRIx64 "}" # define RAW_LDEC "{k:raw/%" PRId64 "}" # define THRESH_HEX "{P:\t}{k:flags/%#02.2x}{P:\t}{k:nominal/%#01.1x}{P:\t}{k:worst/%#01.1x}" # define THRESH_DEC "{P:\t}{k:flags/%d}{P:\t}{k:nominal/%d}{P:\t}{k:worst/%d}" # define DESC_STR "{:description}{P:\t}" #endif #define THRESH_COUNT 3 /* Convert an 128-bit unsigned integer to a string */ static char * __smart_u128_str(smart_attr_t *sa) { /* Max size is log10(x) = log2(x) / log2(10) ~= log2(x) / 3.322 */ #define MAX_LEN (128 / 3 + 1 + 1) static char s[MAX_LEN]; char *p = s + MAX_LEN - 1; uint32_t *a = (uint32_t *)sa->raw; uint64_t r, d; *p-- = '\0'; do { r = a[3]; d = r / 10; r = ((r - d * 10) << 32) + a[2]; a[3] = d; d = r / 10; r = ((r - d * 10) << 32) + a[1]; a[2] = d; d = r / 10; r = ((r - d * 10) << 32) + a[0]; a[1] = d; d = r / 10; r = r - d * 10; a[0] = d; *p-- = '0' + r; } while (a[0] || a[1] || a[2] || a[3]); p++; while ((*p == '0') && (p < &s[sizeof(s) - 2])) p++; return p; } static void __smart_print_thresh(smart_map_t *tm, uint32_t flags) { bool do_hex = false; if (!tm) { return; } if (flags & SMART_OPEN_F_HEX) do_hex = true; __smart_print_val(do_hex ? THRESH_HEX : THRESH_DEC, *((uint16_t *)tm->attr[0].raw), *((uint8_t *)tm->attr[1].raw), *((uint8_t *)tm->attr[2].raw)); } /* Does the attribute match one requested by the caller? */ static bool __smart_attr_match(smart_matches_t *match, smart_attr_t *attr) { uint32_t i; assert((match != NULL) && (attr != NULL)); for (i = 0; i < match->count; i++) { if ((match->m[i].page != -1) && ((uint32_t)match->m[i].page != attr->page)) continue; if ((uint32_t)match->m[i].id == attr->id) return true; } return false; } void smart_print(__attribute__((unused)) smart_h h, smart_map_t *sm, smart_matches_t *which, uint32_t flags) { uint32_t i; bool do_hex = false, do_descr = false; uint32_t bytes = 0; if (!sm) { return; } if (flags & SMART_OPEN_F_HEX) do_hex = true; if (flags & SMART_OPEN_F_DESCR) do_descr = true; #ifdef LIBXO xo_open_container("attributes"); xo_open_list("attribute"); #endif for (i = 0; i < sm->count; i++) { /* If we're printing a specific attribute, is this it? */ if ((which != NULL) && !__smart_attr_match(which, &sm->attr[i])) { continue; } #ifdef LIBXO xo_open_instance("attribute"); #endif /* Print the page / attribute ID if selecting all attributes */ if (which == NULL) { if (do_descr && (sm->attr[i].description != NULL)) __smart_print_val(DESC_STR, sm->attr[i].description); else { __smart_print_val(do_hex ? PAGE_HEX : PAGE_DEC, sm->attr[i].page); __smart_print_val(do_hex ? ID_HEX : ID_DEC, sm->attr[i].id); } } bytes = sm->attr[i].bytes; /* Print the attribute based on its size */ if (sm->attr[i].flags & SMART_ATTR_F_STR) { __smart_print_val(RAW_STR, (char *)sm->attr[i].raw); } else if (bytes > 8) { if (do_hex) ; else __smart_print_val(RAW_STR, __smart_u128_str(&sm->attr[i])); } else if (bytes > 4) { uint64_t v64 = 0; uint64_t mask = UINT64_MAX; memcpy(&v64, sm->attr[i].raw, bytes); if (sm->attr[i].flags & SMART_ATTR_F_BE) { v64 = be64toh(v64); } else { v64 = le64toh(v64); } mask >>= 8 * (sizeof(uint64_t) - bytes); v64 &= mask; __smart_print_val(do_hex ? RAW_LHEX : RAW_LDEC, v64); } else if (bytes > 2) { uint32_t v32 = 0; uint32_t mask = UINT32_MAX; memcpy(&v32, sm->attr[i].raw, bytes); if (sm->attr[i].flags & SMART_ATTR_F_BE) { v32 = be32toh(v32); } else { v32 = le32toh(v32); } mask >>= 8 * (sizeof(uint32_t) - bytes); v32 &= mask; __smart_print_val(do_hex ? RAW_HEX : RAW_DEC, v32); } else if (bytes > 1) { uint16_t v16 = 0; uint16_t mask = UINT16_MAX; memcpy(&v16, sm->attr[i].raw, bytes); if (sm->attr[i].flags & SMART_ATTR_F_BE) { v16 = be16toh(v16); } else { v16 = le16toh(v16); } mask >>= 8 * (sizeof(uint16_t) - bytes); v16 &= mask; __smart_print_val(do_hex ? RAW_HEX : RAW_DEC, v16); } else if (bytes > 0) { uint8_t v8 = *((uint8_t *)sm->attr[i].raw); __smart_print_val(do_hex ? RAW_HEX : RAW_DEC, v8); } if ((flags & SMART_OPEN_F_THRESH) && sm->attr[i].thresh) { xo_open_container("threshold"); __smart_print_thresh(sm->attr[i].thresh, flags); xo_close_container("threshold"); } __smart_print_val("\n"); #ifdef LIBXO xo_close_instance("attribute"); #endif } #ifdef LIBXO xo_close_list("attribute"); xo_close_container("attributes"); #endif } void smart_print_device_info(smart_h h) { smart_t *s = h; if (!s) { return; } if (*s->info.vendor != '\0') __smart_print_val(VEND_STR, s->info.vendor); if (*s->info.device != '\0') __smart_print_val(DEV_STR, s->info.device); if (*s->info.rev != '\0') __smart_print_val(REV_STR, s->info.device); if (*s->info.serial != '\0') __smart_print_val(SERIAL_STR, s->info.serial); } static uint32_t __smart_attr_max_ata(smart_buf_t *sb) { uint32_t max = 0; if (sb) { max = 30; } return max; } static uint32_t __smart_attr_max_nvme(smart_buf_t *sb) { uint32_t max = 0; if (sb) { max = 512; } return max; } static uint32_t __smart_attr_max_scsi(smart_buf_t *sb) { uint32_t max = 0; if (sb) { max = 512; } return max; } static uint32_t __smart_attribute_max(smart_buf_t *sb) { uint32_t count = 0; if (sb != NULL) { switch (sb->protocol) { case SMART_PROTO_ATA: count = __smart_attr_max_ata(sb); break; case SMART_PROTO_NVME: count = __smart_attr_max_nvme(sb); break; case SMART_PROTO_SCSI: count = __smart_attr_max_scsi(sb); break; default: ; } } return count; } /** * Return the total buffer size needed by the protocol's page list */ static uint32_t __smart_buffer_size(smart_h h) { smart_t *s = h; uint32_t size = 0; if ((s != NULL) && (s->pg_list != NULL)) { smart_page_list_t *plist = s->pg_list; uint32_t p = 0; for (p = 0; p < plist->pg_count; p++) { size += plist->pages[p].bytes; } } return size; } /* * Map SMART READ DATA threshold attributes * * Read the 3 consecutive values (flags, nominal, and worst) */ static smart_map_t * __smart_map_ata_thresh(uint8_t *b) { smart_map_t *sm = NULL; sm = malloc(sizeof(smart_map_t) + (THRESH_COUNT * sizeof(smart_attr_t))); if (sm) { uint32_t i; sm->count = THRESH_COUNT; sm->attr[0].page = 0; sm->attr[0].id = 0; sm->attr[0].bytes = 2; sm->attr[0].flags = 0; sm->attr[0].raw = b; sm->attr[0].thresh = NULL; b +=2; for (i = 1; i < sm->count; i++) { sm->attr[i].page = 0; sm->attr[i].id = i; sm->attr[i].bytes = 1; sm->attr[i].flags = 0; sm->attr[i].raw = b; sm->attr[i].thresh = NULL; b ++; } } return sm; } /* * Map SMART READ DATA attributes * * The format for the READ DATA buffer is: * 2 bytes Revision * 360 bytes Attributes (12 bytes each) * * Each attribute consists of: * 1 byte ID * 2 byte Status Flags * 1 byte Nominal value * 1 byte Worst value * 7 byte Raw value * Note that many attributes do not use the entire 7 bytes of the raw value. */ static void __smart_map_ata_read_data(smart_map_t *sm, void *buf, size_t bsize) { uint8_t *b = NULL; uint8_t *b_end = NULL; uint32_t max_attr = 0; uint32_t a; max_attr = __smart_attr_max_ata(sm->sb); a = sm->count; b = buf; /* skip revision */ b += 2; b_end = b + (max_attr * 12); if (b_end > (b + bsize)) { sm->count = 0; return; } while (b < b_end) { if (*b != 0) { if ((a - sm->count) >= max_attr) { warnx("More attributes (%d) than fit in map", a - sm->count); break; } sm->attr[a].page = PAGE_ID_ATA_SMART_READ_DATA; sm->attr[a].id = b[0]; sm->attr[a].description = __smart_ata_desc( PAGE_ID_ATA_SMART_READ_DATA, sm->attr[a].id); sm->attr[a].bytes = 7; sm->attr[a].flags = 0; sm->attr[a].raw = b + 5; sm->attr[a].thresh = __smart_map_ata_thresh(b + 1); a++; } b += 12; } sm->count = a; } static void __smart_map_ata_return_status(smart_map_t *sm, void *buf) { uint8_t *b = NULL; uint32_t a; a = sm->count; b = buf; sm->attr[a].page = PAGE_ID_ATA_SMART_RET_STATUS; sm->attr[a].id = 0; sm->attr[a].description = __smart_ata_desc(PAGE_ID_ATA_SMART_RET_STATUS, sm->attr[a].id); sm->attr[a].bytes = 1; sm->attr[a].flags = 0; sm->attr[a].raw = b; sm->attr[a].thresh = NULL; a++; sm->count = a; } static void __smart_map_ata(smart_h h, smart_buf_t *sb, smart_map_t *sm) { smart_t *s = h; smart_page_list_t *pg_list = NULL; uint8_t *b = NULL; uint32_t p; pg_list = s->pg_list; b = sb->b; sm->count = 0; for (p = 0; p < pg_list->pg_count; p++) { switch (pg_list->pages[p].id) { case PAGE_ID_ATA_SMART_READ_DATA: __smart_map_ata_read_data(sm, b, pg_list->pages[p].bytes); break; case PAGE_ID_ATA_SMART_RET_STATUS: __smart_map_ata_return_status(sm, b); break; } b += pg_list->pages[p].bytes; } } #ifndef ARRAYLEN #define ARRAYLEN(p) sizeof(p)/sizeof(p[0]) #endif #define NVME_VS(mjr,mnr,ter) (((mjr) << 16) | ((mnr) << 8) | (ter)) #define NVME_VS_1_0 NVME_VS(1,0,0) #define NVME_VS_1_1 NVME_VS(1,1,0) #define NVME_VS_1_2 NVME_VS(1,2,0) #define NVME_VS_1_2_1 NVME_VS(1,2,1) #define NVME_VS_1_3 NVME_VS(1,3,0) #define NVME_VS_1_4 NVME_VS(1,4,0) static struct { uint32_t off; /* buffer offset */ uint32_t bytes; /* size in bytes */ uint32_t ver; /* first version available */ const char *description; } __smart_nvme_values[] = { { 0, 1, NVME_VS_1_0, "Critical Warning" }, { 1, 2, NVME_VS_1_0, "Composite Temperature" }, { 3, 1, NVME_VS_1_0, "Available Spare" }, { 4, 1, NVME_VS_1_0, "Available Spare Threshold" }, { 5, 1, NVME_VS_1_0, "Percentage Used" }, { 6, 1, NVME_VS_1_4, "Endurance Group Critical Warning Summary" }, { 32, 16, NVME_VS_1_0, "Data Units Read" }, { 48, 16, NVME_VS_1_0, "Data Units Written" }, { 64, 16, NVME_VS_1_0, "Host Read Commands" }, { 80, 16, NVME_VS_1_0, "Host Write Commands" }, { 96, 16, NVME_VS_1_0, "Controller Busy Time" }, { 112, 16, NVME_VS_1_0, "Power Cycles" }, { 128, 16, NVME_VS_1_0, "Power On Hours" }, { 144, 16, NVME_VS_1_0, "Unsafe Shutdowns" }, { 160, 16, NVME_VS_1_0, "Media and Data Integrity Errors" }, { 176, 16, NVME_VS_1_0, "Number of Error Information Log Entries" }, { 192, 4, NVME_VS_1_2, "Warning Composite Temperature Time" }, { 196, 4, NVME_VS_1_2, "Critical Composite Temperature Time" }, { 200, 2, NVME_VS_1_2, "Temperature Sensor 1" }, { 202, 2, NVME_VS_1_2, "Temperature Sensor 2" }, { 204, 2, NVME_VS_1_2, "Temperature Sensor 3" }, { 206, 2, NVME_VS_1_2, "Temperature Sensor 4" }, { 208, 2, NVME_VS_1_2, "Temperature Sensor 5" }, { 210, 2, NVME_VS_1_2, "Temperature Sensor 6" }, { 212, 2, NVME_VS_1_2, "Temperature Sensor 7" }, { 214, 2, NVME_VS_1_2, "Temperature Sensor 8" }, { 216, 4, NVME_VS_1_3, "Thermal Management Temperature 1 Transition Count" }, { 220, 4, NVME_VS_1_3, "Thermal Management Temperature 2 Transition Count" }, { 224, 4, NVME_VS_1_3, "Total Time For Thermal Management Temperature 1" }, { 228, 4, NVME_VS_1_3, "Total Time For Thermal Management Temperature 2" }, }; /** * NVMe doesn't define attribute IDs like ATA does, but we can * approximate this behavior by treating the byte offset as the * attribute ID. */ static void __smart_map_nvme(smart_buf_t *sb, smart_map_t *sm) { uint8_t *b = NULL; uint32_t vs = NVME_VS_1_0; // XXX assume device is 1.0 uint32_t i, a; sm->count = 0; b = sb->b; for (i = 0, a = 0; i < ARRAYLEN(__smart_nvme_values); i++) { if (vs >= __smart_nvme_values[i].ver) { sm->attr[a].page = 0x2; sm->attr[a].id = __smart_nvme_values[i].off; sm->attr[a].description = __smart_nvme_values[i].description; sm->attr[a].bytes = __smart_nvme_values[i].bytes; sm->attr[a].flags = 0; sm->attr[a].raw = b + __smart_nvme_values[i].off; sm->attr[a].thresh = NULL; a++; } } sm->count = a; } /* * Create a SMART map for SCSI error counter pages * * Several SCSI log pages have a similar format for the error counter log * pages */ static void __smart_map_scsi_err_page(smart_map_t *sm, void *b) { struct scsi_err_page { uint8_t page_code; uint8_t subpage_code; uint16_t page_length; uint8_t param[]; } __attribute__((packed)) *err = b; struct scsi_err_counter_param { uint16_t code; uint8_t format:2, tmc:2, etc:1, tsd:1, :1, du:1; uint8_t length; uint8_t counter[]; } __attribute__((packed)) *param = NULL; uint32_t a, p, page_length; const char *cmd = NULL, *desc = NULL; switch (err->page_code) { case PAGE_ID_SCSI_WRITE_ERR: cmd = "Write"; break; case PAGE_ID_SCSI_READ_ERR: cmd = "Read"; break; case PAGE_ID_SCSI_VERIFY_ERR: cmd = "Verify"; break; case PAGE_ID_SCSI_NON_MEDIUM_ERR: cmd = "Non-Medium"; break; default: fprintf(stderr, "Unknown command %#x\n", err->page_code); cmd = "Unknown"; break; } a = sm->count; p = 0; page_length = be16toh(err->page_length); while (p < page_length) { param = (struct scsi_err_counter_param *) (err->param + p); sm->attr[a].page = err->page_code; sm->attr[a].id = be16toh(param->code); desc = __smart_scsi_err_desc(sm->attr[a].id); if (desc != NULL) { size_t bytes; char *str; bytes = snprintf(NULL, 0, "%s %s", cmd, desc); str = malloc(bytes + 1); if (str != NULL) { snprintf(str, bytes + 1, "%s %s", cmd, desc); sm->attr[a].description = str; sm->attr[a].flags |= SMART_ATTR_F_ALLOC; } } sm->attr[a].bytes = param->length; sm->attr[a].flags = SMART_ATTR_F_BE; sm->attr[a].raw = param->counter; sm->attr[a].thresh = NULL; p += 4 + param->length; a++; } sm->count = a; } static void __smart_map_scsi_last_err(smart_map_t *sm, void *b) { struct scsi_last_n_error_event_page { uint8_t page_code:6, spf:1, ds:1; uint8_t subpage_code; uint16_t page_length; uint8_t event[]; } __attribute__((packed)) *lastn = b; struct scsi_last_n_error_event { uint16_t code; uint8_t format:2, tmc:2, etc:1, tsd:1, :1, du:1; uint8_t length; uint8_t data[]; } __attribute__((packed)) *event = NULL; uint32_t a, p, page_length; a = sm->count; p = 0; page_length = be16toh(lastn->page_length); while (p < page_length) { event = (struct scsi_last_n_error_event *) (lastn->event + p); sm->attr[a].page = lastn->page_code; sm->attr[a].id = be16toh(event->code); sm->attr[a].bytes = event->length; sm->attr[a].flags = SMART_ATTR_F_BE; sm->attr[a].raw = event->data; sm->attr[a].thresh = NULL; p += 4 + event->length; a++; } sm->count = a; } static void __smart_map_scsi_temp(smart_map_t *sm, void *b) { struct scsi_temperature_log_page { uint8_t page_code; uint8_t subpage_code; uint16_t page_length; struct scsi_temperature_log_entry { uint16_t code; uint8_t control; uint8_t length; uint8_t rsvd; uint8_t temperature; } param[]; } __attribute__((packed)) *temp = b; uint32_t a, p, count; count = be16toh(temp->page_length) / sizeof(struct scsi_temperature_log_entry); a = sm->count; for (p = 0; p < count; p++) { uint16_t code = be16toh(temp->param[p].code); switch (code) { case 0: case 1: sm->attr[a].page = temp->page_code; sm->attr[a].id = be16toh(temp->param[p].code); sm->attr[a].description = code == 0 ? "Temperature" : "Reference Temperature"; sm->attr[a].bytes = 1; sm->attr[a].flags = 0; sm->attr[a].raw = &(temp->param[p].temperature); sm->attr[a].thresh = NULL; a++; break; default: break; } } sm->count = a; } static void __smart_map_scsi_start_stop(smart_map_t *sm, void *b) { struct scsi_start_stop_page { uint8_t page_code; #define START_STOP_CODE_DATE_MFG 0x0001 #define START_STOP_CODE_DATE_ACCTN 0x0002 #define START_STOP_CODE_CYCLES_LIFE 0x0003 #define START_STOP_CODE_CYCLES_ACCUM 0x0004 #define START_STOP_CODE_LOAD_LIFE 0x0005 #define START_STOP_CODE_LOAD_ACCUM 0x0006 uint8_t subpage_code; uint16_t page_length; uint8_t param[]; } __attribute__((packed)) *sstop = b; struct scsi_start_stop_param { uint16_t code; uint8_t format:2, tmc:2, etc:1, tsd:1, :1, du:1; uint8_t length; uint8_t data[]; } __attribute__((packed)) *param; uint32_t a, p, page_length; a = sm->count; p = 0; page_length = be16toh(sstop->page_length); while (p < page_length) { param = (struct scsi_start_stop_param *) (sstop->param + p); sm->attr[a].page = sstop->page_code; sm->attr[a].id = be16toh(param->code); sm->attr[a].bytes = param->length; switch (sm->attr[a].id) { case START_STOP_CODE_DATE_MFG: sm->attr[a].description = "Date of Manufacture"; sm->attr[a].flags = SMART_ATTR_F_STR; break; case START_STOP_CODE_DATE_ACCTN: sm->attr[a].description = "Accounting Date"; sm->attr[a].flags = SMART_ATTR_F_STR; break; case START_STOP_CODE_CYCLES_LIFE: sm->attr[a].description = "Specified Cycle Count Over Device Lifetime"; sm->attr[a].flags = SMART_ATTR_F_BE; break; case START_STOP_CODE_CYCLES_ACCUM: sm->attr[a].description = "Accumulated Start-Stop Cycles"; sm->attr[a].flags = SMART_ATTR_F_BE; break; case START_STOP_CODE_LOAD_LIFE: sm->attr[a].description = "Specified Load-Unload Count Over Device Lifetime"; sm->attr[a].flags = SMART_ATTR_F_BE; break; case START_STOP_CODE_LOAD_ACCUM: sm->attr[a].description = "Accumulated Load-Unload Cycles"; sm->attr[a].flags = SMART_ATTR_F_BE; break; } sm->attr[a].raw = param->data; sm->attr[a].thresh = NULL; p += 4 + param->length; a++; } sm->count = a; } static void __smart_map_scsi_info_exception(smart_map_t *sm, void *b) { struct scsi_info_exception_log_page { uint8_t page_code; uint8_t subpage_code; uint16_t page_length; uint8_t param[]; } __attribute__((packed)) *ie = b; struct scsi_ie_param { uint16_t code; uint8_t control; uint8_t length; uint8_t asc; /* IE Additional Sense Code */ uint8_t ascq; /* IE Additional Sense Code Qualifier */ uint8_t temp_recent; uint8_t temp_trip_point; uint8_t temp_max; } __attribute__((packed)) *param; uint32_t a, p, page_length; a = sm->count; p = 0; page_length = be16toh(ie->page_length); while (p < page_length) { param = (struct scsi_ie_param *)(ie->param + p); p += 4 + param->length; sm->attr[a].page = ie->page_code; sm->attr[a].id = offsetof(struct scsi_ie_param, asc); sm->attr[a].description = "Informational Exception ASC"; sm->attr[a].bytes = 1; sm->attr[a].flags = 0; sm->attr[a].raw = ¶m->asc; sm->attr[a].thresh = NULL; a++; sm->attr[a].page = ie->page_code; sm->attr[a].id = offsetof(struct scsi_ie_param, ascq); sm->attr[a].description = "Informational Exception ASCQ"; sm->attr[a].bytes = 1; sm->attr[a].flags = 0; sm->attr[a].raw = ¶m->ascq; sm->attr[a].thresh = NULL; a++; sm->attr[a].page = ie->page_code; sm->attr[a].id = offsetof(struct scsi_ie_param, temp_recent); sm->attr[a].description = "Informational Exception Most recent temperature"; sm->attr[a].bytes = 1; sm->attr[a].flags = 0; sm->attr[a].raw = ¶m->temp_recent; sm->attr[a].thresh = NULL; a++; sm->attr[a].page = ie->page_code; sm->attr[a].id = offsetof(struct scsi_ie_param, temp_trip_point); sm->attr[a].description = "Informational Exception Vendor HDA temperature trip point"; sm->attr[a].bytes = 1; sm->attr[a].flags = 0; sm->attr[a].raw = ¶m->temp_trip_point; sm->attr[a].thresh = NULL; a++; sm->attr[a].page = ie->page_code; sm->attr[a].id = offsetof(struct scsi_ie_param, temp_max); sm->attr[a].description = "Informational Exception Maximum temperature"; sm->attr[a].bytes = 1; sm->attr[a].flags = 0; sm->attr[a].raw = ¶m->temp_max; sm->attr[a].thresh = NULL; a++; } sm->count = a; } /* * Create a map based on the page list */ static void __smart_map_scsi(smart_h h, smart_buf_t *sb, smart_map_t *sm) { smart_t *s = h; smart_page_list_t *pg_list = NULL; uint8_t *b = NULL; uint32_t p; pg_list = s->pg_list; b = sb->b; sm->count = 0; for (p = 0; p < pg_list->pg_count; p++) { switch (pg_list->pages[p].id) { case PAGE_ID_SCSI_WRITE_ERR: case PAGE_ID_SCSI_READ_ERR: case PAGE_ID_SCSI_VERIFY_ERR: case PAGE_ID_SCSI_NON_MEDIUM_ERR: __smart_map_scsi_err_page(sm, b); break; case PAGE_ID_SCSI_LAST_N_ERR: __smart_map_scsi_last_err(sm, b); break; case PAGE_ID_SCSI_TEMPERATURE: __smart_map_scsi_temp(sm, b); break; case PAGE_ID_SCSI_START_STOP_CYCLE: __smart_map_scsi_start_stop(sm, b); break; case PAGE_ID_SCSI_INFO_EXCEPTION: __smart_map_scsi_info_exception(sm, b); break; } b += pg_list->pages[p].bytes; } } /** * Create a map of SMART values */ static void __smart_attribute_map(smart_h h, smart_buf_t *sb, smart_map_t *sm) { if (!sb || !sm) { return; } switch (sb->protocol) { case SMART_PROTO_ATA: __smart_map_ata(h, sb, sm); break; case SMART_PROTO_NVME: __smart_map_nvme(sb, sm); break; case SMART_PROTO_SCSI: __smart_map_scsi(h, sb, sm); break; default: sm->count = 0; } } static smart_map_t * __smart_map(smart_h h, smart_buf_t *sb) { smart_map_t *sm = NULL; uint32_t max = 0; max = sb->attr_count; if (max == 0) { warnx("Attribute count is zero?!?"); return NULL; } sm = malloc(sizeof(smart_map_t) + (max * sizeof(smart_attr_t))); if (sm) { memset(sm, 0, sizeof(smart_map_t) + (max * sizeof(smart_attr_t))); sm->sb = sb; /* count starts as the max but is adjusted to reflect the actual number */ sm->count = max; __smart_attribute_map(h, sb, sm); } return sm; } typedef struct { uint8_t page_code; uint8_t subpage_code; uint16_t page_length; uint8_t supported_pages[]; } __attribute__((packed)) scsi_supported_log_pages; static smart_page_list_t * __smart_page_list_scsi(smart_t *s) { smart_page_list_t *pg_list = NULL; scsi_supported_log_pages *b = NULL; uint32_t bsize = 68; /* 4 byte header + 63 entries + 1 just cuz */ int32_t rc; b = malloc(bsize); if (!b) { return NULL; } /* Supported Pages page ID is 0 */ rc = device_read_log(s, PAGE_ID_SCSI_SUPPORTED_PAGES, (uint8_t *)b, bsize); if (rc < 0) { fprintf(stderr, "Read Supported Log Pages failed\n"); } else { uint8_t *supported_page = b->supported_pages; uint32_t n_supported = be16toh(b->page_length); uint32_t pg, p, pmax = pg_list_scsi.pg_count; /* Build a page list using only pages the device supports */ pg_list = malloc(sizeof(pg_list_scsi)); if (pg_list == NULL) { n_supported = 0; } else { pg_list->pg_count = 0; } /* * Loop through all supported pages looking for those related * to SMART. The below assumes the supported page list from the * device and in pg_lsit_scsi are sorted in increasing order. */ dprintf("Supported SCSI pages:\n"); for (pg = 0, p = 0; (pg < n_supported) && (p < pmax); pg++) { dprintf("\t[%u] = %#x\n", pg, supported_page[pg]); while ((supported_page[pg] > pg_list_scsi.pages[p].id) && (p < pmax)) { p++; } if (supported_page[pg] == pg_list_scsi.pages[p].id) { pg_list->pages[pg_list->pg_count] = pg_list_scsi.pages[p]; pg_list->pg_count++; p++; } } } free(b); return pg_list; } static smart_page_list_t * __smart_page_list(smart_h h) { smart_t *s = h; smart_page_list_t *pg_list = NULL; if (!s) { return NULL; } switch (s->protocol) { case SMART_PROTO_ATA: pg_list = &pg_list_ata; break; case SMART_PROTO_NVME: pg_list = &pg_list_nvme; break; case SMART_PROTO_SCSI: pg_list = __smart_page_list_scsi(s); break; default: pg_list = NULL; } return pg_list; } static int32_t __smart_read_pages(smart_h h, smart_buf_t *sb) { smart_t *s = h; smart_page_list_t *plist = NULL; uint8_t *buf = NULL; int32_t rc = 0; uint32_t p = 0; plist = s->pg_list; buf = sb->b; for (p = 0; p < s->pg_list->pg_count; p++) { memset(buf, 0, plist->pages[p].bytes); rc = device_read_log(h, plist->pages[p].id, buf, plist->pages[p].bytes); if (rc) { dprintf("bad read (%d) from page %#x (bytes=%lu)\n", rc, plist->pages[p].id, plist->pages[p].bytes); break; } buf += plist->pages[p].bytes; } return rc; }