다음을 통해 공유


NVMe 드라이브와 함께 작업하기

적용 대상:

  • Windows 10
  • Windows Server 2016

Windows 애플리케이션에서 고속 NVMe 디바이스를 사용하는 방법을 알아봅니다. 디바이스 액세스는 Windows Server 2012 R2 및 Windows 8.1에서 처음 도입된 기본 제공 드라이버인 StorNVMe.sys통해 사용하도록 설정됩니다. 또한 KB 핫 픽스(Hot Fix)를 통해 Windows 7 디바이스에서도 사용할 수 있습니다. Windows 10에서는 공급업체별 NVMe 명령에 대한 통과 메커니즘과 기존 IOCTL에 대한 업데이트를 포함하여 몇 가지 새로운 기능이 도입되었습니다.

이 항목에서는 Windows 10에서 NVMe 드라이브에 액세스하는 데 사용할 수 있는 일반적인 API에 대한 개요를 제공합니다. 또한 다음을 설명합니다.

NVMe 드라이브 작업을 위한 API

다음 일반 사용 API를 사용하여 Windows 10에서 NVMe 드라이브에 액세스할 수 있습니다. 이러한 API는 사용자 모드 애플리케이션에 대한 winioctl.h, 커널 모드 드라이버에 대한 ntddstor.h 찾을 수 있습니다. 헤더 파일에 대한 자세한 내용은 헤더 파일참조하세요.

  • IOCTL_STORAGE_PROTOCOL_COMMAND : 이 IOCTL을 STORAGE_PROTOCOL_COMMAND 구조와 함께 사용하여 NVMe 명령을 실행합니다. 이 IOCTL은 NVMe 패스스루를 사용하도록 설정하고 NVMe의 명령 효과 로그를 지원합니다. 공급업체별 명령과 함께 사용할 수 있습니다. 자세한 내용은 패스스루 메커니즘 를 참조하세요.

  • STORAGE_PROTOCOL_COMMAND : 이 입력 버퍼 구조에는 다음 상태 값을 보고하는 데 사용할 수 있는 ReturnStatus 필드가 포함됩니다.

    • 저장 프로토콜 상태 대기 중 (STORAGE_PROTOCOL_STATUS_PENDING)
    • STORAGE_PROTOCOL_STATUS_SUCCESS (저장 프로토콜 상태 성공)
    • STORAGE_PROTOCOL_STATUS_ERROR
    • 저장_프로토콜_상태_잘못된_요청
    • STORAGE_PROTOCOL_STATUS_NO_DEVICE
    • STORAGE_PROTOCOL_STATUS_BUSY
    • STORAGE_PROTOCOL_STATUS_DATA_OVERRUN
    • STORAGE_PROTOCOL_STATUS_INSUFFICIENT_RESOURCES (저장 프로토콜 상태 - 리소스 부족)
    • STORAGE_PROTOCOL_STATUS_NOT_SUPPORTED
  • IOCTL_STORAGE_QUERY_PROPERTY : 이 IOCTL을 STORAGE_PROPERTY_QUERY 구조와 함께 사용하여 디바이스 정보를 검색합니다. 자세한 내용은 프로토콜별 쿼리온도 쿼리참조하세요.

  • STORAGE_PROPERTY_QUERY : 이 구조에는 쿼리할 데이터를 지정하는 PropertyIdAdditionalParameters 필드가 포함됩니다. PropertyId 필드에 STORAGE_PROPERTY_ID 열거형을 사용하여 데이터의 형식을 지정합니다. AdditionalParameters 필드를 사용하여 데이터 형식에 따라 자세한 정보를 지정할 수 있습니다. 프로토콜별 데이터의 경우 AdditionalParameters 필드의 STORAGE_PROTOCOL_SPECIFIC_DATA 구조를 사용합니다. 온도 데이터의 경우 AdditionalParameters 필드에서 STORAGE_TEMPERATURE_INFO 구조를 사용합니다.

  • STORAGE_PROPERTY_ID : 이 열거형에는 IOCTL_STORAGE_QUERY_PROPERTY 프로토콜별 및 온도 정보를 검색할 수 있는 새 값이 포함됩니다.

    • StorageAdapterProtocolSpecificProperty: If ProtocolType = ProtocolTypeNvmeDataType = NVMeDataTypeLogPage, 호출자는 512바이트 데이터 청크를 요청해야 합니다.

    • 저장장치프로토콜구체속성

      이러한 프로토콜별 속성 ID 중 하나를 STORAGE_PROTOCOL_SPECIFIC_DATA 함께 사용하여 STORAGE_PROTOCOL_DATA_DESCRIPTOR구조에서 프로토콜별 데이터를 검색합니다.

      • StorageAdapterTemperatureProperty
      • 저장장치온도속성

      이러한 온도 속성 ID 중 하나를 사용하여 STORAGE_TEMPERATURE_DATA_DESCRIPTOR 구조에서 온도 데이터를 검색합니다.

  • STORAGE_PROTOCOL_SPECIFIC_DATA: 이 구조체가 STORAGE_PROPERTY_QUERYAdditionalParameters 필드에 사용되고 STORAGE_PROTOCOL_NVME_DATA_TYPE 열거형 값이 지정된 경우 NVMe 관련 데이터를 검색합니다. STORAGE_PROTOCOL_SPECIFIC_DATA 구조체의 DataType 필드에 다음 STORAGE_PROTOCOL_NVME_DATA_TYPE 값 중 하나를 사용합니다.

    • NVMeDataTypeIdentify을 사용하여 컨트롤러 데이터 식별 또는 네임스페이스 데이터 식별 정보를 얻습니다.
    • NVMeDataTypeLogPage 사용하여 로그 페이지(SMART/상태 데이터 포함)를 가져옵니다.
    • NVMeDataTypeFeature 사용하여 NVMe 드라이브의 기능을 가져옵니다.
  • STORAGE_TEMPERATURE_INFO : 이 구조체는 특정 온도 데이터를 보유하는 데 사용됩니다. STORAGE_TEMPERATURE_DATA_DESCRIPTOR에서 온도 쿼리의 결과를 반환하는 데 사용됩니다.

  • IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD : 이 IOCTL을 STORAGE_TEMPERATURE_THRESHOLD 구조와 함께 사용하여 온도 임계값을 설정합니다. 자세한 내용은 동작 변경 명령참조하세요.

  • STORAGE_TEMPERATURE_THRESHOLD : 이 구조체는 온도 임계값을 지정하는 입력 버퍼로 사용됩니다. OverThreshold 필드(부울)는 임계값 필드가 임계값을 초과하는지 여부를 지정합니다(그렇지 않으면 임계값 미만임).

