Quantcast
Channel: プログラミング
Viewing all articles
Browse latest Browse all 7831

C# 12&Win API NVMe接続SSDのS.M.A.R.T.情報を取得するコード - potisanのプログラミングメモ

$
0
0

C# 12 (.NET 8.0)でNVMe接続されたSSDのS.M.A.R.T.情報を取得するコードです。前回のC++23用コードをほぼそのままC#へ移植したものです。詳細は前回記事をご参照ください。 プロジェクトは暗黙的なglobal usingの有効なコンソールプロジェクトでターゲットOSはWindowsです。

ビットフィールドなどの扱いは自信がないので、間違っていたらコメント等で指摘いただけますと幸いです。

コード

// Program.csusing Utility.Windows.IO;

// NVMe SSDの物理ディスク番号を1で決め打ちしています。var healthInfo =new NvmeHealthInfoLog(1);

Console.WriteLine();
// NvmeHealthInfoLog.csusing System.Buffers;
using System.Buffers.Binary;
using System.Collections.Immutable;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

namespace Utility.Windows.IO;

[SupportedOSPlatform("windows")]
publicsealedclassNvmeHealthInfoLog
{
    [StructLayout(LayoutKind.Sequential, Pack =1)]
    public record CriticalWarningValue(byte Value)
    {
        publicbool AvailableSpaceLow => (Value & 0b00000001) !=0;
        publicbool TemperatureThreshold => (Value & 0b00000010) !=0;
        publicbool ReliabilityDegraded => (Value & 0b00000100) !=0;
        publicbool ReadOnly => (Value & 0b00001000) !=0;
        publicbool VolatileMemoryBackupDeviceFailed => (Value & 0b00010000) !=0;
        publicbyte Reserved => (byte)((Value & 0b11100000) >>5);
    }

    public CriticalWarningValue CriticalWarning { get; init; }
    publicushort Temperature { get; init; }
    publicbyte AvailableSpare { get; init; }
    publicbyte AvailableSpareThreshold { get; init; }
    publicbyte PercentageUsed { get; init; }
    public ImmutableArray<byte> Reserved0 { get; init; }
    public UInt128 DataUnitRead { get; init; }
    public UInt128 DataUnitWritten { get; init; }
    public UInt128 HostReadCommands { get; init; }
    public UInt128 HostWrittenCommands { get; init; }
    public UInt128 ControllerBusyTime { get; init; }
    public UInt128 PowerCycle { get; init; }
    public UInt128 PowerOnHours { get; init; }
    public UInt128 UnsafeShutdowns { get; init; }
    public UInt128 MediaErrors { get; init; }
    public UInt128 ErrorInfoLogEntryCount { get; init; }
    publicuint WarningCompositeTemperatureTime { get; init; }
    publicuint CriticalCompositeTemperatureTime { get; init; }
    publicushort TemperatureSensor1 { get; init; }
    publicushort TemperatureSensor2 { get; init; }
    publicushort TemperatureSensor3 { get; init; }
    publicushort TemperatureSensor4 { get; init; }
    publicushort TemperatureSensor5 { get; init; }
    publicushort TemperatureSensor6 { get; init; }
    publicushort TemperatureSensor7 { get; init; }
    publicushort TemperatureSensor8 { get; init; }
    public ImmutableArray<byte> Reserved1 { get; init; }

    public NvmeHealthInfoLog(uint physicalDriveNumber)
    {
        constuint IOCTL_STORAGE_QUERY_PROPERTY =0x002d1400;
        constuint StorageAdapterProtocolSpecificProperty =49;
        constuint PropertyStandardQuery =0;
        constuint ProtocolTypeNvme =3;
        constuint NVMeDataTypeLogPage =2;
        constuint NVME_LOG_PAGE_HEALTH_INFO =0x02;
        constuint NVME_NAMESPACE_ALL =0xffffffff;

        usingvar f = File.Open(@$"\\.\PhysicalDrive{physicalDriveNumber}", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);

        // 入力データを作成します。var propQuerySize = Marshal.SizeOf<STORAGE_PROPERTY_QUERY>();
        var protoSpecDataSize = Marshal.SizeOf<STORAGE_PROTOCOL_SPECIFIC_DATA>();
        var nvmeHealthInfoLogSize = Marshal.SizeOf<NVME_HEALTH_INFO_LOG>();
        var propQuery =new STORAGE_PROPERTY_QUERY(StorageAdapterProtocolSpecificProperty, PropertyStandardQuery);
        var protoSpecData =new STORAGE_PROTOCOL_SPECIFIC_DATA(ProtocolTypeNvme, NVMeDataTypeLogPage,
            NVME_LOG_PAGE_HEALTH_INFO, NVME_NAMESPACE_ALL, (uint)protoSpecDataSize, (uint)nvmeHealthInfoLogSize, 0, 0, 0, 0);
        var buffer = ArrayPool<byte>.Shared.Rent(propQuerySize + protoSpecDataSize + nvmeHealthInfoLogSize);
        try
        {
            {
                var bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                var p = bufferHandle.AddrOfPinnedObject();
                try
                {
                    Marshal.StructureToPtr(propQuery, p, false);
                    Marshal.StructureToPtr(protoSpecData, p + propQuerySize, false);
                }
                finally
                {
                    bufferHandle.Free();
                }
            }

            if (!NativeMethods.DeviceIoControl(f.SafeFileHandle,
                    IOCTL_STORAGE_QUERY_PROPERTY,
                    ref MemoryMarshal.GetArrayDataReference(buffer), (uint)buffer.Length,
                    ref MemoryMarshal.GetArrayDataReference(buffer), (uint)buffer.Length,
                    out_, 0))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

            // 取得したデータをインスタンスのフィールドに渡します。
            {
                var bufferHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
                var p = bufferHandle.AddrOfPinnedObject();
                try
                {
                    var raw = Marshal.PtrToStructure<NVME_HEALTH_INFO_LOG>(p + propQuerySize + protoSpecDataSize)
                        ??thrownew InvalidOperationException();

                    CriticalWarning = raw.CriticalWarning;
                    Temperature = BinaryPrimitives.ReadUInt16LittleEndian(raw.Temperature);
                    AvailableSpare = raw.AvailableSpare;
                    AvailableSpareThreshold = raw.AvailableSpareThreshold;
                    PercentageUsed = raw.PercentageUsed;
                    Reserved0 = [.. raw.Reserved0];
                    DataUnitRead = BinaryPrimitives.ReadUInt128LittleEndian(raw.DataUnitRead);
                    DataUnitWritten = BinaryPrimitives.ReadUInt128LittleEndian(raw.DataUnitWritten);
                    HostReadCommands = BinaryPrimitives.ReadUInt128LittleEndian(raw.HostReadCommands);
                    HostWrittenCommands = BinaryPrimitives.ReadUInt128LittleEndian(raw.HostWrittenCommands);
                    ControllerBusyTime = BinaryPrimitives.ReadUInt128LittleEndian(raw.ControllerBusyTime);
                    PowerCycle = BinaryPrimitives.ReadUInt128LittleEndian(raw.PowerCycle);
                    PowerOnHours = BinaryPrimitives.ReadUInt128LittleEndian(raw.PowerOnHours);
                    UnsafeShutdowns = BinaryPrimitives.ReadUInt128LittleEndian(raw.UnsafeShutdowns);
                    MediaErrors = BinaryPrimitives.ReadUInt128LittleEndian(raw.MediaErrors);
                    ErrorInfoLogEntryCount = BinaryPrimitives.ReadUInt128LittleEndian(raw.ErrorInfoLogEntryCount);
                    WarningCompositeTemperatureTime = raw.WarningCompositeTemperatureTime;
                    CriticalCompositeTemperatureTime = raw.CriticalCompositeTemperatureTime;
                    TemperatureSensor1 = raw.TemperatureSensor1;
                    TemperatureSensor2 = raw.TemperatureSensor2;
                    TemperatureSensor3 = raw.TemperatureSensor3;
                    TemperatureSensor4 = raw.TemperatureSensor4;
                    TemperatureSensor5 = raw.TemperatureSensor5;
                    TemperatureSensor6 = raw.TemperatureSensor6;
                    TemperatureSensor7 = raw.TemperatureSensor7;
                    TemperatureSensor8 = raw.TemperatureSensor8;
                    Reserved1 = [.. raw.Reserved1];
                }
                finally
                {
                    bufferHandle.Free();
                }
            }
        }
        finally
        {
            ArrayPool<byte>.Shared.Return(buffer);
        }
    }

    private record struct STORAGE_PROPERTY_QUERY(
        uint PropertyId,
        uint QueryType);

    private record struct STORAGE_PROTOCOL_SPECIFIC_DATA(
        uint ProtocolType,
        uint DataType,
        uint ProtocolDataRequestValue,
        uint ProtocolDataRequestSubValue,
        uint ProtocolDataOffset,
        uint ProtocolDataLength,
        uint FixedProtocolReturnData,
        uint ProtocolDataRequestSubValue2,
        uint ProtocolDataRequestSubValue3,
        uint ProtocolDataRequestSubValue4);

    /// <summary>/// nvme.hのNVME_HEALTH_INFO_LOG構造体に対応します。/// </summary>
    [StructLayout(LayoutKind.Sequential, Pack =1)]
    privateclassNVME_HEALTH_INFO_LOG
    {
        public CriticalWarningValue CriticalWarning;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =2)]
        publicbyte[] Temperature;
        publicbyte AvailableSpare;
        publicbyte AvailableSpareThreshold;
        publicbyte PercentageUsed;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =26)]
        publicbyte[] Reserved0;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] DataUnitRead;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] DataUnitWritten;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] HostReadCommands;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] HostWrittenCommands;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] ControllerBusyTime;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] PowerCycle;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] PowerOnHours;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] UnsafeShutdowns;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] MediaErrors;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =16)]
        publicbyte[] ErrorInfoLogEntryCount;
        publicuint WarningCompositeTemperatureTime;
        publicuint CriticalCompositeTemperatureTime;
        publicushort TemperatureSensor1;
        publicushort TemperatureSensor2;
        publicushort TemperatureSensor3;
        publicushort TemperatureSensor4;
        publicushort TemperatureSensor5;
        publicushort TemperatureSensor6;
        publicushort TemperatureSensor7;
        publicushort TemperatureSensor8;
        [MarshalAs(UnmanagedType.ByValArray, SizeConst =296)]
        publicbyte[] Reserved1;

    }

    privatestaticclassNativeMethods
    {
        [DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError =true)]
        [DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
        publicstaticexternbool DeviceIoControl(
            SafeHandle hDevice,
            uint dwIoControlCode,
            refbyte lpInBuffer,
            uint nInBufferSize,
            refbyte lpOutBuffer,
            uint nOutBufferSize,
            outuint lpBytesReturned,
            nint lpOverlapped);
    }
}

補足

File.Openは物理ドライブのハンドルも開ける。

File.OpenはWin APICreateFile関数をほぼそのまま呼び出しており、物理ドライブのハンドルも開けます。ただしアクセス方法(読み書き等)の指定が必須なので、今回は不要な読み込みアクセスの指定が必要となります。

その他

  • コード中に二つあるGCHandleスコープ制限用の{~}はまとめてもよいかもしれません。

Viewing all articles
Browse latest Browse all 7831

Trending Articles