#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
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;
/////////////////////////////////////////////////////////////////////////////
int get_fd() const
{ return m_fd; }
-
private:
int m_fd; ///< filedesc, -1 if not open.
const char * m_mode; ///< Mode string for deviceopen().
}
// 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);
// 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),
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;
// 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;
{
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;
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");
}
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);
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);
// 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);
}
*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);
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()
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, "");
}
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;
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;
}