4 * Home page of code is: http://www.smartmontools.org
6 * Copyright (C) 2004-8 Geoffrey Keating <geoffk@geoffk.org>
7 * Copyright (C) 2014 Alex Samorukov <samm@os2.kiev.ua>
9 * SPDX-License-Identifier: GPL-2.0-or-later
15 #include <mach/mach.h>
16 #include <mach/mach_error.h>
17 #include <mach/mach_init.h>
18 #include <sys/utsname.h>
19 #include <IOKit/IOCFPlugIn.h>
20 #include <IOKit/IOKitLib.h>
21 #include <IOKit/IOReturn.h>
22 #include <IOKit/IOBSD.h>
23 #include <IOKit/storage/IOBlockStorageDevice.h>
24 #include <IOKit/storage/IOStorageDeviceCharacteristics.h>
25 #include <IOKit/storage/IOMedia.h>
26 #include <IOKit/storage/ata/IOATAStorageDefines.h>
27 #include <IOKit/storage/ata/ATASMARTLib.h>
28 #include <CoreFoundation/CoreFoundation.h>
36 #include "os_darwin.h"
37 #include "dev_interface.h"
39 #define ARGUSED(x) ((void)(x))
40 // Needed by '-V' option (CVS versioning) of smartd/smartctl
41 const char *os_darwin_cpp_cvsid
="$Id: os_darwin.cpp 4831 2018-11-09 07:18:23Z samm2 $" \
42 ATACMDS_H_CVSID CONFIG_H_CVSID OS_DARWIN_H_CVSID SCSICMDS_H_CVSID UTILITY_H_CVSID
;
44 // examples for smartctl
45 static const char smartctl_examples
[] =
46 "=================================================== SMARTCTL EXAMPLES =====\n\n"
47 " smartctl -a disk0 (Prints all SMART information)\n\n"
48 " smartctl -t long /dev/disk0 (Executes extended disk self-test)\n\n"
49 " smartctl --smart=on --saveauto=on /dev/rdisk0 (Enables SMART on first disk)\n\n"
50 " smartctl --attributes --log=selftest --quietmode=errorsonly /dev/disk0\n"
51 " (Prints Self-Test & Attribute errors)\n\n"
52 " smartctl -a IOService:/MacRISC2PE/pci@f4000000/AppleMacRiscPCI/ata-6@D/AppleKauaiATA/ATADeviceNub@0/IOATABlockStorageDriver/IOATABlockStorageDevice\n"
53 " (You can use IOService: ...)\n\n"
54 " smartctl -c IODeviceTree:/pci@f4000000/ata-6@D/@0:0\n"
55 " (... Or IODeviceTree:)\n"
59 // Information that we keep about each device.
63 IOCFPlugInInterface
**plugin
;
64 IOATASMARTInterface
**smartIf
; // ATA devices
65 IONVMeSMARTInterface
**smartIfNVMe
;
68 const char * dev_darwin_cpp_cvsid
= "$Id: os_darwin.cpp 4831 2018-11-09 07:18:23Z samm2 $"
69 DEV_INTERFACE_H_CVSID
;
71 /////////////////////////////////////////////////////////////////////////////
73 namespace os
{ // No need to publish anything, name provided for Doxygen
75 /////////////////////////////////////////////////////////////////////////////
76 /// Implement shared open/close routines with old functions.
78 class darwin_smart_device
79 : virtual public /*implements*/ smart_device
82 explicit darwin_smart_device(const char * mode
)
83 : smart_device(never_called
),
84 m_fd(-1), m_mode(mode
) { }
86 virtual ~darwin_smart_device() throw();
88 virtual bool is_open() const;
95 /// Return filedesc for derived classes.
100 int m_fd
; ///< filedesc, -1 if not open.
101 const char * m_mode
; ///< Mode string for deviceopen().
105 darwin_smart_device::~darwin_smart_device() throw()
108 darwin_smart_device::close();
111 bool darwin_smart_device::is_open() const
116 // Determine whether 'dev' is a SMART-capable device.
117 static bool is_smart_capable (io_object_t dev
, const char * type
) {
118 CFTypeRef smartCapableKey
= NULL
;
119 CFDictionaryRef diskChars
;
121 // If the device has kIOPropertySMARTCapableKey, then it's capable,
122 // no matter what it looks like.
123 if (!strcmp("ATA", type
)) {
124 smartCapableKey
= IORegistryEntryCreateCFProperty
125 (dev
, CFSTR (kIOPropertySMARTCapableKey
),
126 kCFAllocatorDefault
, 0);
129 else if (!strcmp("NVME", type
)) {
130 smartCapableKey
= IORegistryEntryCreateCFProperty
131 (dev
, CFSTR (kIOPropertyNVMeSMARTCapableKey
),
132 kCFAllocatorDefault
, 0);
137 CFRelease (smartCapableKey
);
141 // If it's an kIOATABlockStorageDeviceClass then we're successful
142 // only if its ATA features indicate it supports SMART.
143 // This will be broken for NVMe, however it is not needed
144 if (IOObjectConformsTo (dev
, kIOATABlockStorageDeviceClass
)
145 && (diskChars
= (CFDictionaryRef
)IORegistryEntryCreateCFProperty
146 (dev
, CFSTR (kIOPropertyDeviceCharacteristicsKey
),
147 kCFAllocatorDefault
, kNilOptions
)) != NULL
)
149 CFNumberRef diskFeatures
= NULL
;
150 UInt32 ataFeatures
= 0;
152 if (CFDictionaryGetValueIfPresent (diskChars
, CFSTR ("ATA Features"),
153 (const void **)&diskFeatures
))
154 CFNumberGetValue (diskFeatures
, kCFNumberLongType
,
156 CFRelease (diskChars
);
158 CFRelease (diskFeatures
);
160 return (ataFeatures
& kIOATAFeatureSMART
) != 0;
165 bool darwin_smart_device::open()
167 // Acceptable device names are:
176 const char *pathname
= get_dev_name();
177 char *type
= const_cast<char*>(m_mode
);
179 if (!(strcmp("ATA", type
) || strcmp("NVME", type
)))
185 // Find a free device number.
186 for (devnum
= 0; devnum
< sizeof (devices
) / sizeof (devices
[0]); devnum
++)
187 if (! devices
[devnum
].ioob
)
189 if (devnum
== sizeof (devices
) / sizeof (devices
[0]))
196 if (strncmp (pathname
, "/dev/rdisk", 10) == 0)
197 devname
= pathname
+ 6;
198 else if (strncmp (pathname
, "/dev/disk", 9) == 0)
199 devname
= pathname
+ 5;
200 else if (strncmp (pathname
, "disk", 4) == 0)
201 // allow user to just say 'disk0'
204 // Find the device. This part should be the same for the NVMe and ATA
207 CFMutableDictionaryRef matcher
;
208 matcher
= IOBSDNameMatching (kIOMasterPortDefault
, 0, devname
);
209 disk
= IOServiceGetMatchingService (kIOMasterPortDefault
, matcher
);
213 disk
= IORegistryEntryFromPath (kIOMasterPortDefault
, pathname
);
220 // Find a SMART-capable driver which is a parent of this device.
221 while (! is_smart_capable (disk
, type
))
224 io_object_t prevdisk
= disk
;
226 // Find this device's parent and try again.
227 err
= IORegistryEntryGetParentEntry (disk
, kIOServicePlane
, &disk
);
228 if (err
!= kIOReturnSuccess
|| ! disk
)
231 IOObjectRelease (prevdisk
);
236 devices
[devnum
].ioob
= disk
;
241 devices
[devnum
].plugin
= NULL
;
242 devices
[devnum
].smartIf
= NULL
;
243 devices
[devnum
].smartIfNVMe
= NULL
;
245 CFUUIDRef pluginType
= NULL
;
246 CFUUIDRef smartInterfaceId
= NULL
;
247 void ** SMARTptr
= NULL
;
249 if (!strcmp("ATA", type
)) {
250 pluginType
= kIOATASMARTUserClientTypeID
;
251 smartInterfaceId
= kIOATASMARTInterfaceID
;
252 SMARTptr
= (void **)&devices
[devnum
].smartIf
;
254 else if (!strcmp("NVME", type
)) {
255 pluginType
= kIONVMeSMARTUserClientTypeID
;
256 smartInterfaceId
= kIONVMeSMARTInterfaceID
;
257 SMARTptr
= (void **)&devices
[devnum
].smartIfNVMe
;
260 // Create an interface to the ATA SMART library.
261 if (IOCreatePlugInInterfaceForService (disk
,
263 kIOCFPlugInInterfaceID
,
264 &devices
[devnum
].plugin
,
265 &dummy
) == kIOReturnSuccess
)
266 (*devices
[devnum
].plugin
)->QueryInterface
267 (devices
[devnum
].plugin
,
268 CFUUIDGetUUIDBytes ( smartInterfaceId
),
271 return set_err(ENOSYS
, "IOCreatePlugInInterfaceForService failed");
277 set_err((errno
==ENOENT
|| errno
==ENOTDIR
) ? ENODEV
: errno
);
283 bool darwin_smart_device::close()
285 int fd
= m_fd
; m_fd
= -1;
286 if (devices
[fd
].smartIf
)
287 (*devices
[fd
].smartIf
)->Release (devices
[fd
].smartIf
);
288 if (devices
[fd
].smartIfNVMe
)
289 (*devices
[fd
].smartIfNVMe
)->Release (devices
[fd
].smartIfNVMe
);
290 if (devices
[fd
].plugin
)
291 IODestroyPlugInInterface (devices
[fd
].plugin
);
292 IOObjectRelease (devices
[fd
].ioob
);
293 devices
[fd
].ioob
= MACH_PORT_NULL
;
297 // makes a list of ATA or SCSI devices for the DEVICESCAN directive of
298 // smartd. Returns number N of devices, or -1 if out of
299 // memory. Allocates N+1 arrays: one of N pointers (devlist); the
300 // other N arrays each contain null-terminated character strings. In
301 // the case N==0, no arrays are allocated because the array of 0
302 // pointers has zero length, equivalent to calling malloc(0).
303 static int make_device_names (char*** devlist
, const char* name
) {
306 io_object_t device
= MACH_PORT_NULL
;
310 if (!(strcmp("ATA", name
) || strcmp("NVME", name
))) {
314 err
= IOServiceGetMatchingServices
315 (kIOMasterPortDefault
, IOServiceMatching (kIOBlockStorageDeviceClass
), &i
);
316 if (err
!= kIOReturnSuccess
)
319 // Count the devices.
321 while ((device
= IOIteratorNext (i
)) != MACH_PORT_NULL
) {
322 if (is_smart_capable (device
, name
))
324 IOObjectRelease (device
);
327 // Create an array of service names.
331 *devlist
= (char**)calloc (result
, sizeof (char *));
333 while ((device
= IOIteratorNext (i
)) != MACH_PORT_NULL
) {
334 if (is_smart_capable (device
, name
))
337 IORegistryEntryGetPath(device
, kIOServicePlane
, devName
);
338 (*devlist
)[index
] = strdup (devName
);
339 if (! (*devlist
)[index
])
343 IOObjectRelease (device
);
350 if (device
!= MACH_PORT_NULL
)
351 IOObjectRelease (device
);
355 for (index
= 0; index
< result
; index
++)
356 if ((*devlist
)[index
])
357 free ((*devlist
)[index
]);
360 if(!result
) // no devs found
366 /////////////////////////////////////////////////////////////////////////////
367 /// Implement standard ATA support
369 class darwin_ata_device
370 : public /*implements*/ ata_device
,
371 public /*extends*/ darwin_smart_device
374 darwin_ata_device(smart_interface
* intf
, const char * dev_name
, const char * req_type
);
375 virtual bool ata_pass_through(const ata_cmd_in
& in
, ata_cmd_out
& out
);
378 // virtual int ata_command_interface(smart_command_set command, int select, char * data);
381 darwin_ata_device::darwin_ata_device(smart_interface
* intf
, const char * dev_name
, const char * req_type
)
382 : smart_device(intf
, dev_name
, "ata", req_type
),
383 darwin_smart_device("ATA")
387 bool darwin_ata_device::ata_pass_through(const ata_cmd_in
& in
, ata_cmd_out
& out
)
389 if (!ata_cmd_is_ok(in
,
390 true, // data_out_support
391 true, // multi_sector_support
392 false) // not supported by API
397 char * data
= (char *)in
.buffer
;
399 IOATASMARTInterface
**ifp
= devices
[fd
].smartIf
;
400 IOATASMARTInterface
*smartIf
;
402 int timeoutCount
= 5;
408 clear_err(); errno
= 0;
410 switch (in
.in_regs
.command
) {
411 case ATA_IDENTIFY_DEVICE
:
414 err
= smartIf
->GetATAIdentifyData (ifp
, data
, 512, &dummy
);
415 if (err
!= kIOReturnSuccess
&& err
!= kIOReturnTimeout
416 && err
!= kIOReturnNotResponding
)
417 printf ("identify failed: %#x\n", (unsigned) rc
);
418 if (err
== kIOReturnSuccess
&& isbigendian())
421 /* The system has already byte-swapped, undo it. */
422 for (i
= 0; i
< 256; i
+=2)
427 case ATA_IDENTIFY_PACKET_DEVICE
:
428 case ATA_CHECK_POWER_MODE
:
433 switch (in
.in_regs
.features
) {
434 case ATA_SMART_READ_VALUES
:
435 err
= smartIf
->SMARTReadData (ifp
, (ATASMARTData
*)data
);
437 case ATA_SMART_READ_THRESHOLDS
:
438 err
= smartIf
->SMARTReadDataThresholds (ifp
,
439 (ATASMARTDataThresholds
*)data
);
441 case ATA_SMART_READ_LOG_SECTOR
:
442 err
= smartIf
->SMARTReadLogAtAddress (ifp
, in
.in_regs
.lba_low
, data
, 512 * in
.in_regs
.sector_count
);
444 case ATA_SMART_WRITE_LOG_SECTOR
:
445 err
= smartIf
->SMARTWriteLogAtAddress (ifp
, in
.in_regs
.lba_low
, data
, 512 * in
.in_regs
.sector_count
);
447 case ATA_SMART_ENABLE
:
448 case ATA_SMART_DISABLE
:
449 err
= smartIf
->SMARTEnableDisableOperations (ifp
, in
.in_regs
.features
== ATA_SMART_ENABLE
);
451 case ATA_SMART_STATUS
:
452 if (in
.out_needed
.lba_high
) // statuscheck
455 err
= smartIf
->SMARTReturnStatus (ifp
, &is_failing
);
456 if (err
== kIOReturnSuccess
&& is_failing
) {
457 err
= -1; // thresholds exceeded condition
458 out
.out_regs
.lba_high
= 0x2c; out
.out_regs
.lba_mid
= 0xf4;
461 out
.out_regs
.lba_high
= 0xc2; out
.out_regs
.lba_mid
= 0x4f;
467 case ATA_SMART_AUTOSAVE
:
468 err
= smartIf
->SMARTEnableDisableAutosave (ifp
,
469 (in
.in_regs
.sector_count
== 241 ? true : false));
471 case ATA_SMART_IMMEDIATE_OFFLINE
:
472 select
= in
.in_regs
.lba_low
;
473 if (select
!= SHORT_SELF_TEST
&& select
!= EXTEND_SELF_TEST
)
476 return set_err(ENOSYS
, "Unsupported SMART self-test mode");
478 err
= smartIf
->SMARTExecuteOffLineImmediate (ifp
,
479 select
== EXTEND_SELF_TEST
);
481 case ATA_SMART_AUTO_OFFLINE
:
482 return set_err(ENOSYS
, "SMART command not supported");
484 return set_err(ENOSYS
, "Unknown SMART command");
488 return set_err(ENOSYS
, "Non-SMART commands not implemented");
490 } while ((err
== kIOReturnTimeout
|| err
== kIOReturnNotResponding
)
491 && timeoutCount
-- > 0);
492 if (err
== kIOReturnExclusiveAccess
)
494 rc
= err
== kIOReturnSuccess
? 0 : -1;
503 /////////////////////////////////////////////////////////////////////////////
504 /// Implement platform interface
506 class darwin_smart_interface
507 : public /*implements*/ smart_interface
510 virtual std::string
get_os_version_str();
512 virtual std::string
get_app_examples(const char * appname
);
514 virtual bool scan_smart_devices(smart_device_list
& devlist
, const char * type
,
515 const char * pattern
= 0);
518 virtual ata_device
* get_ata_device(const char * name
, const char * type
);
520 virtual scsi_device
* get_scsi_device(const char * name
, const char * type
);
522 virtual nvme_device
* get_nvme_device(const char * name
, const char * type
,
525 virtual smart_device
* autodetect_smart_device(const char * name
);
529 /////////////////////////////////////////////////////////////////////////////
532 class darwin_nvme_device
533 : public /*implements*/ nvme_device
,
534 public /*extends*/ darwin_smart_device
537 darwin_nvme_device(smart_interface
* intf
, const char * dev_name
,
538 const char * req_type
, unsigned nsid
);
540 virtual bool nvme_pass_through(const nvme_cmd_in
& in
, nvme_cmd_out
& out
);
543 darwin_nvme_device::darwin_nvme_device(smart_interface
* intf
, const char * dev_name
,
544 const char * req_type
, unsigned nsid
)
545 : smart_device(intf
, dev_name
, "nvme", req_type
),
547 darwin_smart_device("NVME")
551 bool darwin_nvme_device::nvme_pass_through(const nvme_cmd_in
& in
, nvme_cmd_out
& out
)
555 IONVMeSMARTInterface
**ifp
= devices
[fd
].smartIfNVMe
;
556 IONVMeSMARTInterface
*smartIfNVMe
;
558 unsigned int page
= in
.cdw10
& 0xff;
563 // currently only GetIdentifyData and SMARTReadData are supported
565 case smartmontools::nvme_admin_identify
:
566 err
= smartIfNVMe
->GetIdentifyData(ifp
, (struct nvme_id_ctrl
*) in
.buffer
, in
.nsid
); // FIXME
568 case smartmontools::nvme_admin_get_log_page
:
570 err
= smartIfNVMe
->SMARTReadData(ifp
, (struct nvme_smart_log
*) in
.buffer
);
571 else /* GetLogPage() is not working yet */
572 return set_err(ENOSYS
, "NVMe admin command:0x%02x/page:0x%02x is not supported",
576 return set_err(ENOSYS
, "NVMe admin command 0x%02x is not supported", in
.opcode
);
580 //////////////////////////////////////////////////////////////////////
582 std::string
darwin_smart_interface::get_os_version_str()
584 // now we are just getting darwin runtime version, to get OSX version more things needs to be done, see
585 // http://stackoverflow.com/questions/11072804/how-do-i-determine-the-os-version-at-runtime-in-os-x-or-ios-without-using-gesta
586 struct utsname osname
;
588 return strprintf("%s %s %s", osname
.sysname
, osname
.release
, osname
.machine
);
591 std::string
darwin_smart_interface::get_app_examples(const char * appname
)
593 if (!strcmp(appname
, "smartctl"))
594 return smartctl_examples
;
595 return ""; // ... so don't print again.
598 ata_device
* darwin_smart_interface::get_ata_device(const char * name
, const char * type
)
600 return new darwin_ata_device(this, name
, type
);
603 scsi_device
* darwin_smart_interface::get_scsi_device(const char *, const char *)
605 return 0; // scsi devices are not supported [yet]
608 nvme_device
* darwin_smart_interface::get_nvme_device(const char * name
, const char * type
,
611 return new darwin_nvme_device(this, name
, type
, nsid
);
614 smart_device
* darwin_smart_interface::autodetect_smart_device(const char * name
)
615 { // TODO - refactor as a function
616 // Acceptable device names are:
622 const char *devname
= NULL
;
625 if (strncmp (name
, "/dev/rdisk", 10) == 0)
627 else if (strncmp (name
, "/dev/disk", 9) == 0)
629 else if (strncmp (name
, "disk", 4) == 0)
630 // allow user to just say 'disk0'
632 // Find the device. This part should be the same for the NVMe and ATA
634 CFMutableDictionaryRef matcher
;
635 matcher
= IOBSDNameMatching (kIOMasterPortDefault
, 0, devname
);
636 disk
= IOServiceGetMatchingService (kIOMasterPortDefault
, matcher
);
639 disk
= IORegistryEntryFromPath (kIOMasterPortDefault
, name
);
644 io_registry_entry_t tmpdisk
=disk
;
647 while (! is_smart_capable (tmpdisk
, "ATA"))
650 io_object_t prevdisk
= tmpdisk
;
652 // Find this device's parent and try again.
653 err
= IORegistryEntryGetParentEntry (tmpdisk
, kIOServicePlane
, &tmpdisk
);
654 if (err
!= kIOReturnSuccess
|| ! tmpdisk
)
656 IOObjectRelease (prevdisk
);
661 return new darwin_ata_device(this, name
, "");
663 while (! is_smart_capable (tmpdisk
, "NVME"))
666 io_object_t prevdisk
= tmpdisk
;
668 // Find this device's parent and try again.
669 err
= IORegistryEntryGetParentEntry (tmpdisk
, kIOServicePlane
, &tmpdisk
);
670 if (err
!= kIOReturnSuccess
|| ! tmpdisk
)
672 IOObjectRelease (prevdisk
);
677 return new darwin_nvme_device(this, name
, "", 0);
679 // try ATA as a last option, for compatibility
680 return new darwin_ata_device(this, name
, "");
683 static void free_devnames(char * * devnames
, int numdevs
)
685 for (int i
= 0; i
< numdevs
; i
++)
690 bool darwin_smart_interface::scan_smart_devices(smart_device_list
& devlist
,
691 const char * type
, const char * pattern
/*= 0*/)
694 set_err(EINVAL
, "DEVICESCAN with pattern not implemented yet");
699 char * * atanames
= 0; int numata
= 0;
700 if (!type
|| !strcmp(type
, "ata")) {
701 numata
= make_device_names(&atanames
, "ATA");
707 char * * nvmenames
= 0; int numnvme
= 0;
709 #ifdef WITH_NVME_DEVICESCAN // TODO: Remove when NVMe support is no longer EXPERIMENTAL
714 !strcmp(type
, "nvme")) {
715 numnvme
= make_device_names(&nvmenames
, "NVME");
726 for (i
= 0; i
< numata
; i
++) {
727 ata_device
* atadev
= get_ata_device(atanames
[i
], type
);
729 devlist
.push_back(atadev
);
731 free_devnames(atanames
, numata
);
733 for (i
= 0; i
< numnvme
; i
++) {
734 nvme_device
* nvmedev
= get_nvme_device(nvmenames
[i
], type
, 0); // default nsid
736 devlist
.push_back(nvmedev
);
738 free_devnames(nvmenames
, numnvme
);
746 /////////////////////////////////////////////////////////////////////////////
747 /// Initialize platform interface and register with smi()
749 void smart_interface::init()
751 static os::darwin_smart_interface the_interface
;
752 smart_interface::set(&the_interface
);