]> git.proxmox.com Git - mirror_smartmontools-debian.git/blobdiff - os_darwin.cpp
New upstream version 6.6
[mirror_smartmontools-debian.git] / os_darwin.cpp
index 53a0f4609c60c5dfdc01ac8d92317e8c1acd22fe..134f510efa9467ccad01d8ddbe36a2430a58d998 100644 (file)
 #include <IOKit/storage/ata/ATASMARTLib.h>
 #include <CoreFoundation/CoreFoundation.h>
 
-  // No, I don't know why there isn't a header for this.
-#define kIOATABlockStorageDeviceClass   "IOATABlockStorageDevice"
-
 #include "config.h"
 #include "int64.h"
 #include "atacmds.h"
 #include "scsicmds.h"
+#include "nvmecmds.h"
 #include "utility.h"
 #include "os_darwin.h"
 #include "dev_interface.h"
 
+#define ARGUSED(x) ((void)(x))
 // Needed by '-V' option (CVS versioning) of smartd/smartctl
-const char *os_darwin_cpp_cvsid="$Id: os_darwin.cpp 4214 2016-01-24 22:53:37Z samm2 $" \
+const char *os_darwin_cpp_cvsid="$Id: os_darwin.cpp 4552 2017-10-11 10:11:35Z samm2 $" \
 ATACMDS_H_CVSID CONFIG_H_CVSID INT64_H_CVSID OS_DARWIN_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID;
 
 // examples for smartctl
@@ -75,10 +74,11 @@ static const char  smartctl_examples[] =
 static struct {
   io_object_t ioob;
   IOCFPlugInInterface **plugin;
-  IOATASMARTInterface **smartIf;
+  IOATASMARTInterface **smartIf; // ATA devices
+  IONVMeSMARTInterface **smartIfNVMe;
 } devices[20];
 
-const char * dev_darwin_cpp_cvsid = "$Id: os_darwin.cpp 4214 2016-01-24 22:53:37Z samm2 $"
+const char * dev_darwin_cpp_cvsid = "$Id: os_darwin.cpp 4552 2017-10-11 10:11:35Z samm2 $"
   DEV_INTERFACE_H_CVSID;
 
 /////////////////////////////////////////////////////////////////////////////
@@ -109,7 +109,6 @@ protected:
   int get_fd() const
     { return m_fd; }
     
-
 private:
   int m_fd; ///< filedesc, -1 if not open.
   const char * m_mode; ///< Mode string for deviceopen().
@@ -128,15 +127,24 @@ bool darwin_smart_device::is_open() const
 }
 
 // Determine whether 'dev' is a SMART-capable device.
