feat: initialized SMART reads for disks that are SMART capable (#1407)

This commit is contained in:
Serhiy Mytrovtsiy
2023-04-18 17:32:15 +02:00
parent 4ce067d68f
commit 3b5dcd5056
3 changed files with 136 additions and 28 deletions

View File

@@ -10,3 +10,37 @@
//
#include <libproc.h>
#include <IOKit/hidsystem/IOHIDEventSystemClient.h>
struct nvme_smart_log {
UInt8 critical_warning;
UInt8 temperature[2];
UInt8 avail_spare;
UInt8 spare_thresh;
UInt8 percent_used;
UInt8 rsvd6[26];
UInt8 data_units_read[16];
UInt8 data_units_written[16];
UInt8 host_reads[16];
UInt8 host_writes[16];
UInt8 ctrl_busy_time[16];
UInt32 power_cycles[4];
UInt32 power_on_hours[4];
UInt32 unsafe_shutdowns[4];
UInt32 media_errors[4];
UInt16 temp_sensor[8];
UInt32 thm_temp1_trans_count;
UInt32 thm_temp2_trans_count;
UInt32 thm_temp1_total_time;
UInt32 thm_temp2_total_time;
UInt8 rsvd232[280];
};
typedef struct IONVMeSMARTInterface {
IUNKNOWN_C_GUTS;
UInt16 version;
UInt16 revision;
IOReturn ( *SMARTReadData )( void * interface, struct nvme_smart_log * NVMeSMARTData );
} IONVMeSMARTInterface;

View File

@@ -20,8 +20,13 @@ public struct stats {
var writeBytes: Int64 = 0
}
public struct smart_t {
var temperature: Int = 0
var life: Int = 0
}
public struct drive {
var parent: io_registry_entry_t = 0
var parent: io_object_t = 0
var mediaName: String = ""
var BSDName: String = ""
@@ -38,6 +43,7 @@ public struct drive {
var free: Int64 = 0
var activity: stats = stats()
var smart: smart_t? = nil
}
public class Disks {
@@ -126,6 +132,12 @@ public class Disks {
self.array[idx].activity.write = newValue
}
}
func updateSMARTData(_ idx: Int, smart: smart_t) {
self.queue.async(flags: .barrier) {
self.array[idx].smart = smart
}
}
}
public struct Disk_process: IOProcess_p {
@@ -171,9 +183,9 @@ public class Disk: Module {
private let settingsView: Settings = Settings()
private let portalView: Portal = Portal()
private var capacityReader: CapacityReader? = nil
private var activityReader: ActivityReader? = nil
private var processReader: ProcessReader? = nil
private var capacityReader: CapacityReader = CapacityReader()
private var activityReader: ActivityReader = ActivityReader()
private var processReader: ProcessReader = ProcessReader()
private var selectedDisk: String = ""
private var notificationLevelState: Bool = false
@@ -191,27 +203,23 @@ public class Disk: Module {
)
guard self.available else { return }
self.capacityReader = CapacityReader()
self.activityReader = ActivityReader()
self.processReader = ProcessReader()
self.selectedDisk = Store.shared.string(key: "\(Disk.name)_disk", defaultValue: self.selectedDisk)
self.capacityReader?.callbackHandler = { [unowned self] value in
self.capacityReader.callbackHandler = { [unowned self] value in
if let value = value {
self.capacityCallback(value)
}
}
self.capacityReader?.readyCallback = { [unowned self] in
self.capacityReader.readyCallback = { [unowned self] in
self.readyHandler()
}
self.activityReader?.callbackHandler = { [unowned self] value in
self.activityReader.callbackHandler = { [unowned self] value in
if let value = value {
self.activityCallback(value)
}
}
self.processReader?.callbackHandler = { [unowned self] value in
self.processReader.callbackHandler = { [unowned self] value in
if let list = value {
self.popupView.processCallback(list)
}
@@ -219,34 +227,28 @@ public class Disk: Module {
self.settingsView.selectedDiskHandler = { [unowned self] value in
self.selectedDisk = value
self.capacityReader?.read()
self.capacityReader.read()
}
self.settingsView.callback = { [unowned self] in
self.capacityReader?.read()
self.capacityReader.read()
}
self.settingsView.setInterval = { [unowned self] value in
self.capacityReader?.setInterval(value)
self.capacityReader.setInterval(value)
}
self.settingsView.callbackWhenUpdateNumberOfProcesses = {
self.popupView.numberOfProcessesUpdated()
DispatchQueue.global(qos: .background).async {
self.processReader?.read()
self.processReader.read()
}
}
if let reader = self.capacityReader {
self.addReader(reader)
}
if let reader = self.activityReader {
self.addReader(reader)
}
if let reader = self.processReader {
self.addReader(reader)
}
self.addReader(self.capacityReader)
self.addReader(self.activityReader)
self.addReader(self.processReader)
}
public override func widgetDidSet(_ type: widget_t) {
if type == .speed && self.capacityReader?.interval != 1 {
if type == .speed && self.capacityReader.interval != 1 {
self.settingsView.setUpdateInterval(value: 1)
}
}

View File

@@ -11,8 +11,26 @@
import Cocoa
import Kit
import IOKit
import Darwin
import IOKit.storage
let kIONVMeSMARTUserClientTypeID = CFUUIDGetConstantUUIDWithBytes(nil,
0xAA, 0x0F, 0xA6, 0xF9,
0xC2, 0xD6, 0x45, 0x7F,
0xB1, 0x0B, 0x59, 0xA1,
0x32, 0x53, 0x29, 0x2F
)
let kIONVMeSMARTInterfaceID = CFUUIDGetConstantUUIDWithBytes(nil,
0xCC, 0xD1, 0xDB, 0x19,
0xFD, 0x9A, 0x4D, 0xAF,
0xBF, 0x95, 0x12, 0x45,
0x4B, 0x23, 0x0A, 0xB6
)
let kIOCFPlugInInterfaceID = CFUUIDGetConstantUUIDWithBytes(nil,
0xC2, 0x44, 0xE8, 0x58,
0x10, 0x9C, 0x11, 0xD4,
0x91, 0xD4, 0x00, 0x50,
0xE4, 0xC6, 0x42, 0x6F
)
internal class CapacityReader: Reader<Disks> {
internal var list: Disks = Disks()
@@ -43,6 +61,9 @@ internal class CapacityReader: Reader<Disks> {
if let path = d.path {
self.list.updateFreeSize(idx, newValue: self.freeDiskSpaceInBytes(path))
if let smart = self.getSMARTDetails(for: BSDName) {
self.list.updateSMARTData(idx, smart: smart)
}
}
continue
@@ -53,6 +74,9 @@ internal class CapacityReader: Reader<Disks> {
d.free = self.freeDiskSpaceInBytes(path)
d.size = self.totalDiskSpaceInBytes(path)
}
if let smart = self.getSMARTDetails(for: BSDName) {
d.smart = smart
}
self.list.append(d)
self.list.sort()
}
@@ -117,6 +141,54 @@ internal class CapacityReader: Reader<Disks> {
return 0
}
private func getSMARTDetails(for BSDName: String) -> smart_t? {
var disk = IOServiceGetMatchingService(kIOMasterPortDefault, IOBSDNameMatching(kIOMasterPortDefault, 0, BSDName.cString(using: .utf8)))
guard disk != kIOReturnSuccess else { return nil }
defer { IOObjectRelease(disk) }
var parent = disk
while IOObjectConformsTo(disk, kIOBlockStorageDeviceClass) == 0 {
let error = IORegistryEntryGetParentEntry(disk, kIOServicePlane, &parent)
if error != kIOReturnSuccess || parent == kIOReturnSuccess { return nil }
disk = parent
}
guard IOObjectConformsTo(disk, kIOBlockStorageDeviceClass) > 0,
let raw = IORegistryEntryCreateCFProperty(disk, "NVMe SMART Capable" as CFString, kCFAllocatorDefault, 0),
let val = raw.takeRetainedValue() as? Bool, val else {
return nil
}
var pluginInterface: UnsafeMutablePointer<UnsafeMutablePointer<IOCFPlugInInterface>?>?
var smartInterface: UnsafeMutablePointer<UnsafeMutablePointer<IONVMeSMARTInterface>?>?
var score: Int32 = 0
var result = IOCreatePlugInInterfaceForService(disk, kIONVMeSMARTUserClientTypeID, kIOCFPlugInInterfaceID, &pluginInterface, &score)
guard result == kIOReturnSuccess else { return nil }
defer { IODestroyPlugInInterface(pluginInterface) }
result = withUnsafeMutablePointer(to: &smartInterface) {
$0.withMemoryRebound(to: Optional<LPVOID>.self, capacity: 1) {
pluginInterface?.pointee?.pointee.QueryInterface(pluginInterface, CFUUIDGetUUIDBytes(kIONVMeSMARTInterfaceID), $0) ?? KERN_NOT_FOUND
}
}
defer { _ = pluginInterface?.pointee?.pointee.Release(smartInterface) }
guard result == KERN_SUCCESS, let smart = smartInterface?.pointee else { return nil }
var smartData: nvme_smart_log = nvme_smart_log()
guard smart.pointee.SMARTReadData(smartInterface, &smartData) == kIOReturnSuccess else { return nil }
let temperatures: [UInt8] = [UInt8(smartData.temperature.1), UInt8(smartData.temperature.0)]
var temperature: UInt16 = 0
let data = NSData(bytes: temperatures, length: 2)
data.getBytes(&temperature, length: 2)
return smart_t(
temperature: Int(UInt16(bigEndian: temperature) - 273),
life: 100 - Int(smartData.percent_used)
)
}
}
internal class ActivityReader: Reader<Disks> {