first commit
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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) { }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
@@ -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)
|
||||
{ }
|
||||
}
|
||||
@@ -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)
|
||||
{ }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{ }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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) { }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user