first commit

This commit is contained in:
2025-04-07 07:44:27 -07:00
commit d6cde0c05e
512 changed files with 142392 additions and 0 deletions

View File

@@ -0,0 +1,292 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
public abstract class AtaStorage : AbstractStorage
{
// array of all hard drive types, matching type is searched in this order
private static readonly Type[] _hddTypes = { typeof(SsdPlextor), typeof(SsdIntel), typeof(SsdSandforce), typeof(SsdIndilinx), typeof(SsdSamsung), typeof(SsdMicron), typeof(GenericHardDisk) };
private IDictionary<SmartAttribute, Sensor> _sensors;
/// <summary>
/// Gets the SMART data.
/// </summary>
public ISmart Smart { get; }
/// <summary>
/// Gets the SMART attributes.
/// </summary>
public IReadOnlyList<SmartAttribute> SmartAttributes { get; }
internal AtaStorage(StorageInfo storageInfo, ISmart smart, string name, string firmwareRevision, string id, int index, IReadOnlyList<SmartAttribute> smartAttributes, ISettings settings)
: base(storageInfo, name, firmwareRevision, id, index, settings)
{
Smart = smart;
if (smart.IsValid)
smart.EnableSmart();
SmartAttributes = smartAttributes;
CreateSensors();
}
internal static AbstractStorage CreateInstance(StorageInfo storageInfo, ISettings settings)
{
ISmart smart = new WindowsSmart(storageInfo.Index);
string name = null;
string firmwareRevision = null;
Kernel32.SMART_ATTRIBUTE[] smartAttributes = { };
if (smart.IsValid)
{
bool nameValid = smart.ReadNameAndFirmwareRevision(out name, out firmwareRevision);
bool smartEnabled = smart.EnableSmart();
if (smartEnabled)
smartAttributes = smart.ReadSmartData();
if (!nameValid)
{
name = null;
firmwareRevision = null;
}
}
else
{
string[] logicalDrives = WindowsStorage.GetLogicalDrives(storageInfo.Index);
if (logicalDrives == null || logicalDrives.Length == 0)
{
smart.Close();
return null;
}
bool hasNonZeroSizeDrive = false;
foreach (string logicalDrive in logicalDrives)
{
try
{
var driveInfo = new DriveInfo(logicalDrive);
if (driveInfo.TotalSize > 0)
{
hasNonZeroSizeDrive = true;
break;
}
}
catch (ArgumentException) { }
catch (IOException) { }
catch (UnauthorizedAccessException) { }
}
if (!hasNonZeroSizeDrive)
{
smart.Close();
return null;
}
}
if (string.IsNullOrEmpty(name))
name = string.IsNullOrEmpty(storageInfo.Name) ? "Generic Hard Disk" : storageInfo.Name;
if (string.IsNullOrEmpty(firmwareRevision))
firmwareRevision = string.IsNullOrEmpty(storageInfo.Revision) ? "Unknown" : storageInfo.Revision;
foreach (Type type in _hddTypes)
{
// get the array of the required SMART attributes for the current type
// check if all required attributes are present
bool allAttributesFound = true;
if (type.GetCustomAttributes(typeof(RequireSmartAttribute), true) is RequireSmartAttribute[] requiredAttributes)
{
foreach (RequireSmartAttribute requireAttribute in requiredAttributes)
{
bool attributeFound = false;
foreach (Kernel32.SMART_ATTRIBUTE value in smartAttributes)
{
if (value.Id == requireAttribute.AttributeId)
{
attributeFound = true;
break;
}
}
if (!attributeFound)
{
allAttributesFound = false;
break;
}
}
}
// if an attribute is missing, then try the next type
if (!allAttributesFound)
continue;
// check if there is a matching name prefix for this type
if (type.GetCustomAttributes(typeof(NamePrefixAttribute), true) is NamePrefixAttribute[] namePrefixes)
{
foreach (NamePrefixAttribute prefix in namePrefixes)
{
if (name.StartsWith(prefix.Prefix, StringComparison.InvariantCulture))
{
const BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
return Activator.CreateInstance(type, flags, null, new object[] { storageInfo, smart, name, firmwareRevision, storageInfo.Index, settings }, null) as AtaStorage;
}
}
}
}
// no matching type has been found
smart.Close();
return null;
}
protected sealed override void CreateSensors()
{
_sensors = new Dictionary<SmartAttribute, Sensor>();
if (Smart.IsValid)
{
byte[] smartIds = Smart.ReadSmartData().Select(x => x.Id).ToArray();
// unique attributes by SensorType and SensorChannel.
IEnumerable<SmartAttribute> smartAttributes = SmartAttributes
.Where(x => x.SensorType.HasValue && smartIds.Contains(x.Id))
.GroupBy(x => new { x.SensorType.Value, x.SensorChannel })
.Select(x => x.First());
_sensors = smartAttributes.ToDictionary(attribute => attribute,
attribute => new Sensor(attribute.SensorName,
attribute.SensorChannel,
attribute.DefaultHiddenSensor,
attribute.SensorType.GetValueOrDefault(),
this,
attribute.ParameterDescriptions,
_settings));
foreach (KeyValuePair<SmartAttribute, Sensor> sensor in _sensors)
ActivateSensor(sensor.Value);
}
base.CreateSensors();
}
protected virtual void UpdateAdditionalSensors(Kernel32.SMART_ATTRIBUTE[] values) { }
protected override void UpdateSensors()
{
if (Smart.IsValid)
{
Kernel32.SMART_ATTRIBUTE[] smartAttributes = Smart.ReadSmartData();
foreach (KeyValuePair<SmartAttribute, Sensor> keyValuePair in _sensors)
{
SmartAttribute attribute = keyValuePair.Key;
foreach (Kernel32.SMART_ATTRIBUTE value in smartAttributes)
{
if (value.Id == attribute.Id)
{
Sensor sensor = keyValuePair.Value;
sensor.Value = attribute.ConvertValue(value, sensor.Parameters);
}
}
}
UpdateAdditionalSensors(smartAttributes);
}
}
protected override void GetReport(StringBuilder r)
{
if (Smart.IsValid)
{
Kernel32.SMART_ATTRIBUTE[] values = Smart.ReadSmartData();
Kernel32.SMART_THRESHOLD[] thresholds = Smart.ReadSmartThresholds();
if (values.Length > 0)
{
r.AppendFormat(CultureInfo.InvariantCulture,
" {0}{1}{2}{3}{4}{5}{6}{7}",
"Id".PadRight(3),
"Description".PadRight(35),
"Raw Value".PadRight(13),
"Worst".PadRight(6),
"Value".PadRight(6),
"Threshold".PadRight(6),
"Physical".PadRight(8),
Environment.NewLine);
foreach (Kernel32.SMART_ATTRIBUTE value in values)
{
if (value.Id == 0x00)
break;
byte? threshold = null;
foreach (Kernel32.SMART_THRESHOLD t in thresholds)
{
if (t.Id == value.Id)
{
threshold = t.Threshold;
}
}
string description = "Unknown";
float? physical = null;
foreach (SmartAttribute a in SmartAttributes)
{
if (a.Id == value.Id)
{
description = a.Name;
if (a.HasRawValueConversion | a.SensorType.HasValue)
physical = a.ConvertValue(value, null);
else
physical = null;
}
}
string raw = BitConverter.ToString(value.RawValue);
r.AppendFormat(CultureInfo.InvariantCulture,
" {0}{1}{2}{3}{4}{5}{6}{7}",
value.Id.ToString("X2").PadRight(3),
description.PadRight(35),
raw.Replace("-", string.Empty).PadRight(13),
value.WorstValue.ToString(CultureInfo.InvariantCulture).PadRight(6),
value.CurrentValue.ToString(CultureInfo.InvariantCulture).PadRight(6),
(threshold.HasValue
? threshold.Value.ToString(CultureInfo.InvariantCulture)
: "-").PadRight(6),
(physical.HasValue ? physical.Value.ToString(CultureInfo.InvariantCulture) : "-").PadRight(8),
Environment.NewLine);
}
r.AppendLine();
}
}
}
protected static float RawToInt(byte[] raw, byte value, IReadOnlyList<IParameter> parameters)
{
return (raw[3] << 24) | (raw[2] << 16) | (raw[1] << 8) | raw[0];
}
public override void Close()
{
Smart.Close();
base.Close();
}
}

View File

@@ -0,0 +1,296 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
public abstract class AbstractStorage : Hardware
{
private readonly PerformanceValue _perfTotal = new();
private readonly PerformanceValue _perfWrite = new();
private readonly PerformanceValue _perfRead = new();
private readonly StorageInfo _storageInfo;
private readonly TimeSpan _updateInterval = TimeSpan.FromSeconds(60);
private ulong _lastReadCount;
private long _lastTime;
private DateTime _lastUpdate = DateTime.MinValue;
private ulong _lastWriteCount;
private Sensor _sensorDiskReadRate;
private Sensor _sensorDiskTotalActivity;
private Sensor _sensorDiskWriteActivity;
private Sensor _sensorDiskReadActivity;
private Sensor _sensorDiskWriteRate;
private Sensor _usageSensor;
internal AbstractStorage(StorageInfo storageInfo, string name, string firmwareRevision, string id, int index, ISettings settings)
: base(name, new Identifier(id, index.ToString(CultureInfo.InvariantCulture)), settings)
{
_storageInfo = storageInfo;
FirmwareRevision = firmwareRevision;
Index = index;
string[] logicalDrives = WindowsStorage.GetLogicalDrives(index);
var driveInfoList = new List<DriveInfo>(logicalDrives.Length);
foreach (string logicalDrive in logicalDrives)
{
try
{
var di = new DriveInfo(logicalDrive);
if (di.TotalSize > 0)
driveInfoList.Add(new DriveInfo(logicalDrive));
}
catch (ArgumentException)
{ }
catch (IOException)
{ }
catch (UnauthorizedAccessException)
{ }
}
DriveInfos = driveInfoList.ToArray();
}
public DriveInfo[] DriveInfos { get; }
public string FirmwareRevision { get; }
public override HardwareType HardwareType => HardwareType.Storage;
public int Index { get; }
/// <inheritdoc />
public override void Close()
{
_storageInfo.Handle?.Close();
base.Close();
}
public static AbstractStorage CreateInstance(string deviceId, uint driveNumber, ulong diskSize, int scsiPort, ISettings settings)
{
StorageInfo info = WindowsStorage.GetStorageInfo(deviceId, driveNumber);
if (info == null || info.Removable || info.BusType is Kernel32.STORAGE_BUS_TYPE.BusTypeVirtual or Kernel32.STORAGE_BUS_TYPE.BusTypeFileBackedVirtual)
return null;
info.DiskSize = diskSize;
info.DeviceId = deviceId;
info.Handle = Kernel32.OpenDevice(deviceId);
info.Scsi = $@"\\.\SCSI{scsiPort}:";
//fallback, when it is not possible to read out with the nvme implementation,
//try it with the sata smart implementation
if (info.BusType == Kernel32.STORAGE_BUS_TYPE.BusTypeNvme)
{
AbstractStorage x = NVMeGeneric.CreateInstance(info, settings);
if (x != null)
return x;
}
return info.BusType is Kernel32.STORAGE_BUS_TYPE.BusTypeAta or Kernel32.STORAGE_BUS_TYPE.BusTypeSata or Kernel32.STORAGE_BUS_TYPE.BusTypeNvme
? AtaStorage.CreateInstance(info, settings)
: StorageGeneric.CreateInstance(info, settings);
}
protected virtual void CreateSensors()
{
if (DriveInfos.Length > 0)
{
_usageSensor = new Sensor("Used Space", 0, SensorType.Load, this, _settings);
ActivateSensor(_usageSensor);
}
_sensorDiskReadActivity = new Sensor("Read Activity", 31, SensorType.Load, this, _settings);
ActivateSensor(_sensorDiskReadActivity);
_sensorDiskWriteActivity = new Sensor("Write Activity", 32, SensorType.Load, this, _settings);
ActivateSensor(_sensorDiskWriteActivity);
_sensorDiskTotalActivity = new Sensor("Total Activity", 33, SensorType.Load, this, _settings);
ActivateSensor(_sensorDiskTotalActivity);
_sensorDiskReadRate = new Sensor("Read Rate", 34, SensorType.Throughput, this, _settings);
ActivateSensor(_sensorDiskReadRate);
_sensorDiskWriteRate = new Sensor("Write Rate", 35, SensorType.Throughput, this, _settings);
ActivateSensor(_sensorDiskWriteRate);
}
protected abstract void UpdateSensors();
public override void Update()
{
// Update statistics.
if (_storageInfo != null)
{
try
{
UpdatePerformanceSensors();
}
catch
{
// Ignored.
}
}
// Read out at update interval.
TimeSpan tDiff = DateTime.UtcNow - _lastUpdate;
if (tDiff > _updateInterval)
{
_lastUpdate = DateTime.UtcNow;
UpdateSensors();
if (_usageSensor != null)
{
long totalSize = 0;
long totalFreeSpace = 0;
for (int i = 0; i < DriveInfos.Length; i++)
{
if (!DriveInfos[i].IsReady)
continue;
try
{
totalSize += DriveInfos[i].TotalSize;
totalFreeSpace += DriveInfos[i].TotalFreeSpace;
}
catch (IOException)
{ }
catch (UnauthorizedAccessException)
{ }
}
if (totalSize > 0)
_usageSensor.Value = 100.0f - (100.0f * totalFreeSpace / totalSize);
else
_usageSensor.Value = null;
}
}
}
private void UpdatePerformanceSensors()
{
if (!Kernel32.DeviceIoControl(_storageInfo.Handle,
Kernel32.IOCTL.IOCTL_DISK_PERFORMANCE,
IntPtr.Zero,
0,
out Kernel32.DISK_PERFORMANCE diskPerformance,
Marshal.SizeOf<Kernel32.DISK_PERFORMANCE>(),
out _,
IntPtr.Zero))
{
return;
}
_perfRead.Update(diskPerformance.ReadTime, diskPerformance.QueryTime);
_sensorDiskReadActivity.Value = (float)_perfRead.Result;
_perfWrite.Update(diskPerformance.WriteTime, diskPerformance.QueryTime);
_sensorDiskWriteActivity.Value = (float)_perfWrite.Result;
_perfTotal.Update(diskPerformance.IdleTime, diskPerformance.QueryTime);
_sensorDiskTotalActivity.Value = (float)(100 - _perfTotal.Result);
ulong readCount = diskPerformance.BytesRead;
ulong readDiff = readCount - _lastReadCount;
_lastReadCount = readCount;
ulong writeCount = diskPerformance.BytesWritten;
ulong writeDiff = writeCount - _lastWriteCount;
_lastWriteCount = writeCount;
long currentTime = Stopwatch.GetTimestamp();
if (_lastTime != 0)
{
double timeDeltaSeconds = TimeSpan.FromTicks(currentTime - _lastTime).TotalSeconds;
double writeSpeed = writeDiff * (1 / timeDeltaSeconds);
_sensorDiskWriteRate.Value = (float)writeSpeed;
double readSpeed = readDiff * (1 / timeDeltaSeconds);
_sensorDiskReadRate.Value = (float)readSpeed;
}
_lastTime = currentTime;
}
protected abstract void GetReport(StringBuilder r);
public override string GetReport()
{
var r = new StringBuilder();
r.AppendLine("Storage");
r.AppendLine();
r.AppendLine("Drive Name: " + _name);
r.AppendLine("Firmware Version: " + FirmwareRevision);
r.AppendLine();
GetReport(r);
foreach (DriveInfo di in DriveInfos)
{
if (!di.IsReady)
continue;
try
{
r.AppendLine("Logical Drive Name: " + di.Name);
r.AppendLine("Format: " + di.DriveFormat);
r.AppendLine("Total Size: " + di.TotalSize);
r.AppendLine("Total Free Space: " + di.TotalFreeSpace);
r.AppendLine();
}
catch (IOException)
{ }
catch (UnauthorizedAccessException)
{ }
}
return r.ToString();
}
public override void Traverse(IVisitor visitor)
{
foreach (ISensor sensor in Sensors)
sensor.Accept(visitor);
}
/// <summary>
/// Helper to calculate the disk performance with base timestamps
/// https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-perfrawdata
/// </summary>
private class PerformanceValue
{
public double Result { get; private set; }
private ulong Time { get; set; }
private ulong Value { get; set; }
public void Update(ulong val, ulong valBase)
{
ulong diffValue = val - Value;
ulong diffTime = valBase - Time;
Value = val;
Time = valBase;
Result = 100.0 / diffTime * diffValue;
//sometimes it is possible that diff_value > diff_timebase
//limit result to 100%, this is because timing issues during read from pcie controller an latency between IO operation
if (Result > 100)
Result = 100;
}
}
}

