Files
Sylve/pkg/disk/smart/libsmart.c
T

1345 lines
29 KiB
C

/*
* Copyright (c) 2016-2021 Chuck Tuffli <chuck@tuffli.net>
*
* 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.
*/
//go:build freebsd
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stddef.h>
#include <assert.h>
#include <err.h>
#include <string.h>
#include <sys/endian.h>
#ifdef LIBXO
#include <libxo/xo.h>
#endif
#include "libsmart.h"
#include "libsmart_priv.h"
#include "libsmart_dev.h"
/* Default page lists */
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
smart_page_list_t pg_list_nvme = {
.pg_count = 1,
.pages = {
{ .id = PAGE_ID_NVME_SMART_HEALTH, .bytes = 512 }
}
};
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 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(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\t%#01.1x"
# define THRESH_DEC "\t%d\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:threshold/%#02.2x\t%#01.1x\t%#01.1x\t%#01.1x}"
# define THRESH_DEC "{P:\t}{k:threshold/%d\t%d\t%d\t%d}"
# define DESC_STR "{:description}{P:\t}"
#endif
/* 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;
uint32_t last = 0;
*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;
bool do_thresh = false;
if (!tm) {
return;
}
if (flags & SMART_OPEN_F_HEX)
do_hex = true;
if (flags & SMART_OPEN_F_THRESH)
do_thresh = true;
if (do_thresh && tm) {
__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),
*((uint8_t *)tm->attr[3].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) && (match->m[i].page != attr->page))
continue;
if (match->m[i].id == attr->id)
return true;
}
return false;
}
void
smart_print(smart_h h, smart_map_t *sm, smart_matches_t *which, uint32_t flags)
{
uint32_t i;
const char *fmt, *lfmt;
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;
bcopy(sm->attr[i].raw, &v64, 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;
bcopy(sm->attr[i].raw, &v32, 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;
bcopy(sm->attr[i].raw, &v16, 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);
}
__smart_print_thresh(sm->attr[i].thresh, flags);
__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 */
static smart_map_t *
__smart_map_ata_thresh(uint8_t *b)
{
smart_map_t *sm = NULL;
sm = malloc(sizeof(smart_map_t) + (4 * sizeof(smart_attr_t)));
if (sm) {
uint32_t i;
sm->count = 4;
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 ++;
if (i == 2)
b += 6;
}
}
return sm;
}
/* Map SMART READ DATA attributes */
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;
b += 2;
b_end = b + (max_attr * 12);
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 = 6;
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, size_t bsize)
{
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, pg_list->pages[p].bytes);
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)
struct {
uint32_t off; /* buffer offset */
uint32_t bytes; /* size in bytes */
uint32_t ver; /* first version available */
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, size_t bsize)
{
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;
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, size_t bsize)
{
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, size_t bsize)
{
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, size_t bsize)
{
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, size_t bsize)
{
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 = &param->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 = &param->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 = &param->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 = &param->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 = &param->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, pg_list->pages[p].bytes);
break;
case PAGE_ID_SCSI_LAST_N_ERR:
__smart_map_scsi_last_err(sm, b, pg_list->pages[p].bytes);
break;
case PAGE_ID_SCSI_TEMPERATURE:
__smart_map_scsi_temp(sm, b, pg_list->pages[p].bytes);
break;
case PAGE_ID_SCSI_START_STOP_CYCLE:
__smart_map_scsi_start_stop(sm, b, pg_list->pages[p].bytes);
break;
case PAGE_ID_SCSI_INFO_EXCEPTION:
__smart_map_scsi_info_exception(sm, b, pg_list->pages[p].bytes);
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 s, 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 (s = 0, p = 0; (s < n_supported) && (p < pmax); s++) {
dprintf("\t[%u] = %#x\n", s, supported_page[s]);
while ((supported_page[s] > pg_list_scsi.pages[p].id) &&
(p < pmax)) {
p++;
}
if (supported_page[s] == 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++) {
bzero(buf, 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;
}