]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - os_netbsd.cpp
New upstream version 6.6
[mirror_smartmontools-debian.git] / os_netbsd.cpp
index ebcd013ee2739b882489aa0088ff64180c86031f..9b818970335c70c635f732123bb8f416b881c6c9 100644 (file)
@@ -3,7 +3,8 @@
  *
  * Home page of code is: http://www.smartmontools.org
  *
- * Copyright (C) 2003-8 Sergey Svishchev <smartmontools-support@lists.sourceforge.net>
+ * 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
 #include "utility.h"
 #include "os_netbsd.h"
 
+#include <sys/drvctlio.h>
+#include <sys/utsname.h>
 #include <errno.h>
 #include <unistd.h>
 
-const char * os_netbsd_cpp_cvsid = "$Id: os_netbsd.cpp 4320 2016-05-10 13:39:19Z chrfranke $"
+// based on "sys/dev/ic/nvmeio.h" from NetBSD kernel sources
+#include "netbsd_nvme_ioctl.h" // NVME_PASSTHROUGH_CMD, nvme_completion_is_error
+
+const char * os_netbsd_cpp_cvsid = "$Id: os_netbsd.cpp 4431 2017-08-08 19:38:15Z chrfranke $"
   OS_NETBSD_H_CVSID;
 
 enum warnings {
@@ -48,323 +54,384 @@ printwarning(int msgNo, const char *extra)
       printed[msgNo] = 1;
       pout("%s", message[msgNo]);
       if (extra)
-       pout("%s", extra);
+        pout("%s", extra);
     }
   }
   return;
 }
 
-static const char *net_dev_prefix = "/dev/r";
+#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";
+
+/////////////////////////////////////////////////////////////////////////////
+/// Implement shared open/close routines with old functions.
 
-/* Guess device type (ATA or SCSI) based on device name */
-int
-guess_device_type(const char *dev_name)
+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();
+
+protected:
+  /// Return filedesc for derived classes.
+  int get_fd() const
+    { return m_fd; }
 
-  if (!strncmp(net_dev_scsi_disk, dev_name, strlen(net_dev_scsi_disk)))
-    return CONTROLLER_SCSI;
+  void set_fd(int fd)
+    { m_fd = fd; }
 
-  if (!strncmp(net_dev_scsi_tape, dev_name, strlen(net_dev_scsi_tape)))
-    return CONTROLLER_SCSI;
+private:
+  int m_fd; ///< filedesc, -1 if not open.
+};
 
-  return CONTROLLER_UNKNOWN;
+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());
-    n++;
-  }
+  } else
+    return false;
 
-  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;
+  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;
   }
 
-  retval = ioctl(fd, ATAIOCCOMMAND, &req);
-  if (retval < 0) {
-    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);
+
+  virtual smart_device * autodetect_open();
+
+  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)
+{
+}
 
-  if (report > 0) {
-    size_t k;
+bool netbsd_scsi_device::scsi_pass_through(scsi_cmnd_io * iop)
+{
+  struct scsireq sc;
+  int fd = get_fd();
 
-    const unsigned char *ucp = iop->cmnd;
-    const char *np;
+  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 : "<unknown opcode>");
     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;
@@ -377,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;
@@ -386,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");
@@ -398,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;
 }
 
-/* print examples for smartctl */
-void 
-print_smartctl_examples()
+/////////////////////////////////////////////////////////////////////////////
+///// SCSI open with autodetection support
+
+smart_device * netbsd_scsi_device::autodetect_open()
 {
-  char p;
+  // 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;
+  }
 
-  p = 'a' + getrawpartition();
-  printf("=================================================== SMARTCTL EXAMPLES =====\n\n");
+  // 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);
+}
+
+std::string netbsd_smart_interface::get_app_examples(const char * appname)
+{
+  if (!strcmp(appname, "smartctl")) {
+    char p;
+
+    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 : */