통과 메커니즘

NVMe 사양에 정의되지 않은 명령은 호스트 OS에서 처리하기가 가장 어렵습니다. 호스트에는 명령이 대상 디바이스에 미칠 수 있는 영향, 노출된 인프라(네임스페이스/블록 크기) 및 동작에 대한 인사이트가 없습니다.

Windows 스토리지 스택을 통해 이러한 디바이스 특정 명령을 더 잘 수행하기 위해 새로운 통과 메커니즘을 사용하면 공급업체별 명령을 파이프할 수 있습니다. 이 통과 파이프는 관리 및 테스트 도구 개발에도 도움이 됩니다. 그러나 이 통과 메커니즘을 사용하려면 명령 효과 로그를 사용해야 합니다. 또한 StoreNVMe.sys 명령 효과 로그에는 통과 명령뿐만 아니라 모든 명령을 설명해야 합니다.

중요하다

StorNVMe.sysStorport.sys는 명령 효과 로그에 설명되지 않은 경우, 디바이스에 대한 모든 명령을 차단합니다.

명령 효과 로그 지원

명령 효과 로그(지원되는 명령 및 효과에 설명된 대로 NVMe 사양 1.2섹션 5.10.1.5)는 사양 정의 명령과 함께 공급업체별 명령의 효과에 대한 설명을 허용합니다. 이렇게 하면 명령 지원 유효성 검사와 명령 동작 최적화가 모두 용이하므로 디바이스에서 지원하는 전체 명령 집합에 대해 구현해야 합니다. 다음 조건에서는 명령 효과 로그 항목에 따라 명령을 보내는 방법에 대한 결과를 설명합니다.

명령 효과 로그에 설명된 특정 명령의 경우...

동안:

  • CSUPP(명령 지원됨)가 '1'로 설정되어 컨트롤러에서 명령이 지원됨을 나타냅니다(비트 01).

    메모

    CSUPP가 '0'으로 설정되면(명령이 지원되지 않음을 의미) 명령이 차단됩니다.

다음 중 하나라도 설정된 경우:

  • CCC(컨트롤러 기능 변경)가 '1'로 설정되어 명령이 컨트롤러 기능을 변경할 수 있음을 나타냅니다(비트 04).
  • NIC(네임스페이스 인벤토리 변경)는 명령이 여러 네임스페이스의 수 또는 기능을 변경할 수 있음을 나타내는 '1'로 설정됩니다(비트 03).
  • NCC(네임스페이스 기능 변경)가 '1'로 설정되어 명령이 단일 네임스페이스의 기능을 변경할 수 있음을 나타냅니다(비트 02).
  • CSE(명령 제출 및 실행)는 001b 또는 010b로 설정됩니다. 이는 동일한 네임스페이스 또는 네임스페이스에 다른 미해결 명령이 없을 때 명령이 제출될 수 있으며 이 명령이 완료될 때까지 다른 명령을 동일하거나 임의의 네임스페이스에 제출해서는 안 됨을 의미합니다(Bits 18:16).

명령은 어댑터에 보내지는 유일한 대기 명령으로 전송됩니다.

그렇지 않으면:

  • CSE(명령 제출 및 실행)는 001b로 설정됩니다. 이는 동일한 네임스페이스에 다른 미해결 명령이 없을 때 명령이 제출될 수 있으며, 이 명령이 완료될 때까지 다른 명령을 동일한 네임스페이스에 제출해서는 안 된다는 의미입니다(Bits 18:16).

그런 다음 명령이 LUN(논리 단위 번호 개체)에 대한 유일한 명령으로 전송될.

그렇지 않으면명령이 억제 없이 처리 중인 다른 명령과 함께 전송됩니다. 예를 들어 사양이 정의되지 않은 통계 정보를 검색하기 위해 공급업체별 명령을 디바이스로 보내는 경우 I/O 명령을 실행하기 위해 디바이스의 동작이나 기능을 변경할 위험이 없습니다. 이러한 요청은 I/O와 병렬로 처리될 수 있으며 일시 중지 다시 시작이 필요하지 않습니다.

IOCTL_STORAGE_PROTOCOL_COMMAND 사용하여 명령 보내기

