diff --git a/LibreHardwareMonitorLib/Hardware/Storage/WindowsSmart.cs b/LibreHardwareMonitorLib/Hardware/Storage/WindowsSmart.cs index 9a0f339..52125ed 100644 --- a/LibreHardwareMonitorLib/Hardware/Storage/WindowsSmart.cs +++ b/LibreHardwareMonitorLib/Hardware/Storage/WindowsSmart.cs @@ -119,6 +119,52 @@ namespace LibreHardwareMonitor.Hardware.Storage return true; } + /// + /// Reads Smart health status of the drive + /// + /// True, if drive is healthy; False, if unhealthy; Null, if it cannot be read + public bool? ReadSmartHealth() + { + if (_handle.IsClosed) + throw new ObjectDisposedException(nameof(WindowsSmart)); + + var parameter = new Kernel32.SENDCMDINPARAMS + { + bDriveNumber = (byte)_driveNumber, + irDriveRegs = { + bFeaturesReg = Kernel32.SMART_FEATURES.RETURN_SMART_STATUS, + bCylLowReg = Kernel32.SMART_LBA_MID, + bCylHighReg = Kernel32.SMART_LBA_HI, + bCommandReg = Kernel32.ATA_COMMAND.ATA_SMART + } + }; + + bool isValid = Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_SEND_DRIVE_COMMAND, ref parameter, Marshal.SizeOf(parameter), + out Kernel32.STATUSCMDOUTPARAMS result, Marshal.SizeOf(), out _, IntPtr.Zero); + + if (!isValid) + { + return null; + } + + // reference: https://github.com/smartmontools/smartmontools/blob/master/smartmontools/atacmds.cpp + if (Kernel32.SMART_LBA_HI == result.irDriveRegs.bCylHighReg && Kernel32.SMART_LBA_MID == result.irDriveRegs.bCylLowReg) + { + // high and mid registers are unchanged, which means that the drive is healthy + return true; + } + else if (Kernel32.SMART_LBA_HI_EXCEEDED == result.irDriveRegs.bCylHighReg && Kernel32.SMART_LBA_MID_EXCEEDED == result.irDriveRegs.bCylLowReg) + { + // high and mid registers are exceeded, which means that the drive is unhealthy + return false; + } + else + { + // response is not clear + return null; + } + } + protected void Dispose(bool disposing) { if (disposing) diff --git a/LibreHardwareMonitorLib/Interop/Kernel32.cs b/LibreHardwareMonitorLib/Interop/Kernel32.cs index 187d41f..5f37e5b 100644 --- a/LibreHardwareMonitorLib/Interop/Kernel32.cs +++ b/LibreHardwareMonitorLib/Interop/Kernel32.cs @@ -24,6 +24,8 @@ namespace LibreHardwareMonitor.Interop internal const uint NVME_PASS_THROUGH_SRB_IO_CODE = 0xe0002000; internal const byte SMART_LBA_HI = 0xC2; internal const byte SMART_LBA_MID = 0x4F; + internal const byte SMART_LBA_HI_EXCEEDED = 0x2C; + internal const byte SMART_LBA_MID_EXCEEDED = 0xF4; private const string DllName = "kernel32.dll"; [Flags] @@ -149,6 +151,19 @@ namespace LibreHardwareMonitor.Interop out uint lpBytesReturned, IntPtr lpOverlapped); + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] + [return: MarshalAs(UnmanagedType.Bool)] + internal static extern bool DeviceIoControl + ( + SafeHandle hDevice, + DFP dwIoControlCode, + ref SENDCMDINPARAMS lpInBuffer, + int nInBufferSize, + out STATUSCMDOUTPARAMS lpOutBuffer, + int nOutBufferSize, + out uint lpBytesReturned, + IntPtr lpOverlapped); + [DllImport(DllName, CallingConvention = CallingConvention.Winapi, CharSet = CharSet.Auto, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] internal static extern bool DeviceIoControl @@ -589,6 +604,14 @@ namespace LibreHardwareMonitor.Interop public SMART_THRESHOLD[] Thresholds; } + [StructLayout(LayoutKind.Sequential, Pack = 1)] + internal struct STATUSCMDOUTPARAMS + { + public uint cBufferSize; + public DRIVERSTATUS DriverStatus; + public IDEREGS irDriveRegs; + } + [StructLayout(LayoutKind.Sequential, Pack = 1)] internal struct IDENTIFY_DATA {