View File

@@ -0,0 +1,424 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
#if DEBUG
using System;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage
{
internal class DebugSmart : ISmart
{
private readonly Drive[] _drives = {
new("KINGSTON SNV425S264GB", null, 16,
@" 01 000000000000 100 100
02 000000000000 100 100
03 000000000000 100 100
05 000000000000 100 100
07 000000000000 100 100
08 000000000000 100 100
09 821E00000000 100 100
0A 000000000000 100 100
0C 950200000000 100 100
A8 000000000000 100 100
AF 000000000000 100 100
C0 000000000000 100 100
C2 290014002B00 100 41
C5 000000000000 100 100
F0 000000000000 100 100
AA 07007B000000 100 100
AD 0E1E71304919 100 100"),
new("PLEXTOR PX-128M2S", "1.03", 16,
@" 01 000000000000 100 100 0
03 000000000000 100 100 0
04 000000000000 100 100 0
05 000000000000 100 100 0
09 250100000000 100 100 0
0A 000000000000 100 100 0
0C D10000000000 100 100 0
B2 000000000000 100 100 0
BB 000000000000 100 100 0
BE 000000000000 100 100 0
C0 000000000000 100 100 0
C1 000000000000 100 100 0
C2 000000000000 100 100 0
C3 000000000000 100 100 0
C5 000000000000 100 100 0
C6 000000000000 100 100 0
C7 000000000000 100 100 0"),
new("OCZ-VERTEX2", "1.25", 16,
@" 01 DADAD5000000 100 106 50
05 000000000000 100 100 3
09 DF0900004A2F 100 100 0
0C FC0100000000 100 100 0
AB 000000000000 0 0 0
AC 000000000000 0 0 0
AE 1F0000000000 0 0 0
B1 000000000000 0 0 0
B5 000000000000 0 0 0
B6 000000000000 0 0 0
BB 000000000000 100 100 0
C2 010081007F00 129 1 0
C3 DADAD5000000 100 106 0
C4 000000000000 100 100 0
E7 000000000000 100 100 10
E9 800400000000 0 0 0
EA 000600000000 0 0 0
F1 000600000000 0 0 0
F2 801200000000 0 0 0"),
new("WDC WD5000AADS-00S9B0", null, 10,
@" 1 000000000000 200 200
3 820D00000000 149 150
4 610800000000 98 98
5 000000000000 200 200
7 000000000000 253 100
9 0F1F00000000 90 90
10 000000000000 100 100
11 000000000000 100 100
12 880200000000 100 100
192 6B0000000000 200 200
193 E9CB03000000 118 118
194 280000000000 94 103
196 000000000000 200 200
197 000000000000 200 200
198 000000000000 200 200
199 000000000000 200 200
200 000000000000 200 200
130 7B0300010002 1 41
5 000000000000 0 0
1 000000000000 0 0"),
new("INTEL SSDSA2M080G2GC", null, 10,
@" 3 000000000000 100 100
4 000000000000 100 100
5 010000000000 100 100
9 B10B00000000 100 100
12 DD0300000000 100 100
192 480000000000 100 100
225 89DB00000000 200 200
226 3D1B00000000 100 100
227 030000000000 100 100
228 7F85703C0000 100 100
232 000000000000 99 99
233 000000000000 98 98
184 000000000000 100 100
1 000000000000 0 0"),
new("OCZ-VERTEX", null, 10,
@" 1 000000000000 0 8
9 000000000000 30 99
12 000000000000 0 15
184 000000000000 0 7
195 000000000000 0 0
196 000000000000 0 2
197 000000000000 0 0
198 B9ED00000000 214 176
199 352701000000 143 185
200 B10500000000 105 55
201 F40A00000000 238 194
202 020000000000 137 35
203 020000000000 125 63
204 000000000000 0 0
205 000000000000 19 136
206 000000000000 22 54
207 010000000000 113 226
208 000000000000 49 232
209 000000000000 0 98
211 000000000000 0 0
212 000000000000 0 0
213 000000000000 0 0"),
new("INTEL SSDSA2CW120G3", null, 16,
@"03 000000000000 100 100 0
04 000000000000 100 100 0
05 000000000000 100 100 0
09 830200000000 100 100 0
0C 900100000000 100 100 0
AA 000000000000 100 100 0
AB 000000000000 100 100 0
AC 000000000000 100 100 0
B8 000000000000 100 100 0
BB 000000000000 100 100 0
C0 040000000000 100 100 0
E1 FF4300000000 100 100 0
E2 E57D14000000 100 100 0
E3 000000000000 100 100 0
E4 E39600000000 100 100 0
E8 000000000000 100 100 0
E9 000000000000 100 100 0
F1 FF4300000000 100 100 0
F2 264F00000000 100 100 0"),
new("CORSAIR CMFSSD-128GBG2D", "VBM19C1Q", 16,
@"09 100900000000 99 99 0
0C 560200000000 99 99 0
AF 000000000000 100 100 10
B0 000000000000 100 100 10
B1 2A0000000000 99 99 17
B2 180000000000 60 60 10
B3 4B0000000000 98 98 10
B4 B50E00000000 98 98 10
B5 000000000000 100 100 10
B6 000000000000 100 100 10
B7 000000000000 100 100 10
BB 000000000000 100 100 0
C3 000000000000 200 200 0
C6 000000000000 100 100 0
C7 810100000000 253 253 0
E8 240000000000 60 60 10
E9 630594120000 92 92 0"),
new("Maxtor 6L300R0", null, 10,
@"3 9E5500000000 183 193
4 0A0D00000000 252 252
5 010000000000 253 253
6 000000000000 253 253
7 000000000000 252 253
8 DFA700000000 224 245
9 CE5700000000 155 155
10 000000000000 252 253
11 000000000000 252 253
12 BA0400000000 250 250
192 000000000000 253 253
193 000000000000 253 253
194 3D0000000000 253 42
195 5D1F00000000 252 253
196 000000000000 253 253
197 010000000000 253 253
198 000000000000 253 253
199 030000000000 196 199
200 000000000000 252 253
201 000000000000 252 253
202 000000000000 252 253
203 000000000000 252 253
204 000000000000 252 253
205 000000000000 252 253
207 000000000000 252 253
208 000000000000 252 253
209 EA0000000000 234 234
210 000000000000 252 253
211 000000000000 252 253
212 000000000000 252 253
130 5B0300010002 1 9
59 FC3203030100 205 0
1 000000000000 0 0
144 000000000000 0 34 "),
new("M4-CT256M4SSD2", "0309", 16,
@"01 000000000000 100 100 50
05 000000000000 100 100 10
09 AB0100000000 100 100 1
0C 6E0000000000 100 100 1
AA 000000000000 100 100 10
AB 000000000000 100 100 1
AC 000000000000 100 100 1
AD 060000000000 100 100 10
AE 000000000000 100 100 1
B5 79003D00B700 100 100 1
B7 000000000000 100 100 1
B8 000000000000 100 100 50
BB 000000000000 100 100 1
BC 000000000000 100 100 1
BD 5B0000000000 100 100 1
C2 000000000000 100 100 0
C3 000000000000 100 100 1
C4 000000000000 100 100 1
C5 000000000000 100 100 1
C6 000000000000 100 100 1
C7 000000000000 100 100 1
CA 000000000000 100 100 1
CE 000000000000 100 100 1 "),
new("C300-CTFDDAC256MAG", "0007", 16,
@"01 000000000000 100 100 0
05 000000000000 100 100 0
09 4C0A00000000 100 100 0
0C 0F0100000000 100 100 0
AA 000000000000 100 100 0
AB 000000000000 100 100 0
AC 000000000000 100 100 0
AD 1B0000000000 100 100 0
AE 000000000000 100 100 0
B5 D30357012B05 100 100 0
B7 000000000000 100 100 0
B8 000000000000 100 100 0
BB 000000000000 100 100 0
BC 000000000000 100 100 0
BD C60100000000 100 100 0
C3 000000000000 100 100 0
C4 000000000000 100 100 0
C5 000000000000 100 100 0
C6 000000000000 100 100 0
C7 000000000000 100 100 0
CA 000000000000 100 100 0
CE 000000000000 100 100 0"),
new("M4-CT064M4SSD2", "0009", 16,
@"01 000000000000 100 100 50
05 000000000000 100 100 10
09 260000000000 100 100 1
0C 5A0000000000 100 100 1
AA 000000000000 100 100 10
AB 000000000000 100 100 1
AC 000000000000 100 100 1
AD 010000000000 100 100 10
AE 000000000000 100 100 1
B5 2B000E003A00 100 100 1
B7 000000000000 100 100 1
B8 000000000000 100 100 50
BB 000000000000 100 100 1
BC 000000000000 100 100 1
BD 310000000000 100 100 1
C2 000000000000 100 100 0
C3 000000000000 100 100 1
C4 000000000000 100 100 1
C5 000000000000 100 100 1
C6 000000000000 100 100 1
C7 000000000000 100 100 1
CA 000000000000 100 100 1
CE 000000000000 100 100 1"),
new("M4-CT128M4SSD2", "000F", 16,
@"01 000000000000 100 100 50
05 000000000000 100 100 10
09 CA1400000000 100 100 1
0C A30200000000 100 100 1
AA 000000000000 100 100 10
AB 000000000000 100 100 1
AC 000000000000 100 100 1
AD 1F0000000000 99 99 10
AE 140000000000 100 100 1
B5 12037C028E05 100 100 1
B7 000000000000 100 100 1
B8 000000000000 100 100 50
BB 000000000000 100 100 1
BC 000000000000 100 100 1
BD 510000000000 100 100 1
C2 000000000000 100 100 0
C3 000000000000 100 100 1
C4 000000000000 100 100 1
C5 000000000000 100 100 1
C6 000000000000 100 100 1
C7 000000000000 100 100 1
CA 010000000000 99 99 1
CE 000000000000 100 100 1 "),
new("Samsung SSD 840 PRO Series", "DXM05B0Q", 16,
@"05 000000000000 100 100 10
09 541200000000 99 99 0
0C 820500000000 98 98 0
B1 B90200000000 80 80 0
B3 000000000000 100 100 10
B5 000000000000 100 100 10
B6 000000000000 100 100 10
B7 000000000000 100 100 10
BB 000000000000 100 100 0
BE 1C0000000000 48 72 0
C3 000000000000 200 200 0
C7 020000000000 99 99 0
EB 690000000000 99 99 0
F1 A56AA1F60200 99 99 0")
};
private int _driveNumber;
public DebugSmart(int driveNumber)
{
_driveNumber = driveNumber;
}
public bool IsValid => true;
public void Close()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public bool EnableSmart()
{
if (_driveNumber < 0)
throw new ObjectDisposedException(nameof(DebugSmart));
return true;
}
public Kernel32.SMART_ATTRIBUTE[] ReadSmartData()
{
if (_driveNumber < 0)
throw new ObjectDisposedException(nameof(DebugSmart));
return _drives[_driveNumber].DriveAttributeValues;
}
public Kernel32.SMART_THRESHOLD[] ReadSmartThresholds()
{
if (_driveNumber < 0)
throw new ObjectDisposedException(nameof(DebugSmart));
return _drives[_driveNumber].DriveThresholdValues;
}
public bool ReadNameAndFirmwareRevision(out string name, out string firmwareRevision)
{
if (_driveNumber < 0)
throw new ObjectDisposedException(nameof(DebugSmart));
name = _drives[_driveNumber].Name;
firmwareRevision = _drives[_driveNumber].FirmwareVersion;
return true;
}
public void Dispose()
{
Close();
}
protected void Dispose(bool disposing)
{
if (disposing)
_driveNumber = -1;
}
private class Drive
{
public Drive(string name, string firmware, int idBase, string value)
{
Name = name;
FirmwareVersion = firmware;
string[] lines = value.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries);
DriveAttributeValues = new Kernel32.SMART_ATTRIBUTE[lines.Length];
var thresholds = new System.Collections.Generic.List<Kernel32.SMART_THRESHOLD>();
for (int i = 0; i < lines.Length; i++)
{
string[] array = lines[i].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (array.Length is not 4 and not 5)
throw new Exception();
var v = new Kernel32.SMART_ATTRIBUTE { Id = Convert.ToByte(array[0], idBase), RawValue = new byte[6] };
for (int j = 0; j < 6; j++)
{
v.RawValue[j] = Convert.ToByte(array[1].Substring(2 * j, 2), 16);
}
v.WorstValue = Convert.ToByte(array[2], 10);
v.CurrentValue = Convert.ToByte(array[3], 10);
DriveAttributeValues[i] = v;
if (array.Length == 5)
{
var t = new Kernel32.SMART_THRESHOLD { Id = v.Id, Threshold = Convert.ToByte(array[4], 10) };
thresholds.Add(t);
}
}
DriveThresholdValues = thresholds.ToArray();
}
public Kernel32.SMART_ATTRIBUTE[] DriveAttributeValues { get; }
public Kernel32.SMART_THRESHOLD[] DriveThresholdValues { get; }
public string FirmwareVersion { get; }
public string Name { get; }
}
}
}
#endif

View File

@@ -0,0 +1,88 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware.Storage;
[NamePrefix("")]
public class GenericHardDisk : AtaStorage
{
private static readonly List<SmartAttribute> _smartAttributes = new()
{
new SmartAttribute(0x01, SmartNames.ReadErrorRate),
new SmartAttribute(0x02, SmartNames.ThroughputPerformance),
new SmartAttribute(0x03, SmartNames.SpinUpTime),
new SmartAttribute(0x04, SmartNames.StartStopCount, RawToInt),
new SmartAttribute(0x05, SmartNames.ReallocatedSectorsCount),
new SmartAttribute(0x06, SmartNames.ReadChannelMargin),
new SmartAttribute(0x07, SmartNames.SeekErrorRate),
new SmartAttribute(0x08, SmartNames.SeekTimePerformance),
new SmartAttribute(0x09, SmartNames.PowerOnHours, RawToInt),
new SmartAttribute(0x0A, SmartNames.SpinRetryCount),
new SmartAttribute(0x0B, SmartNames.RecalibrationRetries),
new SmartAttribute(0x0C, SmartNames.PowerCycleCount, RawToInt),
new SmartAttribute(0x0D, SmartNames.SoftReadErrorRate),
new SmartAttribute(0xAA, SmartNames.Unknown),
new SmartAttribute(0xAB, SmartNames.Unknown),
new SmartAttribute(0xAC, SmartNames.Unknown),
new SmartAttribute(0xB7, SmartNames.SataDownshiftErrorCount, RawToInt),
new SmartAttribute(0xB8, SmartNames.EndToEndError),
new SmartAttribute(0xB9, SmartNames.HeadStability),
new SmartAttribute(0xBA, SmartNames.InducedOpVibrationDetection),
new SmartAttribute(0xBB, SmartNames.ReportedUncorrectableErrors, RawToInt),
new SmartAttribute(0xBC, SmartNames.CommandTimeout, RawToInt),
new SmartAttribute(0xBD, SmartNames.HighFlyWrites),
new SmartAttribute(0xBF, SmartNames.GSenseErrorRate),
new SmartAttribute(0xC0, SmartNames.EmergencyRetractCycleCount),
new SmartAttribute(0xC1, SmartNames.LoadCycleCount),
new SmartAttribute(0xC3, SmartNames.HardwareEccRecovered),
new SmartAttribute(0xC4, SmartNames.ReallocationEventCount),
new SmartAttribute(0xC5, SmartNames.CurrentPendingSectorCount),
new SmartAttribute(0xC6, SmartNames.UncorrectableSectorCount),
new SmartAttribute(0xC7, SmartNames.UltraDmaCrcErrorCount),
new SmartAttribute(0xC8, SmartNames.WriteErrorRate),
new SmartAttribute(0xCA, SmartNames.DataAddressMarkErrors),
new SmartAttribute(0xCB, SmartNames.RunOutCancel),
new SmartAttribute(0xCC, SmartNames.SoftEccCorrection),
new SmartAttribute(0xCD, SmartNames.ThermalAsperityRate),
new SmartAttribute(0xCE, SmartNames.FlyingHeight),
new SmartAttribute(0xCF, SmartNames.SpinHighCurrent),
new SmartAttribute(0xD0, SmartNames.SpinBuzz),
new SmartAttribute(0xD1, SmartNames.OfflineSeekPerformance),
new SmartAttribute(0xD3, SmartNames.VibrationDuringWrite),
new SmartAttribute(0xD4, SmartNames.ShockDuringWrite),
new SmartAttribute(0xDC, SmartNames.DiskShift),
new SmartAttribute(0xDD, SmartNames.AlternativeGSenseErrorRate),
new SmartAttribute(0xDE, SmartNames.LoadedHours),
new SmartAttribute(0xDF, SmartNames.LoadUnloadRetryCount),
new SmartAttribute(0xE0, SmartNames.LoadFriction),
new SmartAttribute(0xE1, SmartNames.LoadUnloadCycleCount),
new SmartAttribute(0xE2, SmartNames.LoadInTime),
new SmartAttribute(0xE3, SmartNames.TorqueAmplificationCount),
new SmartAttribute(0xE4, SmartNames.PowerOffRetractCycle),
new SmartAttribute(0xE6, SmartNames.GmrHeadAmplitude),
new SmartAttribute(0xE8, SmartNames.EnduranceRemaining),
new SmartAttribute(0xE9, SmartNames.PowerOnHours),
new SmartAttribute(0xF0, SmartNames.HeadFlyingHours),
new SmartAttribute(0xF1, SmartNames.TotalLbasWritten),
new SmartAttribute(0xF2, SmartNames.TotalLbasRead),
new SmartAttribute(0xFA, SmartNames.ReadErrorRetryRate),
new SmartAttribute(0xFE, SmartNames.FreeFallProtection),
new SmartAttribute(0xC2, SmartNames.Temperature, (r, _, p) => r[0] + (p?[0].Value ?? 0),
SensorType.Temperature, 0, SmartNames.Temperature, false,
new[] { new ParameterDescription("Offset [°C]", "Temperature offset of the thermal sensor.\n" + "Temperature = Value + Offset.", 0) }),
new SmartAttribute(0xE7, SmartNames.Temperature, (r, _, p) => r[0] + (p?[0].Value ?? 0),
SensorType.Temperature, 0, SmartNames.Temperature, false,
new[] { new ParameterDescription("Offset [°C]", "Temperature offset of the thermal sensor.\n" + "Temperature = Value + Offset.", 0) }),
new SmartAttribute(0xBE, SmartNames.TemperatureDifferenceFrom100, (r, _, p) => r[0] + (p?[0].Value ?? 0),
SensorType.Temperature, 0, "Temperature", false,
new[] { new ParameterDescription("Offset [°C]", "Temperature offset of the thermal sensor.\n" + "Temperature = Value + Offset.", 0) })
};
internal GenericHardDisk(StorageInfo storageInfo, ISmart smart, string name, string firmwareRevision, int index, ISettings settings)
: base(storageInfo, smart, name, firmwareRevision, "hdd", index, _smartAttributes, settings) { }
}

View File

@@ -0,0 +1,18 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
internal interface INVMeDrive
{
SafeHandle Identify(StorageInfo storageInfo);
bool IdentifyController(SafeHandle hDevice, out Kernel32.NVME_IDENTIFY_CONTROLLER_DATA data);
bool HealthInfoLog(SafeHandle hDevice, out Kernel32.NVME_HEALTH_INFO_LOG data);
}

View File

@@ -0,0 +1,25 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
public interface ISmart : IDisposable
{
bool IsValid { get; }
void Close();
bool EnableSmart();
Kernel32.SMART_ATTRIBUTE[] ReadSmartData();
Kernel32.SMART_THRESHOLD[] ReadSmartThresholds();
bool ReadNameAndFirmwareRevision(out string name, out string firmwareRevision);
}

View File

@@ -0,0 +1,187 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using System.Text;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
public sealed class NVMeGeneric : AbstractStorage
{
private const ulong Scale = 1000000;
private const ulong Units = 512;
private readonly NVMeInfo _info;
private readonly List<NVMeSensor> _sensors = new();
/// <summary>
/// Gets the SMART data.
/// </summary>
public NVMeSmart Smart { get; }
private NVMeGeneric(StorageInfo storageInfo, NVMeInfo info, int index, ISettings settings)
: base(storageInfo, info.Model, info.Revision, "nvme", index, settings)
{
Smart = new NVMeSmart(storageInfo);
_info = info;
CreateSensors();
}
private static NVMeInfo GetDeviceInfo(StorageInfo storageInfo)
{
var smart = new NVMeSmart(storageInfo);
return smart.GetInfo();
}
internal static AbstractStorage CreateInstance(StorageInfo storageInfo, ISettings settings)
{
NVMeInfo nvmeInfo = GetDeviceInfo(storageInfo);
return nvmeInfo == null ? null : new NVMeGeneric(storageInfo, nvmeInfo, storageInfo.Index, settings);
}
protected override void CreateSensors()
{
NVMeHealthInfo log = Smart.GetHealthInfo();
if (log != null)
{
AddSensor("Temperature", 0, false, SensorType.Temperature, health => health.Temperature);
AddSensor("Available Spare", 1, false, SensorType.Level, health => health.AvailableSpare);
AddSensor("Available Spare Threshold", 2, false, SensorType.Level, health => health.AvailableSpareThreshold);
AddSensor("Percentage Used", 3, false, SensorType.Level, health => health.PercentageUsed);
AddSensor("Data Read", 4, false, SensorType.Data, health => UnitsToData(health.DataUnitRead));
AddSensor("Data Written", 5, false, SensorType.Data, health => UnitsToData(health.DataUnitWritten));
int sensorIdx = 6;
for (int i = 0; i < log.TemperatureSensors.Length; i++)
{
int idx = i;
if (log.TemperatureSensors[idx] > short.MinValue)
{
AddSensor("Temperature " + (idx + 1), sensorIdx, false, SensorType.Temperature, health => health.TemperatureSensors[idx]);
sensorIdx++;
}
}
}
base.CreateSensors();
}
private void AddSensor(string name, int index, bool defaultHidden, SensorType sensorType, GetSensorValue getValue)
{
var sensor = new NVMeSensor(name, index, defaultHidden, sensorType, this, _settings, getValue)
{
Value = 0
};
ActivateSensor(sensor);
_sensors.Add(sensor);
}
private static float UnitsToData(ulong u)
{
// one unit is 512 * 1000 bytes, return in GB (not GiB)
return Units * u / Scale;
}
protected override void UpdateSensors()
{
NVMeHealthInfo health = Smart.GetHealthInfo();
if (health == null)
return;
foreach (NVMeSensor sensor in _sensors)
sensor.Update(health);
}
protected override void GetReport(StringBuilder r)
{
if (_info == null)
return;
r.AppendLine("PCI Vendor ID: 0x" + _info.VID.ToString("x04"));
if (_info.VID != _info.SSVID)
r.AppendLine("PCI Subsystem Vendor ID: 0x" + _info.VID.ToString("x04"));
r.AppendLine("IEEE OUI Identifier: 0x" + _info.IEEE[2].ToString("x02") + _info.IEEE[1].ToString("x02") + _info.IEEE[0].ToString("x02"));
r.AppendLine("Total NVM Capacity: " + _info.TotalCapacity);
r.AppendLine("Unallocated NVM Capacity: " + _info.UnallocatedCapacity);
r.AppendLine("Controller ID: " + _info.ControllerId);
r.AppendLine("Number of Namespaces: " + _info.NumberNamespaces);
NVMeHealthInfo health = Smart.GetHealthInfo();
if (health == null)
return;
if (health.CriticalWarning == Kernel32.NVME_CRITICAL_WARNING.None)
r.AppendLine("Critical Warning: -");
else
{
if ((health.CriticalWarning & Kernel32.NVME_CRITICAL_WARNING.AvailableSpaceLow) != 0)
r.AppendLine("Critical Warning: the available spare space has fallen below the threshold.");
if ((health.CriticalWarning & Kernel32.NVME_CRITICAL_WARNING.TemperatureThreshold) != 0)
r.AppendLine("Critical Warning: a temperature is above an over temperature threshold or below an under temperature threshold.");
if ((health.CriticalWarning & Kernel32.NVME_CRITICAL_WARNING.ReliabilityDegraded) != 0)
r.AppendLine("Critical Warning: the device reliability has been degraded due to significant media related errors or any internal error that degrades device reliability.");
if ((health.CriticalWarning & Kernel32.NVME_CRITICAL_WARNING.ReadOnly) != 0)
r.AppendLine("Critical Warning: the media has been placed in read only mode.");
if ((health.CriticalWarning & Kernel32.NVME_CRITICAL_WARNING.VolatileMemoryBackupDeviceFailed) != 0)
r.AppendLine("Critical Warning: the volatile memory backup device has failed.");
}
r.AppendLine("Temperature: " + health.Temperature + " Celsius");
r.AppendLine("Available Spare: " + health.AvailableSpare + "%");
r.AppendLine("Available Spare Threshold: " + health.AvailableSpareThreshold + "%");
r.AppendLine("Percentage Used: " + health.PercentageUsed + "%");
r.AppendLine("Data Units Read: " + health.DataUnitRead);
r.AppendLine("Data Units Written: " + health.DataUnitWritten);
r.AppendLine("Host Read Commands: " + health.HostReadCommands);
r.AppendLine("Host Write Commands: " + health.HostWriteCommands);
r.AppendLine("Controller Busy Time: " + health.ControllerBusyTime);
r.AppendLine("Power Cycles: " + health.PowerCycle);
r.AppendLine("Power On Hours: " + health.PowerOnHours);
r.AppendLine("Unsafe Shutdowns: " + health.UnsafeShutdowns);
r.AppendLine("Media Errors: " + health.MediaErrors);
r.AppendLine("Number of Error Information Log Entries: " + health.ErrorInfoLogEntryCount);
r.AppendLine("Warning Composite Temperature Time: " + health.WarningCompositeTemperatureTime);
r.AppendLine("Critical Composite Temperature Time: " + health.CriticalCompositeTemperatureTime);
for (int i = 0; i < health.TemperatureSensors.Length; i++)
{
if (health.TemperatureSensors[i] > short.MinValue)
r.AppendLine("Temperature Sensor " + (i + 1) + ": " + health.TemperatureSensors[i] + " Celsius");
}
}
public override void Close()
{
Smart?.Close();
base.Close();
}
private delegate float GetSensorValue(NVMeHealthInfo health);
private class NVMeSensor : Sensor
{
private readonly GetSensorValue _getValue;
public NVMeSensor(string name, int index, bool defaultHidden, SensorType sensorType, Hardware hardware, ISettings settings, GetSensorValue getValue)
: base(name, index, defaultHidden, sensorType, hardware, null, settings)
{
_getValue = getValue;
}
public void Update(NVMeHealthInfo health)
{
float v = _getValue(health);
if (SensorType == SensorType.Temperature && v is < -1000 or > 1000)
return;
Value = v;
}
}
}

View File

@@ -0,0 +1,47 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
public abstract class NVMeHealthInfo
{
public byte AvailableSpare { get; protected set; }
public byte AvailableSpareThreshold { get; protected set; }
public ulong ControllerBusyTime { get; protected set; }
public uint CriticalCompositeTemperatureTime { get; protected set; }
public Kernel32.NVME_CRITICAL_WARNING CriticalWarning { get; protected set; }
public ulong DataUnitRead { get; protected set; }
public ulong DataUnitWritten { get; protected set; }
public ulong ErrorInfoLogEntryCount { get; protected set; }
public ulong HostReadCommands { get; protected set; }
public ulong HostWriteCommands { get; protected set; }
public ulong MediaErrors { get; protected set; }
public byte PercentageUsed { get; protected set; }
public ulong PowerCycle { get; protected set; }
public ulong PowerOnHours { get; protected set; }
public short Temperature { get; protected set; }
public short[] TemperatureSensors { get; protected set; }
public ulong UnsafeShutdowns { get; protected set; }
public uint WarningCompositeTemperatureTime { get; protected set; }
}

View File

@@ -0,0 +1,31 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Storage;
public abstract class NVMeInfo
{
public ushort ControllerId { get; protected set; }
public byte[] IEEE { get; protected set; }
public int Index { get; protected set; }
public string Model { get; protected set; }
public uint NumberNamespaces { get; protected set; }
public string Revision { get; protected set; }
public string Serial { get; protected set; }
public ushort SSVID { get; protected set; }
public ulong TotalCapacity { get; protected set; }
public ulong UnallocatedCapacity { get; protected set; }
public ushort VID { get; protected set; }
}

View File

@@ -0,0 +1,156 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using System.Text;
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware.Storage;
internal class NVMeIntel : INVMeDrive
{
//intel nvme access
public SafeHandle Identify(StorageInfo storageInfo)
{
return NVMeWindows.IdentifyDevice(storageInfo);
}
public bool IdentifyController(SafeHandle hDevice, out Kernel32.NVME_IDENTIFY_CONTROLLER_DATA data)
{
data = Kernel32.CreateStruct<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>();
if (hDevice?.IsInvalid != false)
return false;
bool result = false;
Kernel32.NVME_PASS_THROUGH_IOCTL passThrough = Kernel32.CreateStruct<Kernel32.NVME_PASS_THROUGH_IOCTL>();
passThrough.srb.HeaderLenght = (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.srb.Signature = Encoding.ASCII.GetBytes(Kernel32.IntelNVMeMiniPortSignature1);
passThrough.srb.Timeout = 10;
passThrough.srb.ControlCode = Kernel32.NVME_PASS_THROUGH_SRB_IO_CODE;
passThrough.srb.ReturnCode = 0;
passThrough.srb.Length = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>() - (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.NVMeCmd = new uint[16];
passThrough.NVMeCmd[0] = 6; //identify
passThrough.NVMeCmd[10] = 1; //return to host
passThrough.Direction = Kernel32.NVME_DIRECTION.NVME_FROM_DEV_TO_HOST;
passThrough.QueueId = 0;
passThrough.DataBufferLen = (uint)passThrough.DataBuffer.Length;
passThrough.MetaDataLen = 0;
passThrough.ReturnBufferLen = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
int length = Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(passThrough, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_SCSI_MINIPORT, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
IntPtr offset = Marshal.OffsetOf<Kernel32.NVME_PASS_THROUGH_IOCTL>(nameof(Kernel32.NVME_PASS_THROUGH_IOCTL.DataBuffer));
var newPtr = IntPtr.Add(buffer, offset.ToInt32());
int finalSize = Marshal.SizeOf<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>();
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>());
Kernel32.RtlZeroMemory(ptr, finalSize);
int len = Math.Min(finalSize, passThrough.DataBuffer.Length);
Kernel32.RtlCopyMemory(ptr, newPtr, (uint)len);
Marshal.FreeHGlobal(buffer);
data = Marshal.PtrToStructure<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>(ptr);
Marshal.FreeHGlobal(ptr);
result = true;
}
else
{
Marshal.FreeHGlobal(buffer);
}
return result;
}
public bool HealthInfoLog(SafeHandle hDevice, out Kernel32.NVME_HEALTH_INFO_LOG data)
{
data = Kernel32.CreateStruct<Kernel32.NVME_HEALTH_INFO_LOG>();
if (hDevice?.IsInvalid != false)
return false;
bool result = false;
Kernel32.NVME_PASS_THROUGH_IOCTL passThrough = Kernel32.CreateStruct<Kernel32.NVME_PASS_THROUGH_IOCTL>();
passThrough.srb.HeaderLenght = (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.srb.Signature = Encoding.ASCII.GetBytes(Kernel32.IntelNVMeMiniPortSignature1);
passThrough.srb.Timeout = 10;
passThrough.srb.ControlCode = Kernel32.NVME_PASS_THROUGH_SRB_IO_CODE;
passThrough.srb.ReturnCode = 0;
passThrough.srb.Length = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>() - (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.NVMeCmd[0] = (uint)Kernel32.STORAGE_PROTOCOL_NVME_DATA_TYPE.NVMeDataTypeLogPage; // GetLogPage
passThrough.NVMeCmd[1] = 0xFFFFFFFF; // address
passThrough.NVMeCmd[10] = 0x007f0002; // uint cdw10 = 0x000000002 | (((size / 4) - 1) << 16);
passThrough.Direction = Kernel32.NVME_DIRECTION.NVME_FROM_DEV_TO_HOST;
passThrough.QueueId = 0;
passThrough.DataBufferLen = (uint)passThrough.DataBuffer.Length;
passThrough.MetaDataLen = 0;
passThrough.ReturnBufferLen = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
int length = Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(passThrough, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_SCSI_MINIPORT, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
IntPtr offset = Marshal.OffsetOf<Kernel32.NVME_PASS_THROUGH_IOCTL>(nameof(Kernel32.NVME_PASS_THROUGH_IOCTL.DataBuffer));
var newPtr = IntPtr.Add(buffer, offset.ToInt32());
data = Marshal.PtrToStructure<Kernel32.NVME_HEALTH_INFO_LOG>(newPtr);
Marshal.FreeHGlobal(buffer);
result = true;
}
else
{
Marshal.FreeHGlobal(buffer);
}
return result;
}
public static SafeHandle IdentifyDevice(StorageInfo storageInfo)
{
SafeFileHandle handle = Kernel32.OpenDevice(storageInfo.Scsi);
if (handle?.IsInvalid != false)
return null;
Kernel32.NVME_PASS_THROUGH_IOCTL passThrough = Kernel32.CreateStruct<Kernel32.NVME_PASS_THROUGH_IOCTL>();
passThrough.srb.HeaderLenght = (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.srb.Signature = Encoding.ASCII.GetBytes(Kernel32.IntelNVMeMiniPortSignature1);
passThrough.srb.Timeout = 10;
passThrough.srb.ControlCode = Kernel32.NVME_PASS_THROUGH_SRB_IO_CODE;
passThrough.srb.ReturnCode = 0;
passThrough.srb.Length = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>() - (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.NVMeCmd = new uint[16];
passThrough.NVMeCmd[0] = 6; //identify
passThrough.NVMeCmd[10] = 1; //return to host
passThrough.Direction = Kernel32.NVME_DIRECTION.NVME_FROM_DEV_TO_HOST;
passThrough.QueueId = 0;
passThrough.DataBufferLen = (uint)passThrough.DataBuffer.Length;
passThrough.MetaDataLen = 0;
passThrough.ReturnBufferLen = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
int length = Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(passThrough, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(handle, Kernel32.IOCTL.IOCTL_SCSI_MINIPORT, buffer, length, buffer, length, out _, IntPtr.Zero);
Marshal.FreeHGlobal(buffer);
if (validTransfer) { }
else
{
handle.Close();
handle = null;
}
return handle;
}
}

View File

@@ -0,0 +1,157 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using System.Text;
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware.Storage;
internal class NVMeIntelRst : INVMeDrive
{
//intel RST (raid) nvme access
public SafeHandle Identify(StorageInfo storageInfo)
{
return NVMeWindows.IdentifyDevice(storageInfo);
}
public bool IdentifyController(SafeHandle hDevice, out Kernel32.NVME_IDENTIFY_CONTROLLER_DATA data)
{
data = Kernel32.CreateStruct<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>();
if (hDevice?.IsInvalid != false)
return false;
bool result = false;
Kernel32.NVME_PASS_THROUGH_IOCTL passThrough = Kernel32.CreateStruct<Kernel32.NVME_PASS_THROUGH_IOCTL>();
passThrough.srb.HeaderLenght = (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.srb.Signature = Encoding.ASCII.GetBytes(Kernel32.IntelNVMeMiniPortSignature2);
passThrough.srb.Timeout = 10;
passThrough.srb.ControlCode = Kernel32.NVME_PASS_THROUGH_SRB_IO_CODE;
passThrough.srb.ReturnCode = 0;
passThrough.srb.Length = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>() - (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.NVMeCmd = new uint[16];
passThrough.NVMeCmd[0] = 6; //identify
passThrough.NVMeCmd[10] = 1; //return to host
passThrough.Direction = Kernel32.NVME_DIRECTION.NVME_FROM_DEV_TO_HOST;
passThrough.QueueId = 0;
passThrough.DataBufferLen = (uint)passThrough.DataBuffer.Length;
passThrough.MetaDataLen = 0;
passThrough.ReturnBufferLen = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
int length = Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(passThrough, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_SCSI_MINIPORT, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
IntPtr offset = Marshal.OffsetOf<Kernel32.NVME_PASS_THROUGH_IOCTL>(nameof(Kernel32.NVME_PASS_THROUGH_IOCTL.DataBuffer));
IntPtr newPtr = IntPtr.Add(buffer, offset.ToInt32());
int finalSize = Marshal.SizeOf<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>();
IntPtr ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>());
Kernel32.RtlZeroMemory(ptr, finalSize);
int len = Math.Min(finalSize, passThrough.DataBuffer.Length);
Kernel32.RtlCopyMemory(ptr, newPtr, (uint)len);
Marshal.FreeHGlobal(buffer);
data = Marshal.PtrToStructure<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>(ptr);
Marshal.FreeHGlobal(ptr);
result = true;
}
else
{
Marshal.FreeHGlobal(buffer);
}
return result;
}
public bool HealthInfoLog(SafeHandle hDevice, out Kernel32.NVME_HEALTH_INFO_LOG data)
{
data = Kernel32.CreateStruct<Kernel32.NVME_HEALTH_INFO_LOG>();
if (hDevice?.IsInvalid != false)
return false;
bool result = false;
Kernel32.NVME_PASS_THROUGH_IOCTL passThrough = Kernel32.CreateStruct<Kernel32.NVME_PASS_THROUGH_IOCTL>();
passThrough.srb.HeaderLenght = (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.srb.Signature = Encoding.ASCII.GetBytes(Kernel32.IntelNVMeMiniPortSignature2);
passThrough.srb.Timeout = 10;
passThrough.srb.ControlCode = Kernel32.NVME_PASS_THROUGH_SRB_IO_CODE;
passThrough.srb.ReturnCode = 0;
passThrough.srb.Length = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>() - (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.NVMeCmd[0] = (uint)Kernel32.STORAGE_PROTOCOL_NVME_DATA_TYPE.NVMeDataTypeLogPage; // GetLogPage
passThrough.NVMeCmd[1] = 0xFFFFFFFF; // address
passThrough.NVMeCmd[10] = 0x007f0002; // uint cdw10 = 0x000000002 | (((size / 4) - 1) << 16);
passThrough.Direction = Kernel32.NVME_DIRECTION.NVME_FROM_DEV_TO_HOST;
passThrough.QueueId = 0;
passThrough.DataBufferLen = (uint)passThrough.DataBuffer.Length;
passThrough.MetaDataLen = 0;
passThrough.ReturnBufferLen = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
int length = Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(passThrough, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_SCSI_MINIPORT, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
IntPtr offset = Marshal.OffsetOf<Kernel32.NVME_PASS_THROUGH_IOCTL>(nameof(Kernel32.NVME_PASS_THROUGH_IOCTL.DataBuffer));
IntPtr newPtr = IntPtr.Add(buffer, offset.ToInt32());
data = Marshal.PtrToStructure<Kernel32.NVME_HEALTH_INFO_LOG>(newPtr);
Marshal.FreeHGlobal(buffer);
result = true;
}
else
{
Marshal.FreeHGlobal(buffer);
}
return result;
}
public static SafeHandle IdentifyDevice(StorageInfo storageInfo)
{
SafeFileHandle handle = Kernel32.OpenDevice(storageInfo.Scsi);
if (handle?.IsInvalid != false)
return null;
Kernel32.NVME_PASS_THROUGH_IOCTL passThrough = Kernel32.CreateStruct<Kernel32.NVME_PASS_THROUGH_IOCTL>();
passThrough.srb.HeaderLenght = (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.srb.Signature = Encoding.ASCII.GetBytes(Kernel32.IntelNVMeMiniPortSignature2);
passThrough.srb.Timeout = 10;
passThrough.srb.ControlCode = Kernel32.NVME_PASS_THROUGH_SRB_IO_CODE;
passThrough.srb.ReturnCode = 0;
passThrough.srb.Length = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>() - (uint)Marshal.SizeOf<Kernel32.SRB_IO_CONTROL>();
passThrough.NVMeCmd = new uint[16];
passThrough.NVMeCmd[0] = 6; //identify
passThrough.NVMeCmd[10] = 1; //return to host
passThrough.Direction = Kernel32.NVME_DIRECTION.NVME_FROM_DEV_TO_HOST;
passThrough.QueueId = 0;
passThrough.DataBufferLen = (uint)passThrough.DataBuffer.Length;
passThrough.MetaDataLen = 0;
passThrough.ReturnBufferLen = (uint)Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
int length = Marshal.SizeOf<Kernel32.NVME_PASS_THROUGH_IOCTL>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(passThrough, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(handle, Kernel32.IOCTL.IOCTL_SCSI_MINIPORT, buffer, length, buffer, length, out _, IntPtr.Zero);
Marshal.FreeHGlobal(buffer);
if (validTransfer) { }
else
{
handle.Close();
handle = null;
}
return handle;
}
}

View File

@@ -0,0 +1,260 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Linq;
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware.Storage;
internal class NVMeSamsung : INVMeDrive
{
//samsung nvme access
//https://github.com/hiyohiyo/CrystalDiskInfo
//https://github.com/hiyohiyo/CrystalDiskInfo/blob/master/AtaSmart.cpp
public SafeHandle Identify(StorageInfo storageInfo)
{
return NVMeWindows.IdentifyDevice(storageInfo);
}
public bool IdentifyController(SafeHandle hDevice, out Kernel32.NVME_IDENTIFY_CONTROLLER_DATA data)
{
data = Kernel32.CreateStruct<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>();
if (hDevice?.IsInvalid != false)
return false;
bool result = false;
Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS buffers = Kernel32.CreateStruct<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffers.Spt.Length = (ushort)Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH>();
buffers.Spt.PathId = 0;
buffers.Spt.TargetId = 0;
buffers.Spt.Lun = 0;
buffers.Spt.SenseInfoLength = 24;
buffers.Spt.DataTransferLength = (uint)buffers.DataBuf.Length;
buffers.Spt.TimeOutValue = 2;
buffers.Spt.DataBufferOffset = Marshal.OffsetOf(typeof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS), nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.DataBuf));
buffers.Spt.SenseInfoOffset = (uint)Marshal.OffsetOf(typeof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS), nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.SenseBuf));
buffers.Spt.CdbLength = 16;
buffers.Spt.Cdb[0] = 0xB5; // SECURITY PROTOCOL IN
buffers.Spt.Cdb[1] = 0xFE; // Samsung Protocol
buffers.Spt.Cdb[3] = 5; // Identify
buffers.Spt.Cdb[8] = 0; // Transfer Length
buffers.Spt.Cdb[9] = 0x40; // Transfer Length
buffers.Spt.DataIn = (byte)Kernel32.SCSI_IOCTL_DATA.SCSI_IOCTL_DATA_OUT;
buffers.DataBuf[0] = 1;
int length = Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(buffers, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_SCSI_PASS_THROUGH, buffer, length, buffer, length, out _, IntPtr.Zero);
Marshal.FreeHGlobal(buffer);
if (validTransfer)
{
//read data from samsung SSD
buffers = Kernel32.CreateStruct<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffers.Spt.Length = (ushort)Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH>();
buffers.Spt.PathId = 0;
buffers.Spt.TargetId = 0;
buffers.Spt.Lun = 0;
buffers.Spt.SenseInfoLength = 24;
buffers.Spt.DataTransferLength = (uint)buffers.DataBuf.Length;
buffers.Spt.TimeOutValue = 2;
buffers.Spt.DataBufferOffset = Marshal.OffsetOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.DataBuf));
buffers.Spt.SenseInfoOffset = (uint)Marshal.OffsetOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.SenseBuf));
buffers.Spt.CdbLength = 16;
buffers.Spt.Cdb[0] = 0xA2; // SECURITY PROTOCOL IN
buffers.Spt.Cdb[1] = 0xFE; // Samsung Protocol
buffers.Spt.Cdb[3] = 5; // Identify
buffers.Spt.Cdb[8] = 2; // Transfer Length (high)
buffers.Spt.Cdb[9] = 0; // Transfer Length (low)
buffers.Spt.DataIn = (byte)Kernel32.SCSI_IOCTL_DATA.SCSI_IOCTL_DATA_IN;
length = Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(buffers, buffer, false);
validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_SCSI_PASS_THROUGH, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer && buffers.DataBuf.Any(x => x != 0))
{
IntPtr offset = Marshal.OffsetOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.DataBuf));
IntPtr newPtr = IntPtr.Add(buffer, offset.ToInt32());
data = Marshal.PtrToStructure<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>(newPtr);
Marshal.FreeHGlobal(buffer);
result = true;
}
else
{
Marshal.FreeHGlobal(buffer);
}
}
return result;
}
public bool HealthInfoLog(SafeHandle hDevice, out Kernel32.NVME_HEALTH_INFO_LOG data)
{
data = Kernel32.CreateStruct<Kernel32.NVME_HEALTH_INFO_LOG>();
if (hDevice?.IsInvalid != false)
return false;
bool result = false;
Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS buffers = Kernel32.CreateStruct<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffers.Spt.Length = (ushort)Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH>();
buffers.Spt.PathId = 0;
buffers.Spt.TargetId = 0;
buffers.Spt.Lun = 0;
buffers.Spt.SenseInfoLength = 24;
buffers.Spt.DataTransferLength = (uint)buffers.DataBuf.Length;
buffers.Spt.TimeOutValue = 2;
buffers.Spt.DataBufferOffset = Marshal.OffsetOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.DataBuf));
buffers.Spt.SenseInfoOffset = (uint)Marshal.OffsetOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.SenseBuf));
buffers.Spt.CdbLength = 16;
buffers.Spt.Cdb[0] = 0xB5; // SECURITY PROTOCOL IN
buffers.Spt.Cdb[1] = 0xFE; // Samsung Protocol
buffers.Spt.Cdb[3] = 6; // Log Data
buffers.Spt.Cdb[8] = 0; // Transfer Length
buffers.Spt.Cdb[9] = 0x40; // Transfer Length
buffers.Spt.DataIn = (byte)Kernel32.SCSI_IOCTL_DATA.SCSI_IOCTL_DATA_OUT;
buffers.DataBuf[0] = 2;
buffers.DataBuf[4] = 0xff;
buffers.DataBuf[5] = 0xff;
buffers.DataBuf[6] = 0xff;
buffers.DataBuf[7] = 0xff;
int length = Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(buffers, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_SCSI_PASS_THROUGH, buffer, length, buffer, length, out _, IntPtr.Zero);
Marshal.FreeHGlobal(buffer);
if (validTransfer)
{
//read data from samsung SSD
buffers = Kernel32.CreateStruct<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffers.Spt.Length = (ushort)Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH>();
buffers.Spt.PathId = 0;
buffers.Spt.TargetId = 0;
buffers.Spt.Lun = 0;
buffers.Spt.SenseInfoLength = 24;
buffers.Spt.DataTransferLength = (uint)buffers.DataBuf.Length;
buffers.Spt.TimeOutValue = 2;
buffers.Spt.DataBufferOffset = Marshal.OffsetOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.DataBuf));
buffers.Spt.SenseInfoOffset = (uint)Marshal.OffsetOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.SenseBuf));
buffers.Spt.CdbLength = 16;
buffers.Spt.Cdb[0] = 0xA2; // SECURITY PROTOCOL IN
buffers.Spt.Cdb[1] = 0xFE; // Samsung Protocol
buffers.Spt.Cdb[3] = 6; // Log Data
buffers.Spt.Cdb[8] = 2; // Transfer Length (high)
buffers.Spt.Cdb[9] = 0; // Transfer Length (low)
buffers.Spt.DataIn = (byte)Kernel32.SCSI_IOCTL_DATA.SCSI_IOCTL_DATA_IN;
length = Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(buffers, buffer, false);
validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_SCSI_PASS_THROUGH, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
IntPtr offset = Marshal.OffsetOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.DataBuf));
IntPtr newPtr = IntPtr.Add(buffer, offset.ToInt32());
data = Marshal.PtrToStructure<Kernel32.NVME_HEALTH_INFO_LOG>(newPtr);
Marshal.FreeHGlobal(buffer);
result = true;
}
else
{
Marshal.FreeHGlobal(buffer);
}
}
return result;
}
public static SafeHandle IdentifyDevice(StorageInfo storageInfo)
{
SafeFileHandle handle = Kernel32.OpenDevice(storageInfo.DeviceId);
if (handle?.IsInvalid != false)
return null;
Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS buffers = Kernel32.CreateStruct<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffers.Spt.Length = (ushort)Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH>();
buffers.Spt.PathId = 0;
buffers.Spt.TargetId = 0;
buffers.Spt.Lun = 0;
buffers.Spt.SenseInfoLength = 24;
buffers.Spt.DataTransferLength = (uint)buffers.DataBuf.Length;
buffers.Spt.TimeOutValue = 2;
buffers.Spt.DataBufferOffset = Marshal.OffsetOf(typeof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS), nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.DataBuf));
buffers.Spt.SenseInfoOffset = (uint)Marshal.OffsetOf(typeof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS), nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.SenseBuf));
buffers.Spt.CdbLength = 16;
buffers.Spt.Cdb[0] = 0xB5; // SECURITY PROTOCOL IN
buffers.Spt.Cdb[1] = 0xFE; // Samsung Protocol
buffers.Spt.Cdb[3] = 5; // Identify
buffers.Spt.Cdb[8] = 0; // Transfer Length
buffers.Spt.Cdb[9] = 0x40;
buffers.Spt.DataIn = (byte)Kernel32.SCSI_IOCTL_DATA.SCSI_IOCTL_DATA_OUT;
buffers.DataBuf[0] = 1;
int length = Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(buffers, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(handle, Kernel32.IOCTL.IOCTL_SCSI_PASS_THROUGH, buffer, length, buffer, length, out _, IntPtr.Zero);
Marshal.FreeHGlobal(buffer);
if (validTransfer)
{
//read data from samsung SSD
buffers = Kernel32.CreateStruct<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffers.Spt.Length = (ushort)Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH>();
buffers.Spt.PathId = 0;
buffers.Spt.TargetId = 0;
buffers.Spt.Lun = 0;
buffers.Spt.SenseInfoLength = 24;
buffers.Spt.DataTransferLength = (uint)buffers.DataBuf.Length;
buffers.Spt.TimeOutValue = 2;
buffers.Spt.DataBufferOffset = Marshal.OffsetOf(typeof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS), nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.DataBuf));
buffers.Spt.SenseInfoOffset = (uint)Marshal.OffsetOf(typeof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS), nameof(Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS.SenseBuf));
buffers.Spt.CdbLength = 16;
buffers.Spt.Cdb[0] = 0xA2; // SECURITY PROTOCOL IN
buffers.Spt.Cdb[1] = 0xFE; // Samsung Protocol
buffers.Spt.Cdb[3] = 5; // Identify
buffers.Spt.Cdb[8] = 2; // Transfer Length
buffers.Spt.Cdb[9] = 0;
buffers.Spt.DataIn = (byte)Kernel32.SCSI_IOCTL_DATA.SCSI_IOCTL_DATA_IN;
length = Marshal.SizeOf<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>();
buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(buffers, buffer, false);
validTransfer = Kernel32.DeviceIoControl(handle, Kernel32.IOCTL.IOCTL_SCSI_PASS_THROUGH, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS result = Marshal.PtrToStructure<Kernel32.SCSI_PASS_THROUGH_WITH_BUFFERS>(buffer);
if (result.DataBuf.All(x => x == 0))
{
handle.Close();
handle = null;
}
}
else
{
handle.Close();
handle = null;
}
Marshal.FreeHGlobal(buffer);
}
return handle;
}
}

