mirror of
https://github.com/AlchemillaHQ/Sylve.git
synced 2026-06-24 02:27:19 +03:00
1345 lines
29 KiB
C
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 = ¶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, 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;
|
|
}
|