통과는 Windows 10에 도입된 IOCTL_STORAGE_PROTOCOL_COMMAND 사용하여 수행할 수 있습니다. 이 IOCTL은 대상 디바이스에 포함된 명령을 보내기 위해 기존 SCSI 및 ATA 통과 IOCTL과 유사한 동작을 갖도록 설계되었습니다. 이 IOCTL을 통해 NVMe 드라이브를 포함하여 스토리지 장치에 데이터 전달을 보낼 수 있습니다.

예를 들어 NVMe에서 IOCTL은 다음 명령 코드의 전송을 허용합니다.

  • 공급업체별 관리자 명령(C0h – FFh)
  • 공급업체별 NVMe 명령(80h – FFh)

다른 모든 IOCTL과 마찬가지로 DeviceIoControl을 사용하여 통과 IOCTL을 아래로 보냅니다. IOCTL은 ntddstor.h에 있는 STORAGE_PROTOCOL_COMMAND 입력 버퍼 구조를 사용하여 채워집니다. 명령 필드를 공급업체별 명령으로 채웁다.

typedef struct _STORAGE_PROTOCOL_COMMAND {

    ULONG   Version;                        // STORAGE_PROTOCOL_STRUCTURE_VERSION
    ULONG   Length;                         // sizeof(STORAGE_PROTOCOL_COMMAND)

    STORAGE_PROTOCOL_TYPE  ProtocolType;
    ULONG   Flags;                          // Flags for the request

    ULONG   ReturnStatus;                   // return value
    ULONG   ErrorCode;                      // return value, optional

    ULONG   CommandLength;                  // non-zero value should be set by caller
    ULONG   ErrorInfoLength;                // optional, can be zero
    ULONG   DataToDeviceTransferLength;     // optional, can be zero. Used by WRITE type of request.
    ULONG   DataFromDeviceTransferLength;   // optional, can be zero. Used by READ type of request.

    ULONG   TimeOutValue;                   // in unit of seconds

    ULONG   ErrorInfoOffset;                // offsets need to be pointer aligned
    ULONG   DataToDeviceBufferOffset;       // offsets need to be pointer aligned
    ULONG   DataFromDeviceBufferOffset;     // offsets need to be pointer aligned

    ULONG   CommandSpecific;                // optional information passed along with Command.
    ULONG   Reserved0;

    ULONG   FixedProtocolReturnData;        // return data, optional. Some protocol, such as NVMe, may return a small amount data (DWORD0 from completion queue entry) without the need of separate device data transfer.
    ULONG   Reserved1[3];

    _Field_size_bytes_full_(CommandLength) UCHAR Command[ANYSIZE_ARRAY];

} STORAGE_PROTOCOL_COMMAND, *PSTORAGE_PROTOCOL_COMMAND;

보내려는 공급업체별 명령은 위의 강조 표시된 필드에 채워져야 합니다. 다시 한 번 주의하세요, 통과 명령에 대해 명령 효과 로그를 구현해야 합니다. 특히 이러한 명령은 명령 효과 로그에서 지원되는 것으로 보고되어야 합니다(자세한 내용은 이전 섹션 참조). 또한 PRP 필드는 드라이버별로 지정되므로 명령을 보내는 애플리케이션은 이를 그대로 둘 0수 있습니다.

마지막으로, 이 패스스루 IOCTL은 공급업체별 명령을 보내기 위해 설계되었습니다. 다른 관리자 또는 공급업체가 아닌 NVMe 명령(예: 식별)을 보내려면 이 통과 IOCTL을 사용하면 안 됩니다. 예를 들어 로그 페이지 식별 또는 가져오기에 IOCTL_STORAGE_QUERY_PROPERTY 사용해야 합니다. 자세한 내용은 프로토콜 관련 쿼리 다음 섹션을 참조하세요.

통과 메커니즘을 통해 펌웨어를 업데이트하지 마세요.

펌웨어 다운로드 및 활성화 명령은 통과를 사용하여 전송해서는 안 됩니다. IOCTL_STORAGE_PROTOCOL_COMMAND 공급업체별 명령에만 사용해야 합니다.

대신 다음 일반 스토리지 IOCTL(Windows 10에서 도입됨)을 사용하여 SCSI_miniport 버전의 펌웨어 IOCTL을 직접 사용하지 않도록 합니다. 스토리지 드라이버는 IOCTL을 SCSI 명령 또는 IOCTL의 SCSI_miniport 버전으로 미니포트로 변환합니다.

이러한 IOCTL은 Windows 10 및 Windows Server 2016에서 펌웨어 업그레이드 도구를 개발하는 데 권장됩니다.

스토리지 정보를 가져오고 펌웨어를 업데이트하기 위해 Windows는 이 작업을 신속하게 수행하기 위한 PowerShell cmdlet도 지원합니다.

  • Get-StorageFirmwareInfo
  • Update-StorageFirmware

통과 메커니즘을 통해 오류 반환

SCSI 및 ATA 통과 IOCTL과 마찬가지로 명령/요청이 미니포트 또는 디바이스로 전송되면 IOCTL은 성공 여부를 반환합니다. STORAGE_PROTOCOL_COMMAND 구조에서 IOCTL은 ReturnStatus 필드를 통해 상태를 반환합니다.

예: 공급업체별 명령 보내기

이 예제에서는 임의 공급업체별 명령(0xFF)이 통과를 통해 NVMe 드라이브로 전송됩니다. 다음 코드는 버퍼를 할당하고 쿼리를 초기화한 다음 DeviceIoControl을 통해 디바이스로 명령을 보냅니다.

