Limitations
劈頭就要講限制因為 Windows 就是麻煩,不像在 Linux 上可以直接使用 linux-nvme/nvme-cli 就搞定。限制就是只有特定幾個 NVMe Admin Command 可以下,網路上的 VS Studio 專案可以直接拿來用,也有表格註明能用的 Command 們。但人生就是這個但是,我編譯不過啊@@ 只好自己寫再編譯,並且就有了本篇。
NVMe IO Command – NVM Command Set 的部分大都可以透過一對一的 SCSI Command [2] 來做到,因此以下主要講 Admin Command。
Sample Codes
我知道大家都只要這個。
For Generic Commands
#include <windows.h>
#include "nvme.h" //remember to include this
typedef VOID (*CQ_CALLBACK)(DWORD req_val, DWORD cdw0);
/*!
* For Identify, Get Feature, Get Log Page Only
* @param data_type can only be NVMeDataTypeIdentify, NVMeDataTypeFeature, or NVMeDataTypeLogPage
* @param req_val request value means CNS, FID, or LID
* @param req_sub_val
* @param pdata used if additional data is request
* @param xfer_len size of pdata in bytes
* @param callback a void function takes 2 parameters: void (req_val, cdw0)
*/
DWORD nvme_specific(HANDLE FileHandle, STORAGE_PROTOCOL_NVME_DATA_TYPE data_type, DWORD req_val, DWORD req_sub_val, PVOID pdata, DWORD xfer_len, CQ_CALLBACK callback)
{
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) + xfer_len;
buffer = malloc(bufferLength);
if (buffer == NULL)
{
printf("DeviceNVMeQueryProtocolDataTest: allocate buffer failed, exit.\n");
return 1;
}
//
// 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 = data_type;
protocolData->ProtocolDataRequestValue = req_val;
protocolData->ProtocolDataRequestSubValue = req_sub_val;
if (xfer_len)
{
protocolData->ProtocolDataOffset = sizeof(STORAGE_PROTOCOL_SPECIFIC_DATA);
protocolData->ProtocolDataLength = xfer_len;
}
//
// Send request down.
//
result = DeviceIoControl(FileHandle,
IOCTL_STORAGE_QUERY_PROPERTY,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL);
if (!result || (returnedLength == 0))
{
printf("FAIL, Error Code=%d\n", GetLastError());
return GetLastError();
}
//
// Validate the returned data.
//
if ((protocolDataDescr->Version != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)) ||
(protocolDataDescr->Size != sizeof(STORAGE_PROTOCOL_DATA_DESCRIPTOR)))
{
printf("Data descriptor header not valid\n");
return 1;
}
protocolData = &protocolDataDescr->ProtocolSpecificData;
memcpy_s(pdata, xfer_len, (PCHAR)protocolData + protocolData->ProtocolDataOffset, xfer_len);
if (callback != NULL)
{
callback(req_val, protocolDataDescr->ProtocolSpecificData.FixedProtocolReturnData);
}
free(buffer);
return 0;
}
然後就可以下個 Identify 之類:
DWORD nvme_identify()
{
HANDLE FileHandle = CreateFileA(
"\\\\.\\physicaldrive0", GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL
);
CHAR pdata[NVME_MAX_LOG_SIZE];
if (nvme_specific(FileHandle, NVMeDataTypeIdentify, NVME_IDENTIFY_CNS_CONTROLLER, 0, pdata, NVME_MAX_LOG_SIZE, NULL)) {
return 1;
}
PNVME_IDENTIFY_CONTROLLER_DATA identifyControllerData = (PNVME_IDENTIFY_CONTROLLER_DATA)pdata;
printf("[IDENTIFY] vid : 0x%02X", identifyControllerData->VID);
printf("[IDENTIFY] nn : 0x%02X", identifyControllerData->NN);
unsigned char str[41];
memcpy(str, identifyControllerData->SN, 20);
str[20] = '\0';
printf("[IDENTIFY] serial_num : %s\r\n", str);
memcpy(str, identifyControllerData->MN, 40);
str[40] = '\0';
printf("[IDENTIFY] model_num : %s\r\n", str);
memcpy(str, identifyControllerData->FR, 8);
str[8] = '\0';
printf("[IDENTIFY] firmware_rev: %s\r\n", str);
return 0;
}
或者 Get Log Page:
DWORD nvme_get_log_page(NVME_LOG_PAGES lid)
{
HANDLE FileHandle = CreateFileA(
"\\\\.\\physicaldrive0", GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL
);
CHAR pdata[NVME_MAX_LOG_SIZE];
if(nvme_specific(FileHandle, NVMeDataTypeLogPage, lid, 0, pdata, NVME_MAX_LOG_SIZE, NULL)) {
return 1;
}
switch(lid)
{
case NVME_LOG_PAGE_HEALTH_INFO:
PNVME_HEALTH_INFO_LOG smartInfo = (PNVME_HEALTH_INFO_LOG)pdata;
printf("SMART/Health Info - Temperature %d.\n", ((ULONG)smartInfo->Temperature[1] << 8 | smartInfo->Temperature[0]) - 273);
}
return 0;
}
或者 Get Feature:
void nvme_fid_callback(DWORD fid, DWORD cdw0)
{
printf("[GET FEATURE] ");
switch (fid)
{
case NVME_FEATURE_POWER_MANAGEMENT:
printf("PS=%d\r\n", cdw0);
break;
default:
printf("CDW0=%d\r\n", cdw0);
break;
}
};
DWORD nvme_get_feature(NVME_FEATURES fid)
{
HANDLE FileHandle = CreateFileA(
"\\\\.\\physicaldrive0", GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL, OPEN_EXISTING, 0, NULL
);
return nvme_specific(FileHandle, NVMeDataTypeFeature, fid, 0, NULL, 0, nvme_fid_callback);
}
For VUC Commands
VUC 的話(OPC =0xC0~0xFF)可以透過以下函式呼叫。
/*!
* For VUC command only, OPC ranges 0xC0-0xFF
* @param data_type can only be NVMeDataTypeIdentify, NVMeDataTypeFeature, or NVMeDataTypeLogPage
* @param sqe the standard 64-byte NVMe Submission Queue Entry
* @param prtc data transfer direction, can be NVME_PROTOCOL_NON_DATA, NVME_PROTOCOL_DATA_IN, or NVME_PROTOCOL_DATA_OUT
* @param pdata used if additional data is request
* @param xfer_len size of pdata in bytes
*/
DWORD nvme_vuc(HANDLE FileHandle, PNVME_COMMAND sqe, NVME_PROTOCOLS prtc, PCHAR pdata, DWORD xfer_len)
{
BOOL result;
PVOID buffer = NULL;
ULONG bufferLength = 0;
ULONG returnedLength = 0;
PSTORAGE_PROTOCOL_COMMAND protocolCommand = NULL;
PNVME_COMMAND command = NULL;
//
// Allocate buffer for use.
//
bufferLength = sizeof(STORAGE_PROTOCOL_COMMAND) + STORAGE_PROTOCOL_COMMAND_LENGTH_NVME + sizeof(NVME_ERROR_INFO_LOG) + xfer_len;
buffer = malloc(bufferLength);
if (buffer == NULL)
{
printf("DeviceNVMeQueryProtocolDataTest: allocate buffer failed, exit.\n");
return 0;
}
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->TimeOutValue = 10;
protocolCommand->ErrorInfoOffset = FIELD_OFFSET(STORAGE_PROTOCOL_COMMAND, Command) + STORAGE_PROTOCOL_COMMAND_LENGTH_NVME;
protocolCommand->CommandSpecific = STORAGE_PROTOCOL_SPECIFIC_NVME_ADMIN_COMMAND;
if (prtc == NVME_PROTOCOL_DATA_IN)
{
protocolCommand->DataFromDeviceTransferLength = xfer_len;
protocolCommand->DataFromDeviceBufferOffset = protocolCommand->ErrorInfoOffset + protocolCommand->ErrorInfoLength;
}
else if (prtc == NVME_PROTOCOL_DATA_OUT)
{
protocolCommand->DataToDeviceTransferLength = xfer_len;
protocolCommand->DataToDeviceBufferOffset = protocolCommand->ErrorInfoOffset + protocolCommand->ErrorInfoLength;
memcpy_s((PCHAR)buffer + protocolCommand->DataToDeviceBufferOffset, xfer_len, pdata, xfer_len);
}
memcpy_s(protocolCommand->Command, STORAGE_PROTOCOL_COMMAND_LENGTH_NVME, sqe, STORAGE_PROTOCOL_COMMAND_LENGTH_NVME);
//
// Send request down.
//
result = DeviceIoControl(FileHandle,
IOCTL_STORAGE_PROTOCOL_COMMAND,
buffer,
bufferLength,
buffer,
bufferLength,
&returnedLength,
NULL);
if (protocolCommand->ReturnStatus != STORAGE_PROTOCOL_STATUS_SUCCESS)
{
PNVME_ERROR_INFO_LOG err = (PNVME_ERROR_INFO_LOG)((PCHAR)buffer + protocolCommand->ErrorInfoOffset);
printf("Fail, Return Status=%d, ", protocolCommand->ReturnStatus);
printf("SCT=0x%02X, SC=0x%02X\n", err->Status.SCT, err->Status.SC);
return err->Status.AsUshort;
}
else
{
printf("PASS\r\n");
if (prtc == NVME_PROTOCOL_DATA_IN)
{
memcpy_s(pdata, xfer_len, (PCHAR)buffer + protocolCommand->DataFromDeviceBufferOffset, xfer_len);
}
}
return 0;
}
前提是 Controller 要支援 Get Log Page – Commands Supported and Effects 頁且相應的 VUC Opcode 有描述正確。
然後就可以:
NVME_COMMAND sq = {0};
sq.CDW0.OPC = opc;
sq.u.GENERAL.CDW10 = 0;
sq.u.GENERAL.CDW11 = 0;
sq.u.GENERAL.CDW12 = 0;
sq.u.GENERAL.CDW13 = 0;
sq.u.GENERAL.CDW14 = 0;
sq.u.GENERAL.CDW15 = 0;
nvme_vuc(FileHandle, &sq, (NVME_PROTOCOLS)protocol, NULL, 0);
雖然看起來有點囉嗦(真的是滿囉嗦的),但這樣我們就可以下 NVMe Admin Command 惹,也只有那幾個基本的呵呵。
編譯就懶得用 Makefile 了,重點是還要另外裝啊啊啊,Windows 哎~
g++ main.cpp -o test.exe
其他 NVMe Command
你如果在好奇,想下前面提到的 Identify、Get Log Page、Get Feature、VUC 以外的 Command 怎麼辦?微軟提供了另一種迂迴方式讓你走,SCSI Translation,也就是要你對 NVMe 磁碟機下 SCSI 指令,內建的 Driver 再根據 Spec 幫你轉成對應的 NVMe Command。
提供大家文件參考,就不再贅述具體的實作細節。
Reference