tus & 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 ***