ZeroMemory(buffer, bufferLength);  
protocolCommand = (PSTORAGE_PROTOCOL_COMMAND)buffer;  

protocolCommand->Version = STORAGE_PROTOCOL_STRUCTURE_VERSION;  
protocolCommand->Length = sizeof(STORAGE_PROTOCOL_COMMAND);  
protocolCommand->ProtocolType = ProtocolTypeNvme;  
protocolCommand->Flags = STORAGE_PROTOCOL_COMMAND_FLAG_ADAPTER_REQUEST;  
protocolCommand->CommandLength = STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;  
protocolCommand->ErrorInfoLength = sizeof(NVME_ERROR_INFO_LOG);  
protocolCommand->DataFromDeviceTransferLength = 4096;  
protocolCommand->TimeOutValue = 10;  
protocolCommand->ErrorInfoOffset = FIELD_OFFSET(STORAGE_PROTOCOL_COMMAND, Command) + STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;  
protocolCommand->DataFromDeviceBufferOffset = protocolCommand->ErrorInfoOffset + protocolCommand->ErrorInfoLength;  
protocolCommand->CommandSpecific = STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND;  

command = (PNVME_COMMAND)protocolCommand->Command;  

command->CDW0.OPC = 0xFF;  
command->u.GENERAL.CDW10 = 0xto_fill_in;  
command->u.GENERAL.CDW12 = 0xto_fill_in;  
command->u.GENERAL.CDW13 = 0xto_fill_in;  

//  
// Send request down.  
//  
result = DeviceIoControl(DeviceList[DeviceIndex].Handle,  
                            IOCTL_STORAGE_PROTOCOL_COMMAND,  
                            buffer,  
                            bufferLength,  
                            buffer,  
                            bufferLength,  
                            &returnedLength,  
                            NULL 
                            );  

이 예제에서는 명령이 디바이스에 성공한 경우 protocolCommand->ReturnStatus == STORAGE_PROTOCOL_STATUS_SUCCESS 예상합니다.

프로토콜별 쿼리

Windows 8.1에서는 데이터 검색을 위한 IOCTL_STORAGE_QUERY_PROPERTY 도입했습니다. Windows 10에서는 로그 페이지 가져오기, 기능 가져오기, 식별같은 일반적으로 요청되는 NVMe 기능을 지원하도록 IOCTL이 향상되었습니다. 이를 통해 모니터링 및 인벤토리를 위해 NVMe 특정 정보를 검색할 수 있습니다.

IOCTL, STORAGE_PROPERTY_QUERY (Windows 10 이상) 입력 버퍼는 다음과 같습니다.

typedef struct _STORAGE_PROPERTY_QUERY {
    STORAGE_PROPERTY_ID PropertyId;
    STORAGE_QUERY_TYPE QueryType;
    UCHAR  AdditionalParameters[1];
} STORAGE_PROPERTY_QUERY, *PSTORAGE_PROPERTY_QUERY;

IOCTL_STORAGE_QUERY_PROPERTY 사용하여 STORAGE_PROTOCOL_DATA_DESCRIPTOR NVMe 프로토콜 관련 정보를 검색하는 경우 다음과 같이 STORAGE_PROPERTY_QUERY 구조를 구성합니다.

  • STORAGE_PROPERTY_QUERYSTORAGE_PROTOCOL_SPECIFIC_DATA 구조를 모두 포함할 수 있는 버퍼를 할당합니다.
  • 컨트롤러 또는 디바이스/네임스페이스 요청에 대해 PropertyID 필드를 StorageAdapterProtocolSpecificProperty 또는 StorageDeviceProtocolSpecificProperty로 설정합니다.
  • QueryType 필드를 PropertyStandardQuery로 설정합니다.
  • STORAGE_PROTOCOL_SPECIFIC_DATA 구조체를 원하는 값으로 채웁니다. STORAGE_PROTOCOL_SPECIFIC_DATA 시작은 STORAGE_PROPERTY_QUERYAdditionalParameters 필드입니다.

STORAGE_PROTOCOL_SPECIFIC_DATA 구조(Windows 10 이상)는 다음과 같습니다.

typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA {

    STORAGE_PROTOCOL_TYPE ProtocolType;
    ULONG   DataType;                 

    ULONG   ProtocolDataRequestValue;
    ULONG   ProtocolDataRequestSubValue;

    ULONG   ProtocolDataOffset;         
    ULONG   ProtocolDataLength;

    ULONG   FixedProtocolReturnData;   
    ULONG   Reserved[3];

} STORAGE_PROTOCOL_SPECIFIC_DATA, *PSTORAGE_PROTOCOL_SPECIFIC_DATA;

NVMe 프로토콜 관련 정보의 형식을 지정하려면 다음과 같이 STORAGE_PROTOCOL_SPECIFIC_DATA 구조를 구성합니다.

  • ProtocolType 필드를 ProtocolTypeNVMe로 설정합니다.
  • DataType 필드를 STORAGE_PROTOCOL_NVME_DATA_TYPE정의된 열거형 값으로 설정합니다.
    • NVMeDataTypeIdentify을 사용하여 컨트롤러 데이터 식별 또는 네임스페이스 데이터 식별 정보를 얻습니다.
    • NVMeDataTypeLogPage 사용하여 로그 페이지(SMART/상태 데이터 포함)를 가져옵니다.
    • NVMeDataTypeFeature 사용하여 NVMe 드라이브의 기능을 가져옵니다.

