From nobody Tue Apr 28 17:10:36 2026 X-Original-To: dev-commits-src-all@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4g4n4y1KMrz6bnXJ for ; Tue, 28 Apr 2026 17:10:42 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "R13" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4g4n4x6w9hz3cnw for ; Tue, 28 Apr 2026 17:10:41 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1777396241; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=KmbU0tHvpDjMZ2vSF5azyKd1hkq3WXB/yxeQoWD40sU=; b=SNNTUwfKNDLmf7lep2VVvcdL09P443nSAwbwZyHeSdDW+aRzu7iU51o6w00eZnwHBA7g/f 9cOlokHNKzdM8WtydxmU+/wr2o/bnWY1c1z+gZbS40Dyx675v0RkLcJBjyD6X0dkU9R9cE i7yIM9tAyRlNpqkJf10f9FZpbYaUYJ3ueHoC63Ltv+/FpMTpEF3lGaBAreDyc+DD2O224z C+6+M4/UhFzlOOwPvDx8ea4+0s295FG4r8fiiofewBRsHd6BpLWWMjV2cyLs8CptfsWCpb pHJVGRunt0ATYVsf2mCdnCTSoxWhpGxPJ5790bVl8HIp8cLFAqmd4LbNEr3FXw== ARC-Seal: i=1; s=dkim; d=freebsd.org; t=1777396241; a=rsa-sha256; cv=none; b=TcyehlCyAi7ITcI6CIULF6gxn4x+1fAiUrMVjvqOLo0z/Jhqd59eSP/zTSFirkDHSLsEAP mZQfrHNpnosWW8ovn5cXrj9sdzJqM0EnIgQWhw1n/qr0wQHVEtIPwJwXVwYHJDO1kM/dt3 0HqqcjY92YFNqKP0KoQltyZ8Yjl+RMQHm4sS7eH+XT1QzH/Jez2a3oymHNx2zEnYqGPnvn YPWZQhrPSxnCGWC1kWl86VUHZhO1Wx9cJJexrGujq5jHWXiEE4SmbQbSZ2rFH8FTPx7zLT mG8Nrw3UdnqFV1Vf1+3LNxc7HyaDmqMuhBfhOegDU8JJwK5tVgpkKB0k/AfpvQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1777396241; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=KmbU0tHvpDjMZ2vSF5azyKd1hkq3WXB/yxeQoWD40sU=; b=wSrBNthzvdD05RPjPu5svJa+qNJIhgdHBopTY33JbhhEzXDn1Vo4E0j+0JKpqBrnaDI6me iR2VUK8h4YYj4hkjaPQQcQqtdaTgflB/7e5kZm74Mg2784P2VnAI9VDgyfGYzJQBcKvWwp Oymlz/dO6c5tuue0ng5aJ4VpGOXKrKTuIYLrmBQ3D1emo0KiuC0ljOz1DBRzDV97fW89Z+ iLmIZdevveDaZQidmXTB4sEnpAlShNXxXt6ABpxV22cyI0Ut+H/iawE+H+zXFzn2r0Or6i TMS/EDGfLOyv3+3b4h7YXdHOT3Y9Dy5KGRoKWZlxdfp1jOQiH+7+FM6kmt1iJw== Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4g4n4x5x6kz12Sr for ; Tue, 28 Apr 2026 17:10:41 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 19f57 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Tue, 28 Apr 2026 17:10:36 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Chuck Tuffli Subject: git: 68e5b71517e9 - main - Vendor import of smart at 1.0.2 List-Id: Commit messages for all branches of the src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-all List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-all@freebsd.org Sender: owner-dev-commits-src-all@FreeBSD.org MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: chuck X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: 68e5b71517e947b4e3f349c970af362b47b45f27 Auto-Submitted: auto-generated Date: Tue, 28 Apr 2026 17:10:36 +0000 Message-Id: <69f0ea0c.19f57.339fcc06@gitrepo.freebsd.org> The branch main has been updated by chuck: URL: https://cgit.FreeBSD.org/src/commit/?id=68e5b71517e947b4e3f349c970af362b47b45f27 commit 68e5b71517e947b4e3f349c970af362b47b45f27 Author: Chuck Tuffli AuthorDate: 2026-04-28 16:55:50 +0000 Commit: Chuck Tuffli CommitDate: 2026-04-28 17:08:27 +0000 Vendor import of smart at 1.0.2 smart/diskhealth is a command line application to monitor disk health from a storage device via SMART. Reviewed by: fuz, jrm Relnotes: yes Differential Revision: https://reviews.freebsd.org/D56638 --- contrib/smart/Changelog | 37 ++ contrib/smart/LICENSE | 13 + contrib/smart/Makefile | 26 + contrib/smart/freebsd_dev.c | 828 +++++++++++++++++++++++++ contrib/smart/libsmart.c | 1359 +++++++++++++++++++++++++++++++++++++++++ contrib/smart/libsmart.h | 174 ++++++ contrib/smart/libsmart_desc.c | 158 +++++ contrib/smart/libsmart_dev.h | 60 ++ contrib/smart/libsmart_priv.h | 83 +++ contrib/smart/smart.8 | 245 ++++++++ contrib/smart/smart.c | 334 ++++++++++ packages/Makefile | 1 + packages/smart/Makefile | 4 + packages/smart/smart.ucl | 30 + usr.sbin/Makefile | 1 + usr.sbin/smart/Makefile | 8 + 16 files changed, 3361 insertions(+) diff --git a/contrib/smart/Changelog b/contrib/smart/Changelog new file mode 100644 index 000000000000..42b79bc34070 --- /dev/null +++ b/contrib/smart/Changelog @@ -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 diff --git a/contrib/smart/LICENSE b/contrib/smart/LICENSE new file mode 100644 index 000000000000..8b0a0bf6a4a6 --- /dev/null +++ b/contrib/smart/LICENSE @@ -0,0 +1,13 @@ +Copyright (c) 2016-2026 Chuck Tuffli + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. diff --git a/contrib/smart/Makefile b/contrib/smart/Makefile new file mode 100644 index 000000000000..64cab720e08f --- /dev/null +++ b/contrib/smart/Makefile @@ -0,0 +1,26 @@ +# +# Copyright (c) 2016-2021 Chuck Tuffli +# +# Permission to use, copy, modify, and distribute this software for any +# purpose with or without fee is hereby granted, provided that the above +# copyright notice and this permission notice appear in all copies. +# +# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +# +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 diff --git a/contrib/smart/freebsd_dev.c b/contrib/smart/freebsd_dev.c new file mode 100644 index 000000000000..d1dda2289742 --- /dev/null +++ b/contrib/smart/freebsd_dev.c @@ -0,0 +1,828 @@ +/* + * Copyright (c) 2016-2021 Chuck Tuffli + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include +#include +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/contrib/smart/libsmart.c b/contrib/smart/libsmart.c new file mode 100644 index 000000000000..a1732de09ed9 --- /dev/null +++ b/contrib/smart/libsmart.c @@ -0,0 +1,1359 @@ +/* + * Copyright (c) 2016-2026 Chuck Tuffli + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +#include *** 2517 LINES SKIPPED ***