View File

@@ -0,0 +1,190 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using System.Text;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
public class NVMeSmart : IDisposable
{
private readonly int _driveNumber;
private readonly SafeHandle _handle;
internal NVMeSmart(StorageInfo storageInfo)
{
_driveNumber = storageInfo.Index;
NVMeDrive = null;
string name = storageInfo.Name;
// Test Samsung protocol.
if (NVMeDrive == null && name.IndexOf("Samsung", StringComparison.OrdinalIgnoreCase) > -1)
{
_handle = NVMeSamsung.IdentifyDevice(storageInfo);
if (_handle != null)
{
NVMeDrive = new NVMeSamsung();
if (!NVMeDrive.IdentifyController(_handle, out _))
{
NVMeDrive = null;
}
}
}
// Test Intel protocol.
if (NVMeDrive == null && name.IndexOf("Intel", StringComparison.OrdinalIgnoreCase) > -1)
{
_handle = NVMeIntel.IdentifyDevice(storageInfo);
if (_handle != null)
{
NVMeDrive = new NVMeIntel();
}
}
// Test Intel raid protocol.
if (NVMeDrive == null && name.IndexOf("Intel", StringComparison.OrdinalIgnoreCase) > -1)
{
_handle = NVMeIntelRst.IdentifyDevice(storageInfo);
if (_handle != null)
{
NVMeDrive = new NVMeIntelRst();
}
}
// Test Windows generic driver protocol.
if (NVMeDrive == null)
{
_handle = NVMeWindows.IdentifyDevice(storageInfo);
if (_handle != null)
{
NVMeDrive = new NVMeWindows();
}
}
}
public bool IsValid
{
get
{
return _handle is { IsInvalid: false };
}
}
internal INVMeDrive NVMeDrive { get; }
public void Dispose()
{
Close();
}
private static string GetString(byte[] s)
{
return Encoding.ASCII.GetString(s).Trim('\t', '\n', '\r', ' ', '\0');
}
private static short KelvinToCelsius(ushort k)
{
return (short)(k > 0 ? k - 273 : short.MinValue);
}
private static short KelvinToCelsius(byte[] k)
{
return KelvinToCelsius(BitConverter.ToUInt16(k, 0));
}
public void Close()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing && _handle is { IsClosed: false })
{
_handle.Close();
}
}
public Storage.NVMeInfo GetInfo()
{
if (_handle?.IsClosed != false)
return null;
bool valid = false;
var data = new Kernel32.NVME_IDENTIFY_CONTROLLER_DATA();
if (NVMeDrive != null)
valid = NVMeDrive.IdentifyController(_handle, out data);
if (!valid)
return null;
return new NVMeInfo(_driveNumber, data);
}
public Storage.NVMeHealthInfo GetHealthInfo()
{
if (_handle?.IsClosed != false)
return null;
bool valid = false;
var data = new Kernel32.NVME_HEALTH_INFO_LOG();
if (NVMeDrive != null)
valid = NVMeDrive.HealthInfoLog(_handle, out data);
if (!valid)
return null;
return new NVMeHealthInfo(data);
}
private class NVMeInfo : Storage.NVMeInfo
{
public NVMeInfo(int index, Kernel32.NVME_IDENTIFY_CONTROLLER_DATA data)
{
Index = index;
VID = data.VID;
SSVID = data.SSVID;
Serial = GetString(data.SN);
Model = GetString(data.MN);
Revision = GetString(data.FR);
IEEE = data.IEEE;
TotalCapacity = BitConverter.ToUInt64(data.TNVMCAP, 0); // 128bit little endian
UnallocatedCapacity = BitConverter.ToUInt64(data.UNVMCAP, 0);
ControllerId = data.CNTLID;
NumberNamespaces = data.NN;
}
}
private class NVMeHealthInfo : Storage.NVMeHealthInfo
{
public NVMeHealthInfo(Kernel32.NVME_HEALTH_INFO_LOG log)
{
CriticalWarning = (Kernel32.NVME_CRITICAL_WARNING)log.CriticalWarning;
Temperature = KelvinToCelsius(log.CompositeTemp);
AvailableSpare = log.AvailableSpare;
AvailableSpareThreshold = log.AvailableSpareThreshold;
PercentageUsed = log.PercentageUsed;
DataUnitRead = BitConverter.ToUInt64(log.DataUnitRead, 0);
DataUnitWritten = BitConverter.ToUInt64(log.DataUnitWritten, 0);
HostReadCommands = BitConverter.ToUInt64(log.HostReadCommands, 0);
HostWriteCommands = BitConverter.ToUInt64(log.HostWriteCommands, 0);
ControllerBusyTime = BitConverter.ToUInt64(log.ControllerBusyTime, 0);
PowerCycle = BitConverter.ToUInt64(log.PowerCycles, 0);
PowerOnHours = BitConverter.ToUInt64(log.PowerOnHours, 0);
UnsafeShutdowns = BitConverter.ToUInt64(log.UnsafeShutdowns, 0);
MediaErrors = BitConverter.ToUInt64(log.MediaAndDataIntegrityErrors, 0);
ErrorInfoLogEntryCount = BitConverter.ToUInt64(log.NumberErrorInformationLogEntries, 0);
WarningCompositeTemperatureTime = log.WarningCompositeTemperatureTime;
CriticalCompositeTemperatureTime = log.CriticalCompositeTemperatureTime;
TemperatureSensors = new short[log.TemperatureSensor.Length];
for (int i = 0; i < TemperatureSensors.Length; i++)
TemperatureSensors[i] = KelvinToCelsius(log.TemperatureSensor[i]);
}
}
}