ProtocolTypeNVMeProtocolType사용하면 프로토콜 관련 정보에 대한 쿼리를 NVMe 드라이브의 다른 I/O와 병렬로 검색할 수 있습니다.

중요하다

IOCTL_STORAGE_QUERY_PROPERTYSTORAGE_PROPERTY_IDStorageAdapterProtocolSpecificProperty을 사용하고, STORAGE_PROTOCOL_SPECIFIC_DATA 또는 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 구조체가 ProtocolType=ProtocolTypeNvmeDataType=NVMeDataTypeLogPage로 설정된 경우, 동일한 구조체의 ProtocolDataLength 멤버를 최소 512바이트로 설정합니다.

다음 예제에서는 NVMe 프로토콜 관련 쿼리를 보여 줍니다.

예: NVMe 식별 쿼리

이 예제에서는 식별 요청이 NVMe 드라이브로 전송됩니다. 다음 코드는 쿼리 데이터 구조를 초기화한 다음 DeviceIoControl을 통해 디바이스로 명령을 보냅니다.

BOOL    result;
PVOID   buffer = NULL;
ULONG   bufferLength = 0;
ULONG   returnedLength = 0;

PSTORAGE_PROPERTY_QUERY query = NULL;
PSTORAGE_PROTOCOL_SPECIFIC_DATA protocolData = NULL;
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR protocolDataDescr = NULL;

//
// Allocate buffer for use.
//
bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE;
buffer = malloc(bufferLength);

if (buffer == NULL) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: allocate buffer failed, exit.\n"));
    goto exit;
}

//
// Initialize query data structure to get Identify Controller Data.
//
ZeroMemory(buffer, bufferLength);

query = (PSTORAGE_PROPERTY_QUERY)buffer;
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;

query->PropertyId = StorageAdapterProtocolSpecificProperty;
query->QueryType = PropertyStandardQuery;

protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeIdentify;
protocolData->ProtocolDataRequestValue = NVME_IDENTIFY_CNS_CONTROLLER;
protocolData->ProtocolDataRequestSubValue = 0;
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = NVME_MAX_LOG_SIZE;

//
// Send request down.
//
result = DeviceIoControl(DeviceList[Index].Handle,
                            IOCTL_STORAGE_QUERY_PROPERTY,
                            buffer,
                            bufferLength,
                            buffer,
                            bufferLength,
                            &returnedLength,
                            NULL
                            );

ZeroMemory(buffer, bufferLength);
query = (PSTORAGE_PROPERTY_QUERY)buffer;  
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;  
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;  

query->PropertyId = StorageDeviceProtocolSpecificProperty;  
query->QueryType = PropertyStandardQuery;  

protocolData->ProtocolType = ProtocolTypeNvme;  
protocolData->DataType = NVMeDataTypeLogPage;  
protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;  
protocolData->ProtocolDataRequestSubValue = 0;  
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);  
protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);  

//  
// Send request down.  
//  
result = DeviceIoControl(DeviceList[Index].Handle,  
                            IOCTL_STORAGE_QUERY_PROPERTY,  
                            buffer,  
                            bufferLength,  
                            buffer, 
                            bufferLength,  
                            &returnedLength,  
                            NULL  
                            );  

//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
    (protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Identify Controller Data - data descriptor header not valid.\n"));
    return;
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
    (protocolData->ProtocolDataLength < NVME_MAX_LOG_SIZE)) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Identify Controller Data - ProtocolData Offset/Length not valid.\n"));
    goto exit;
}

//
// Identify Controller Data 
//
{
    PNVME_IDENTIFY_CONTROLLER_DATA identifyControllerData = (PNVME_IDENTIFY_CONTROLLER_DATA)((PCHAR)protocolData + protocolData->ProtocolDataOffset);

    if ((identifyControllerData->VID == 0) ||
        (identifyControllerData->NN == 0)) {
        _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Identify Controller Data not valid.\n"));
        goto exit;
    } else {
        _tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***Identify Controller Data succeeded***.\n"));
    }
}

중요하다

IOCTL_STORAGE_QUERY_PROPERTYSTORAGE_PROPERTY_IDStorageAdapterProtocolSpecificProperty을 사용하고, STORAGE_PROTOCOL_SPECIFIC_DATA 또는 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 구조체가 ProtocolType=ProtocolTypeNvmeDataType=NVMeDataTypeLogPage로 설정된 경우, 동일한 구조체의 ProtocolDataLength 멤버를 최소 512바이트로 설정합니다.

호출자는 STORAGE_PROPERTY_QUERY 및 STORAGE_PROTOCOL_SPECIFIC_DATA 크기를 포함하는 단일 버퍼를 할당해야 합니다. 이 예제에서는 속성 쿼리의 입력 및 출력에 동일한 버퍼를 사용합니다. 따라서 할당된 버퍼의 크기는 "FIELD_OFFSET(STORAGE_PROPERTY_QUERY, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA) + NVME_MAX_LOG_SIZE"입니다. 입력 및 출력 모두에 대해 별도의 버퍼를 할당할 수 있지만 단일 버퍼를 사용하여 NVMe 관련 정보를 쿼리하는 것이 좋습니다.

identifyControllerData->NN은 NN(네임스페이스 수)입니다. Windows는 네임스페이스를 실제 드라이브로 검색합니다.

예: NVMe 로그 페이지 가져오기 쿼리

이 예제에서는 이전 로그 페이지 가져오기 요청이 NVMe 드라이브로 전송됩니다. 다음 코드는 쿼리 데이터 구조를 준비한 다음 DeviceIoControl을 통해 디바이스로 명령을 보냅니다.

