X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=os_netbsd.cpp;h=9b818970335c70c635f732123bb8f416b881c6c9;hb=f9e10201baef7a8dcb11ab6d11a2046d724af9d0;hp=dce2d9d5a6a6a69f70ed478e9abbc74da4feb2e4;hpb=ee38a438aafef7a04b7df628ca5ad38810a1d63e;p=mirror_smartmontools-debian.git diff --git a/os_netbsd.cpp b/os_netbsd.cpp index dce2d9d..9b81897 100644 --- a/os_netbsd.cpp +++ b/os_netbsd.cpp @@ -1,9 +1,10 @@ /* * os_netbsd.cpp * - * Home page of code is: http://smartmontools.sourceforge.net + * Home page of code is: http://www.smartmontools.org * - * Copyright (C) 2003-8 Sergey Svishchev + * Copyright (C) 2003-8 Sergey Svishchev + * Copyright (C) 2016 Kimihiro Nonaka * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,17 +24,19 @@ #include "utility.h" #include "os_netbsd.h" +#include +#include #include #include -const char * os_netbsd_cpp_cvsid = "$Id: os_netbsd.cpp 3806 2013-03-29 20:17:03Z chrfranke $" - OS_NETBSD_H_CVSID; +// based on "sys/dev/ic/nvmeio.h" from NetBSD kernel sources +#include "netbsd_nvme_ioctl.h" // NVME_PASSTHROUGH_CMD, nvme_completion_is_error -/* global variable holding byte count of allocated memory */ -extern long long bytes; +const char * os_netbsd_cpp_cvsid = "$Id: os_netbsd.cpp 4431 2017-08-08 19:38:15Z chrfranke $" + OS_NETBSD_H_CVSID; enum warnings { - BAD_SMART, NO_3WARE, NO_ARECA, MAX_MSG + BAD_SMART, MAX_MSG }; /* Utility function for printing warnings */ @@ -51,317 +54,384 @@ printwarning(int msgNo, const char *extra) printed[msgNo] = 1; pout("%s", message[msgNo]); if (extra) - pout("%s", extra); + pout("%s", extra); } } return; } +#define ARGUSED(x) ((void)(x)) + +///////////////////////////////////////////////////////////////////////////// + +namespace os_netbsd { // No need to publish anything, name provided for Doxygen + static const char *net_dev_prefix = "/dev/"; +static const char *net_dev_raw_prefix = "/dev/r"; static const char *net_dev_ata_disk = "wd"; static const char *net_dev_scsi_disk = "sd"; static const char *net_dev_scsi_tape = "enrst"; +static const char *net_dev_nvme_ctrl = "nvme"; -/* Guess device type (ATA or SCSI) based on device name */ -int -guess_device_type(const char *dev_name) +///////////////////////////////////////////////////////////////////////////// +/// Implement shared open/close routines with old functions. + +class netbsd_smart_device +: virtual public /*implements*/ smart_device { - int len; - int dev_prefix_len = strlen(net_dev_prefix); +public: + explicit netbsd_smart_device() + : smart_device(never_called), + m_fd(-1) { } - if (!dev_name || !(len = strlen(dev_name))) - return CONTROLLER_UNKNOWN; + virtual ~netbsd_smart_device() throw(); - if (!strncmp(net_dev_prefix, dev_name, dev_prefix_len)) { - if (len <= dev_prefix_len) - return CONTROLLER_UNKNOWN; - else - dev_name += dev_prefix_len; - } - if (!strncmp(net_dev_ata_disk, dev_name, strlen(net_dev_ata_disk))) - return CONTROLLER_ATA; + virtual bool is_open() const; + + virtual bool open(); + + virtual bool close(); - if (!strncmp(net_dev_scsi_disk, dev_name, strlen(net_dev_scsi_disk))) - return CONTROLLER_SCSI; +protected: + /// Return filedesc for derived classes. + int get_fd() const + { return m_fd; } - if (!strncmp(net_dev_scsi_tape, dev_name, strlen(net_dev_scsi_tape))) - return CONTROLLER_SCSI; + void set_fd(int fd) + { m_fd = fd; } - return CONTROLLER_UNKNOWN; +private: + int m_fd; ///< filedesc, -1 if not open. +}; + +netbsd_smart_device::~netbsd_smart_device() throw() +{ + if (m_fd >= 0) + os_netbsd::netbsd_smart_device::close(); } -int -get_dev_names(char ***names, const char *prefix) +bool netbsd_smart_device::is_open() const { - char *disknames, *p, **mp; - int n = 0; - int sysctl_mib[2]; - size_t sysctl_len; + return (m_fd >= 0); +} - *names = NULL; - sysctl_mib[0] = CTL_HW; - sysctl_mib[1] = HW_DISKNAMES; - if (-1 == sysctl(sysctl_mib, 2, NULL, &sysctl_len, NULL, 0)) { - pout("Failed to get value of sysctl `hw.disknames'\n"); - return -1; - } - if (!(disknames = (char *)malloc(sysctl_len))) { - pout("Out of memory constructing scan device list\n"); - return -1; - } - if (-1 == sysctl(sysctl_mib, 2, disknames, &sysctl_len, NULL, 0)) { - pout("Failed to get value of sysctl `hw.disknames'\n"); - return -1; - } - if (!(mp = (char **) calloc(strlen(disknames) / 2, sizeof(char *)))) { - pout("Out of memory constructing scan device list\n"); - return -1; - } - for (p = strtok(disknames, " "); p; p = strtok(NULL, " ")) { - if (strncmp(p, prefix, strlen(prefix))) { - continue; +bool netbsd_smart_device::open() +{ + const char *dev = get_dev_name(); + int fd; + + if (is_scsi()) { + fd = ::open(dev,O_RDWR|O_NONBLOCK); + if (fd < 0 && errno == EROFS) + fd = ::open(dev,O_RDONLY|O_NONBLOCK); + if (fd < 0) { + set_err(errno); + return false; } - mp[n] = (char *)malloc(strlen(net_dev_prefix) + strlen(p) + 2); - if (!mp[n]) { - pout("Out of memory constructing scan device list\n"); - return -1; + } else if (is_ata() || is_nvme()) { + if ((fd = ::open(dev,O_RDWR|O_NONBLOCK))<0) { + set_err(errno); + return false; } - sprintf(mp[n], "%s%s%c", net_dev_prefix, p, 'a' + getrawpartition()); - bytes += strlen(mp[n]) + 1; - n++; - } + } else + return false; - mp = (char **)realloc(mp, n * (sizeof(char *))); - bytes += (n) * (sizeof(char *)); - *names = mp; - return n; + set_fd(fd); + return true; } -int -make_device_names(char ***devlist, const char *name) +bool netbsd_smart_device::close() { - if (!strcmp(name, "SCSI")) - return get_dev_names(devlist, net_dev_scsi_disk); - else if (!strcmp(name, "ATA")) - return get_dev_names(devlist, net_dev_ata_disk); - else - return 0; + int failed = 0; + // close device, if open + if (is_open()) + failed=::close(get_fd()); + + set_fd(-1); + + if(failed) return false; + else return true; } -int -deviceopen(const char *pathname, char *type) +///////////////////////////////////////////////////////////////////////////// +/// Implement standard ATA support + +class netbsd_ata_device +: public /*implements*/ ata_device, + public /*extends*/ netbsd_smart_device +{ +public: + netbsd_ata_device(smart_interface * intf, const char * dev_name, const char * req_type); + virtual bool ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out); + +protected: + virtual int do_cmd(struct atareq* request, bool is_48bit_cmd); +}; + +netbsd_ata_device::netbsd_ata_device(smart_interface * intf, const char * dev_name, const char * req_type) +: smart_device(intf, dev_name, "ata", req_type), + netbsd_smart_device() { - if (!strcmp(type, "SCSI")) { - int fd = open(pathname, O_RDWR | O_NONBLOCK); - if (fd < 0 && errno == EROFS) - fd = open(pathname, O_RDONLY | O_NONBLOCK); - return fd; - } else if (!strcmp(type, "ATA")) - return open(pathname, O_RDWR | O_NONBLOCK); - else - return -1; } -int -deviceclose(int fd) +int netbsd_ata_device::do_cmd( struct atareq* request, bool is_48bit_cmd) { - return close(fd); + int fd = get_fd(), ret; + ARGUSED(is_48bit_cmd); // no support for 48 bit commands in the ATAIOCCOMMAND + ret = ioctl(fd, ATAIOCCOMMAND, request); + if (ret) set_err(errno); + return ret; } -int -ata_command_interface(int fd, smart_command_set command, int select, char *data) +bool netbsd_ata_device::ata_pass_through(const ata_cmd_in & in, ata_cmd_out & out) { - struct atareq req; - unsigned char inbuf[DEV_BSIZE]; - int retval, copydata = 0; + bool ata_48bit = false; // no ata_48bit_support via ATAIOCCOMMAND + + if (!ata_cmd_is_ok(in, + true, // data_out_support + true, // multi_sector_support + ata_48bit) + ) { + set_err(ENOSYS, "48-bit ATA commands not implemented"); + return false; + } + struct atareq req; memset(&req, 0, sizeof(req)); + req.timeout = 1000; + req.command = in.in_regs.command; + req.features = in.in_regs.features; + req.sec_count = in.in_regs.sector_count; + req.sec_num = in.in_regs.lba_low; + req.head = in.in_regs.device; + req.cylinder = le16toh(in.in_regs.lba_mid | (in.in_regs.lba_high << 8)); + + switch (in.direction) { + case ata_cmd_in::no_data: + req.flags = ATACMD_READREG; + break; + case ata_cmd_in::data_in: + req.flags = ATACMD_READ | ATACMD_READREG; + req.databuf = (char *)in.buffer; + req.datalen = in.size; + break; + case ata_cmd_in::data_out: + req.flags = ATACMD_WRITE | ATACMD_READREG; + req.databuf = (char *)in.buffer; + req.datalen = in.size; + break; + default: + return set_err(ENOSYS); + } - memset(&inbuf, 0, sizeof(inbuf)); - - switch (command) { - case READ_VALUES: - req.flags = ATACMD_READ; - req.features = WDSM_RD_DATA; - req.command = WDCC_SMART; - req.databuf = (char *)inbuf; - req.datalen = sizeof(inbuf); - req.cylinder = WDSMART_CYL; - copydata = 1; - break; - case READ_THRESHOLDS: - req.flags = ATACMD_READ; - req.features = WDSM_RD_THRESHOLDS; - req.command = WDCC_SMART; - req.databuf = (char *)inbuf; - req.datalen = sizeof(inbuf); - req.cylinder = WDSMART_CYL; - copydata = 1; - break; - case READ_LOG: - req.flags = ATACMD_READ; - req.features = ATA_SMART_READ_LOG_SECTOR; /* XXX missing from wdcreg.h */ - req.command = WDCC_SMART; - req.databuf = (char *)inbuf; - req.datalen = sizeof(inbuf); - req.cylinder = WDSMART_CYL; - req.sec_num = select; - req.sec_count = 1; - copydata = 1; - break; - case WRITE_LOG: - memcpy(inbuf, data, 512); - req.flags = ATACMD_WRITE; - req.features = ATA_SMART_WRITE_LOG_SECTOR; /* XXX missing from wdcreg.h */ - req.command = WDCC_SMART; - req.databuf = (char *)inbuf; - req.datalen = sizeof(inbuf); - req.cylinder = WDSMART_CYL; - req.sec_num = select; - req.sec_count = 1; - break; - case IDENTIFY: - req.flags = ATACMD_READ; - req.command = WDCC_IDENTIFY; - req.databuf = (char *)inbuf; - req.datalen = sizeof(inbuf); - copydata = 1; - break; - case PIDENTIFY: - req.flags = ATACMD_READ; - req.command = ATAPI_IDENTIFY_DEVICE; - req.databuf = (char *)inbuf; - req.datalen = sizeof(inbuf); - copydata = 1; - break; - case ENABLE: - req.flags = ATACMD_READREG; - req.features = WDSM_ENABLE_OPS; - req.command = WDCC_SMART; - req.cylinder = WDSMART_CYL; - break; - case DISABLE: - req.flags = ATACMD_READREG; - req.features = WDSM_DISABLE_OPS; - req.command = WDCC_SMART; - req.cylinder = WDSMART_CYL; - break; - case AUTO_OFFLINE: - /* NOTE: According to ATAPI 4 and UP, this command is obsolete */ - req.flags = ATACMD_READREG; - req.features = ATA_SMART_AUTO_OFFLINE; /* XXX missing from wdcreg.h */ - req.command = WDCC_SMART; - req.cylinder = WDSMART_CYL; - req.sec_count = select; - break; - case AUTOSAVE: - req.flags = ATACMD_READREG; - req.features = ATA_SMART_AUTOSAVE; /* XXX missing from wdcreg.h */ - req.command = WDCC_SMART; - req.cylinder = WDSMART_CYL; - req.sec_count = select; - break; - case IMMEDIATE_OFFLINE: - /* NOTE: According to ATAPI 4 and UP, this command is obsolete */ - req.flags = ATACMD_READREG; - req.features = ATA_SMART_IMMEDIATE_OFFLINE; /* XXX missing from wdcreg.h */ - req.command = WDCC_SMART; - req.cylinder = WDSMART_CYL; - req.sec_num = select; - req.sec_count = 1; - break; - case STATUS: /* should return 0 if SMART is enabled at all */ - case STATUS_CHECK: /* should return 0 if disk's health is ok */ - req.flags = ATACMD_READREG; - req.features = WDSM_STATUS; - req.command = WDCC_SMART; - req.cylinder = WDSMART_CYL; - break; - case CHECK_POWER_MODE: - req.flags = ATACMD_READREG; - req.command = WDCC_CHECK_PWR; - break; - default: - pout("Unrecognized command %d in ata_command_interface()\n", command); - errno = ENOSYS; - return -1; + clear_err(); + errno = 0; + if (do_cmd(&req, in.in_regs.is_48bit_cmd())) + return false; + if (req.retsts != ATACMD_OK) + return set_err(EIO, "request failed, error code 0x%02x", req.retsts); + + out.out_regs.error = req.error; + out.out_regs.sector_count = req.sec_count; + out.out_regs.lba_low = req.sec_num; + out.out_regs.device = req.head; + out.out_regs.lba_mid = le16toh(req.cylinder); + out.out_regs.lba_high = le16toh(req.cylinder) >> 8; + out.out_regs.status = req.command; + + // Command specific processing + if (in.in_regs.command == ATA_SMART_CMD + && in.in_regs.features == ATA_SMART_STATUS + && in.out_needed.lba_high) + { + unsigned const char normal_lo=0x4f, normal_hi=0xc2; + unsigned const char failed_lo=0xf4, failed_hi=0x2c; + + // Cyl low and Cyl high unchanged means "Good SMART status" + if (!(out.out_regs.lba_mid==normal_lo && out.out_regs.lba_high==normal_hi) + // These values mean "Bad SMART status" + && !(out.out_regs.lba_mid==failed_lo && out.out_regs.lba_high==failed_hi)) + + { + // We haven't gotten output that makes sense; print out some debugging info + char buf[512]; + snprintf(buf, sizeof(buf), + "CMD=0x%02x\nFR =0x%02x\nNS =0x%02x\nSC =0x%02x\nCL =0x%02x\nCH =0x%02x\nRETURN =0x%04x\n", + (int)req.command, + (int)req.features, + (int)req.sec_count, + (int)req.sec_num, + (int)(le16toh(req.cylinder) & 0xff), + (int)((le16toh(req.cylinder) >> 8) & 0xff), + (int)req.error); + printwarning(BAD_SMART,buf); + out.out_regs.lba_high = failed_hi; + out.out_regs.lba_mid = failed_lo; + } } - if (command == STATUS_CHECK || command == AUTOSAVE || command == AUTO_OFFLINE) { - char buf[512]; + return true; +} - unsigned const short normal = WDSMART_CYL, failed = 0x2cf4; +///////////////////////////////////////////////////////////////////////////// +/// NVMe support - if ((retval = ioctl(fd, ATAIOCCOMMAND, &req))) { - perror("Failed command"); - return -1; +class netbsd_nvme_device +: public /*implements*/ nvme_device, + public /*extends*/ netbsd_smart_device +{ +public: + netbsd_nvme_device(smart_interface * intf, const char * dev_name, + const char * req_type, unsigned nsid); + + virtual bool open(); + + virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out); +}; + +netbsd_nvme_device::netbsd_nvme_device(smart_interface * intf, const char * dev_name, + const char * req_type, unsigned nsid) +: smart_device(intf, dev_name, "nvme", req_type), + nvme_device(nsid), + netbsd_smart_device() +{ +} + +bool netbsd_nvme_device::open() +{ + const char *dev = get_dev_name(); + if (strncmp(dev, NVME_PREFIX, strlen(NVME_PREFIX))) { + set_err(EINVAL, "NVMe controller controller/namespace ids must begin with '%s'", + NVME_PREFIX); + return false; + } + + int nsid = -1, ctrlid = -1; + char tmp; + + if(sscanf(dev, NVME_PREFIX"%d%c", &ctrlid, &tmp) == 1) + { + if(ctrlid < 0) { + set_err(EINVAL, "Invalid NVMe controller number"); + return false; } - if (req.retsts != ATACMD_OK) { - return -1; + nsid = 0xFFFFFFFF; // broadcast id + } + else if (sscanf(dev, NVME_PREFIX"%d"NVME_NS_PREFIX"%d%c", + &ctrlid, &nsid, &tmp) == 2) + { + if(ctrlid < 0 || nsid <= 0) { + set_err(EINVAL, "Invalid NVMe controller/namespace number"); + return false; } - /* Cyl low and Cyl high unchanged means "Good SMART status" */ - if (req.cylinder == normal) - return 0; - - /* These values mean "Bad SMART status" */ - if (req.cylinder == failed) - return 1; - - /* We haven't gotten output that makes sense; - * print out some debugging info */ - snprintf(buf, sizeof(buf), - "CMD=0x%02x\nFR =0x%02x\nNS =0x%02x\nSC =0x%02x\nCL =0x%02x\nCH =0x%02x\nRETURN =0x%04x\n", - (int) req.command, (int) req.features, (int) req.sec_count, (int) req.sec_num, - (int) (le16toh(req.cylinder) & 0xff), (int) ((le16toh(req.cylinder) >> 8) & 0xff), - (int) req.error); - printwarning(BAD_SMART, buf); - return 0; + } + else { + set_err(EINVAL, "Invalid NVMe controller/namespace syntax"); + return false; } - if ((retval = ioctl(fd, ATAIOCCOMMAND, &req))) { - perror("Failed command"); - return -1; + // we should always open controller, not namespace device + char full_path[64]; + snprintf(full_path, sizeof(full_path), NVME_PREFIX"%d", ctrlid); + + int fd; + if ((fd = ::open(full_path, O_RDWR))<0) { + set_err(errno); + return false; } - if (req.retsts != ATACMD_OK) { - return -1; + set_fd(fd); + + if (!get_nsid()) { + set_nsid(nsid); } - if (command == CHECK_POWER_MODE) - data[0] = req.sec_count; + return true; +} - if (copydata) - memcpy(data, inbuf, 512); +bool netbsd_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out) +{ + struct nvme_pt_command pt; + memset(&pt, 0, sizeof(pt)); - return 0; + pt.cmd.opcode = in.opcode; + pt.cmd.nsid = in.nsid; + pt.buf = in.buffer; + pt.len = in.size; + pt.cmd.cdw10 = in.cdw10; + pt.cmd.cdw11 = in.cdw11; + pt.cmd.cdw12 = in.cdw12; + pt.cmd.cdw13 = in.cdw13; + pt.cmd.cdw14 = in.cdw14; + pt.cmd.cdw15 = in.cdw15; + pt.is_read = 1; // should we use in.direction()? + + int status = ioctl(get_fd(), NVME_PASSTHROUGH_CMD, &pt); + + if (status < 0) + return set_err(errno, "NVME_PASSTHROUGH_CMD: %s", strerror(errno)); + + out.result=pt.cpl.cdw0; // Command specific result (DW0) + + if (nvme_completion_is_error(&pt.cpl)) + return set_nvme_err(out, nvme_completion_is_error(&pt.cpl)); + + return true; } -int -do_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report) +///////////////////////////////////////////////////////////////////////////// +/// Standard SCSI support + +class netbsd_scsi_device +: public /*implements*/ scsi_device, + public /*extends*/ netbsd_smart_device { - struct scsireq sc; +public: + netbsd_scsi_device(smart_interface * intf, const char * dev_name, const char * req_type, bool scanning = false); - if (report > 0) { - size_t k; + virtual smart_device * autodetect_open(); - const unsigned char *ucp = iop->cmnd; - const char *np; + virtual bool scsi_pass_through(scsi_cmnd_io * iop); + +private: + bool m_scanning; ///< true if created within scan_smart_devices +}; + +netbsd_scsi_device::netbsd_scsi_device(smart_interface * intf, + const char * dev_name, const char * req_type, bool scanning /* = false */) +: smart_device(intf, dev_name, "scsi", req_type), + netbsd_smart_device(), + m_scanning(scanning) +{ +} + +bool netbsd_scsi_device::scsi_pass_through(scsi_cmnd_io * iop) +{ + struct scsireq sc; + int fd = get_fd(); + + if (scsi_debugmode) { + unsigned int k; + const unsigned char * ucp = iop->cmnd; + const char * np; np = scsi_get_opcode_name(ucp[0]); pout(" [%s: ", np ? np : ""); for (k = 0; k < iop->cmnd_len; ++k) pout("%02x ", ucp[k]); - if ((report > 1) && + if ((scsi_debugmode > 1) && (DXFER_TO_DEVICE == iop->dxfer_dir) && (iop->dxferp)) { - int trunc = (iop->dxfer_len > 256) ? 1 : 0; + int trunc = (iop->dxfer_len > 256) ? 1 : 0; - pout("]\n Outgoing data, len=%d%s:\n", (int) iop->dxfer_len, - (trunc ? " [only first 256 bytes shown]" : "")); - dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len), 1); - } else - pout("]"); + pout("]\n Outgoing data, len=%d%s:\n", (int)iop->dxfer_len, + (trunc ? " [only first 256 bytes shown]" : "")); + dStrHex(iop->dxferp, (trunc ? 256 : iop->dxfer_len) , 1); + } + else + pout("]\n"); } + memset(&sc, 0, sizeof(sc)); memcpy(sc.cmd, iop->cmnd, iop->cmnd_len); sc.cmdlen = iop->cmnd_len; @@ -374,8 +444,10 @@ do_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report) (iop->dxfer_dir == DXFER_FROM_DEVICE ? SCCMD_READ : SCCMD_WRITE)); if (ioctl(fd, SCIOCCOMMAND, &sc) < 0) { - warn("error sending SCSI ccb"); - return -1; + if (scsi_debugmode) { + pout(" error sending SCSI ccb\n"); + } + return set_err(EIO); } iop->resid = sc.datalen - sc.datalen_used; iop->scsi_status = sc.status; @@ -383,7 +455,7 @@ do_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report) memcpy(iop->sensep, sc.sense, sc.senselen_used); iop->resp_sense_len = sc.senselen_used; } - if (report > 0) { + if (scsi_debugmode) { int trunc; pout(" status=0\n"); @@ -395,43 +467,417 @@ do_scsi_cmnd_io(int fd, struct scsi_cmnd_io * iop, int report) } switch (sc.retsts) { case SCCMD_OK: - return 0; + break; case SCCMD_TIMEOUT: - return -ETIMEDOUT; + return set_err(ETIMEDOUT); case SCCMD_BUSY: - return -EBUSY; + return set_err(EBUSY); default: - return -EIO; + return set_err(EIO); } + + return true; +} + +///////////////////////////////////////////////////////////////////////////// +///// SCSI open with autodetection support + +smart_device * netbsd_scsi_device::autodetect_open() +{ + // Open device + if (!open()) + return this; + + // No Autodetection if device type was specified by user + bool sat_only = false; + if (*get_req_type()) { + // Detect SAT if device object was created by scan_smart_devices(). + if (!(m_scanning && !strcmp(get_req_type(), "sat"))) + return this; + sat_only = true; + } + + // The code below is based on smartd.cpp:SCSIFilterKnown() + + // Get INQUIRY + unsigned char req_buff[64] = {0, }; + int req_len = 36; + if (scsiStdInquiry(this, req_buff, req_len)) { + // Marvell controllers fail on a 36 bytes StdInquiry, but 64 suffices + // watch this spot ... other devices could lock up here + req_len = 64; + if (scsiStdInquiry(this, req_buff, req_len)) { + // device doesn't like INQUIRY commands + close(); + set_err(EIO, "INQUIRY failed"); + return this; + } + } + + int avail_len = req_buff[4] + 5; + int len = (avail_len < req_len ? avail_len : req_len); + if (len < 36) { + if (sat_only) { + close(); + set_err(EIO, "INQUIRY too short for SAT"); + } + return this; + } + + // Use INQUIRY to detect type + + // SAT or USB, skip MFI controllers because of bugs + { + smart_device * newdev = smi()->autodetect_sat_device(this, req_buff, len); + if (newdev) { + // NOTE: 'this' is now owned by '*newdev' + return newdev; + } + } + + // Nothing special found + + if (sat_only) { + close(); + set_err(EIO, "Not a SAT device"); + } + return this; +} + +///////////////////////////////////////////////////////////////////////////// +/// Implement platform interface with old functions. + +class netbsd_smart_interface +: public /*implements*/ smart_interface +{ +public: + virtual std::string get_os_version_str(); + + virtual std::string get_app_examples(const char * appname); + + virtual bool scan_smart_devices(smart_device_list & devlist, const char * type, + const char * pattern = 0); + +protected: + virtual ata_device * get_ata_device(const char * name, const char * type); + + virtual scsi_device * get_scsi_device(const char * name, const char * type); + + virtual nvme_device * get_nvme_device(const char * name, const char * type, + unsigned nsid); + + virtual smart_device * autodetect_smart_device(const char * name); + + virtual smart_device * get_custom_smart_device(const char * name, const char * type); + + virtual std::string get_valid_custom_dev_types_str(); + +private: + int get_dev_names(char ***, const char *); + + bool get_nvme_devlist(smart_device_list & devlist, const char * type); +}; + + +////////////////////////////////////////////////////////////////////// + +std::string netbsd_smart_interface::get_os_version_str() +{ + struct utsname osname; + uname(&osname); + return strprintf("%s %s %s", osname.sysname, osname.release, osname.machine); } -/* print examples for smartctl */ -void -print_smartctl_examples() +std::string netbsd_smart_interface::get_app_examples(const char * appname) { - char p; + if (!strcmp(appname, "smartctl")) { + char p; - p = 'a' + getrawpartition(); - printf("=================================================== SMARTCTL EXAMPLES =====\n\n"); + p = 'a' + getrawpartition(); + return strprintf( + "=================================================== SMARTCTL EXAMPLES =====\n\n" #ifdef HAVE_GETOPT_LONG - printf( - " smartctl -a /dev/wd0%c (Prints all SMART information)\n\n" - " smartctl --smart=on --offlineauto=on --saveauto=on /dev/wd0%c\n" - " (Enables SMART on first disk)\n\n" - " smartctl -t long /dev/wd0%c (Executes extended disk self-test)\n\n" - " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/wd0%c\n" - " (Prints Self-Test & Attribute errors)\n", - p, p, p, p - ); + " smartctl -a /dev/wd0%c (Prints all SMART information)\n\n" + " smartctl --smart=on --offlineauto=on --saveauto=on /dev/wd0%c\n" + " (Enables SMART on first disk)\n\n" + " smartctl -t long /dev/wd0%c (Executes extended disk self-test)\n\n" + " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/wd0%c\n" + " (Prints Self-Test & Attribute errors)\n" #else - printf( - " smartctl -a /dev/wd0%c (Prints all SMART information)\n" - " smartctl -s on -o on -S on /dev/wd0%c (Enables SMART on first disk)\n" - " smartctl -t long /dev/wd0%c (Executes extended disk self-test)\n" - " smartctl -A -l selftest -q errorsonly /dev/wd0%c" - " (Prints Self-Test & Attribute errors)\n", - p, p, p, p - ); + " smartctl -a /dev/wd0%c (Prints all SMART information)\n" + " smartctl -s on -o on -S on /dev/wd0%c (Enables SMART on first disk)\n" + " smartctl -t long /dev/wd0%c (Executes extended disk self-test)\n" + " smartctl -A -l selftest -q errorsonly /dev/wd0%c" + " (Prints Self-Test & Attribute errors)\n" #endif - return; + , p, p, p, p); + } + return ""; +} + +ata_device * netbsd_smart_interface::get_ata_device(const char * name, const char * type) +{ + return new netbsd_ata_device(this, name, type); +} + +scsi_device * netbsd_smart_interface::get_scsi_device(const char * name, const char * type) +{ + return new netbsd_scsi_device(this, name, type); +} + +nvme_device * netbsd_smart_interface::get_nvme_device(const char * name, const char * type, unsigned nsid) +{ + return new netbsd_nvme_device(this, name, type, nsid); +} + +int netbsd_smart_interface::get_dev_names(char ***names, const char *prefix) +{ + char *disknames, *p, **mp; + int n = 0; + int sysctl_mib[2]; + size_t sysctl_len; + + *names = NULL; + + sysctl_mib[0] = CTL_HW; + sysctl_mib[1] = HW_DISKNAMES; + if (-1 == sysctl(sysctl_mib, 2, NULL, &sysctl_len, NULL, 0)) { + pout("Failed to get value of sysctl `hw.disknames'\n"); + return -1; + } + if (!(disknames = (char *)malloc(sysctl_len))) { + pout("Out of memory constructing scan device list\n"); + return -1; + } + if (-1 == sysctl(sysctl_mib, 2, disknames, &sysctl_len, NULL, 0)) { + pout("Failed to get value of sysctl `hw.disknames'\n"); + return -1; + } + if (!(mp = (char **) calloc(strlen(disknames) / 2, sizeof(char *)))) { + pout("Out of memory constructing scan device list\n"); + return -1; + } + for (p = strtok(disknames, " "); p; p = strtok(NULL, " ")) { + if (strncmp(p, prefix, strlen(prefix))) { + continue; + } + mp[n] = (char *)malloc(strlen(net_dev_raw_prefix) + strlen(p) + 2); + if (!mp[n]) { + pout("Out of memory constructing scan device list\n"); + return -1; + } + sprintf(mp[n], "%s%s%c", net_dev_raw_prefix, p, 'a' + getrawpartition()); + n++; + } + + char ** tmp = (char **)realloc(mp, n * (sizeof(char *))); + if (NULL == tmp) { + pout("Out of memory constructing scan device list\n"); + free(mp); + return -1; + } + else + mp = tmp; + *names = mp; + return n; +} + +bool netbsd_smart_interface::get_nvme_devlist(smart_device_list & devlist, + const char * type) +{ + char ctrlpath[64], nspath[64]; + struct stat sb; + struct devlistargs laa; + nvme_device * nvmedev; + + int drvfd = ::open(DRVCTLDEV, O_RDONLY, 0); + if (drvfd < 0) { + set_err(errno); + return false; + } + + for (int ctrl = 0;; ctrl++) { + snprintf(ctrlpath, sizeof(ctrlpath), NVME_PREFIX"%d", ctrl); + if (stat(ctrlpath, &sb) == -1 || !S_ISCHR(sb.st_mode)) + break; + + snprintf(laa.l_devname, sizeof(laa.l_devname), "%s%d", net_dev_nvme_ctrl, + ctrl); + laa.l_childname = NULL; + laa.l_children = 0; + if (ioctl(drvfd, DRVLISTDEV, &laa) == -1) { + if (errno == ENXIO) + continue; + break; + } + + nvmedev = get_nvme_device(ctrlpath, type, 0); + if (nvmedev) + devlist.push_back(nvmedev); + + uint32_t n = 0; + for (int nsid = 1; n < laa.l_children; nsid++) { + snprintf(nspath, sizeof(nspath), NVME_PREFIX"%d"NVME_NS_PREFIX"%d", + ctrl, nsid); + if (stat(nspath, &sb) == -1 || !S_ISCHR(sb.st_mode)) + break; + int nsfd = ::open(nspath, O_RDONLY, 0); + if (nsfd < 0) + continue; + ::close(nsfd); + + n++; + nvmedev = get_nvme_device(nspath, type, nsid); + if (nvmedev) + devlist.push_back(nvmedev); + } + } + + ::close(drvfd); + return true; } + +bool netbsd_smart_interface::scan_smart_devices(smart_device_list & devlist, + const char * type, const char * pattern /*= 0*/) +{ + if (pattern) { + set_err(EINVAL, "DEVICESCAN with pattern not implemented yet"); + return false; + } + + if (type == NULL) + type = ""; + + bool scan_ata = !*type || !strcmp(type, "ata"); + bool scan_scsi = !*type || !strcmp(type, "scsi") || !strcmp(type, "sat"); + +#ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL + bool scan_nvme = !*type || !strcmp(type, "nvme"); +#else + bool scan_nvme = !strcmp(type, "nvme"); +#endif + + // Make namelists + char * * atanames = 0; int numata = 0; + if (scan_ata) { + numata = get_dev_names(&atanames, net_dev_ata_disk); + if (numata < 0) { + set_err(ENOMEM); + return false; + } + } + + char * * scsinames = 0; int numscsi = 0; + char * * scsitapenames = 0; int numscsitape = 0; + if (scan_scsi) { + numscsi = get_dev_names(&scsinames, net_dev_scsi_disk); + if (numscsi < 0) { + set_err(ENOMEM); + return false; + } + numscsitape = get_dev_names(&scsitapenames, net_dev_scsi_tape); + if (numscsitape < 0) { + set_err(ENOMEM); + return false; + } + } + + // Add to devlist + int i; + for (i = 0; i < numata; i++) { + ata_device * atadev = get_ata_device(atanames[i], type); + if (atadev) + devlist.push_back(atadev); + free(atanames[i]); + } + if(numata) free(atanames); + + for (i = 0; i < numscsi; i++) { + scsi_device * scsidev = new netbsd_scsi_device(this, scsinames[i], type, true /*scanning*/); + if (scsidev) + devlist.push_back(scsidev); + free(scsinames[i]); + } + if(numscsi) free(scsinames); + + for (i = 0; i < numscsitape; i++) { + scsi_device * scsidev = get_scsi_device(scsitapenames[i], type); + if (scsidev) + devlist.push_back(scsidev); + free(scsitapenames[i]); + } + if(numscsitape) free(scsitapenames); + + if (scan_nvme) + get_nvme_devlist(devlist, type); + + return true; +} + +smart_device * netbsd_smart_interface::autodetect_smart_device(const char * name) +{ + const char * test_name = name; + + // if dev_name null, or string length zero + if (!name || !*name) + return 0; + + // Dereference symlinks + struct stat st; + std::string pathbuf; + if (!lstat(name, &st) && S_ISLNK(st.st_mode)) { + char * p = realpath(name, (char *)0); + if (p) { + pathbuf = p; + free(p); + test_name = pathbuf.c_str(); + } + } + + if (str_starts_with(test_name, net_dev_raw_prefix)) { + test_name += strlen(net_dev_raw_prefix); + if (!strncmp(net_dev_ata_disk, test_name, strlen(net_dev_ata_disk))) + return get_ata_device(test_name, "ata"); + if (!strncmp(net_dev_scsi_disk, test_name, strlen(net_dev_scsi_disk))) { + // XXX Try to detect possible USB->(S)ATA bridge + // XXX get USB vendor ID, product ID and version from sd(4)/umass(4). + // XXX check sat device via get_usb_dev_type_by_id(). + + // No USB bridge found, assume regular SCSI device + return get_scsi_device(test_name, "scsi"); + } + if (!strncmp(net_dev_scsi_tape, test_name, strlen(net_dev_scsi_tape))) + return get_scsi_device(test_name, "scsi"); + } else if (str_starts_with(test_name, net_dev_prefix)) { + if (!strncmp(NVME_PREFIX, test_name, strlen(NVME_PREFIX))) + return get_nvme_device(test_name, "nvme", 0 /* use default nsid */); + } + + // device type unknown + return 0; +} + +smart_device * netbsd_smart_interface::get_custom_smart_device(const char * name, const char * type) +{ + ARGUSED(name); + ARGUSED(type); + return 0; +} + +std::string netbsd_smart_interface::get_valid_custom_dev_types_str() +{ + return ""; +} + +} // namespace + +///////////////////////////////////////////////////////////////////////////// +/// Initialize platform interface and register with smi() + +void smart_interface::init() +{ + static os_netbsd::netbsd_smart_interface the_interface; + smart_interface::set(&the_interface); +} + +/* vim: set ts=2 sw=2 et ff=unix : */