mirror of
https://git.FreeBSD.org/src.git
synced 2026-06-02 11:24:32 +00:00
Add 'contrib/smart/' from commit 'eb3b1302382b1d0cbe37eeebabfcdd546aa2fc4e'
git-subtree-dir: contrib/smart git-subtree-mainline:95b4436e98git-subtree-split:eb3b130238
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
This file documents changes for smart releases
|
||||
|
||||
version 1.0.2
|
||||
- Bring man page up to snuff
|
||||
- Fix various complier warnings
|
||||
|
||||
version 1.0.1
|
||||
- Fix don't print attribute ID with description
|
||||
|
||||
version 1.0.0
|
||||
- Fix ATA threshold output (gh-10). This is a breaking change as it
|
||||
reduces the output from 4 fields to 3 (drops the "reserved" byte
|
||||
from threshold).
|
||||
- Fix the ATA raw output. This is a breaking change as it increase the
|
||||
output from 6 bytes to 7 (i.e., includes the "reserved" byte). Note
|
||||
that while some attributes use this byte, most do not.
|
||||
- Fix direct debug output (--debug) to standard error
|
||||
- Use POSIX memcpy and memset instead of older bXXX equivalents
|
||||
|
||||
version 0.4.2
|
||||
- Update README contents
|
||||
|
||||
version 0.4.1
|
||||
- Allow a comma-separated list of attributes
|
||||
- Code refactor + update code comments
|
||||
|
||||
version 0.3.0
|
||||
|
||||
- Reclaim the -d option from debug
|
||||
- Change field separator from spaces to tab
|
||||
- Add textual descriptions of attribute IDs for ATA, NVMe, and SCSI
|
||||
- Add a manual page
|
||||
- Fixes
|
||||
* libxo structure for attribute and attributes
|
||||
* simplify LIBXO ifdef sprawl
|
||||
* display of threshold values
|
||||
* display of long values
|
||||
@@ -0,0 +1,13 @@
|
||||
Copyright (c) 2016-2026 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.
|
||||
@@ -0,0 +1,26 @@
|
||||
#
|
||||
# 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.
|
||||
#
|
||||
PROG= smart
|
||||
SRCS= smart.c libsmart.c libsmart_desc.c
|
||||
SRCS+= freebsd_dev.c
|
||||
LIBADD= cam xo
|
||||
MAN=smart.8
|
||||
MLINKS= smart.8 diskhealth.8
|
||||
#CFLAGS+= -ggdb -O0
|
||||
CFLAGS+= -DLIBXO
|
||||
LINKS= ${BINDIR}/smart ${BINDIR}/diskhealth
|
||||
|
||||
.include <bsd.prog.mk>
|
||||
@@ -0,0 +1,62 @@
|
||||
# README #
|
||||
|
||||
### What is this repository for? ###
|
||||
|
||||
* Application to output the SMART values of disks
|
||||
|
||||
### How do I get set up? ###
|
||||
|
||||
Clone this repository onto a FreeBSD box and run make
|
||||
|
||||
$ git clone https://github.com/ctuffli/smart
|
||||
$ cd smart && make
|
||||
|
||||
or install it from ports ( http://www.freshports.org/sysutils/smart/ )
|
||||
|
||||
### How to use
|
||||
Usage: smart [-htxi] [-a attribute[,attribute]...] <device name>
|
||||
-h, --help
|
||||
-t, --threshold : also print out the threshold values
|
||||
-x, --hex : print the values out in hexadecimal
|
||||
-a, --attribute : print a specific attribute(s)
|
||||
-i, --info : print general device information
|
||||
-d, --decode: decode the attribute IDs
|
||||
-D, --no-decode: don't decode the attribute IDs
|
||||
-v, --version : print the version and copyright
|
||||
|
||||
### Example
|
||||
* List the raw attributes of SATA device /dev/ada0
|
||||
|
||||
smart ada0
|
||||
|
||||
* List the decoded attributes of NVMe device /dev/nda0
|
||||
|
||||
smart -d nda0
|
||||
|
||||
or
|
||||
|
||||
diskhealth nda0
|
||||
|
||||
### What does the raw output mean?
|
||||
The format and location of SMART / health data varies across protocols.
|
||||
To simplify the output, the application uses a Dumb Unified Model of
|
||||
SMART Buffers. In this model, SMART data is located in one or more log
|
||||
pages. Each page contains one or more values ("attributes")
|
||||
differentiated by an ID. Note that ID's are only unique within a log
|
||||
page. Thus, the application outputs:
|
||||
|
||||
<Log Page ID> <Attribute ID> <Attribute value>
|
||||
for each selected attribute. Threshold values, if defined by the protocol
|
||||
and selected by the user, are printed after the attribute value.
|
||||
|
||||
See the shell scripts `atasmart`, `nvmesmart`, and `scsismart` for examples of parsing the output.
|
||||
|
||||
### What is the decoded output?
|
||||
Decoded output converts numeric values (Log Page, Attribute ID and Value tuples) into human-readable descriptions.
|
||||
|
||||
The output for protocols like NVMe and SCSI which provide standardized descriptions will match the text in the specification. ATA, however, allows vendors to define their own attributes. For ATA, descriptions come from the "SMART Attribute Descriptions (SAD)" (ANSI - INCITS TR-54) specification which documents attributes common to multiple ATA drive vendors.
|
||||
|
||||
### Protocol Specific Notes
|
||||
* __ATA__ : The attribute and values follow the 'standard'. The log page is the Feature value used in ATA command. Thus, the default page is 208 / 0xd0 (a.k.a SMART Read Data). The threshold values printed are status flags, current value, and worst value. The SMART Return Status (Feature 218 / 0xda) indicates the reliability status of the device and is sometimes used as a top-level SMART health indication. While this command does not return data, the application encodes "no errors" as 0x0 and "threshold exceeded" as 0x1 in attribute 0.
|
||||
* __NVMe__ : The Log Page is the SMART / Health Information LID value in the Get Log Page command (i.e. 0x2). The attribute ID is the byte offset within this page.
|
||||
* __SCSI__ : The Log Page ID is the Page Code value in the Mode Sense command. The attribute ID is the parameter code defined by this page (e.g. 0 in the Write Error Counters log page is 'Errors corrected without substantial delay'). The values will depend on the Page Codes supported by a drive.
|
||||
@@ -0,0 +1,828 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <fcntl.h>
|
||||
#include <string.h>
|
||||
#include <err.h>
|
||||
#include <errno.h>
|
||||
#include <camlib.h>
|
||||
#include <cam/scsi/scsi_message.h>
|
||||
|
||||
#include "libsmart.h"
|
||||
#include "libsmart_priv.h"
|
||||
#include "libsmart_dev.h"
|
||||
|
||||
/* Provide compatibility for FreeBSD 11.0 */
|
||||
#if (__FreeBSD_version < 1101000)
|
||||
|
||||
struct scsi_log_informational_exceptions {
|
||||
struct scsi_log_param_header hdr;
|
||||
#define SLP_IE_GEN 0x0000
|
||||
uint8_t ie_asc;
|
||||
uint8_t ie_ascq;
|
||||
uint8_t temperature;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
struct fbsd_smart {
|
||||
smart_t common;
|
||||
struct cam_device *camdev;
|
||||
};
|
||||
|
||||
static smart_protocol_e __device_get_proto(struct fbsd_smart *);
|
||||
static bool __device_proto_tunneled(struct fbsd_smart *);
|
||||
static int32_t __device_get_info(struct fbsd_smart *);
|
||||
|
||||
smart_h
|
||||
device_open(smart_protocol_e protocol, char *devname)
|
||||
{
|
||||
struct fbsd_smart *h = NULL;
|
||||
|
||||
h = malloc(sizeof(struct fbsd_smart));
|
||||
if (h == NULL)
|
||||
return NULL;
|
||||
|
||||
memset(h, 0, sizeof(struct fbsd_smart));
|
||||
|
||||
h->common.protocol = SMART_PROTO_MAX;
|
||||
h->camdev = cam_open_device(devname, O_RDWR);
|
||||
if (h->camdev == NULL) {
|
||||
printf("%s: error opening %s - %s\n",
|
||||
__func__, devname,
|
||||
cam_errbuf);
|
||||
free(h);
|
||||
h = NULL;
|
||||
} else {
|
||||
smart_protocol_e proto = __device_get_proto(h);
|
||||
|
||||
if ((protocol == SMART_PROTO_AUTO) ||
|
||||
(protocol == proto)) {
|
||||
h->common.protocol = proto;
|
||||
} else {
|
||||
printf("%s: protocol mismatch %d vs %d\n",
|
||||
__func__, protocol, proto);
|
||||
}
|
||||
|
||||
if (proto == SMART_PROTO_SCSI) {
|
||||
if (__device_proto_tunneled(h)) {
|
||||
h->common.protocol = SMART_PROTO_ATA;
|
||||
h->common.info.tunneled = 1;
|
||||
}
|
||||
}
|
||||
|
||||
__device_get_info(h);
|
||||
}
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
void
|
||||
device_close(smart_h h)
|
||||
{
|
||||
struct fbsd_smart *fsmart = h;
|
||||
|
||||
if (fsmart != NULL) {
|
||||
if (fsmart->camdev != NULL) {
|
||||
cam_close_device(fsmart->camdev);
|
||||
}
|
||||
|
||||
free(fsmart);
|
||||
}
|
||||
}
|
||||
|
||||
static const uint8_t smart_read_data[] = {
|
||||
0xb0, 0xd0, 0x00, 0x4f, 0xc2, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
static const uint8_t smart_return_status[] = {
|
||||
0xb0, 0xda, 0x00, 0x4f, 0xc2, 0x00,
|
||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00
|
||||
};
|
||||
|
||||
static int32_t
|
||||
__device_read_ata(smart_h h, uint32_t page, void *buf, size_t bsize, union ccb *ccb)
|
||||
{
|
||||
struct fbsd_smart *fsmart = h;
|
||||
const uint8_t *smart_fis;
|
||||
uint32_t smart_fis_size = 0;
|
||||
uint32_t cam_flags = 0;
|
||||
uint16_t sector_count = 0;
|
||||
uint8_t protocol = 0;
|
||||
|
||||
switch (page) {
|
||||
case PAGE_ID_ATA_SMART_READ_DATA: /* Support SMART READ DATA */
|
||||
smart_fis = smart_read_data;
|
||||
smart_fis_size = sizeof(smart_read_data);
|
||||
cam_flags = CAM_DIR_IN;
|
||||
sector_count = 1;
|
||||
protocol = AP_PROTO_PIO_IN;
|
||||
break;
|
||||
case PAGE_ID_ATA_SMART_RET_STATUS: /* Support SMART RETURN STATUS */
|
||||
smart_fis = smart_return_status;
|
||||
smart_fis_size = sizeof(smart_return_status);
|
||||
/* Command has no data but uses the return status */
|
||||
cam_flags = CAM_DIR_NONE;
|
||||
protocol = AP_PROTO_NON_DATA;
|
||||
bsize = 0;
|
||||
break;
|
||||
default:
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
if (fsmart->common.info.tunneled) {
|
||||
struct ata_pass_16 *cdb;
|
||||
uint8_t cdb_flags;
|
||||
|
||||
if (bsize > 0) {
|
||||
cdb_flags = AP_FLAG_TDIR_FROM_DEV |
|
||||
AP_FLAG_BYT_BLOK_BLOCKS |
|
||||
AP_FLAG_TLEN_SECT_CNT;
|
||||
} else {
|
||||
cdb_flags = AP_FLAG_CHK_COND |
|
||||
AP_FLAG_TDIR_FROM_DEV |
|
||||
AP_FLAG_BYT_BLOK_BLOCKS;
|
||||
}
|
||||
|
||||
cdb = (struct ata_pass_16 *)ccb->csio.cdb_io.cdb_bytes;
|
||||
memset(cdb, 0, sizeof(*cdb));
|
||||
|
||||
scsi_ata_pass_16(&ccb->csio,
|
||||
/*retries*/ 1,
|
||||
/*cbfcnp*/ NULL,
|
||||
/*flags*/ cam_flags,
|
||||
/*tag_action*/ MSG_SIMPLE_Q_TAG,
|
||||
/*protocol*/ protocol,
|
||||
/*ata_flags*/ cdb_flags,
|
||||
/*features*/ page,
|
||||
/*sector_count*/sector_count,
|
||||
/*lba*/ 0,
|
||||
/*command*/ ATA_SMART_CMD,
|
||||
/*control*/ 0,
|
||||
/*data_ptr*/ buf,
|
||||
/*dxfer_len*/ bsize,
|
||||
/*sense_len*/ SSD_FULL_SIZE,
|
||||
/*timeout*/ 5000
|
||||
);
|
||||
cdb->lba_mid = 0x4f;
|
||||
cdb->lba_high = 0xc2;
|
||||
cdb->device = 0; /* scsi_ata_pass_16() sets this */
|
||||
} else {
|
||||
memcpy(&ccb->ataio.cmd.command, smart_fis, smart_fis_size);
|
||||
|
||||
cam_fill_ataio(&ccb->ataio,
|
||||
/* retries */1,
|
||||
/* cbfcnp */NULL,
|
||||
/* flags */cam_flags,
|
||||
/* tag_action */0,
|
||||
/* data_ptr */buf,
|
||||
/* dxfer_len */bsize,
|
||||
/* timeout */5000);
|
||||
ccb->ataio.cmd.flags |= CAM_ATAIO_NEEDRESULT;
|
||||
ccb->ataio.cmd.control = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
__device_read_scsi(__attribute__((unused)) smart_h h, uint32_t page, void *buf, size_t bsize, union ccb *ccb)
|
||||
{
|
||||
|
||||
scsi_log_sense(&ccb->csio,
|
||||
/* retries */1,
|
||||
/* cbfcnp */NULL,
|
||||
/* tag_action */0,
|
||||
/* page_code */SLS_PAGE_CTRL_CUMULATIVE,
|
||||
/* page */page,
|
||||
/* save_pages */0,
|
||||
/* ppc */0,
|
||||
/* paramptr */0,
|
||||
/* param_buf */buf,
|
||||
/* param_len */bsize,
|
||||
/* sense_len */0,
|
||||
/* timeout */5000);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
__device_read_nvme(__attribute__((unused)) smart_h h, uint32_t page, void *buf, size_t bsize, union ccb *ccb)
|
||||
{
|
||||
struct ccb_nvmeio *nvmeio = &ccb->nvmeio;
|
||||
uint32_t numd = 0; /* number of dwords */
|
||||
|
||||
/*
|
||||
* NVME CAM passthru
|
||||
* 1200000 > version > 1101510 uses nvmeio->cmd.opc
|
||||
* 1200059 > version > 1200038 uses nvmeio->cmd.opc
|
||||
* 1200081 > version > 1200058 uses nvmeio->cmd.opc_fuse
|
||||
* > 1200080 uses nvmeio->cmd.opc
|
||||
* This code doesn't support the brief 'opc_fuse' period.
|
||||
*/
|
||||
#if ((__FreeBSD_version > 1200038) || ((__FreeBSD_version > 1101510) && (__FreeBSD_version < 1200000)))
|
||||
switch (page) {
|
||||
case NVME_LOG_HEALTH_INFORMATION:
|
||||
numd = (sizeof(struct nvme_health_information_page) / sizeof(uint32_t));
|
||||
break;
|
||||
default:
|
||||
/* Unsupported log page */
|
||||
return EINVAL;
|
||||
}
|
||||
|
||||
/* Subtract 1 because NUMD is a zero based value */
|
||||
numd--;
|
||||
|
||||
nvmeio->cmd.opc = NVME_OPC_GET_LOG_PAGE;
|
||||
nvmeio->cmd.nsid = NVME_GLOBAL_NAMESPACE_TAG;
|
||||
nvmeio->cmd.cdw10 = page | (numd << 16);
|
||||
|
||||
cam_fill_nvmeadmin(&ccb->nvmeio,
|
||||
/* retries */1,
|
||||
/* cbfcnp */NULL,
|
||||
/* flags */CAM_DIR_IN,
|
||||
/* data_ptr */buf,
|
||||
/* dxfer_len */bsize,
|
||||
/* timeout */5000);
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Retrieve the SMART RETURN STATUS
|
||||
*
|
||||
* SMART RETURN STATUS provides the reliability status of the
|
||||
* device and can be used as a high-level indication of health.
|
||||
*/
|
||||
static int32_t
|
||||
__device_status_ata(smart_h h, union ccb *ccb)
|
||||
{
|
||||
struct fbsd_smart *fsmart = h;
|
||||
uint8_t *buf = NULL;
|
||||
uint32_t page = 0;
|
||||
uint8_t lba_high = 0, lba_mid = 0, device = 0, status = 0;
|
||||
|
||||
if (fsmart->common.info.tunneled) {
|
||||
struct ata_res_pass16 {
|
||||
u_int16_t reserved[5];
|
||||
u_int8_t flags;
|
||||
u_int8_t error;
|
||||
u_int8_t sector_count_exp;
|
||||
u_int8_t sector_count;
|
||||
u_int8_t lba_low_exp;
|
||||
u_int8_t lba_low;
|
||||
u_int8_t lba_mid_exp;
|
||||
u_int8_t lba_mid;
|
||||
u_int8_t lba_high_exp;
|
||||
u_int8_t lba_high;
|
||||
u_int8_t device;
|
||||
u_int8_t status;
|
||||
} *res_pass16 = (struct ata_res_pass16 *)(uintptr_t)
|
||||
&ccb->csio.sense_data;
|
||||
|
||||
buf = ccb->csio.data_ptr;
|
||||
page = ((struct ata_pass_16 *)ccb->csio.cdb_io.cdb_bytes)->features;
|
||||
lba_high = res_pass16->lba_high;
|
||||
lba_mid = res_pass16->lba_mid;
|
||||
device = res_pass16->device;
|
||||
status = res_pass16->status;
|
||||
|
||||
/*
|
||||
* Note that this generates an expected CHECK CONDITION.
|
||||
* Mask it so the outer function doesn't print an error
|
||||
* message.
|
||||
*/
|
||||
ccb->ccb_h.status &= ~CAM_STATUS_MASK;
|
||||
ccb->ccb_h.status |= CAM_REQ_CMP;
|
||||
} else {
|
||||
struct ccb_ataio *ataio = (struct ccb_ataio *)&ccb->ataio;
|
||||
|
||||
buf = ataio->data_ptr;
|
||||
page = ataio->cmd.features;
|
||||
lba_high = ataio->res.lba_high;
|
||||
lba_mid = ataio->res.lba_mid;
|
||||
device = ataio->res.device;
|
||||
status = ataio->res.status;
|
||||
}
|
||||
|
||||
switch (page) {
|
||||
case PAGE_ID_ATA_SMART_RET_STATUS:
|
||||
/*
|
||||
* Typically, SMART related log pages return data, but this
|
||||
* command is different in that the data is encoded in the
|
||||
* result registers.
|
||||
*
|
||||
* Handle this in a UNIX-like way by writing a 0 (no errors)
|
||||
* or 1 (threshold exceeded condition) to the output buffer.
|
||||
*/
|
||||
dprintf("SMART_RET_STATUS: lba mid=%#x high=%#x device=%#x status=%#x\n",
|
||||
lba_mid,
|
||||
lba_high,
|
||||
device,
|
||||
status);
|
||||
if ((lba_high == 0x2c) && (lba_mid == 0xf4)) {
|
||||
buf[0] = 1;
|
||||
} else if ((lba_high == 0xc2) && (lba_mid == 0x4f)) {
|
||||
buf[0] = 0;
|
||||
} else {
|
||||
/* Ruh-roh ... */
|
||||
buf[0] = 255;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int32_t
|
||||
device_read_log(smart_h h, uint32_t page, void *buf, size_t bsize)
|
||||
{
|
||||
struct fbsd_smart *fsmart = h;
|
||||
union ccb *ccb = NULL;
|
||||
int rc = 0;
|
||||
|
||||
if (fsmart == NULL)
|
||||
return EINVAL;
|
||||
|
||||
dprintf("read log page %#x\n", page);
|
||||
|
||||
ccb = cam_getccb(fsmart->camdev);
|
||||
if (ccb == NULL)
|
||||
return ENOMEM;
|
||||
|
||||
CCB_CLEAR_ALL_EXCEPT_HDR(ccb);
|
||||
|
||||
switch (fsmart->common.protocol) {
|
||||
case SMART_PROTO_ATA:
|
||||
rc = __device_read_ata(h, page, buf, bsize, ccb);
|
||||
break;
|
||||
case SMART_PROTO_SCSI:
|
||||
rc = __device_read_scsi(h, page, buf, bsize, ccb);
|
||||
break;
|
||||
case SMART_PROTO_NVME:
|
||||
rc = __device_read_nvme(h, page, buf, bsize, ccb);
|
||||
break;
|
||||
default:
|
||||
warnx("unsupported protocol %d", fsmart->common.protocol);
|
||||
cam_freeccb(ccb);
|
||||
return ENODEV;
|
||||
}
|
||||
|
||||
if (rc) {
|
||||
if (rc == EINVAL)
|
||||
warnx("unsupported page %#x", page);
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
if (((rc = cam_send_ccb(fsmart->camdev, ccb)) < 0)
|
||||
|| ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP)) {
|
||||
if (rc < 0)
|
||||
warn("error sending command");
|
||||
}
|
||||
|
||||
/*
|
||||
* Most commands don't need any post-processing. But then there's
|
||||
* ATA. It's why we can't have nice things :(
|
||||
*/
|
||||
switch (fsmart->common.protocol) {
|
||||
case SMART_PROTO_ATA:
|
||||
__device_status_ata(h, ccb);
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
if ((ccb->ccb_h.status & CAM_STATUS_MASK) != CAM_REQ_CMP) {
|
||||
cam_error_print(fsmart->camdev, ccb, CAM_ESF_ALL,
|
||||
CAM_EPF_ALL, stderr);
|
||||
}
|
||||
|
||||
cam_freeccb(ccb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* The SCSI / ATA Translation (SAT) requires devices to support the ATA
|
||||
* Information VPD Page (T10/2126-D Revision 04). Use the existence of
|
||||
* this page to identify tunneled devices.
|
||||
*/
|
||||
static bool
|
||||
__device_proto_tunneled(struct fbsd_smart *fsmart)
|
||||
{
|
||||
union ccb *ccb = NULL;
|
||||
struct scsi_vpd_supported_page_list supportedp;
|
||||
uint32_t i;
|
||||
bool is_tunneled = false;
|
||||
|
||||
if (fsmart->common.protocol != SMART_PROTO_SCSI) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ccb = cam_getccb(fsmart->camdev);
|
||||
if (!ccb) {
|
||||
warn("Allocation failure ccb=%p", ccb);
|
||||
goto __device_proto_tunneled_out;
|
||||
}
|
||||
|
||||
scsi_inquiry(&ccb->csio,
|
||||
3, // retries
|
||||
NULL, // callback function
|
||||
MSG_SIMPLE_Q_TAG, // tag action
|
||||
(uint8_t *)&supportedp,
|
||||
sizeof(struct scsi_vpd_supported_page_list),
|
||||
1, // EVPD
|
||||
SVPD_SUPPORTED_PAGE_LIST, // page code
|
||||
SSD_FULL_SIZE, // sense length
|
||||
5000); // timeout
|
||||
|
||||
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
|
||||
|
||||
if ((cam_send_ccb(fsmart->camdev, ccb) >= 0) &&
|
||||
((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)) {
|
||||
dprintf("Looking for page %#x (total = %u):\n", SVPD_ATA_INFORMATION,
|
||||
supportedp.length);
|
||||
for (i = 0; i < supportedp.length; i++) {
|
||||
dprintf("\t[%u] = %#x\n", i, supportedp.list[i]);
|
||||
if (supportedp.list[i] == SVPD_ATA_INFORMATION) {
|
||||
is_tunneled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cam_freeccb(ccb);
|
||||
|
||||
__device_proto_tunneled_out:
|
||||
return is_tunneled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the device protocol type via the transport settings
|
||||
*
|
||||
* @return protocol type or SMART_PROTO_MAX on error
|
||||
*/
|
||||
static smart_protocol_e
|
||||
__device_get_proto(struct fbsd_smart *fsmart)
|
||||
{
|
||||
smart_protocol_e proto = SMART_PROTO_MAX;
|
||||
union ccb *ccb;
|
||||
|
||||
if (!fsmart || !fsmart->camdev) {
|
||||
warn("Bad handle %p", fsmart);
|
||||
return proto;
|
||||
}
|
||||
|
||||
ccb = cam_getccb(fsmart->camdev);
|
||||
if (ccb != NULL) {
|
||||
CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->cts);
|
||||
|
||||
ccb->ccb_h.func_code = XPT_GET_TRAN_SETTINGS;
|
||||
ccb->cts.type = CTS_TYPE_CURRENT_SETTINGS;
|
||||
|
||||
if (cam_send_ccb(fsmart->camdev, ccb) >= 0) {
|
||||
if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
|
||||
struct ccb_trans_settings *cts = &ccb->cts;
|
||||
|
||||
switch (cts->protocol) {
|
||||
case PROTO_ATA:
|
||||
proto = SMART_PROTO_ATA;
|
||||
break;
|
||||
case PROTO_SCSI:
|
||||
proto = SMART_PROTO_SCSI;
|
||||
break;
|
||||
case PROTO_NVME:
|
||||
proto = SMART_PROTO_NVME;
|
||||
break;
|
||||
default:
|
||||
printf("%s: unknown protocol %d\n",
|
||||
__func__,
|
||||
cts->protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cam_freeccb(ccb);
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
__device_info_ata(struct fbsd_smart *fsmart, struct ccb_getdev *cgd)
|
||||
{
|
||||
smart_info_t *sinfo = NULL;
|
||||
|
||||
if (!fsmart || !cgd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sinfo = &fsmart->common.info;
|
||||
|
||||
sinfo->supported = cgd->ident_data.support.command1 &
|
||||
ATA_SUPPORT_SMART;
|
||||
|
||||
dprintf("ATA command1 = %#x\n", cgd->ident_data.support.command1);
|
||||
|
||||
cam_strvis((uint8_t *)sinfo->device, cgd->ident_data.model,
|
||||
sizeof(cgd->ident_data.model),
|
||||
sizeof(sinfo->device));
|
||||
cam_strvis((uint8_t *)sinfo->rev, cgd->ident_data.revision,
|
||||
sizeof(cgd->ident_data.revision),
|
||||
sizeof(sinfo->rev));
|
||||
cam_strvis((uint8_t *)sinfo->serial, cgd->ident_data.serial,
|
||||
sizeof(cgd->ident_data.serial),
|
||||
sizeof(sinfo->serial));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
__device_info_scsi(struct fbsd_smart *fsmart, struct ccb_getdev *cgd)
|
||||
{
|
||||
smart_info_t *sinfo = NULL;
|
||||
union ccb *ccb = NULL;
|
||||
struct scsi_vpd_unit_serial_number *snum = NULL;
|
||||
struct scsi_log_informational_exceptions ie = {0};
|
||||
|
||||
if (!fsmart || !cgd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sinfo = &fsmart->common.info;
|
||||
|
||||
cam_strvis((uint8_t *)sinfo->vendor, (uint8_t *)cgd->inq_data.vendor,
|
||||
sizeof(cgd->inq_data.vendor),
|
||||
sizeof(sinfo->vendor));
|
||||
cam_strvis((uint8_t *)sinfo->device, (uint8_t *)cgd->inq_data.product,
|
||||
sizeof(cgd->inq_data.product),
|
||||
sizeof(sinfo->device));
|
||||
cam_strvis((uint8_t *)sinfo->rev, (uint8_t *)cgd->inq_data.revision,
|
||||
sizeof(cgd->inq_data.revision),
|
||||
sizeof(sinfo->rev));
|
||||
|
||||
ccb = cam_getccb(fsmart->camdev);
|
||||
snum = malloc(sizeof(struct scsi_vpd_unit_serial_number));
|
||||
if (!ccb || !snum) {
|
||||
warn("Allocation failure ccb=%p snum=%p", ccb, snum);
|
||||
goto __device_info_scsi_out;
|
||||
}
|
||||
|
||||
/* Get the serial number */
|
||||
CCB_CLEAR_ALL_EXCEPT_HDR(&ccb->csio);
|
||||
|
||||
scsi_inquiry(&ccb->csio,
|
||||
3, // retries
|
||||
NULL, // callback function
|
||||
MSG_SIMPLE_Q_TAG, // tag action
|
||||
(uint8_t *)snum,
|
||||
sizeof(struct scsi_vpd_unit_serial_number),
|
||||
1, // EVPD
|
||||
SVPD_UNIT_SERIAL_NUMBER, // page code
|
||||
SSD_FULL_SIZE, // sense length
|
||||
5000); // timeout
|
||||
|
||||
ccb->ccb_h.flags |= CAM_DEV_QFRZDIS;
|
||||
|
||||
if ((cam_send_ccb(fsmart->camdev, ccb) >= 0) &&
|
||||
((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)) {
|
||||
cam_strvis((uint8_t *)sinfo->serial, snum->serial_num,
|
||||
snum->length,
|
||||
sizeof(sinfo->serial));
|
||||
sinfo->serial[sizeof(sinfo->serial) - 1] = '\0';
|
||||
}
|
||||
|
||||
memset(ccb, 0, sizeof(*ccb));
|
||||
|
||||
scsi_log_sense(&ccb->csio,
|
||||
/* retries */1,
|
||||
/* cbfcnp */NULL,
|
||||
/* tag_action */0,
|
||||
/* page_code */SLS_PAGE_CTRL_CUMULATIVE,
|
||||
/* page */SLS_IE_PAGE,
|
||||
/* save_pages */0,
|
||||
/* ppc */0,
|
||||
/* paramptr */0,
|
||||
/* param_buf */(uint8_t *)&ie,
|
||||
/* param_len */sizeof(ie),
|
||||
/* sense_len */0,
|
||||
/* timeout */5000);
|
||||
|
||||
/*
|
||||
* Note: The existance of the Informational Exceptions (IE) log page
|
||||
* appears to be the litmus test for SMART support in SCSI
|
||||
* devices. Confusingly, smartctl will report SMART health
|
||||
* status as 'OK' if the device doesn't support the IE page.
|
||||
* For now, just report the facts.
|
||||
*/
|
||||
if ((cam_send_ccb(fsmart->camdev, ccb) >= 0) &&
|
||||
((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP)) {
|
||||
if ((ie.hdr.param_len < 4) || ie.ie_asc || ie.ie_ascq) {
|
||||
printf("Log Sense, Informational Exceptions failed "
|
||||
"(length=%u asc=%#x ascq=%#x)\n",
|
||||
ie.hdr.param_len, ie.ie_asc, ie.ie_ascq);
|
||||
} else {
|
||||
sinfo->supported = true;
|
||||
}
|
||||
}
|
||||
|
||||
__device_info_scsi_out:
|
||||
free(snum);
|
||||
if (ccb)
|
||||
cam_freeccb(ccb);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
__device_info_nvme(struct fbsd_smart *fsmart, struct ccb_getdev *cgd)
|
||||
{
|
||||
union ccb *ccb;
|
||||
smart_info_t *sinfo = NULL;
|
||||
struct nvme_controller_data cd;
|
||||
|
||||
if (!fsmart || !cgd) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
sinfo = &fsmart->common.info;
|
||||
|
||||
sinfo->supported = true;
|
||||
|
||||
ccb = cam_getccb(fsmart->camdev);
|
||||
if (ccb != NULL) {
|
||||
struct ccb_dev_advinfo *cdai = &ccb->cdai;
|
||||
|
||||
CCB_CLEAR_ALL_EXCEPT_HDR(cdai);
|
||||
|
||||
cdai->ccb_h.func_code = XPT_DEV_ADVINFO;
|
||||
cdai->ccb_h.flags = CAM_DIR_IN;
|
||||
cdai->flags = CDAI_FLAG_NONE;
|
||||
#ifdef CDAI_TYPE_NVME_CNTRL
|
||||
cdai->buftype = CDAI_TYPE_NVME_CNTRL;
|
||||
#else
|
||||
cdai->buftype = 6;
|
||||
#endif
|
||||
cdai->bufsiz = sizeof(struct nvme_controller_data);
|
||||
cdai->buf = (uint8_t *)&cd;
|
||||
|
||||
if (cam_send_ccb(fsmart->camdev, ccb) >= 0) {
|
||||
if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
|
||||
cam_strvis((uint8_t *)sinfo->device, cd.mn,
|
||||
sizeof(cd.mn),
|
||||
sizeof(sinfo->device));
|
||||
cam_strvis((uint8_t *)sinfo->rev, cd.fr,
|
||||
sizeof(cd.fr),
|
||||
sizeof(sinfo->rev));
|
||||
cam_strvis((uint8_t *)sinfo->serial, cd.sn,
|
||||
sizeof(cd.sn),
|
||||
sizeof(sinfo->serial));
|
||||
}
|
||||
}
|
||||
|
||||
cam_freeccb(ccb);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int32_t
|
||||
__device_info_tunneled_ata(struct fbsd_smart *fsmart)
|
||||
{
|
||||
struct ata_params ident_data;
|
||||
union ccb *ccb = NULL;
|
||||
struct ata_pass_16 *ata_pass_16;
|
||||
struct ata_cmd ata_cmd;
|
||||
int32_t rc = -1;
|
||||
|
||||
ccb = cam_getccb(fsmart->camdev);
|
||||
if (ccb == NULL) {
|
||||
goto __device_info_tunneled_ata_out;
|
||||
}
|
||||
|
||||
memset(&ident_data, 0, sizeof(struct ata_params));
|
||||
|
||||
CCB_CLEAR_ALL_EXCEPT_HDR(ccb);
|
||||
|
||||
scsi_ata_pass_16(&ccb->csio,
|
||||
/*retries*/ 1,
|
||||
/*cbfcnp*/ NULL,
|
||||
/*flags*/ CAM_DIR_IN,
|
||||
/*tag_action*/ MSG_SIMPLE_Q_TAG,
|
||||
/*protocol*/ AP_PROTO_PIO_IN,
|
||||
/*ata_flags*/ AP_FLAG_TLEN_SECT_CNT |
|
||||
AP_FLAG_BYT_BLOK_BLOCKS |
|
||||
AP_FLAG_TDIR_FROM_DEV,
|
||||
/*features*/ 0,
|
||||
/*sector_count*/sizeof(struct ata_params),
|
||||
/*lba*/ 0,
|
||||
/*command*/ ATA_ATA_IDENTIFY,
|
||||
/*control*/ 0,
|
||||
/*data_ptr*/ (uint8_t *)&ident_data,
|
||||
/*dxfer_len*/ sizeof(struct ata_params),
|
||||
/*sense_len*/ SSD_FULL_SIZE,
|
||||
/*timeout*/ 5000
|
||||
);
|
||||
|
||||
ata_pass_16 = (struct ata_pass_16 *)ccb->csio.cdb_io.cdb_bytes;
|
||||
ata_cmd.command = ata_pass_16->command;
|
||||
ata_cmd.control = ata_pass_16->control;
|
||||
ata_cmd.features = ata_pass_16->features;
|
||||
|
||||
rc = cam_send_ccb(fsmart->camdev, ccb);
|
||||
if (rc != 0) {
|
||||
warnx("%s: scsi_ata_pass_16() failed (programmer error?)",
|
||||
__func__);
|
||||
goto __device_info_tunneled_ata_out;
|
||||
}
|
||||
|
||||
fsmart->common.info.supported = ident_data.support.command1 &
|
||||
ATA_SUPPORT_SMART;
|
||||
|
||||
dprintf("ATA command1 = %#x\n", ident_data.support.command1);
|
||||
|
||||
__device_info_tunneled_ata_out:
|
||||
if (ccb) {
|
||||
cam_freeccb(ccb);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the device information and use to populate the info structure
|
||||
*/
|
||||
static int32_t
|
||||
__device_get_info(struct fbsd_smart *fsmart)
|
||||
{
|
||||
union ccb *ccb;
|
||||
int32_t rc = -1;
|
||||
|
||||
if (!fsmart || !fsmart->camdev) {
|
||||
warn("Bad handle %p", fsmart);
|
||||
return -1;
|
||||
}
|
||||
|
||||
ccb = cam_getccb(fsmart->camdev);
|
||||
if (ccb != NULL) {
|
||||
struct ccb_getdev *cgd = &ccb->cgd;
|
||||
|
||||
CCB_CLEAR_ALL_EXCEPT_HDR(cgd);
|
||||
|
||||
/*
|
||||
* GDEV_TYPE doesn't support NVMe. What we do get is:
|
||||
* - device (ata/model, scsi/product)
|
||||
* - revision (ata, scsi)
|
||||
* - serial (ata)
|
||||
* - vendor (scsi)
|
||||
* - supported (ata)
|
||||
*
|
||||
* Serial # for all proto via ccb_dev_advinfo (buftype CDAI_TYPE_SERIAL_NUM)
|
||||
*/
|
||||
ccb->ccb_h.func_code = XPT_GDEV_TYPE;
|
||||
|
||||
if (cam_send_ccb(fsmart->camdev, ccb) >= 0) {
|
||||
if ((ccb->ccb_h.status & CAM_STATUS_MASK) == CAM_REQ_CMP) {
|
||||
switch (cgd->protocol) {
|
||||
case PROTO_ATA:
|
||||
rc = __device_info_ata(fsmart, cgd);
|
||||
break;
|
||||
case PROTO_SCSI:
|
||||
rc = __device_info_scsi(fsmart, cgd);
|
||||
if (!rc && fsmart->common.protocol == SMART_PROTO_ATA) {
|
||||
rc = __device_info_tunneled_ata(fsmart);
|
||||
}
|
||||
break;
|
||||
case PROTO_NVME:
|
||||
rc = __device_info_nvme(fsmart, cgd);
|
||||
break;
|
||||
default:
|
||||
printf("%s: unsupported protocol %d\n",
|
||||
__func__, cgd->protocol);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cam_freeccb(ccb);
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef _LIBSMART_H
|
||||
#define _LIBSMART_H
|
||||
|
||||
#include <inttypes.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
/*
|
||||
* libsmart uses a common model for SMART data (a.k.a. "attributes") across
|
||||
* storage protocols. Each health value consists of:
|
||||
* - The identifier of the log page containing this attribute
|
||||
* - The attribute's identifier
|
||||
* - A description of the attribute
|
||||
* - A pointer to the raw data
|
||||
* - The attribute's size in bytes
|
||||
*
|
||||
* This model most closely resembles SCSI's native representation, but it
|
||||
* can represent ATA and NVMe with the following substitutions:
|
||||
* - ATA : use the Command Feature field value for the log page ID
|
||||
* - NVMe : use the field's starting byte offset for the attribute ID
|
||||
*
|
||||
* libsmart returns a "map" to the SMART/health data read from a device
|
||||
* in the smart_map_t structure. The map consists of:
|
||||
* - A variable-length array of attributes
|
||||
* - The length of the array
|
||||
* - The raw data read from the device
|
||||
*
|
||||
* Consumers of the map will typically iterate through the array of attributes
|
||||
* to print or otherwise process the health data.
|
||||
*/
|
||||
|
||||
/*
|
||||
* A smart handle is an opaque reference to the device
|
||||
*/
|
||||
typedef void * smart_h;
|
||||
|
||||
typedef enum {
|
||||
SMART_PROTO_AUTO,
|
||||
SMART_PROTO_ATA,
|
||||
SMART_PROTO_SCSI,
|
||||
SMART_PROTO_NVME,
|
||||
SMART_PROTO_MAX
|
||||
} smart_protocol_e;
|
||||
|
||||
/*
|
||||
* A smart buffer contains the raw data returned from the protocol-specific
|
||||
* health command.
|
||||
*/
|
||||
typedef struct {
|
||||
smart_protocol_e protocol;
|
||||
void *b; // buffer of raw data
|
||||
size_t bsize; // buffer size
|
||||
uint32_t attr_count; // number of SMART attributes
|
||||
} smart_buf_t;
|
||||
|
||||
struct smart_map_s;
|
||||
|
||||
/*
|
||||
* A smart attribute is an individual health data element
|
||||
*/
|
||||
typedef struct smart_attr_s {
|
||||
uint32_t page;
|
||||
uint32_t id;
|
||||
const char *description; /* human readable description */
|
||||
uint32_t bytes;
|
||||
uint32_t flags;
|
||||
#define SMART_ATTR_F_BE 0x00000001 /* Attribute is big-endian */
|
||||
#define SMART_ATTR_F_STR 0x00000002 /* Attribute is a string */
|
||||
#define SMART_ATTR_F_ALLOC 0x00000004 /* Attribute description dynamically allocated */
|
||||
void *raw;
|
||||
struct smart_map_s *thresh; /* Threshold values (if any) */
|
||||
} smart_attr_t;
|
||||
|
||||
/*
|
||||
* A smart map is the collection of health data elements from the device
|
||||
*/
|
||||
typedef struct smart_map_s {
|
||||
smart_buf_t *sb;
|
||||
uint32_t count; /* Number of attributes */
|
||||
smart_attr_t attr[]; /* Array of attributes */
|
||||
} smart_map_t;
|
||||
|
||||
#define SMART_OPEN_F_HEX 0x1 /* Print values in hexadecimal */
|
||||
#define SMART_OPEN_F_THRESH 0x2 /* Print threshold values */
|
||||
#define SMART_OPEN_F_DESCR 0x4 /* Print textual description */
|
||||
|
||||
/* SMART attribute to match */
|
||||
typedef struct smart_match_s {
|
||||
int32_t page;
|
||||
int32_t id;
|
||||
} smart_match_t;
|
||||
|
||||
/* List of SMART attribute(s) to match */
|
||||
typedef struct smart_matches_s {
|
||||
uint32_t count;
|
||||
smart_match_t m[];
|
||||
} smart_matches_t;
|
||||
|
||||
/**
|
||||
* Connect to a device to read SMART data
|
||||
*
|
||||
* @param p The desired protocol or "auto" to automatically detect it
|
||||
* @param devname The device name to open
|
||||
*
|
||||
* @return An opaque handle or NULL on failure
|
||||
*/
|
||||
smart_h smart_open(smart_protocol_e p, char *devname);
|
||||
/**
|
||||
* Close device connection
|
||||
*
|
||||
* @param handle The handle returned from smart_open()
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
void smart_close(smart_h);
|
||||
/**
|
||||
* Does the device support SMART/health data?
|
||||
*
|
||||
* @param handle The handle returned from smart_open()
|
||||
*
|
||||
* @return true / false
|
||||
*/
|
||||
bool smart_supported(smart_h);
|
||||
/**
|
||||
* Read SMART/health data from the device
|
||||
*
|
||||
* @param handle The handle returned from smart_open()
|
||||
*
|
||||
* @return a pointer to the SMART map or NULL on failure
|
||||
*/
|
||||
smart_map_t *smart_read(smart_h);
|
||||
/**
|
||||
* Free memory associated with the health data read from the device
|
||||
*
|
||||
* @param map Pointer returned from smart_read()
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
void smart_free(smart_map_t *);
|
||||
/**
|
||||
* Print health data matching the desired attributes
|
||||
*
|
||||
* @param handle The handle returned from smart_open()
|
||||
* @param map Pointer returned from smart_read()
|
||||
* @param which Pointer to attributes to match or NULL to match all
|
||||
* @param flags Control display of attributes (hexadecimal, description, ...
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
void smart_print(smart_h, smart_map_t *, smart_matches_t *, uint32_t);
|
||||
/**
|
||||
* Print high-level device information
|
||||
*
|
||||
* @param handle The handle returned from smart_open()
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
void smart_print_device_info(smart_h);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,158 @@
|
||||
/*
|
||||
* Copyright (c) 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.
|
||||
*/
|
||||
#include <stddef.h>
|
||||
|
||||
#include "libsmart.h"
|
||||
#include "libsmart_priv.h"
|
||||
|
||||
/* Strings from "SMART Attribute Descriptions" (SAD) */
|
||||
static const char *
|
||||
desc_ata_data[] = {
|
||||
[1] = "Read Error Rate",
|
||||
[2] = "Throughput Performance",
|
||||
[3] = "Spin-Up Time",
|
||||
[4] = "Start/Stop Count",
|
||||
[5] = "Reallocated Sectors Count",
|
||||
[6] = "Read Channel Margin",
|
||||
[7] = "Seek Error Rate",
|
||||
[8] = "Seek Time Performance",
|
||||
[9] = "Power-On Hours",
|
||||
[10] = "Spin Retry Count",
|
||||
[11] = "Calibration Retry Count",
|
||||
[12] = "Power Cycle Count",
|
||||
[13] = "Soft Read Error Rate",
|
||||
[22] = "Current Helium Level", /* HGST */
|
||||
[170] = "Available Reserved Space", /* Intel */
|
||||
[171] = "SSD Program Fail", /* Kingston? */
|
||||
[172] = "SSD Erase Fail Count", /* Kingston? */
|
||||
[173] = "SSD Wear Leveling Count", /* HPE SSD Endurance Limit */
|
||||
[174] = "Unexpected Power Loss Count", /* Intel */
|
||||
[175] = "Power Loss Protection Failure", /* Intel */
|
||||
[176] = "Erase Fail Count (chip)",
|
||||
[177] = "Wear Range Delta",
|
||||
[179] = "Used Reserved Block Count Total",
|
||||
/* [180] = HPE, Seagate, Intel differences */
|
||||
[181] = "Non-4K Aligned Access Count", /* Micron. Conflict Kingston */
|
||||
[182] = "Erase Fail Count",
|
||||
[183] = "Runtime Bad Block",
|
||||
[184] = "End-to-End Error",
|
||||
[185] = "Head Stability", /* WD */
|
||||
[186] = "Induced Op-Vibration Detection", /* WD */
|
||||
[187] = "Reported Uncorrectable Errors",
|
||||
[188] = "Command Timeout",
|
||||
[189] = "High Fly Writes",
|
||||
[190] = "Airflow Temperature", /* WDC, HPE conflict */
|
||||
[191] = "G-Sense Error Rate",
|
||||
[192] = "Power-Off Count", /* HPE, Seagate */
|
||||
[193] = "Load/Unload Cycle Count",
|
||||
[194] = "Temperature Celsius",
|
||||
[195] = "Hardware ECC Recovered",
|
||||
[196] = "Reallocation Event Count",
|
||||
[197] = "Current Pending Sector Count",
|
||||
[198] = "Uncorrectable Sector Count", /* Fujitsu */
|
||||
[199] = "UltraDMA CRC Error Count",
|
||||
[200] = "Write Error Rate",
|
||||
[201] = "Soft Read Error Rate",
|
||||
[202] = "Data Address Mark Errors",
|
||||
[203] = "Run Out Cancel",
|
||||
[204] = "Soft ECC Correction",
|
||||
[205] = "Thermal Asperity Rate",
|
||||
[206] = "Flying Height",
|
||||
[207] = "Spin High Current",
|
||||
[208] = "Spin Buzz",
|
||||
[209] = "Offline Seek Performnce",
|
||||
[210] = "Vibration, During Write", /* Maxtor */
|
||||
[211] = "Vibration During Write", /* Acronis */
|
||||
[212] = "Shock During Write", /* Acronis */
|
||||
[220] = "Disk Shift",
|
||||
[221] = "G-Sense Error Rate",
|
||||
[222] = "Loaded Hours",
|
||||
[223] = "Load/Unload Retry Count",
|
||||
[224] = "Load Friction",
|
||||
[225] = "Load/Unload Cycle Count",
|
||||
[226] = "Load-in Time",
|
||||
[227] = "Torque Amplification Count",
|
||||
[228] = "Power-off Retract Cycle",
|
||||
[230] = "GMR Head Amplitude Drive Life Protection Status",
|
||||
[231] = "Temperature SSD Life Left", /* Kingston */
|
||||
[232] = "Endurance Remaining", /* Multiple conflict */
|
||||
[233] = "Power-On Hours", /* Multiple conflict */
|
||||
[234] = "Average Erase Count", /* Multiple conflict */
|
||||
[235] = "Good Block Count", /* Multiple conflict */
|
||||
[240] = "Head Flying Hours",
|
||||
[241] = "Total LBAs Written",
|
||||
[242] = "Total LBAs Read",
|
||||
[243] = "Total LBAs Written Expanded", /* Multiple conflict */
|
||||
[244] = "Total LBAs Read Expanded", /* Multiple conflict */
|
||||
[250] = "Read Error Rate",
|
||||
[251] = "Minimum Spares Remaining",
|
||||
[252] = "Newly Added Bad Flash Block",
|
||||
[254] = "Free Fall Protection"
|
||||
};
|
||||
|
||||
const char *
|
||||
__smart_ata_desc(uint32_t page, uint32_t id)
|
||||
{
|
||||
const char *desc = NULL;
|
||||
|
||||
switch (page) {
|
||||
case PAGE_ID_ATA_SMART_READ_DATA:
|
||||
if (desc_ata_data[id] != NULL)
|
||||
desc = desc_ata_data[id];
|
||||
break;
|
||||
case PAGE_ID_ATA_SMART_RET_STATUS:
|
||||
desc = "SMART Status";
|
||||
break;
|
||||
default:
|
||||
;
|
||||
}
|
||||
|
||||
return (desc);
|
||||
}
|
||||
|
||||
const char *
|
||||
__smart_scsi_err_desc(uint32_t id)
|
||||
{
|
||||
const char *param = NULL;
|
||||
|
||||
switch (id) {
|
||||
case 0:
|
||||
param = "Errors corrected without substantial delay";
|
||||
break;
|
||||
case 1:
|
||||
param = "Errors corrected with possible delays";
|
||||
break;
|
||||
case 2:
|
||||
param = "Total retries";
|
||||
break;
|
||||
case 3:
|
||||
param = "Total errors corrected";
|
||||
break;
|
||||
case 4:
|
||||
param = "Total times correction algorithm processed";
|
||||
break;
|
||||
case 5:
|
||||
param = "Total bytes processed";
|
||||
break;
|
||||
case 6:
|
||||
param = "Total uncorrected errors";
|
||||
break;
|
||||
default:
|
||||
return (NULL);
|
||||
}
|
||||
|
||||
return (param);
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2017-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.
|
||||
*/
|
||||
#ifndef _LIBSMART_DEV_H
|
||||
#define _LIBSMART_DEV_H
|
||||
|
||||
/**
|
||||
* Open a device to gather SMART information
|
||||
*
|
||||
* The call performs OS specific functions necessary to prepare the device
|
||||
* to receive read log requests.
|
||||
*
|
||||
* Although opaque to the user, the handle must be a pointer to a structure
|
||||
* with the first member being struct smart_s. The remaining members are OS
|
||||
* specific and are not used by the library.
|
||||
*
|
||||
* @param protocol The desired protocol or "auto" to automatically detect it
|
||||
* @param devname The device name to open
|
||||
*
|
||||
* @return An opaque handle to the device or NULL on failure
|
||||
*/
|
||||
extern smart_h device_open(smart_protocol_e, char *);
|
||||
|
||||
/**
|
||||
* Close a device and release the associated resources
|
||||
*
|
||||
* @param handle The handle returned from device_open()
|
||||
*
|
||||
* @return None
|
||||
*/
|
||||
extern void device_close(smart_h);
|
||||
|
||||
/**
|
||||
* Read the log page
|
||||
*
|
||||
* This call reads the specified log page in the protocol specific manner
|
||||
* needed by the device. The results are placed in the provided buffer.
|
||||
*
|
||||
* @param h SMART handle returned from device_open()
|
||||
* @param page The log page ID
|
||||
* @param buf Pointer to buffer containing the results of the read
|
||||
* @param bsize Size of the buffer in bytes
|
||||
*
|
||||
* @return Zero on success, errno on failure
|
||||
*/
|
||||
extern int32_t device_read_log(smart_h, uint32_t, void *, size_t);
|
||||
|
||||
#endif /* !_LIBSMART_DEV_H */
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
#ifndef _LIBSMART_PRIV_H
|
||||
#define _LIBSMART_PRIV_H
|
||||
|
||||
/* OS-independent structures and definitions internal to libsmart */
|
||||
|
||||
#define PAGE_ID_ATA_SMART_READ_DATA 0xd0 /* SMART Read Data */
|
||||
#define PAGE_ID_ATA_SMART_RET_STATUS 0xda /* SMART Return Status */
|
||||
|
||||
#define PAGE_ID_SCSI_SUPPORTED_PAGES 0x00
|
||||
#define PAGE_ID_SCSI_WRITE_ERR 0x02 /* Write Error counter */
|
||||
#define PAGE_ID_SCSI_READ_ERR 0x03 /* Read Error counter */
|
||||
#define PAGE_ID_SCSI_VERIFY_ERR 0x05 /* Verify Error counter */
|
||||
#define PAGE_ID_SCSI_NON_MEDIUM_ERR 0x06 /* Non-Medium Error */
|
||||
#define PAGE_ID_SCSI_LAST_N_ERR 0x07 /* Last n Error events */
|
||||
#define PAGE_ID_SCSI_TEMPERATURE 0x0d /* Temperature */
|
||||
#define PAGE_ID_SCSI_START_STOP_CYCLE 0x0e /* Start-Stop Cycle counter */
|
||||
#define PAGE_ID_SCSI_INFO_EXCEPTION 0x2f /* Informational Exceptions */
|
||||
|
||||
extern bool do_debug;
|
||||
|
||||
#define dprintf(f, ...) if (do_debug) fprintf(stderr, "dbg: " f, ## __VA_ARGS__)
|
||||
|
||||
/* General information about the device */
|
||||
typedef struct smart_info_s {
|
||||
/* device supports SMART */
|
||||
uint32_t supported:1,
|
||||
/* storage protocol is tunneled (e.g. ATA inside SCSI) */
|
||||
tunneled:1,
|
||||
:30;
|
||||
/*
|
||||
* Device-provided information, including
|
||||
* - vendor name
|
||||
* - device / model
|
||||
* - firmware revision
|
||||
* - serial number
|
||||
* Protocols may provide a subset of this information
|
||||
*/
|
||||
char vendor[16], device[48], rev[16], serial[32];
|
||||
} smart_info_t;
|
||||
|
||||
/* List of pages providing SMART/health data */
|
||||
typedef struct smart_page_list_s {
|
||||
uint32_t pg_count;
|
||||
struct {
|
||||
uint32_t id;
|
||||
size_t bytes;
|
||||
} pages[];
|
||||
} smart_page_list_t;
|
||||
|
||||
/*
|
||||
* The device handle (i.e. smart_h) is an opaque pointer to memory containing
|
||||
* device/OS independent and dependent data. The library uses type punning to
|
||||
* isolate the OS-independent portion (struct smart_s) from the OS-dependent
|
||||
* details. Because of this, the device layer allocates and frees this memory.
|
||||
*/
|
||||
typedef struct smart_s {
|
||||
smart_protocol_e protocol;
|
||||
smart_info_t info;
|
||||
smart_page_list_t *pg_list;
|
||||
/* Device / OS specific follows this structure */
|
||||
} smart_t;
|
||||
|
||||
/* Return a textual description of the ATA attribute */
|
||||
const char * __smart_ata_desc(uint32_t page, uint32_t id);
|
||||
/* Return a textual description of the SCSI error attribute */
|
||||
const char * __smart_scsi_err_desc(uint32_t id);
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,245 @@
|
||||
.\"
|
||||
.\" SPDX-License-Identifier: BSD-2-Clause
|
||||
.\"
|
||||
.\" Copyright (c) 2021-2026 Chuck Tuffli
|
||||
.\"
|
||||
.\" Redistribution and use in source and binary forms, with or without
|
||||
.\" modification, are permitted provided that the following conditions
|
||||
.\" are met:
|
||||
.\" 1. Redistributions of source code must retain the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer.
|
||||
.\" 2. Redistributions in binary form must reproduce the above copyright
|
||||
.\" notice, this list of conditions and the following disclaimer in the
|
||||
.\" documentation and/or other materials provided with the distribution.
|
||||
.\"
|
||||
.\" THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
|
||||
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
|
||||
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
||||
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
||||
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
||||
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
||||
.\" SUCH DAMAGE.
|
||||
.\"
|
||||
.\" Note: The date here should be updated whenever a non-trivial
|
||||
.\" change is made to the manual page.
|
||||
.Dd February 14, 2026
|
||||
.Dt SMART 8
|
||||
.Os
|
||||
.Sh NAME
|
||||
.Nm smart ,
|
||||
.Nm diskhealth
|
||||
.Nd monitor disk health from a storage device via SMART
|
||||
.Sh SYNOPSIS
|
||||
.Nm
|
||||
.Op Fl dhitvx
|
||||
.Oo Fl a Ar page:attribute Ns Oo , Ns Ar page:attribute Oc Ns ... Oc
|
||||
.Op Fl Fl debug
|
||||
.Ar device
|
||||
.Nm diskhealth
|
||||
.Op Fl Dhitvx
|
||||
.Oo Fl a Ar page:attribute Ns Oo , Ns Ar page:attribute Oc Ns ... Oc
|
||||
.Op Fl Fl debug
|
||||
.Ar device
|
||||
.Sh DESCRIPTION
|
||||
The
|
||||
.Nm
|
||||
command allows the user to monitor the various information reported
|
||||
by Self-Monitoring, Analysis and Reporting Technology (SMART) present
|
||||
on most ATA, SCSI, and NVMe storage media.
|
||||
Because the structure of this information varies across protocols,
|
||||
.Nm
|
||||
normalizes entries using the format:
|
||||
.Bd -literal
|
||||
<Page ID> <Attribute ID> <Value> <Thresholds>
|
||||
.Ed
|
||||
.Pp
|
||||
Fields are tab-delimited by default, but the command can output
|
||||
data in any format supported by
|
||||
.Xr libxo 3 .
|
||||
.Pp
|
||||
Because ATA does not have log pages,
|
||||
.Nm
|
||||
uses the Command Feature field value in place of the log page ID.
|
||||
For SMART READ DATA, this value is 208 / 0xd0.
|
||||
Note that devices choose which attribute ID values they support, their
|
||||
descriptions, and the format of the data.
|
||||
The three values displayed with the
|
||||
.Fl Fl threshold
|
||||
option are:
|
||||
.Pp
|
||||
.Bl -dash -compact -offset indent
|
||||
.It
|
||||
Status flags (byte offset 1, 2 bytes)
|
||||
.It
|
||||
Nominal attribute value (byte offset 3, 1 byte)
|
||||
.It
|
||||
Worst Ever attribute value (byte offset 4, 1 byte)
|
||||
.El
|
||||
.Pp
|
||||
Additionally,
|
||||
.Nm
|
||||
reports the value of the SMART STATUS command (Command Feature field
|
||||
value 218 / 0xda).
|
||||
As this command does not return any data,
|
||||
the command represents this entry with a synthetic attribute
|
||||
ID of 0, and it uses the command status (0 or 1) as the attribute
|
||||
value.
|
||||
.Pp
|
||||
NVMe devices support the SMART/Health log page (Page ID 2 / 0x2).
|
||||
The data returned in this log page is not structured as attribute IDs.
|
||||
Instead,
|
||||
.Nm
|
||||
uses the byte offset of each field as the attribute ID.
|
||||
For example,
|
||||
byte 3 is the Available Spare.
|
||||
Thus, for NVMe, attribute ID 3 is
|
||||
Available Spare.
|
||||
Note that NVMe health data does not include threshold
|
||||
values, and as a result, the command will ignore the
|
||||
.Fl Fl threshold
|
||||
option.
|
||||
.Pp
|
||||
SCSI devices can support a number of log pages which report drive
|
||||
health.
|
||||
The command will report the following pages:
|
||||
.Pp
|
||||
.Bl -dash -compact -offset indent
|
||||
.It
|
||||
Write Errors (Page ID 2 / 0x2)
|
||||
.It
|
||||
Read Errors (Page ID 3 / 0x3)
|
||||
.It
|
||||
Verify Errors (Page ID 5 / 0x5)
|
||||
.It
|
||||
Non-medium Errors (Page ID 6 / 0x6)
|
||||
.It
|
||||
Last N Errors (Page ID 7 / 0x7)
|
||||
.It
|
||||
Temperature (Page ID 13 / 0xd)
|
||||
.It
|
||||
Start-stop Cycles (Page ID 14 / 0xe)
|
||||
.It
|
||||
Informational Exceptions (Page ID 47 / 0x2f)
|
||||
.El
|
||||
.Pp
|
||||
Note that all log pages are optional, and a particular drive
|
||||
may not support all these pages.
|
||||
For SCSI devices, the Attribute ID
|
||||
maps to the SCSI parameter code defined by the command.
|
||||
Parameter
|
||||
codes are integer values from 0 to N, and, by themselves, are ambiguous
|
||||
outside the context of a particular log page.
|
||||
Note that SCSI health data
|
||||
does not include threshold values, and as a result, the command will
|
||||
ignore the
|
||||
.Fl Fl threshold
|
||||
option.
|
||||
.Pp
|
||||
The following options are available:
|
||||
.Bl -tag -width "-d argument"
|
||||
.It Fl a Ar page:attribute , Fl Fl attribute= Ns Ar page:attribute
|
||||
A comma-separated list of attributes to display.
|
||||
If page is missing, display the matching attribute from any page.
|
||||
.It Fl d , Fl Fl decode
|
||||
Decode the attribute ID values.
|
||||
This is the default when invoked as
|
||||
.Nm diskhealth .
|
||||
.It Fl D , Fl Fl no-decode
|
||||
Do not decode the attribute ID values.
|
||||
This is the default when invoked as
|
||||
.Nm .
|
||||
.It Fl h , Fl Fl help
|
||||
Prints a usage message and exits.
|
||||
.It Fl i , Fl Fl info
|
||||
Print general device information.
|
||||
.It Fl t , Fl Fl threshold
|
||||
Also print the threshold values.
|
||||
.It Fl v , Fl Fl version
|
||||
Print the version and copyright.
|
||||
.It Fl x , Fl Fl hex
|
||||
Print the values in hexadecimal.
|
||||
.It Ar device
|
||||
An explicit device path
|
||||
.Pq Pa /dev/ada0
|
||||
or GEOM provider
|
||||
.Pq Pa ada0
|
||||
.
|
||||
.El
|
||||
.El
|
||||
.Sh EXIT STATUS
|
||||
.Ex -std
|
||||
.Sh EXAMPLES
|
||||
Print all SMART READ DATA and SMART STATUS including the
|
||||
threshold values for ATA drive ada0.
|
||||
.Pp
|
||||
.Dl # smart -t ada0
|
||||
.Pp
|
||||
Print only attribute ID 5 ("Reallocated Sectors Count") for
|
||||
ATA drive ada0.
|
||||
.Pp
|
||||
.Dl # smart -a 5 ada0
|
||||
.Pp
|
||||
Print attribute IDs 5 ("Reallocated Sectors Count") and 171
|
||||
("SSD Program Fail") for ATA drive ada0.
|
||||
.Pp
|
||||
.Dl # smart -a 5,171 ada0
|
||||
.Pp
|
||||
Print all health pages supported by SCSI device da0 including:
|
||||
.Bl -dash -compact -offset indent
|
||||
.It
|
||||
Write Errors
|
||||
.It
|
||||
Read Errors
|
||||
.It
|
||||
Verify Errors
|
||||
.It
|
||||
Non-medium Errors
|
||||
.It
|
||||
Last N Errors
|
||||
.It
|
||||
Temperature
|
||||
.It
|
||||
Start-stop Cycles
|
||||
.It
|
||||
Informational Exceptions
|
||||
.El
|
||||
.Pp
|
||||
.Dl # smart da0
|
||||
.Pp
|
||||
Print all Health log page entries in hexadecimal for NVMe
|
||||
device nda0.
|
||||
.Pp
|
||||
.Dl # smart -x nda0
|
||||
.Sh DIAGNOSTICS
|
||||
The command may fail for one of the following reasons:
|
||||
.Bl -diag
|
||||
.It "No output displayed"
|
||||
The device does not support health data.
|
||||
.It "CAMGETPASSTHRU ioctl failed"
|
||||
.Nm
|
||||
relies on
|
||||
.Xr cam 4
|
||||
to retrieve data from devices and will display this message if the
|
||||
device does not have a passthrough driver.
|
||||
This can happen, for
|
||||
example, if the system uses the
|
||||
.Xr nvd 4
|
||||
NVMe driver instead of the
|
||||
.Xr nda 4
|
||||
driver.
|
||||
.El
|
||||
.Sh SEE ALSO
|
||||
.Xr libxo 3 ,
|
||||
.Xr cam 4 ,
|
||||
.Xr nda 4 ,
|
||||
.Xr nvd 4
|
||||
.Sh AUTHORS
|
||||
This
|
||||
utility was written by
|
||||
.An Chuck Tuffli Aq Mt chuck@FreeBSD.org .
|
||||
.Sh BUGS
|
||||
Probably.
|
||||
@@ -0,0 +1,334 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2026 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.
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <getopt.h>
|
||||
#include <stdbool.h>
|
||||
#include <string.h>
|
||||
#ifdef LIBXO
|
||||
#include <libxo/xo.h>
|
||||
#endif
|
||||
|
||||
#include "libsmart.h"
|
||||
|
||||
#define SMART_NAME "smart"
|
||||
#define SMART_VERSION "1.0.2"
|
||||
|
||||
extern bool do_debug;
|
||||
|
||||
static const char *pn;
|
||||
bool do_debug = false;
|
||||
static int debugset = 0;
|
||||
|
||||
static struct option opts[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "threshold", no_argument, NULL, 't' },
|
||||
{ "hex", no_argument, NULL, 'x' },
|
||||
{ "attribute", required_argument, NULL, 'a' },
|
||||
{ "info", no_argument, NULL, 'i' },
|
||||
{ "version", no_argument, NULL, 'v' },
|
||||
{ "decode", no_argument, NULL, 'd' },
|
||||
{ "no-decode", no_argument, NULL, 'D' },
|
||||
{ "debug", no_argument, &debugset, 1 },
|
||||
{ NULL, 0, NULL, 0 }
|
||||
};
|
||||
|
||||
static void
|
||||
usage(const char *name)
|
||||
{
|
||||
printf("Usage: %s [-htxi] [-a attribute[,attribute]...] <device name>\n", name);
|
||||
printf("\t-h, --help\n");
|
||||
printf("\t-t, --threshold : also print out the threshold values\n");
|
||||
printf("\t-x, --hex : print the values out in hexadecimal\n");
|
||||
printf("\t-a, --attribute : print specified attribute(s)\n");
|
||||
printf("\t-i, --info : print general device information\n");
|
||||
printf("\t-d, --decode: decode the attribute IDs\n");
|
||||
printf("\t-D, --no-decode: don't decode the attribute IDs\n");
|
||||
printf("\t-v, --version : print the version and copyright\n");
|
||||
printf("\t --debug : output diagnostic information\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert string to an integer
|
||||
*
|
||||
* Returns -1 on error, converted value otherwise
|
||||
*/
|
||||
static int32_t
|
||||
get_val(char *attr, char **next)
|
||||
{
|
||||
char *sep = NULL;
|
||||
long val;
|
||||
|
||||
*next = NULL;
|
||||
|
||||
val = strtol(attr, &sep, 0);
|
||||
if ((val == 0) && (errno != 0)) {
|
||||
printf("Error parsing attribute %s", attr);
|
||||
switch (errno) {
|
||||
case EINVAL:
|
||||
printf(" (not a number?)\n");
|
||||
break;
|
||||
case ERANGE:
|
||||
printf(" (value out of range)\n");
|
||||
break;
|
||||
default:
|
||||
printf("\n");
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (val > INT32_MAX) {
|
||||
printf("Attribute value %ld too big\n", val);
|
||||
return -1;
|
||||
}
|
||||
|
||||
*next = sep;
|
||||
return ((int32_t)val);
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a match specification from the given attribute
|
||||
*
|
||||
* Attribute format is
|
||||
* <Page ID>:<Attribute ID>
|
||||
* where page and attribute IDs are integers. If the page ID is missing,
|
||||
* match the specified attribute ID on any page (i.e. -1). Valid forms are
|
||||
* <int>:<int>
|
||||
* :<int>
|
||||
* <int>
|
||||
*
|
||||
* Returns 0 on success
|
||||
*/
|
||||
static int
|
||||
add_match(smart_matches_t **matches, char *attr)
|
||||
{
|
||||
char *next;
|
||||
int32_t page = -1, id;
|
||||
uint32_t count = 0;
|
||||
|
||||
id = get_val(attr, &next);
|
||||
if (id < 0)
|
||||
return id;
|
||||
|
||||
if (*next == ':') {
|
||||
page = id;
|
||||
id = get_val(next + 1, &next);
|
||||
if (id < 0)
|
||||
return id;
|
||||
}
|
||||
|
||||
if (*matches == NULL) {
|
||||
*matches = calloc(1, sizeof(smart_matches_t) + sizeof(smart_match_t));
|
||||
if (*matches == NULL)
|
||||
return ENOMEM;
|
||||
} else {
|
||||
void *tmp;
|
||||
|
||||
count = (*matches)->count;
|
||||
tmp = realloc(*matches, sizeof(smart_matches_t) + ((count + 1) * sizeof(smart_match_t)));
|
||||
if (tmp == NULL)
|
||||
return ENOMEM;
|
||||
*matches = tmp;
|
||||
}
|
||||
|
||||
(*matches)->m[count].page = page;
|
||||
(*matches)->m[count].id = id;
|
||||
(*matches)->count++;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Parse the comma separated list of attributes to match
|
||||
*
|
||||
* Caller frees memory allocated for the smart_matches_t pointer.
|
||||
*
|
||||
* Returns 0 on success
|
||||
*/
|
||||
static int
|
||||
parse_matches(smart_matches_t **matches, char *attr)
|
||||
{
|
||||
int res;
|
||||
|
||||
if (attr[0] == '\0')
|
||||
return -1;
|
||||
|
||||
while (*attr != '\0') {
|
||||
char *next;
|
||||
size_t len;
|
||||
|
||||
if ((next = strchr(attr, ',')) == NULL) {
|
||||
len = strlen(attr);
|
||||
next = attr + len;
|
||||
} else {
|
||||
len = next - attr;
|
||||
next++;
|
||||
}
|
||||
|
||||
if (len == 0) {
|
||||
printf("Malformed attribute %s\n", attr);
|
||||
return -1;
|
||||
}
|
||||
|
||||
res = add_match(matches, attr);
|
||||
if (res)
|
||||
return res;
|
||||
|
||||
attr = next;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char *argv[])
|
||||
{
|
||||
smart_h h;
|
||||
smart_map_t *sm = NULL;
|
||||
char *devname = NULL;
|
||||
int ch;
|
||||
bool do_thresh = false, do_hex = false, do_info = false, do_version = false,
|
||||
do_descr;
|
||||
smart_matches_t *matches = NULL;
|
||||
int rc = EXIT_SUCCESS;
|
||||
|
||||
/*
|
||||
* By default, keep the original behavior (output numbers only) if
|
||||
* invoked as smart. Otherwise, default to printing the human-friendly
|
||||
* text descriptions.
|
||||
*/
|
||||
pn = getprogname();
|
||||
if (strcmp(pn, SMART_NAME) == 0)
|
||||
do_descr = false;
|
||||
else
|
||||
do_descr = true;
|
||||
|
||||
#ifdef LIBXO
|
||||
argc = xo_parse_args(argc, argv);
|
||||
#endif
|
||||
|
||||
while ((ch = getopt_long(argc, argv, "htxa:idDv", opts, NULL)) != -1) {
|
||||
switch (ch) {
|
||||
case 'h':
|
||||
usage(pn);
|
||||
#ifdef LIBXO
|
||||
xo_finish();
|
||||
#endif
|
||||
return EXIT_SUCCESS;
|
||||
break;
|
||||
case 't':
|
||||
do_thresh = true;
|
||||
break;
|
||||
case 'x':
|
||||
do_hex = true;
|
||||
break;
|
||||
case 'a':
|
||||
if (parse_matches(&matches, optarg)) {
|
||||
usage(pn);
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
break;
|
||||
case 'i':
|
||||
do_info = true;
|
||||
break;
|
||||
case 'd':
|
||||
do_descr = true;
|
||||
break;
|
||||
case 'D':
|
||||
do_descr = false;
|
||||
break;
|
||||
case 'v':
|
||||
do_version = true;
|
||||
break;
|
||||
case 0:
|
||||
if (debugset)
|
||||
do_debug = true;
|
||||
break;
|
||||
default:
|
||||
usage(pn);
|
||||
#ifdef LIBXO
|
||||
xo_finish();
|
||||
#endif
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
if (do_version) {
|
||||
printf("%s, version %s\n", pn, SMART_VERSION);
|
||||
printf("Copyright (c) 2016-2026 Chuck Tuffli\n"
|
||||
"This is free software; see the source for copying conditions.\n");
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
argc -= optind;
|
||||
argv += optind;
|
||||
|
||||
devname = argv[0];
|
||||
|
||||
if (!devname) {
|
||||
printf("no device specified\n");
|
||||
usage(pn);
|
||||
#ifdef LIBXO
|
||||
xo_finish();
|
||||
#endif
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
h = smart_open(SMART_PROTO_AUTO, argv[0]);
|
||||
|
||||
if (h == NULL) {
|
||||
printf("device open failed %s\n", argv[0]);
|
||||
#ifdef LIBXO
|
||||
xo_finish();
|
||||
#endif
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
#ifdef LIBXO
|
||||
xo_open_container("drive");
|
||||
#endif
|
||||
|
||||
if (do_info) {
|
||||
smart_print_device_info(h);
|
||||
}
|
||||
|
||||
if (smart_supported(h)) {
|
||||
sm = smart_read(h);
|
||||
|
||||
if (sm) {
|
||||
uint32_t flags = 0;
|
||||
|
||||
if (do_hex)
|
||||
flags |= SMART_OPEN_F_HEX;
|
||||
if (do_thresh)
|
||||
flags |= SMART_OPEN_F_THRESH;
|
||||
if (do_descr)
|
||||
flags |= SMART_OPEN_F_DESCR;
|
||||
|
||||
smart_print(h, sm, matches, flags);
|
||||
|
||||
smart_free(sm);
|
||||
}
|
||||
} else {
|
||||
rc = EXIT_FAILURE;
|
||||
}
|
||||
#ifdef LIBXO
|
||||
xo_finish();
|
||||
#endif
|
||||
smart_close(h);
|
||||
|
||||
return rc;
|
||||
}
|
||||
Reference in New Issue
Block a user