ZeroMemory(buffer, bufferLength);  

query = (PSTORAGE_PROPERTY_QUERY)buffer;  
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;  
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;  

query->PropertyId = StorageDeviceProtocolSpecificProperty;  
query->QueryType = PropertyStandardQuery;  

protocolData->ProtocolType = ProtocolTypeNvme;  
protocolData->DataType = NVMeDataTypeLogPage;  
protocolData->ProtocolDataRequestValue = NVME_LOG_PAGE_HEALTH_INFO;  
protocolData->ProtocolDataRequestSubValue = 0;  // This will be passed as the lower 32 bit of log page offset if controller supports extended data for the Get Log Page.
protocolData->ProtocolDataRequestSubValue2 = 0; // This will be passed as the higher 32 bit of log page offset if controller supports extended data for the Get Log Page.
protocolData->ProtocolDataRequestSubValue3 = 0; // This will be passed as Log Specific Identifier in CDW11.
protocolData->ProtocolDataRequestSubValue4 = 0; // This will map to STORAGE_PROTOCOL_DATA_SUBVALUE_GET_LOG_PAGE definition, then user can pass Retain Asynchronous Event, Log Specific Field.

protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);  
protocolData->ProtocolDataLength = sizeof(NVME_HEALTH_INFO_LOG);  

//  
// Send request down.  
//  
result = DeviceIoControl(DeviceList[Index].Handle,  
                            IOCTL_STORAGE_QUERY_PROPERTY,  
                            buffer,  
                            bufferLength,  
                            buffer, 
                            bufferLength,  
                            &returnedLength,  
                            NULL  
                            );  

if (!result || (returnedLength == 0)) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log failed. Error Code %d.\n"), GetLastError());
    goto exit;
}

//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
    (protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - data descriptor header not valid.\n"));
    return;
}

protocolData = &protocolDataDescr->ProtocolSpecificData;

if ((protocolData->ProtocolDataOffset < sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA)) ||
    (protocolData->ProtocolDataLength < sizeof(NVME_HEALTH_INFO_LOG))) {
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log - ProtocolData Offset/Length not valid.\n"));
    goto exit;
}

//
// SMART/Health Information Log Data 
//
{
    PNVME_HEALTH_INFO_LOG smartInfo = (PNVME_HEALTH_INFO_LOG)((PCHAR)protocolData + protocolData->ProtocolDataOffset);

    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: SMART/Health Information Log Data - Temperature %d.\n"), ((ULONG)smartInfo->Temperature[1] << 8 | smartInfo->Temperature[0]) - 273);

    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***SMART/Health Information Log succeeded***.\n"));
}

호출자는 StorageAdapterProtocolSpecificPropertySTORAGE_PROPERTY_ID 사용할 수 있으며 STORAGE_PROTOCOL_SPECIFIC_DATA 또는 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 구조가 공급업체별 데이터의 512바이트 청크를 요청하도록 ProtocolDataRequestValue=VENDOR_SPECIFIC_LOG_PAGE_IDENTIFIER 설정되어 있습니다.

예: NVMe 기능 가져오기 쿼리

이 예제에서는 이전 예제를 바탕으로 한 Get Features 요청이 NVMe 드라이브로 전송됩니다. 다음 코드는 쿼리 데이터 구조를 준비한 다음 DeviceIoControl을 통해 디바이스로 명령을 보냅니다.

//  
// Initialize query data structure to Volatile Cache feature.  
//  

ZeroMemory(buffer, bufferLength);  


query = (PSTORAGE_PROPERTY_QUERY)buffer;  
protocolDataDescr = (PSTORAGE_PROTOCOL_DATA_DESCRIPTOR)buffer;  
protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA)query->AdditionalParameters;  

query->PropertyId = StorageDeviceProtocolSpecificProperty;  
query->QueryType = PropertyStandardQuery;  

protocolData->ProtocolType = ProtocolTypeNvme;  
protocolData->DataType = NVMeDataTypeFeature;  
protocolData->ProtocolDataRequestValue = NVME_FEATURE_VOLATILE_WRITE_CACHE;  
protocolData->ProtocolDataRequestSubValue = 0;  
protocolData->ProtocolDataOffset = 0;  
protocolData->ProtocolDataLength = 0;  

//  
// Send request down.  
//  

result = DeviceIoControl(DeviceList[Index].Handle,  
                            IOCTL_STORAGE_QUERY_PROPERTY,  
                            buffer,  
                            bufferLength,  
                            buffer,  
                            bufferLength,  
                            &returnedLength,  
                            NULL  
                            );  

if (!result || (returnedLength == 0)) {  
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache failed. Error Code %d.\n"), GetLastError());  
    goto exit;  
}  

//  
// Validate the returned data.  
//  

if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||  
    (protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR))) {  
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache  - data descriptor header not valid.\n"));  
    return;                                           
}  

//
// Volatile Cache 
//
{
    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: Get Feature - Volatile Cache - %x.\n"), protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData);

    _tprintf(_T("DeviceNVMeQueryProtocolDataTest: ***Get Feature - Volatile Cache succeeded***.\n"));
}

프로토콜별 집합

Windows 10 19H1 이상에서는 NVMe 설정 기능을 지원하도록 IOCTL_STORAGE_SET_PROPERTY가 향상되었습니다.

IOCTL_STORAGE_SET_PROPERTY 대한 입력 버퍼는 다음과 같습니다.