-static bool is_smart_capable (io_object_t dev) {
-  CFTypeRef smartCapableKey;
+static bool is_smart_capable (io_object_t dev, const char * type) {
+  CFTypeRef smartCapableKey = NULL;
   CFDictionaryRef diskChars;
 
   // If the device has kIOPropertySMARTCapableKey, then it's capable,
   // no matter what it looks like.
+  if (!strcmp("ATA", type))  {
   smartCapableKey = IORegistryEntryCreateCFProperty
     (dev, CFSTR (kIOPropertySMARTCapableKey),
      kCFAllocatorDefault, 0);
+  }
+
+  else if (!strcmp("NVME", type)) {
+    smartCapableKey = IORegistryEntryCreateCFProperty
+      (dev, CFSTR (kIOPropertyNVMeSMARTCapableKey),
+       kCFAllocatorDefault, 0);
+  }
+
   if (smartCapableKey)
     {
       CFRelease (smartCapableKey);
@@ -145,6 +153,7 @@ static bool is_smart_capable (io_object_t dev) {
 
   // If it's an kIOATABlockStorageDeviceClass then we're successful
   // only if its ATA features indicate it supports SMART.
+  // This will be broken for NVMe, however it is not needed
   if (IOObjectConformsTo (dev, kIOATABlockStorageDeviceClass)
     && (diskChars = (CFDictionaryRef)IORegistryEntryCreateCFProperty                                                                                                           
       (dev, CFSTR (kIOPropertyDeviceCharacteristicsKey),
@@ -180,7 +189,7 @@ bool darwin_smart_device::open()
   const char *pathname = get_dev_name();
   char *type = const_cast<char*>(m_mode);
   
-  if (strcmp (type, "ATA") != 0)
+  if (!(strcmp("ATA", type) || strcmp("NVME", type)))
     {
       set_err (EINVAL);
       return false;
@@ -205,7 +214,7 @@ bool darwin_smart_device::open()
     // allow user to just say 'disk0'
     devname = pathname;
 
-  // Find the device.
+  // Find the device. This part should be the same for the NVMe and ATA
   if (devname)
     {
       CFMutableDictionaryRef matcher;
@@ -216,15 +225,13 @@ bool darwin_smart_device::open()
     {
       disk = IORegistryEntryFromPath (kIOMasterPortDefault, pathname);
     }
-
   if (! disk)
     {
       set_err(ENOENT);
       return false;
     }
-  
   // Find a SMART-capable driver which is a parent of this device.
-  while (! is_smart_capable (disk))
+  while (! is_smart_capable (disk, type))
     {
       IOReturn err;
       io_object_t prevdisk = disk;
@@ -246,17 +253,35 @@ bool darwin_smart_device::open()
   
     devices[devnum].plugin = NULL;
     devices[devnum].smartIf = NULL;
+    devices[devnum].smartIfNVMe = NULL;
+
+    CFUUIDRef pluginType = NULL;
+    CFUUIDRef smartInterfaceId = NULL;
+    void ** SMARTptr = NULL;
+
+    if (!strcmp("ATA", type))  {
+      pluginType = kIOATASMARTUserClientTypeID;
+      smartInterfaceId = kIOATASMARTInterfaceID;
+      SMARTptr = (void **)&devices[devnum].smartIf;
+    }
+    else if (!strcmp("NVME", type)) {
+      pluginType = kIONVMeSMARTUserClientTypeID;
+      smartInterfaceId = kIONVMeSMARTInterfaceID;
+      SMARTptr = (void **)&devices[devnum].smartIfNVMe;
+    }
 
     // Create an interface to the ATA SMART library.
     if (IOCreatePlugInInterfaceForService (disk,
-      kIOATASMARTUserClientTypeID,
+      pluginType,
       kIOCFPlugInInterfaceID,
       &devices[devnum].plugin,
       &dummy) == kIOReturnSuccess)
     (*devices[devnum].plugin)->QueryInterface
     (devices[devnum].plugin,
-      CFUUIDGetUUIDBytes ( kIOATASMARTInterfaceID),
-      (void **)&devices[devnum].smartIf);
+      CFUUIDGetUUIDBytes ( smartInterfaceId),
+      SMARTptr);
+    else
+      return set_err(ENOSYS, "IOCreatePlugInInterfaceForService failed");
   }
 
 
@@ -273,6 +298,8 @@ bool darwin_smart_device::close()
   int fd = m_fd; m_fd = -1;
   if (devices[fd].smartIf)
     (*devices[fd].smartIf)->Release (devices[fd].smartIf);
+  if (devices[fd].smartIfNVMe)
+    (*devices[fd].smartIfNVMe)->Release (devices[fd].smartIfNVMe);
   if (devices[fd].plugin)
     IODestroyPlugInInterface (devices[fd].plugin);
   IOObjectRelease (devices[fd].ioob);
@@ -293,9 +320,9 @@ static int make_device_names (char*** devlist, const char* name) {
   int result;
   int index;
 
-  // We treat all devices as ATA so long as they support SMARTLib.
-  if (strcmp (name, "ATA") != 0)
+  if (!(strcmp("ATA", name) || strcmp("NVME", name))) {
     return 0;
+  }
 
   err = IOServiceGetMatchingServices 
     (kIOMasterPortDefault, IOServiceMatching (kIOBlockStorageDeviceClass), &i);
@@ -305,7 +332,7 @@ static int make_device_names (char*** devlist, const char* name) {
   // Count the devices.
   result = 0;
   while ((device = IOIteratorNext (i)) != MACH_PORT_NULL) {
-    if (is_smart_capable (device))
+    if (is_smart_capable (device, name))
       result++;
     IOObjectRelease (device);
   }
@@ -317,7 +344,7 @@ static int make_device_names (char*** devlist, const char* name) {
   *devlist = (char**)calloc (result, sizeof (char *)); 
   index = 0;
   while ((device = IOIteratorNext (i)) != MACH_PORT_NULL) {
-    if (is_smart_capable (device))
+    if (is_smart_capable (device, name))
     {
       io_string_t devName;
       IORegistryEntryGetPath(device, kIOServicePlane, devName);
@@ -504,11 +531,64 @@ protected:
   
   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);
 
 };
 
+/////////////////////////////////////////////////////////////////////////////
+/// NVMe support
+
+class darwin_nvme_device
+: public /*implements*/ nvme_device,
+  public /*extends*/ darwin_smart_device
+{
+public:
+  darwin_nvme_device(smart_interface * intf, const char * dev_name,
+    const char * req_type, unsigned nsid);
 
+  virtual bool nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out);
+};
+
+darwin_nvme_device::darwin_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),
+  darwin_smart_device("NVME")
+{
+}
+
+bool darwin_nvme_device::nvme_pass_through(const nvme_cmd_in & in, nvme_cmd_out & out)
+{
+  ARGUSED(out);
+  int fd = get_fd();
+  IONVMeSMARTInterface **ifp = devices[fd].smartIfNVMe;
+  IONVMeSMARTInterface *smartIfNVMe ;
+  IOReturn err = 0;
+  unsigned int page = in.cdw10 & 0xff;
+
+  if (! ifp)
+    return -1;
+  smartIfNVMe = *ifp;
+  // currently only GetIdentifyData and SMARTReadData are supported
+  switch (in.opcode) {
+    case smartmontools::nvme_admin_identify:
+      err = smartIfNVMe->GetIdentifyData(ifp, (struct nvme_id_ctrl *) in.buffer, in.nsid); // FIXME
+      break;
+    case smartmontools::nvme_admin_get_log_page:
+       if(page == 0x02)
+         err = smartIfNVMe->SMARTReadData(ifp, (struct nvme_smart_log *) in.buffer);
+       else /* GetLogPage() is not working yet */
+         return set_err(ENOSYS, "NVMe admin command:0x%02x/page:0x%02x is not supported",
+          in.opcode, page);
+      break;
+    default:
+      return set_err(ENOSYS, "NVMe admin command 0x%02x is not supported", in.opcode);
+  }
+  return true;
+}
 //////////////////////////////////////////////////////////////////////
 
 std::string darwin_smart_interface::get_os_version_str()
@@ -537,9 +617,78 @@ scsi_device * darwin_smart_interface::get_scsi_device(const char *, const char *
   return 0; // scsi devices are not supported [yet]
 }
 
+nvme_device * darwin_smart_interface::get_nvme_device(const char * name, const char * type,
+  unsigned nsid)
+{
+  return new darwin_nvme_device(this, name, type, nsid);
+}
 
 smart_device * darwin_smart_interface::autodetect_smart_device(const char * name)
-{
+{ // TODO - refactor as a function
+  // Acceptable device names are:
+  // /dev/disk*
+  // /dev/rdisk*
+  // disk*
+  // IOService:*
+  // IODeviceTree:*
+  const char *devname = NULL;
+  io_object_t disk;
+  
+  if (strncmp (name, "/dev/rdisk", 10) == 0)
+    devname = name + 6;
+  else if (strncmp (name, "/dev/disk", 9) == 0)
+    devname = name + 5;
+  else if (strncmp (name, "disk", 4) == 0)
+    // allow user to just say 'disk0'
+    devname = name;
+  // Find the device. This part should be the same for the NVMe and ATA
+  if (devname) {
+      CFMutableDictionaryRef matcher;
+      matcher = IOBSDNameMatching (kIOMasterPortDefault, 0, devname);
+      disk = IOServiceGetMatchingService (kIOMasterPortDefault, matcher);
+  }
+  else {
+      disk = IORegistryEntryFromPath (kIOMasterPortDefault, name);
+  }
+  if (! disk) {
+      return 0;
+  }
+  io_registry_entry_t tmpdisk=disk;
+  
+  
+  while (! is_smart_capable (tmpdisk, "ATA"))
+    {
+      IOReturn err;
+      io_object_t prevdisk = tmpdisk;
+
+      // Find this device's parent and try again.
+      err = IORegistryEntryGetParentEntry (tmpdisk, kIOServicePlane, &tmpdisk);
+      if (err != kIOReturnSuccess || ! tmpdisk)
+      {
+        IOObjectRelease (prevdisk);
+        break;
+      }
+    }
+    if (tmpdisk)
+      return new darwin_ata_device(this, name, "");
+    tmpdisk=disk;
+    while (! is_smart_capable (tmpdisk, "NVME"))
+      {
+        IOReturn err;
+        io_object_t prevdisk = tmpdisk;
+
+        // Find this device's parent and try again.
+        err = IORegistryEntryGetParentEntry (tmpdisk, kIOServicePlane, &tmpdisk);
+        if (err != kIOReturnSuccess || ! tmpdisk)
+        {
+          IOObjectRelease (prevdisk);
+          break;
+        }
+      }  
+    if (tmpdisk)
+      return new darwin_nvme_device(this, name, "", 0);
+
+  // try ATA as a last option, for compatibility
   return new darwin_ata_device(this, name, "");
 }
 
@@ -567,6 +716,20 @@ bool darwin_smart_interface::scan_smart_devices(smart_device_list & devlist,
       return false;
     }
   }
+  char * * nvmenames = 0; int numnvme = 0;
+  if (
+#ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL
+      !type ||
+#else
+      type &&
+#endif
+               !strcmp(type, "nvme")) {
+    numnvme = make_device_names(&nvmenames, "NVME");
+    if (numnvme < 0) {
+      set_err(ENOMEM);
+      return false;
+    }
+  }
 
   // Add to devlist
   int i;
@@ -578,6 +741,14 @@ bool darwin_smart_interface::scan_smart_devices(smart_device_list & devlist,
       devlist.push_back(atadev);
   }
   free_devnames(atanames, numata);
+
+  for (i = 0; i < numnvme; i++) {
+    nvme_device * nvmedev = get_nvme_device(nvmenames[i], type, 0); // default nsid
+    if (nvmedev)
+      devlist.push_back(nvmedev);
+  }  
+  free_devnames(nvmenames, numnvme);
+
   return true;
 }