View File

@@ -0,0 +1,128 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware.Storage;
internal class NVMeWindows : INVMeDrive
{
//windows generic driver nvme access
public SafeHandle Identify(StorageInfo storageInfo)
{
return IdentifyDevice(storageInfo);
}
public bool IdentifyController(SafeHandle hDevice, out Kernel32.NVME_IDENTIFY_CONTROLLER_DATA data)
{
data = Kernel32.CreateStruct<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>();
if (hDevice?.IsInvalid != false)
return false;
bool result = false;
Kernel32.STORAGE_QUERY_BUFFER nptwb = Kernel32.CreateStruct<Kernel32.STORAGE_QUERY_BUFFER>();
nptwb.ProtocolSpecific.ProtocolType = Kernel32.STORAGE_PROTOCOL_TYPE.ProtocolTypeNvme;
nptwb.ProtocolSpecific.DataType = (uint)Kernel32.STORAGE_PROTOCOL_NVME_DATA_TYPE.NVMeDataTypeIdentify;
nptwb.ProtocolSpecific.ProtocolDataRequestValue = (uint)Kernel32.STORAGE_PROTOCOL_NVME_PROTOCOL_DATA_REQUEST_VALUE.NVMeIdentifyCnsController;
nptwb.ProtocolSpecific.ProtocolDataOffset = (uint)Marshal.SizeOf<Kernel32.STORAGE_PROTOCOL_SPECIFIC_DATA>();
nptwb.ProtocolSpecific.ProtocolDataLength = (uint)nptwb.Buffer.Length;
nptwb.PropertyId = Kernel32.STORAGE_PROPERTY_ID.StorageAdapterProtocolSpecificProperty;
nptwb.QueryType = Kernel32.STORAGE_QUERY_TYPE.PropertyStandardQuery;
int length = Marshal.SizeOf<Kernel32.STORAGE_QUERY_BUFFER>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(nptwb, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_STORAGE_QUERY_PROPERTY, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
//map NVME_IDENTIFY_CONTROLLER_DATA to nptwb.Buffer
IntPtr offset = Marshal.OffsetOf<Kernel32.STORAGE_QUERY_BUFFER>(nameof(Kernel32.STORAGE_QUERY_BUFFER.Buffer));
var newPtr = IntPtr.Add(buffer, offset.ToInt32());
data = Marshal.PtrToStructure<Kernel32.NVME_IDENTIFY_CONTROLLER_DATA>(newPtr);
Marshal.FreeHGlobal(buffer);
result = true;
}
else
{
Marshal.FreeHGlobal(buffer);
}
return result;
}
public bool HealthInfoLog(SafeHandle hDevice, out Kernel32.NVME_HEALTH_INFO_LOG data)
{
data = Kernel32.CreateStruct<Kernel32.NVME_HEALTH_INFO_LOG>();
if (hDevice?.IsInvalid != false)
return false;
bool result = false;
Kernel32.STORAGE_QUERY_BUFFER nptwb = Kernel32.CreateStruct<Kernel32.STORAGE_QUERY_BUFFER>();
nptwb.ProtocolSpecific.ProtocolType = Kernel32.STORAGE_PROTOCOL_TYPE.ProtocolTypeNvme;
nptwb.ProtocolSpecific.DataType = (uint)Kernel32.STORAGE_PROTOCOL_NVME_DATA_TYPE.NVMeDataTypeLogPage;
nptwb.ProtocolSpecific.ProtocolDataRequestValue = (uint)Kernel32.NVME_LOG_PAGES.NVME_LOG_PAGE_HEALTH_INFO;
nptwb.ProtocolSpecific.ProtocolDataOffset = (uint)Marshal.SizeOf<Kernel32.STORAGE_PROTOCOL_SPECIFIC_DATA>();
nptwb.ProtocolSpecific.ProtocolDataLength = (uint)nptwb.Buffer.Length;
nptwb.PropertyId = Kernel32.STORAGE_PROPERTY_ID.StorageAdapterProtocolSpecificProperty;
nptwb.QueryType = Kernel32.STORAGE_QUERY_TYPE.PropertyStandardQuery;
int length = Marshal.SizeOf<Kernel32.STORAGE_QUERY_BUFFER>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(nptwb, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(hDevice, Kernel32.IOCTL.IOCTL_STORAGE_QUERY_PROPERTY, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
//map NVME_HEALTH_INFO_LOG to nptwb.Buffer
IntPtr offset = Marshal.OffsetOf<Kernel32.STORAGE_QUERY_BUFFER>(nameof(Kernel32.STORAGE_QUERY_BUFFER.Buffer));
var newPtr = IntPtr.Add(buffer, offset.ToInt32());
data = Marshal.PtrToStructure<Kernel32.NVME_HEALTH_INFO_LOG>(newPtr);
Marshal.FreeHGlobal(buffer);
result = true;
}
else
{
Marshal.FreeHGlobal(buffer);
}
return result;
}
public static SafeHandle IdentifyDevice(StorageInfo storageInfo)
{
SafeFileHandle handle = Kernel32.OpenDevice(storageInfo.DeviceId);
if (handle?.IsInvalid != false)
return null;
Kernel32.STORAGE_QUERY_BUFFER nptwb = Kernel32.CreateStruct<Kernel32.STORAGE_QUERY_BUFFER>();
nptwb.ProtocolSpecific.ProtocolType = Kernel32.STORAGE_PROTOCOL_TYPE.ProtocolTypeNvme;
nptwb.ProtocolSpecific.DataType = (uint)Kernel32.STORAGE_PROTOCOL_NVME_DATA_TYPE.NVMeDataTypeIdentify;
nptwb.ProtocolSpecific.ProtocolDataRequestValue = (uint)Kernel32.STORAGE_PROTOCOL_NVME_PROTOCOL_DATA_REQUEST_VALUE.NVMeIdentifyCnsController;
nptwb.ProtocolSpecific.ProtocolDataOffset = (uint)Marshal.SizeOf<Kernel32.STORAGE_PROTOCOL_SPECIFIC_DATA>();
nptwb.ProtocolSpecific.ProtocolDataLength = (uint)nptwb.Buffer.Length;
nptwb.PropertyId = Kernel32.STORAGE_PROPERTY_ID.StorageAdapterProtocolSpecificProperty;
nptwb.QueryType = Kernel32.STORAGE_QUERY_TYPE.PropertyStandardQuery;
int length = Marshal.SizeOf<Kernel32.STORAGE_QUERY_BUFFER>();
IntPtr buffer = Marshal.AllocHGlobal(length);
Marshal.StructureToPtr(nptwb, buffer, false);
bool validTransfer = Kernel32.DeviceIoControl(handle, Kernel32.IOCTL.IOCTL_STORAGE_QUERY_PROPERTY, buffer, length, buffer, length, out _, IntPtr.Zero);
if (validTransfer)
{
Marshal.FreeHGlobal(buffer);
}
else
{
Marshal.FreeHGlobal(buffer);
handle.Close();
handle = null;
}
return handle;
}
}

View File

@@ -0,0 +1,20 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
namespace LibreHardwareMonitor.Hardware.Storage;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
internal class NamePrefixAttribute : Attribute
{
public NamePrefixAttribute(string namePrefix)
{
Prefix = namePrefix;
}
public string Prefix { get; }
}

View File

@@ -0,0 +1,20 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
namespace LibreHardwareMonitor.Hardware.Storage;
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
internal class RequireSmartAttribute : Attribute
{
public RequireSmartAttribute(byte attributeId)
{
AttributeId = attributeId;
}
public byte AttributeId { get; }
}

View File

@@ -0,0 +1,102 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
public class SmartAttribute
{
private readonly RawValueConversion _rawValueConversion;
public delegate float RawValueConversion(byte[] rawValue, byte value, IReadOnlyList<IParameter> parameters);
/// <summary>
/// Initializes a new instance of the <see cref="SmartAttribute" /> class.
/// </summary>
/// <param name="id">The SMART id of the attribute.</param>
/// <param name="name">The name of the attribute.</param>
public SmartAttribute(byte id, string name) : this(id, name, null, null, 0, null)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="SmartAttribute" /> class.
/// </summary>
/// <param name="id">The SMART id of the attribute.</param>
/// <param name="name">The name of the attribute.</param>
/// <param name="rawValueConversion">
/// A delegate for converting the raw byte
/// array into a value (or null to use the attribute value).
/// </param>
public SmartAttribute(byte id, string name, RawValueConversion rawValueConversion) : this(id, name, rawValueConversion, null, 0, null)
{ }
/// <summary>
/// Initializes a new instance of the <see cref="SmartAttribute" /> class.
/// </summary>
/// <param name="id">The SMART id of the attribute.</param>
/// <param name="name">The name of the attribute.</param>
/// <param name="rawValueConversion">
/// A delegate for converting the raw byte
/// array into a value (or null to use the attribute value).
/// </param>
/// <param name="sensorType">
/// Type of the sensor or null if no sensor is to
/// be created.
/// </param>
/// <param name="sensorChannel">
/// If there exists more than one attribute with
/// the same sensor channel and type, then a sensor is created only for the
/// first attribute.
/// </param>
/// <param name="sensorName">
/// The name to be used for the sensor, or null if
/// no sensor is created.
/// </param>
/// <param name="defaultHiddenSensor">True to hide the sensor initially.</param>
/// <param name="parameterDescriptions">
/// Description for the parameters of the sensor
/// (or null).
/// </param>
public SmartAttribute(byte id, string name, RawValueConversion rawValueConversion, SensorType? sensorType, int sensorChannel, string sensorName, bool defaultHiddenSensor = false, ParameterDescription[] parameterDescriptions = null)
{
Id = id;
Name = name;
_rawValueConversion = rawValueConversion;
SensorType = sensorType;
SensorChannel = sensorChannel;
SensorName = sensorName;
DefaultHiddenSensor = defaultHiddenSensor;
ParameterDescriptions = parameterDescriptions;
}
public bool DefaultHiddenSensor { get; }
public bool HasRawValueConversion => _rawValueConversion != null;
/// <summary>
/// Gets the SMART identifier.
/// </summary>
public byte Id { get; }
public string Name { get; }
public ParameterDescription[] ParameterDescriptions { get; }
public int SensorChannel { get; }
public string SensorName { get; }
public SensorType? SensorType { get; }
internal float ConvertValue(Kernel32.SMART_ATTRIBUTE value, IReadOnlyList<IParameter> parameters)
{
if (_rawValueConversion == null)
return value.CurrentValue;
return _rawValueConversion(value.RawValue, value.CurrentValue, parameters);
}
}

View File

@@ -0,0 +1,263 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
namespace LibreHardwareMonitor.Hardware.Storage;
/// <summary>
/// Localization class for SMART attribute names.
/// </summary>
public static class SmartNames
{
public static string AirflowTemperature => "Airflow Temperature";
public static string AlternativeEraseFailCount => "Alternative Erase Fail Count";
public static string AlternativeGSenseErrorRate => "Alternative G-Sense Error Rate";
public static string AlternativeProgramFailCount => "Alternative Program Fail Count";
public static string AvailableReservedSpace => "Available Reserved Space";
public static string AverageEraseCount => "Average Erase Count";
public static string BadBlockFullFlag => "Bad Block Full Flag";
public static string BitErrors => "Bit Errors";
public static string CalibrationRetryCount => "Calibration Retry Count";
public static string CommandTimeout => "Command Timeout";
public static string ControllerWritesToNand => "Controller Writes to NAND";
public static string CorrectedErrors => "Corrected Errors";
public static string CrcErrorCount => "CRC Error Count";
public static string CurrentPendingSectorCount => "Current Pending Sector Count";
public static string DataAddressMarkErrors => "Data Address Mark errors";
public static string DiskShift => "Disk Shift";
public static string DriveTemperature => "Drive Temperature";
public static string EccRate => "ECC Rate";
public static string EmergencyRetractCycleCount => "Emergency Retract Cycle Count";
public static string EndToEndError => "End-to-End error";
public static string EnduranceRemaining => "Endurance Remaining";
public static string EraseFailCount => "Erase Fail Count";
public static string EraseFailCountChip => "Erase Fail Count (Chip)";
public static string EraseFailCountTotal => "Erase Fail Count (Total)";
public static string EraseFailure => "Erase Failure";
public static string ErrorCorrectionCount => "Error Correction Count";
public static string ExceptionModeStatus => "Exception Mode Status";
public static string FactoryBadBlockCount => "Factory Bad Block Count";
public static string FlyingHeight => "Flying Height";
public static string FreeFallProtection => "Free Fall Protection";
public static string FTLProgramNANDPagesCount => "FTL Program NAND Pages Count";
public static string GmrHeadAmplitude => "GMR Head Amplitude";
public static string GSenseErrorRate => "G-sense Error Rate";
public static string HardwareEccRecovered => "Hardware ECC Recovered";
public static string HeadFlyingHours => "Head Flying Hours";
public static string HeadStability => "Head Stability";
public static string HighFlyWrites => "High Fly Writes";
public static string HostProgramNANDPagesCount => "Host Program NAND Pages Count";
public static string HostReads => "Host Reads";
public static string HostWrites => "Host Writes";
public static string HostWritesToController => "Host Writes to Controller";
public static string InducedOpVibrationDetection => "Induced Op-Vibration Detection";
public static string InitialBadBlockCount => "Initial Bad Block Count";
public static string LoadCycleCount => "Load Cycle Count";
public static string LoadedHours => "Loaded Hours";
public static string LoadFriction => "Load Friction";
public static string LoadInTime => "Load 'In'-time";
public static string LoadUnloadCycleCount => "Load/Unload Cycle Count";
public static string LoadUnloadCycleCountFujitsu => "Load/Unload Cycle Count (Fujitus)";
public static string LoadUnloadRetryCount => "Load/Unload Retry Count";
public static string MaxCellCycles => "Max Cell Cycles";
public static string MaxErase => "Max Erase";
public static string MediaWearOutIndicator => "Media Wear Out Indicator";
public static string MinErase => "Min Erase";
public static string MultiZoneErrorRate => "Multi-Zone Error Rate";
public static string NewFailingBlockCount => "New Failing Block Count";
public static string Non4KAlignedAccess => "Non-4k Aligned Access";
public static string OfflineSeekPerformance => "Offline Seek Performance";
public static string OffLineUncorrectableErrorCount => "Off-Line Uncorrectable Error Count";
public static string PowerCycleCount => "Power Cycle Count";
public static string PowerOffRetractCycle => "Power-Off Retract Cycle";
public static string PowerOnHours => "Power-On Hours (POH)";
public static string PowerRecoveryCount => "Power Recovery Count";
public static string ProgramFailCount => "Program Fail Count";
public static string ProgramFailCountChip => "Program Fail Count (Chip)";
public static string ProgramFailCountTotal => "Program Fail Count (Total)";
public static string ProgramFailure => "Program Failure";
public static string RawReadErrorRate => "Raw Read Error Rate";
public static string ReadChannelMargin => "Read Channel Margin";
public static string ReadCommands => "Read Commands";
public static string ReadErrorRate => "Read Error Rate";
public static string ReadErrorRetryRate => "Read Error Retry Rate";
public static string ReadFailure => "Read Failure";
public static string ReallocatedNANDBlockCount => "Reallocated NAND Block Count";
public static string ReallocatedSectorsCount => "Reallocated Sectors Count";
public static string ReallocationEventCount => "Reallocation Event Count";
public static string RecalibrationRetries => "Recalibration Retries";
public static string RemainingLife => "Remaining Life";
public static string ReportedUncorrectableErrors => "Reported Uncorrectable Errors";
public static string RetiredBlockCount => "Retired Block Count";
public static string RunOutCancel => "Run Out Cancel";
public static string RuntimeBadBlockTotal => "Runtime Bad Block Total";
public static string SataDownshiftErrorCount => "SATA Downshift Error Count";
public static string SataErrorCountCrc => "SATA Error Count CRC";
public static string SataErrorCountHandshake => "SATA Error Count Handshake";
public static string SectorsRead => "Sectors Read";
public static string SectorsWritten => "Sectors Written";
public static string SeekErrorRate => "Seek Error Rate";
public static string SeekTimePerformance => "Seek Time Performance";
public static string ShockDuringWrite => "Shock During Write";
public static string SoftEccCorrection => "Soft ECC Correction";
public static string SoftReadErrorRate => "Soft Read Error Rate";
public static string SpinBuzz => "Spin Buzz";
public static string SpinHighCurrent => "Spin High Current";
public static string SpinRetryCount => "Spin Retry Count";
public static string SpinUpTime => "Spin-Up Time";
public static string StartStopCount => "Start/Stop Count";
public static string SuccessfulRAINRecoveryCount => "Successful RAIN Recovery Count";
public static string SupercapStatus => "Supercap Status";
public static string TaCounterDetected => "TA Counter Detected";
public static string Temperature => "Temperature";
public static string TemperatureDifferenceFrom100 => "Temperature Difference from 100";
public static string TemperatureExceedCount => "Temperature Exceed Count";
public static string ThermalAsperityRate => "Thermal Asperity Rate (TAR)";
public static string ThroughputPerformance => "Throughput Performance";
public static string TorqueAmplificationCount => "Torque Amplification Count";
public static string TotalLbasRead => "Total LBAs Read";
public static string TotalLbasWritten => "Total LBAs Written";
public static string TransferErrorRate => "Transfer Error Rate";
public static string UltraDmaCrcErrorCount => "UltraDMA CRC Error Count";
public static string UncorrectableErrorCount => "Uncorrectable Error Count";
public static string UncorrectableSectorCount => "Uncorrectable Sector Count";
public static string UnexpectedPowerLossCount => "Unexpected Power Loss Count";
public static string Unknown => "Unknown";
public static string UnknownUnique => "Unknown Unique";
public static string UnrecoverableEcc => "Unrecoverable ECC";
public static string UnsafeShutdownCount => "Unsafe Shutdown Count";
public static string UnusedReserveNANDBlocks => "Unused Reserve NAND Blocks";
public static string UsedReservedBlockCountChip => "Used Reserved Block Count (Chip)";
public static string UsedReservedBlockCountTotal => "Used Reserved Block Count (Total)";
public static string VibrationDuringWrite => "Vibration During Write";
public static string WearLevelingCount => "Wear Leveling Count";
public static string WearRangeDelta => "Wear Range Delta";
public static string WriteCommands => "Write Commands";
public static string WriteErrorRate => "Write Error Rate";
}

View File

@@ -0,0 +1,43 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware.Storage;
[NamePrefix(""), RequireSmart(0x01), RequireSmart(0x09), RequireSmart(0x0C), RequireSmart(0xD1), RequireSmart(0xCE), RequireSmart(0xCF)]
internal class SsdIndilinx : AtaStorage
{
private static readonly IReadOnlyList<SmartAttribute> _smartAttributes = new List<SmartAttribute>
{
new(0x01, SmartNames.ReadErrorRate),
new(0x09, SmartNames.PowerOnHours),
new(0x0C, SmartNames.PowerCycleCount),
new(0xB8, SmartNames.InitialBadBlockCount),
new(0xC3, SmartNames.ProgramFailure),
new(0xC4, SmartNames.EraseFailure),
new(0xC5, SmartNames.ReadFailure),
new(0xC6, SmartNames.SectorsRead),
new(0xC7, SmartNames.SectorsWritten),
new(0xC8, SmartNames.ReadCommands),
new(0xC9, SmartNames.WriteCommands),
new(0xCA, SmartNames.BitErrors),
new(0xCB, SmartNames.CorrectedErrors),
new(0xCC, SmartNames.BadBlockFullFlag),
new(0xCD, SmartNames.MaxCellCycles),
new(0xCE, SmartNames.MinErase),
new(0xCF, SmartNames.MaxErase),
new(0xD0, SmartNames.AverageEraseCount),
new(0xD1, SmartNames.RemainingLife, null, SensorType.Level, 0, SmartNames.RemainingLife),
new(0xD2, SmartNames.UnknownUnique),
new(0xD3, SmartNames.SataErrorCountCrc),
new(0xD4, SmartNames.SataErrorCountHandshake)
};
public SsdIndilinx(StorageInfo storageInfo, ISmart smart, string name, string firmwareRevision, int index, ISettings settings)
: base(storageInfo, smart, name, firmwareRevision, "ssd", index, _smartAttributes, settings)
{ }
}

View File

@@ -0,0 +1,49 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware.Storage;
[NamePrefix("INTEL SSD"), RequireSmart(0xE1), RequireSmart(0xE8), RequireSmart(0xE9)]
internal class SsdIntel : AtaStorage
{
private static readonly IReadOnlyList<SmartAttribute> _smartAttributes = new List<SmartAttribute>
{
new(0x01, SmartNames.ReadErrorRate),
new(0x03, SmartNames.SpinUpTime),
new(0x04, SmartNames.StartStopCount, RawToInt),
new(0x05, SmartNames.ReallocatedSectorsCount),
new(0x09, SmartNames.PowerOnHours, RawToInt),
new(0x0C, SmartNames.PowerCycleCount, RawToInt),
new(0xAA, SmartNames.AvailableReservedSpace),
new(0xAB, SmartNames.ProgramFailCount),
new(0xAC, SmartNames.EraseFailCount),
new(0xAE, SmartNames.UnexpectedPowerLossCount, RawToInt),
new(0xB7, SmartNames.SataDownshiftErrorCount, RawToInt),
new(0xB8, SmartNames.EndToEndError),
new(0xBB, SmartNames.UncorrectableErrorCount, RawToInt),
new(0xBE,
SmartNames.Temperature,
(r, _, p) => r[0] + (p?[0].Value ?? 0),
SensorType.Temperature,
0,
SmartNames.AirflowTemperature,
false,
new[] { new ParameterDescription("Offset [°C]", "Temperature offset of the thermal sensor.\nTemperature = Value + Offset.", 0) }),
new(0xC0, SmartNames.UnsafeShutdownCount),
new(0xC7, SmartNames.CrcErrorCount, RawToInt),
new(0xE1, SmartNames.HostWrites, (r, v, p) => RawToInt(r, v, p) / 0x20, SensorType.Data, 0, SmartNames.HostWrites),
new(0xE8, SmartNames.RemainingLife, null, SensorType.Level, 0, SmartNames.RemainingLife),
new(0xE9, SmartNames.MediaWearOutIndicator),
new(0xF1, SmartNames.HostWrites, (r, v, p) => RawToInt(r, v, p) / 0x20, SensorType.Data, 0, SmartNames.HostWrites),
new(0xF2, SmartNames.HostReads, (r, v, p) => RawToInt(r, v, p) / 0x20, SensorType.Data, 1, SmartNames.HostReads)
};
public SsdIntel(StorageInfo storageInfo, ISmart smart, string name, string firmwareRevision, int index, ISettings settings)
: base(storageInfo, smart, name, firmwareRevision, "ssd", index, _smartAttributes, settings)
{ }
}

View File

@@ -0,0 +1,114 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
[NamePrefix(""), RequireSmart(0xAB), RequireSmart(0xAC), RequireSmart(0xAD), RequireSmart(0xAE), RequireSmart(0xC4), RequireSmart(0xCA), RequireSmart(0xCE)]
internal class SsdMicron : AtaStorage
{
private static readonly IReadOnlyList<SmartAttribute> _smartAttributes = new List<SmartAttribute>
{
new(0x01, SmartNames.ReadErrorRate, RawToInt),
new(0x05, SmartNames.ReallocatedNANDBlockCount, RawToInt),
new(0x09, SmartNames.PowerOnHours, RawToInt),
new(0x0C, SmartNames.PowerCycleCount, RawToInt),
new(0xAA, SmartNames.NewFailingBlockCount, RawToInt),
new(0xAB, SmartNames.ProgramFailCount, RawToInt),
new(0xAC, SmartNames.EraseFailCount, RawToInt),
new(0xAD, SmartNames.WearLevelingCount, RawToInt),
new(0xAE, SmartNames.UnexpectedPowerLossCount, RawToInt),
new(0xB4, SmartNames.UnusedReserveNANDBlocks, RawToInt),
new(0xB5, SmartNames.Non4KAlignedAccess, (raw, _, _) => 6e4f * ((raw[5] << 8) | raw[4])),
new(0xB7, SmartNames.SataDownshiftErrorCount, RawToInt),
new(0xB8, SmartNames.ErrorCorrectionCount, RawToInt),
new(0xBB, SmartNames.ReportedUncorrectableErrors, RawToInt),
new(0xBC, SmartNames.CommandTimeout, RawToInt),
new(0xBD, SmartNames.FactoryBadBlockCount, RawToInt),
new(0xC2, SmartNames.Temperature, RawToInt),
new(0xC4, SmartNames.ReallocationEventCount, RawToInt),
new(0xC5, SmartNames.CurrentPendingSectorCount),
new(0xC6, SmartNames.OffLineUncorrectableErrorCount, RawToInt),
new(0xC7, SmartNames.UltraDmaCrcErrorCount, RawToInt),
new(0xCA, SmartNames.RemainingLife, (raw, value, p) => 100 - RawToInt(raw, value, p), SensorType.Level, 0, SmartNames.RemainingLife),
new(0xCE, SmartNames.WriteErrorRate, (raw, _, _) => 6e4f * ((raw[1] << 8) | raw[0])),
new(0xD2, SmartNames.SuccessfulRAINRecoveryCount, RawToInt),
new(0xF6,
SmartNames.TotalLbasWritten,
(r, _, _) => (((long)r[5] << 40) |
((long)r[4] << 32) |
((long)r[3] << 24) |
((long)r[2] << 16) |
((long)r[1] << 8) |
r[0]) *
(512.0f / 1024 / 1024 / 1024),
SensorType.Data,
0,
"Total Bytes Written"),
new(0xF7, SmartNames.HostProgramNANDPagesCount, RawToInt),
new(0xF8, SmartNames.FTLProgramNANDPagesCount, RawToInt)
};
private readonly Sensor _temperature;
private readonly Sensor _writeAmplification;
public SsdMicron(StorageInfo storageInfo, ISmart smart, string name, string firmwareRevision, int index, ISettings settings)
: base(storageInfo, smart, name, firmwareRevision, "ssd", index, _smartAttributes, settings)
{
_temperature = new Sensor("Temperature",
0,
false,
SensorType.Temperature,
this,
new[]
{
new ParameterDescription("Offset [°C]",
"Temperature offset of the thermal sensor.\n" +
"Temperature = Value + Offset.",
0)
},
settings);
_writeAmplification = new Sensor("Write Amplification",
0,
SensorType.Factor,
this,
settings);
}
/// <inheritdoc />
protected override void UpdateAdditionalSensors(Kernel32.SMART_ATTRIBUTE[] values)
{
float? hostProgramPagesCount = null;
float? ftlProgramPagesCount = null;
foreach (Kernel32.SMART_ATTRIBUTE value in values)
{
if (value.Id == 0xF7)
hostProgramPagesCount = RawToInt(value.RawValue, value.CurrentValue, null);
if (value.Id == 0xF8)
ftlProgramPagesCount = RawToInt(value.RawValue, value.CurrentValue, null);
if (value.Id == 0xC2)
{
_temperature.Value = value.RawValue[0] + _temperature.Parameters[0].Value;
if (value.RawValue[0] != 0)
ActivateSensor(_temperature);
}
}
if (hostProgramPagesCount.HasValue && ftlProgramPagesCount.HasValue)
{
_writeAmplification.Value = hostProgramPagesCount.Value > 0 ? (hostProgramPagesCount.Value + ftlProgramPagesCount) / hostProgramPagesCount.Value : 0;
ActivateSensor(_writeAmplification);
}
}
}

View File

@@ -0,0 +1,30 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware.Storage;
[NamePrefix("PLEXTOR")]
internal class SsdPlextor : AtaStorage
{
private static readonly IReadOnlyList<SmartAttribute> _smartAttributes = new List<SmartAttribute>
{
new(0x09, SmartNames.PowerOnHours, RawToInt),
new(0x0C, SmartNames.PowerCycleCount, RawToInt),
new(0xF1, SmartNames.HostWrites, RawToGb, SensorType.Data, 0, SmartNames.HostWrites),
new(0xF2, SmartNames.HostReads, RawToGb, SensorType.Data, 1, SmartNames.HostReads)
};
public SsdPlextor(StorageInfo storageInfo, ISmart smart, string name, string firmwareRevision, int index, ISettings settings)
: base(storageInfo, smart, name, firmwareRevision, "ssd", index, _smartAttributes, settings)
{ }
private static float RawToGb(byte[] rawValue, byte value, IReadOnlyList<IParameter> parameters)
{
return RawToInt(rawValue, value, parameters) / 32;
}
}

View File

@@ -0,0 +1,57 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
namespace LibreHardwareMonitor.Hardware.Storage;
[NamePrefix(""), RequireSmart(0xB1), RequireSmart(0xB3), RequireSmart(0xB5), RequireSmart(0xB6), RequireSmart(0xB7), RequireSmart(0xBB), RequireSmart(0xC3), RequireSmart(0xC7)]
internal class SsdSamsung : AtaStorage
{
private static readonly IReadOnlyList<SmartAttribute> _smartAttributes = new List<SmartAttribute>
{
new(0x05, SmartNames.ReallocatedSectorsCount),
new(0x09, SmartNames.PowerOnHours, RawToInt),
new(0x0C, SmartNames.PowerCycleCount, RawToInt),
new(0xAF, SmartNames.ProgramFailCountChip, RawToInt),
new(0xB0, SmartNames.EraseFailCountChip, RawToInt),
new(0xB1, SmartNames.WearLevelingCount, RawToInt),
new(0xB2, SmartNames.UsedReservedBlockCountChip, RawToInt),
new(0xB3, SmartNames.UsedReservedBlockCountTotal, RawToInt),
// Unused Reserved Block Count (Total)
new(0xB4, SmartNames.RemainingLife, null, SensorType.Level, 0, SmartNames.RemainingLife),
new(0xB5, SmartNames.ProgramFailCountTotal, RawToInt),
new(0xB6, SmartNames.EraseFailCountTotal, RawToInt),
new(0xB7, SmartNames.RuntimeBadBlockTotal, RawToInt),
new(0xBB, SmartNames.UncorrectableErrorCount, RawToInt),
new(0xBE,
SmartNames.Temperature,
(r, _, p) => r[0] + (p?[0].Value ?? 0),
SensorType.Temperature,
0,
SmartNames.Temperature,
false,
new[] { new ParameterDescription("Offset [°C]", "Temperature offset of the thermal sensor.\nTemperature = Value + Offset.", 0) }),
new(0xC2, SmartNames.AirflowTemperature),
new(0xC3, SmartNames.EccRate),
new(0xC6, SmartNames.OffLineUncorrectableErrorCount, RawToInt),
new(0xC7, SmartNames.CrcErrorCount, RawToInt),
new(0xC9, SmartNames.SupercapStatus),
new(0xCA, SmartNames.ExceptionModeStatus),
new(0xEB, SmartNames.PowerRecoveryCount),
new(0xF1,
SmartNames.TotalLbasWritten,
(r, _, _) => (((long)r[5] << 40) | ((long)r[4] << 32) | ((long)r[3] << 24) | ((long)r[2] << 16) | ((long)r[1] << 8) | r[0]) * (512.0f / 1024 / 1024 / 1024),
SensorType.Data,
0,
"Total Bytes Written")
};
public SsdSamsung(StorageInfo storageInfo, ISmart smart, string name, string firmwareRevision, int index, ISettings settings)
: base(storageInfo, smart, name, firmwareRevision, "ssd", index, _smartAttributes, settings)
{ }
}

View File

@@ -0,0 +1,76 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Collections.Generic;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
[NamePrefix(""), RequireSmart(0xAB), RequireSmart(0xB1)]
internal class SsdSandforce : AtaStorage
{
private static readonly IReadOnlyList<SmartAttribute> _smartAttributes = new List<SmartAttribute>
{
new(0x01, SmartNames.RawReadErrorRate),
new(0x05, SmartNames.RetiredBlockCount, RawToInt),
new(0x09, SmartNames.PowerOnHours, RawToInt),
new(0x0C, SmartNames.PowerCycleCount, RawToInt),
new(0xAB, SmartNames.ProgramFailCount, RawToInt),
new(0xAC, SmartNames.EraseFailCount, RawToInt),
new(0xAE, SmartNames.UnexpectedPowerLossCount, RawToInt),
new(0xB1, SmartNames.WearRangeDelta, RawToInt),
new(0xB5, SmartNames.AlternativeProgramFailCount, RawToInt),
new(0xB6, SmartNames.AlternativeEraseFailCount, RawToInt),
new(0xBB, SmartNames.UncorrectableErrorCount, RawToInt),
new(0xC2,
SmartNames.Temperature,
(_, value, p) => value + (p?[0].Value ?? 0),
SensorType.Temperature,
0,
SmartNames.Temperature,
true,
new[] { new ParameterDescription("Offset [°C]", "Temperature offset of the thermal sensor.\nTemperature = Value + Offset.", 0) }),
new(0xC3, SmartNames.UnrecoverableEcc),
new(0xC4, SmartNames.ReallocationEventCount, RawToInt),
new(0xE7, SmartNames.RemainingLife, null, SensorType.Level, 0, SmartNames.RemainingLife),
new(0xE9, SmartNames.ControllerWritesToNand, RawToInt, SensorType.Data, 0, SmartNames.ControllerWritesToNand),
new(0xEA, SmartNames.HostWritesToController, RawToInt, SensorType.Data, 1, SmartNames.HostWritesToController),
new(0xF1, SmartNames.HostWrites, RawToInt, SensorType.Data, 1, SmartNames.HostWrites),
new(0xF2, SmartNames.HostReads, RawToInt, SensorType.Data, 2, SmartNames.HostReads)
};
private readonly Sensor _writeAmplification;
public SsdSandforce(StorageInfo storageInfo, ISmart smart, string name, string firmwareRevision, int index, ISettings settings)
: base(storageInfo, smart, name, firmwareRevision, "ssd", index, _smartAttributes, settings)
{
_writeAmplification = new Sensor("Write Amplification", 1, SensorType.Factor, this, settings);
}
protected override void UpdateAdditionalSensors(Kernel32.SMART_ATTRIBUTE[] values)
{
float? controllerWritesToNand = null;
float? hostWritesToController = null;
foreach (Kernel32.SMART_ATTRIBUTE value in values)
{
if (value.Id == 0xE9)
controllerWritesToNand = RawToInt(value.RawValue, value.CurrentValue, null);
if (value.Id == 0xEA)
hostWritesToController = RawToInt(value.RawValue, value.CurrentValue, null);
}
if (controllerWritesToNand.HasValue && hostWritesToController.HasValue)
{
if (hostWritesToController.Value > 0)
_writeAmplification.Value = controllerWritesToNand.Value / hostWritesToController.Value;
else
_writeAmplification.Value = 0;
ActivateSensor(_writeAmplification);
}
}
}

View File

@@ -0,0 +1,29 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System.Text;
namespace LibreHardwareMonitor.Hardware.Storage;
internal sealed class StorageGeneric : AbstractStorage
{
private StorageGeneric(StorageInfo storageInfo, string name, string firmwareRevision, int index, ISettings settings)
: base(storageInfo, name, firmwareRevision, "hdd", index, settings)
{
CreateSensors();
}
public static AbstractStorage CreateInstance(StorageInfo info, ISettings settings)
{
string name = string.IsNullOrEmpty(info.Name) ? "Generic Hard Disk" : info.Name;
string firmwareRevision = string.IsNullOrEmpty(info.Revision) ? "Unknown" : info.Revision;
return new StorageGeneric(info, name, firmwareRevision, info.Index, settings);
}
protected override void UpdateSensors() { }
protected override void GetReport(StringBuilder r) { }
}

View File

@@ -0,0 +1,166 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Management;
namespace LibreHardwareMonitor.Hardware.Storage;
#pragma warning disable CA1416 // Validate platform compatibility
internal class StorageGroup : IGroup
{
private readonly List<AbstractStorage> _hardware = new();
public StorageGroup(ISettings settings)
{
if (Software.OperatingSystem.IsUnix)
return;
Dictionary<uint, List<(uint, ulong)>> storageSpaceDiskToPhysicalDiskMap = GetStorageSpaceDiskToPhysicalDiskMap();
AddHardware(settings, storageSpaceDiskToPhysicalDiskMap);
}
public IReadOnlyList<IHardware> Hardware => _hardware;
/// <summary>
/// Adds the hardware.
/// </summary>
/// <param name="settings">The settings.</param>
/// <param name="storageSpaceDiskToPhysicalDiskMap">The storage space disk to physical disk map.</param>
private void AddHardware(ISettings settings, Dictionary<uint, List<(uint, ulong)>> storageSpaceDiskToPhysicalDiskMap)
{
try
{
// https://docs.microsoft.com/en-us/windows/win32/cimwin32prov/win32-diskdrive
using var diskDriveSearcher = new ManagementObjectSearcher("SELECT * FROM Win32_DiskDrive") { Options = { Timeout = TimeSpan.FromSeconds(10) } };
foreach (ManagementBaseObject diskDrive in diskDriveSearcher.Get())
{
string deviceId = (string)diskDrive.Properties["DeviceId"].Value; // is \\.\PhysicalDrive0..n
uint index = Convert.ToUInt32(diskDrive.Properties["Index"].Value);
ulong diskSize = Convert.ToUInt64(diskDrive.Properties["Size"].Value);
int scsi = Convert.ToInt32(diskDrive.Properties["SCSIPort"].Value);
if (deviceId != null)
{
var instance = AbstractStorage.CreateInstance(deviceId, index, diskSize, scsi, settings);
if (instance != null)
_hardware.Add(instance);
if (storageSpaceDiskToPhysicalDiskMap.ContainsKey(index))
{
foreach ((uint, ulong) physicalDisk in storageSpaceDiskToPhysicalDiskMap[index])
{
var physicalDiskInstance = AbstractStorage.CreateInstance(@$"\\.\PHYSICALDRIVE{physicalDisk.Item1}", physicalDisk.Item1, physicalDisk.Item2, scsi, settings);
if (physicalDiskInstance != null)
_hardware.Add(physicalDiskInstance);
}
}
}
}
}
catch
{
// Ignored.
}
}
/// <summary>
/// Maps each StorageSpace to the PhysicalDisks it is composed of.
/// </summary>
private static Dictionary<uint, List<(uint, ulong)>> GetStorageSpaceDiskToPhysicalDiskMap()
{
var diskToPhysicalDisk = new Dictionary<uint, List<(uint, ulong)>>();
if (!Software.OperatingSystem.IsWindows8OrGreater)
return diskToPhysicalDisk;
try
{
ManagementScope scope = new(@"\root\Microsoft\Windows\Storage");
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/stormgmt/msft-disk
// Lists all the disks visible to your system, the output is the same as Win32_DiskDrive.
// If you're using a storage Space, the "hidden" disks which compose your storage space will not be listed.
using var diskSearcher = new ManagementObjectSearcher(scope, new ObjectQuery("SELECT * FROM MSFT_Disk"));
foreach (ManagementBaseObject disk in diskSearcher.Get())
{
try
{
List<(uint, ulong)> map = MapDiskToPhysicalDisk(disk, scope);
if (map.Count > 0)
diskToPhysicalDisk[(uint)disk["Number"]] = map;
}
catch
{
// Ignored.
}
}
}
catch
{
// Ignored.
}
return diskToPhysicalDisk;
}
/// <summary>
/// Maps a disk to a physical disk.
/// </summary>
/// <param name="disk">The disk.</param>
/// <param name="scope">The scope.</param>
private static List<(uint, ulong)> MapDiskToPhysicalDisk(ManagementBaseObject disk, ManagementScope scope)
{
var map = new List<(uint, ulong)>();
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/stormgmt/msft-virtualdisk
// Maps the current Disk to its corresponding VirtualDisk. If the current Disk is not a storage space, it does not have a corresponding VirtualDisk.
// Each Disk maps to one or zero VirtualDisk.
using var toVirtualDisk = new ManagementObjectSearcher(scope, new ObjectQuery(FollowAssociationQuery("MSFT_Disk", (string)disk["ObjectId"], "MSFT_VirtualDiskToDisk")));
foreach (ManagementBaseObject virtualDisk in toVirtualDisk.Get())
{
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/stormgmt/msft-physicaldisk
// Maps the current VirtualDisk to the PhysicalDisk it is composed of.
// Each VirtualDisk maps to one or more PhysicalDisk.
using var toPhysicalDisk = new ManagementObjectSearcher(scope,
new ObjectQuery(FollowAssociationQuery("MSFT_VirtualDisk",
(string)virtualDisk["ObjectId"],
"MSFT_VirtualDiskToPhysicalDisk")));
foreach (ManagementBaseObject physicalDisk in toPhysicalDisk.Get())
{
ulong physicalDiskSize = (ulong)physicalDisk["Size"];
if (uint.TryParse((string)physicalDisk["DeviceId"], out uint physicalDiskId))
map.Add((physicalDiskId, physicalDiskSize));
}
}
return map;
}
private static string FollowAssociationQuery(string source, string objectId, string associationClass)
{
return @$"ASSOCIATORS OF {{{source}.ObjectId=""{objectId.Replace(@"\", @"\\").Replace(@"""", @"\""")}""}} WHERE AssocClass = {associationClass}";
}
public string GetReport()
{
return null;
}
public void Close()
{
foreach (AbstractStorage storage in _hardware)
storage.Close();
}
}

View File

@@ -0,0 +1,39 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware.Storage;
internal abstract class StorageInfo
{
public Kernel32.STORAGE_BUS_TYPE BusType { get; protected set; }
public string DeviceId { get; set; }
public ulong DiskSize { get; set; }
public SafeFileHandle Handle { get; set; }
public int Index { get; protected set; }
public string Name => (Vendor + " " + Product).Trim();
public string Product { get; protected set; }
public byte[] RawData { get; protected set; }
public bool Removable { get; protected set; }
public string Revision { get; protected set; }
public string Scsi { get; set; }
public string Serial { get; protected set; }
public string Vendor { get; protected set; }
}

View File

@@ -0,0 +1,184 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// Partial Copyright (C) Michael Möller <mmoeller@openhardwaremonitor.org> and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
namespace LibreHardwareMonitor.Hardware.Storage;
internal class WindowsSmart : ISmart
{
private readonly int _driveNumber;
private readonly SafeHandle _handle;
public WindowsSmart(int driveNumber)
{
_driveNumber = driveNumber;
_handle = Kernel32.CreateFile(@"\\.\PhysicalDrive" + driveNumber, FileAccess.ReadWrite, FileShare.ReadWrite, IntPtr.Zero, FileMode.Open, FileAttributes.Normal, IntPtr.Zero);
}
public bool IsValid => !_handle.IsInvalid && !_handle.IsClosed;
public void Dispose()
{
Close();
}
public void Close()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public bool EnableSmart()
{
if (_handle.IsClosed)
throw new ObjectDisposedException(nameof(WindowsSmart));
var parameter = new Kernel32.SENDCMDINPARAMS
{
bDriveNumber = (byte)_driveNumber,
irDriveRegs = { bFeaturesReg = Kernel32.SMART_FEATURES.ENABLE_SMART, bCylLowReg = Kernel32.SMART_LBA_MID, bCylHighReg = Kernel32.SMART_LBA_HI, bCommandReg = Kernel32.ATA_COMMAND.ATA_SMART}
};
return Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_SEND_DRIVE_COMMAND, ref parameter, Marshal.SizeOf(parameter),
out Kernel32.SENDCMDOUTPARAMS _, Marshal.SizeOf<Kernel32.SENDCMDOUTPARAMS>(), out _, IntPtr.Zero);
}
public Kernel32.SMART_ATTRIBUTE[] ReadSmartData()
{
if (_handle.IsClosed)
throw new ObjectDisposedException(nameof(WindowsSmart));
var parameter = new Kernel32.SENDCMDINPARAMS
{
bDriveNumber = (byte)_driveNumber, irDriveRegs = {
bFeaturesReg = Kernel32.SMART_FEATURES.SMART_READ_DATA,
bCylLowReg = Kernel32.SMART_LBA_MID,
bCylHighReg = Kernel32.SMART_LBA_HI,
bCommandReg = Kernel32.ATA_COMMAND.ATA_SMART
}
};
bool isValid = Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_RECEIVE_DRIVE_DATA, ref parameter, Marshal.SizeOf(parameter),
out Kernel32.ATTRIBUTECMDOUTPARAMS result, Marshal.SizeOf<Kernel32.ATTRIBUTECMDOUTPARAMS>(), out _, IntPtr.Zero);
return isValid ? result.Attributes : Array.Empty<Kernel32.SMART_ATTRIBUTE>();
}
public Kernel32.SMART_THRESHOLD[] ReadSmartThresholds()
{
if (_handle.IsClosed)
throw new ObjectDisposedException(nameof(WindowsSmart));
var parameter = new Kernel32.SENDCMDINPARAMS
{
bDriveNumber = (byte)_driveNumber, irDriveRegs = {
bFeaturesReg = Kernel32.SMART_FEATURES.READ_THRESHOLDS,
bCylLowReg = Kernel32.SMART_LBA_MID,
bCylHighReg = Kernel32.SMART_LBA_HI,
bCommandReg = Kernel32.ATA_COMMAND.ATA_SMART
}
};
bool isValid = Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_RECEIVE_DRIVE_DATA, ref parameter, Marshal.SizeOf(parameter),
out Kernel32.THRESHOLDCMDOUTPARAMS result, Marshal.SizeOf<Kernel32.THRESHOLDCMDOUTPARAMS>(), out _, IntPtr.Zero);
return isValid ? result.Thresholds : Array.Empty<Kernel32.SMART_THRESHOLD>();
}
public bool ReadNameAndFirmwareRevision(out string name, out string firmwareRevision)
{
if (_handle.IsClosed)
throw new ObjectDisposedException(nameof(WindowsSmart));
var parameter = new Kernel32.SENDCMDINPARAMS
{
bDriveNumber = (byte)_driveNumber,
irDriveRegs = { bCommandReg = Kernel32.ATA_COMMAND.ATA_IDENTIFY_DEVICE }
};
bool valid = Kernel32.DeviceIoControl(_handle, Kernel32.DFP.DFP_RECEIVE_DRIVE_DATA, ref parameter, Marshal.SizeOf(parameter),
out Kernel32.IDENTIFYCMDOUTPARAMS result, Marshal.SizeOf<Kernel32.IDENTIFYCMDOUTPARAMS>(), out _, IntPtr.Zero);
if (!valid)
{
name = null;
firmwareRevision = null;
return false;
}
name = GetString(result.Identify.ModelNumber);
firmwareRevision = GetString(result.Identify.FirmwareRevision);
return true;
}
/// <summary>
/// Reads Smart health status of the drive
/// </summary>
/// <returns>True, if drive is healthy; False, if unhealthy; Null, if it cannot be read</returns>
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<Kernel32.STATUSCMDOUTPARAMS>(), 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;
}
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;
}
// response is not clear
return null;
}
protected void Dispose(bool disposing)
{
if (disposing && !_handle.IsClosed)
{
_handle.Close();
}
}
private static string GetString(IReadOnlyList<byte> bytes)
{
char[] chars = new char[bytes.Count];
for (int i = 0; i < bytes.Count; i += 2)
{
chars[i] = (char)bytes[i + 1];
chars[i + 1] = (char)bytes[i];
}
return new string(chars).Trim(' ', '\0');
}
}

View File

@@ -0,0 +1,98 @@
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// Copyright (C) LibreHardwareMonitor and Contributors.
// All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Management;
using System.Runtime.InteropServices;
using LibreHardwareMonitor.Interop;
using Microsoft.Win32.SafeHandles;
namespace LibreHardwareMonitor.Hardware.Storage;
internal static class WindowsStorage
{
public static Storage.StorageInfo GetStorageInfo(string deviceId, uint driveIndex)
{
using SafeFileHandle handle = Kernel32.OpenDevice(deviceId);
if (handle?.IsInvalid != false)
return null;
var query = new Kernel32.STORAGE_PROPERTY_QUERY { PropertyId = Kernel32.STORAGE_PROPERTY_ID.StorageDeviceProperty, QueryType = Kernel32.STORAGE_QUERY_TYPE.PropertyStandardQuery };
if (!Kernel32.DeviceIoControl(handle,
Kernel32.IOCTL.IOCTL_STORAGE_QUERY_PROPERTY,
ref query,
Marshal.SizeOf(query),
out Kernel32.STORAGE_DEVICE_DESCRIPTOR_HEADER header,
Marshal.SizeOf<Kernel32.STORAGE_DEVICE_DESCRIPTOR_HEADER>(),
out _,
IntPtr.Zero))
{
return null;
}
IntPtr descriptorPtr = Marshal.AllocHGlobal((int)header.Size);
try
{
return Kernel32.DeviceIoControl(handle, Kernel32.IOCTL.IOCTL_STORAGE_QUERY_PROPERTY, ref query, Marshal.SizeOf(query), descriptorPtr, header.Size, out uint bytesReturned, IntPtr.Zero)
? new StorageInfo((int)driveIndex, descriptorPtr)
: null;
}
finally
{
Marshal.FreeHGlobal(descriptorPtr);
}
}
public static string[] GetLogicalDrives(int driveIndex)
{
var list = new List<string>();
try
{
using var s = new ManagementObjectSearcher("root\\CIMV2", "SELECT * FROM Win32_DiskPartition " + "WHERE DiskIndex = " + driveIndex);
foreach (ManagementBaseObject o in s.Get())
{
if (o is ManagementObject dp)
{
foreach (ManagementBaseObject ld in dp.GetRelated("Win32_LogicalDisk"))
list.Add(((string)ld["Name"]).TrimEnd(':'));
}
}
}
catch
{
// Ignored.
}
return list.ToArray();
}
private class StorageInfo : Storage.StorageInfo
{
public StorageInfo(int index, IntPtr descriptorPtr)
{
Kernel32.STORAGE_DEVICE_DESCRIPTOR descriptor = Marshal.PtrToStructure<Kernel32.STORAGE_DEVICE_DESCRIPTOR>(descriptorPtr);
Index = index;
Vendor = GetString(descriptorPtr, descriptor.VendorIdOffset, descriptor.Size);
Product = GetString(descriptorPtr, descriptor.ProductIdOffset, descriptor.Size);
Revision = GetString(descriptorPtr, descriptor.ProductRevisionOffset, descriptor.Size);
Serial = GetString(descriptorPtr, descriptor.SerialNumberOffset, descriptor.Size);
BusType = descriptor.BusType;
Removable = descriptor.RemovableMedia;
RawData = new byte[descriptor.Size];
Marshal.Copy(descriptorPtr, RawData, 0, RawData.Length);
}
private static string GetString(IntPtr descriptorPtr, uint offset, uint size)
{
return offset > 0 && offset < size ? Marshal.PtrToStringAnsi(IntPtr.Add(descriptorPtr, (int)offset))?.Trim() : string.Empty;
}
}
}