typedef struct _STORAGE_PROPERTY_SET {

    //
    // ID of the property being retrieved
    //

    STORAGE_PROPERTY_ID PropertyId;

    //
    // Flags indicating the type of set property being performed
    //

    STORAGE_SET_TYPE SetType;

    //
    // Space for additional parameters if necessary
    //

    UCHAR AdditionalParameters[1];

} STORAGE_PROPERTY_SET, *PSTORAGE_PROPERTY_SET;

IOCTL_STORAGE_SET_PROPERTY 사용하여 NVMe 기능을 설정하는 경우 다음과 같이 STORAGE_PROPERTY_SET 구조를 구성합니다.

  • STORAGE_PROPERTY_SET 및 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT구조를 모두 포함할 수 있는 버퍼를 할당합니다.
  • 컨트롤러 또는 디바이스/네임스페이스 요청에 대해 PropertyID 필드를 StorageAdapterProtocolSpecificProperty 또는 StorageDeviceProtocolSpecificProperty로 설정합니다.
  • STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 구조체를 원하는 값으로 채웁니다. STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 시작은 STORAGE_PROPERTY_SETAdditionalParameters 필드입니다.

STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 구조체는 여기에 표시됩니다.

typedef struct _STORAGE_PROTOCOL_SPECIFIC_DATA_EXT {

    STORAGE_PROTOCOL_TYPE ProtocolType;
    ULONG   DataType;                   // The value will be protocol specific, as defined in STORAGE_PROTOCOL_NVME_DATA_TYPE or STORAGE_PROTOCOL_ATA_DATA_TYPE.

    ULONG   ProtocolDataValue;
    ULONG   ProtocolDataSubValue;      // Data sub request value

    ULONG   ProtocolDataOffset;         // The offset of data buffer is from beginning of this data structure.
    ULONG   ProtocolDataLength;

    ULONG   FixedProtocolReturnData;
    ULONG   ProtocolDataSubValue2;     // First additional data sub request value

    ULONG   ProtocolDataSubValue3;     // Second additional data sub request value
    ULONG   ProtocolDataSubValue4;     // Third additional data sub request value

    ULONG   ProtocolDataSubValue5;     // Fourth additional data sub request value
    ULONG   Reserved[5];
} STORAGE_PROTOCOL_SPECIFIC_DATA_EXT, *PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT;

설정할 NVMe 기능 유형을 지정하려면 다음과 같이 STORAGE_PROTOCOL_SPECIFIC_DATA_EXT 구조를 구성합니다.

  • ProtocolType 필드를 ProtocolTypeNvme로 설정합니다.
  • DataType 필드를 STORAGE_PROTOCOL_NVME_DATA_TYPE 정의한 NVMeDataTypeFeature 열거형 값으로 설정합니다.

다음 예제에서는 NVMe 기능 집합을 보여 줍니다.

예: NVMe 설정 기능

이 예제에서는 기능 설정 요청이 NVMe 드라이브로 전송됩니다. 다음 코드는 집합 데이터 구조를 준비한 다음 DeviceIoControl을 통해 디바이스로 명령을 보냅니다.

PSTORAGE_PROPERTY_SET                   setProperty = NULL;
PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT     protocolData = NULL;
PSTORAGE_PROTOCOL_DATA_DESCRIPTOR_EXT   protocolDataDescr = NULL;

//
// Allocate buffer for use.
//
bufferLength = FIELD_OFFSET(STORAGE_PROPERTY_SET, AdditionalParameters) + sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA_EXT);
bufferLength += NVME_MAX_LOG_SIZE;

buffer = new UCHAR[bufferLength];

//
// Initialize query data structure to get the desired log page.
//
ZeroMemory(buffer, bufferLength);

setProperty = (PSTORAGE_PROPERTY_SET)buffer;

setProperty->PropertyId = StorageAdapterProtocolSpecificProperty;
setProperty->SetType = PropertyStandardSet;

protocolData = (PSTORAGE_PROTOCOL_SPECIFIC_DATA_EXT)setProperty->AdditionalParameters;

protocolData->ProtocolType = ProtocolTypeNvme;
protocolData->DataType = NVMeDataTypeFeature;
protocolData->ProtocolDataValue = NVME_FEATURE_HOST_CONTROLLED_THERMAL_MANAGEMENT;

protocolData->ProtocolDataSubValue = 0; // This will pass to CDW11.
protocolData->ProtocolDataSubValue2 = 0; // This will pass to CDW12.
protocolData->ProtocolDataSubValue3 = 0; // This will pass to CDW13.
protocolData->ProtocolDataSubValue4 = 0; // This will pass to CDW14.
protocolData->ProtocolDataSubValue5 = 0; // This will pass to CDW15.

protocolData->ProtocolDataOffset = 0;
protocolData->ProtocolDataLength = 0;

//
// Send request down.
//
result = DeviceIoControl(m_deviceHandle,
                            IOCTL_STORAGE_SET_PROPERTY,
                            buffer,
                            bufferLength,
                            buffer,
                            bufferLength,
                            &returnedLength,
                            NULL
);

온도 쿼리

Windows 10 이상에서는 IOCTL_STORAGE_QUERY_PROPERTY 사용하여 NVMe 디바이스의 온도 데이터를 쿼리할 수도 있습니다.

STORAGE_TEMPERATURE_DATA_DESCRIPTOR NVMe 드라이브에서 온도 정보를 검색하려면 다음과 같이 STORAGE_PROPERTY_QUERY 구조를 구성합니다.

  • STORAGE_PROPERTY_QUERY 구조를 포함할 수 있는 버퍼를 할당합니다.
  • PropertyID 필드를 컨트롤러 요청의 경우 StorageAdapterTemperatureProperty로, 디바이스/네임스페이스 요청의 경우 StorageDeviceTemperatureProperty로 설정합니다.
  • QueryType 필드를 PropertyStandardQuery로 설정합니다.

STORAGE_TEMPERATURE_INFO 구조(Windows 10 이상에서 사용 가능)는 다음과 같습니다.

typedef struct _STORAGE_TEMPERATURE_INFO {

    USHORT  Index;                      // Starts from 0. Index 0 may indicate a composite value.
    SHORT   Temperature;                // Signed value; in Celsius.
    SHORT   OverThreshold;              // Signed value; in Celsius.
    SHORT   UnderThreshold;             // Signed value; in Celsius.

    BOOLEAN OverThresholdChangable;     // Can the threshold value being changed by using IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD.
    BOOLEAN UnderThresholdChangable;    // Can the threshold value being changed by using IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD.
    BOOLEAN EventGenerated;             // Indicates that notification will be generated when temperature cross threshold.
    UCHAR   Reserved0;
    ULONG   Reserved1;

} STORAGE_TEMPERATURE_INFO, *PSTORAGE_TEMPERATURE_INFO;

동작 변경 명령

디바이스 특성을 조작하거나 잠재적으로 디바이스 동작에 영향을 주는 명령은 운영 체제에서 처리하기가 더 어렵습니다. I/O가 처리되는 동안 런타임에 디바이스 특성이 변경되면 동기화 또는 데이터 무결성 문제가 제대로 처리되지 않을 경우 발생할 수 있습니다.

NVMe Set-Features 명령은 동작 변경 명령의 좋은 예입니다. 중재 메커니즘의 변경 및 온도 임계값 설정을 허용합니다. 동작에 영향을 주는 set 명령이 전송될 때 기내 데이터가 위험하지 않도록 하기 위해 Windows는 NVMe 디바이스에 대한 모든 I/O를 일시 중지하고 큐를 드레이닝하고 버퍼를 플러시합니다. set 명령이 성공적으로 실행되면 I/O가 다시 시작됩니다(가능한 경우). I/O를 다시 시작하지 못할 경우 디바이스 재설정이 필요할 수 있습니다.

온도 임계값 설정

Windows 10에는 온도 임계값을 가져오고 설정하기 위한 IOCTL인 IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD 도입되었습니다. 디바이스의 현재 온도를 가져오는 데 사용할 수도 있습니다. 이 IOCTL의 입력/출력 버퍼는 이전 코드 섹션의 STORAGE_TEMPERATURE_INFO 구조입니다.

예: 임계값 초과 온도 설정

이 예제에서는 NVMe 드라이브의 임계값 초과 온도가 설정됩니다. 다음 코드는 명령을 준비한 다음 DeviceIoControl을 통해 디바이스로 보냅니다.

BOOL    result;  
ULONG   returnedLength = 0;  

STORAGE_TEMPERATURE_THRESHOLD setThreshold = {0};  

setThreshold.Version = sizeof(STORAGE_TEMPERATURE_THRESHOLD); 
setThreshold.Size = sizeof(STORAGE_TEMPERATURE_THRESHOLD);  
setThreshold.Flags = STORAGE_TEMPERATURE_THRESHOLD_FLAG_ADAPTER_REQUEST;  
setThreshold.Index = SensorIndex;  
setThreshold.Threshold = Threshold;  
setThreshold.OverThreshold = UpdateOverThreshold; 

//  
// Send request down.  
//  
result = DeviceIoControl(DeviceList[DeviceIndex].Handle,  
                            IOCTL_STORAGE_SET_TEMPERATURE_THRESHOLD,  
                            &setThreshold,  
                            sizeof(STORAGE_TEMPERATURE_THRESHOLD),  
                            NULL,  
                            0,  
                            &returnedLength,  
                            NULL  
                            );

공급업체별 기능 설정

명령 효과 로그가 없으면 드라이버는 명령의 파급 효과를 알지 않습니다. 명령 효과 로그가 필요한 이유입니다. 운영 체제에서 명령이 높은 영향을 미치는지와 다른 명령과 함께 드라이브에 병렬로 보낼 수 있는지 여부를 확인하는 데 도움이 됩니다.

명령 효과 로그는 아직 공급업체별 Set-Features 명령을 포함할 만큼 세분화되지 않았습니다. 이러한 이유로 아직 공급업체별 Set-Features 명령을 보낼 수 없습니다. 그러나 앞에서 설명한 통과 메커니즘을 사용하여 공급업체별 명령을 보낼 수 있습니다. 자세한 내용은 패스스루 메커니즘 를 참조하세요.

헤더 파일

다음 파일은 NVMe 개발과 관련이 있습니다. 이러한 파일은 Microsoft SDK(소프트웨어 개발 키트)포함되어 있습니다.

헤더 파일 묘사
ntddstor.h 커널 모드에서 스토리지 클래스 드라이버에 액세스하기 위한 상수 및 형식을 정의합니다.
nvme.h 다른 NVMe 관련 데이터 구조의 경우
winioctl.h 전체 Win32 IOCTL 정의의 경우 사용자 모드 애플리케이션에 대한 스토리지 API를 포함합니다.

NVMe 사양 1.2