Отправка команд ATA непосредственно на устройство в Windows?
я пытаюсь отправить команды ATA на физический диск в Windows и получить ответ от устройства.
Примечание: в этом случае я хочу отправить
IDENTIFY DEVICE
(0xEC) команда. Прибор ответит с в 512-байтовый блок данных. (В особенно меня интересует бит 0 слово 119 - прибора поддержка theTRIM
команда).
я знаю, что мне нужно использовать CreateFile
открыть устройство:
handle = CreateFile(
".PhysicalDrive0", GENERIC_READ, FILE_SHARE_READ,
nil, // no security attributes
OPEN_EXISTING,
0, // flags and attributes
nil // no template file
);
но после этого я не знаю, что делать.
я думал о посылке 0xEC
используя [DeviceIoControl][4]
:
// const ATACommand_IdentifyDevice = 0xEC;
uint bytesReturned = 0;
DeviceIoControl(handle,
0xEC, // IO Control Code
nil, // input buffer not needed
0, // input buffer is zero bytes
@buffer, // output buffer to store the returned 512-bytes
512, // output buffer is 512 bytes long
out bytesReturned,
nil // not an overlapped operation
);
но это совершенно неправильно. IoControlCode отправлен в DeviceIoControl должен быть допустимым IO_CTL, которые являются построен с помощью макроса:
#define CTL_CODE(DeviceType, Function, Method, Access) (
((DeviceType) << 16) | ((Access) << 14) | ((Function) << 2) | (Method)
)
глядя на SDK, есть несколько действительных Управление Дисками Коды, например:
- IOCTL_DISK_CREATE_DISK
- IOCTL_DISK_GET_DRIVE_GEOMETRY
- IOCTL_DISK_GET_DRIVE_GEOMETRY_EX
- IOCTL_DISK_GET_PARTITION_INFO
- IOCTL_STORAGE_QUERY_PROPERTY
но ни один из них не IDENTIFY DEVICE
command или возвращает все, что он возвращает.
поэтому я считаю, что должен использовать какой-то "сырой" метод отправки команд.
поиск по, Я наткнулся и недокументированный IOCTL
#define DFP_RECEIVE_DRIVE_DATA 0x0007c088
который, когда вы разбиваете куски IOCTL, означает:
Custom: (0)
Device Type: (7) FILE_DEVICE_DISK
Required Access: (3) METHOD_NEITHER
Custom: (0)
Function Code: (34)
Transfer Type: (0)
но нигде нет документации о том, что inputBuffer
должен содержать, свой размер, и какой его outputBuffer
будет содержать, или его требуется. И не могу понять, что именно!--18-- > 34 (0x22) is.
мой вопрос: как отправить команды raw ATA (например, 0xEC) на устройство ATA и прочитать его ответ?
посмотреть также
- Ioctl_ata_pass_through код управления
- код управления IOCTL_ATA_PASS_THROUGH_DIRECT
- структура ATA_PASS_THROUGH_EX
ответ штук
откройте диск с доступом ReadWrite:
handle = CreateFile(
".PhysicalDrive0",
GENERIC_READ or GENERIC_WRITE, // IOCTL_ATA_PASS_THROUGH requires read-write
FILE_SHARE_READ,
nil, // no security attributes
OPEN_EXISTING,
0, // flags and attributes
nil // no template file
);
настройка ATA_PASS_THROUGH_EX
структура как наш входной буфер для использования с IOCTL_ATA_PASS_THROUGH
код управления IO:
ATA_PASS_THROUGH_EX inputBuffer;
inputBuffer.Length = sizeof(ATA_PASS_THROUGH_EX);
inputBuffer.AtaFlags = ATA_FLAGS_DATA_IN;
inputBuffer.DataTransferLength = 0;
inputBuffer.DataBufferOffset = 0;
// todo: put the ATA command (e.g. 0xEC) somewhere
uint inputBufferSize = sizeof(ATA_PASS_THROUGH_EX);
настройка выходной буфер для хранения ожидаемого 512-байтового ответа с диска:
Byte[] outputBuffer = new Byte[512];
uint outputBufferSize = 512;
вызов DeviceIoControl
:
int ioControlCode = IOCTL_ATA_PASS_THROUGH; // or maybe IOCTL_ATA_PASS_THROUGH_DIRECT
uint bytesReturned = 0;
DeviceIoControl(handle, ioControlCode,
inputBuffer, inputBufferSize,
outputBuffer, outputBufferSize,
out bytesReturned,
nil // not an overlapped operation
);
закрыть дескриптор файла:
handle.Close();
3 ответов
вам нужно использовать IOCTL_ATA_PASS_THROUGH/IOCTL_ATA_PASS_THROUGH_DIRECT, они довольно хорошо документированы. Также, нужен помощью generic_read|помощью generic_write доступ для CreateFile.
имейте в виду, что pre XP SP2 не поддерживает их должным образом. Кроме того, если у вас есть MB на основе nForce с драйверами nvidia, ваши диски SATA будут отображаться как SCSI, и вы не можете использовать эту передачу.
в некоторых случаях SMART IOCTL (например, SMART_RCV_DRIVE_DATA) будет работать с драйверами nForce. Вы можете используйте их для получения идентификационных и интеллектуальных данных, но не более того.
smartmontools с открытым исходным кодом-хорошее место, чтобы начать поиск образца кода.
EDIT: пример из приложения, говорящего с устройствами ATA.
EResult DeviceOperationManagerWin::executeATACommandIndirect(ATACommand & Cmd) {
const uint32 FillerSize = 0;
Utils::ByteBuffer B;
B.reserve(sizeof(ATA_PASS_THROUGH_EX) + 4 + Cmd.bufferSize());
ATA_PASS_THROUGH_EX & PTE = * (ATA_PASS_THROUGH_EX *) B.appendPointer(sizeof(ATA_PASS_THROUGH_EX) + FillerSize + Cmd.bufferSize());
uint8 * DataPtr = ((uint8 *) &PTE) + sizeof(ATA_PASS_THROUGH_EX) + FillerSize;
memset(&PTE, 0, sizeof(ATA_PASS_THROUGH_EX) + FillerSize);
PTE.Length = sizeof(PTE);
PTE.AtaFlags = 0;
PTE.AtaFlags |= Cmd.requiresDRDY() ? ATA_FLAGS_DRDY_REQUIRED : 0;
switch (Cmd.dataDirection()) {
case ddFromDevice:
PTE.AtaFlags |= ATA_FLAGS_DATA_IN;
break;
case ddToDevice:
PTE.AtaFlags |= ATA_FLAGS_DATA_OUT;
memcpy(DataPtr, Cmd.buffer(), Cmd.bufferSize());
break;
default:
break;
}
PTE.AtaFlags |= Cmd.is48Bit() ? ATA_FLAGS_48BIT_COMMAND : 0;
PTE.AtaFlags |= Cmd.isDMA() ? ATA_FLAGS_USE_DMA : 0;
PTE.DataTransferLength = Cmd.bufferSize();
PTE.TimeOutValue = Cmd.timeout();
PTE.DataBufferOffset = sizeof(PTE) + FillerSize;
PTE.DataTransferLength = Cmd.bufferSize();
PTE.CurrentTaskFile[0] = Cmd.taskFileIn0().Features;
PTE.CurrentTaskFile[1] = Cmd.taskFileIn0().Count;
PTE.CurrentTaskFile[2] = Cmd.taskFileIn0().LBALow;
PTE.CurrentTaskFile[3] = Cmd.taskFileIn0().LBAMid;
PTE.CurrentTaskFile[4] = Cmd.taskFileIn0().LBAHigh;
PTE.CurrentTaskFile[5] = Cmd.taskFileIn0().Device;
PTE.CurrentTaskFile[6] = Cmd.taskFileIn0().Command;
PTE.CurrentTaskFile[7] = 0;
if (Cmd.is48Bit()) {
PTE.PreviousTaskFile[0] = Cmd.taskFileIn1().Features;
PTE.PreviousTaskFile[1] = Cmd.taskFileIn1().Count;
PTE.PreviousTaskFile[2] = Cmd.taskFileIn1().LBALow;
PTE.PreviousTaskFile[3] = Cmd.taskFileIn1().LBAMid;
PTE.PreviousTaskFile[4] = Cmd.taskFileIn1().LBAHigh;
PTE.PreviousTaskFile[5] = Cmd.taskFileIn1().Device;
PTE.PreviousTaskFile[6] = 0;
PTE.PreviousTaskFile[7] = 0;
}
DWORD BR;
if (!DeviceIoControl(FHandle, IOCTL_ATA_PASS_THROUGH, &PTE, B.size(), &PTE, B.size(), &BR, 0)) {
FLastOSError = GetLastError();
LOG_W << "ioctl ATA_PT failed for " << Cmd << ": " << FLastOSError << " (" << Utils::describeOSError(FLastOSError) << ")";
return Utils::mapOSError(FLastOSError);
}
Cmd.taskFileOut0().Error = PTE.CurrentTaskFile[0];
Cmd.taskFileOut0().Count = PTE.CurrentTaskFile[1];
Cmd.taskFileOut0().LBALow = PTE.CurrentTaskFile[2];
Cmd.taskFileOut0().LBAMid = PTE.CurrentTaskFile[3];
Cmd.taskFileOut0().LBAHigh = PTE.CurrentTaskFile[4];
Cmd.taskFileOut0().Device = PTE.CurrentTaskFile[5];
Cmd.taskFileOut0().Status = PTE.CurrentTaskFile[6];
Cmd.taskFileOut1().Error = PTE.PreviousTaskFile[0];
Cmd.taskFileOut1().Count = PTE.PreviousTaskFile[1];
Cmd.taskFileOut1().LBALow = PTE.PreviousTaskFile[2];
Cmd.taskFileOut1().LBAMid = PTE.PreviousTaskFile[3];
Cmd.taskFileOut1().LBAHigh = PTE.PreviousTaskFile[4];
Cmd.taskFileOut1().Device = PTE.PreviousTaskFile[5];
Cmd.taskFileOut1().Status = PTE.PreviousTaskFile[6];
if (Cmd.dataDirection() == ddFromDevice) {
memcpy(Cmd.buffer(), DataPtr, Cmd.bufferSize());
}
return resOK;
}
EDIT: образец без внешних зависимостей.
идентификация требует 512-байтового буфера для данных:
unsigned char Buffer[512 + sizeof(ATA_PASS_THROUGH_EX)] = { 0 };
ATA_PASS_THROUGH_EX & PTE = *(ATA_PASS_THROUGH_EX *) Buffer;
PTE.Length = sizeof(PTE);
PTE.TimeOutValue = 10;
PTE.DataTransferLength = 512;
PTE.DataBufferOffset = sizeof(ATA_PASS_THROUGH_EX);
настройте регистры IDE, как указано в спецификации ATA.
IDEREGS * ir = (IDEREGS *) PTE.CurrentTaskFile;
ir->bCommandReg = 0xEC;
ir->bSectorCountReg = 1;
IDENTIFY не является ни 48-бит, ни DMA, он читает с устройства:
PTE.AtaFlags = ATA_FLAGS_DATA_IN | ATA_FLAGS_DRDY_REQUIRED;
сделайте ioctl:
DeviceIOControl(Handle, IOCTL_ATA_PASS_THROUGH, &PTE, sizeof(Buffer), &PTE, sizeof(Buffer), &BR, 0);
здесь вы должны вставить проверку ошибок, как из DeviceIOControl, так и глядя на IDEREGS для сообщений об ошибках устройства.
получить идентификационные данные, предполагая, что вы определили структуру IdentifyData
IdentifyData * IDData = (IdentifyData *) (Buffer + sizeof(ATA_PASS_THROUGH_EX));
на основе ответа https://stackoverflow.com/a/5071027/15485 by Ерик Я написал следующий автономный код. Я протестировал его на ноутбуке DELL с SSD-диском и под управлением Windows 7.
// Sending ATA commands directly to device in Windows?
// https://stackoverflow.com/questions/5070987/sending-ata-commands-directly-to-device-in-windows
#include <Windows.h>
#include <ntddscsi.h> // for ATA_PASS_THROUGH_EX
#include <iostream>
// I have copied the struct declaration from
// "IDENTIFY_DEVICE_DATA structure" http://msdn.microsoft.com/en-us/library/windows/hardware/ff559006(v=vs.85).aspx
// I think it is better to include the suitable header (MSDN says the header is Ata.h and suggests to include Irb.h)
typedef struct _IDENTIFY_DEVICE_DATA {
struct {
USHORT Reserved1 :1;
USHORT Retired3 :1;
USHORT ResponseIncomplete :1;
USHORT Retired2 :3;
USHORT FixedDevice :1;
USHORT RemovableMedia :1;
USHORT Retired1 :7;
USHORT DeviceType :1;
} GeneralConfiguration;
USHORT NumCylinders;
USHORT ReservedWord2;
USHORT NumHeads;
USHORT Retired1[2];
USHORT NumSectorsPerTrack;
USHORT VendorUnique1[3];
UCHAR SerialNumber[20];
USHORT Retired2[2];
USHORT Obsolete1;
UCHAR FirmwareRevision[8];
UCHAR ModelNumber[40];
UCHAR MaximumBlockTransfer;
UCHAR VendorUnique2;
USHORT ReservedWord48;
struct {
UCHAR ReservedByte49;
UCHAR DmaSupported :1;
UCHAR LbaSupported :1;
UCHAR IordyDisable :1;
UCHAR IordySupported :1;
UCHAR Reserved1 :1;
UCHAR StandybyTimerSupport :1;
UCHAR Reserved2 :2;
USHORT ReservedWord50;
} Capabilities;
USHORT ObsoleteWords51[2];
USHORT TranslationFieldsValid :3;
USHORT Reserved3 :13;
USHORT NumberOfCurrentCylinders;
USHORT NumberOfCurrentHeads;
USHORT CurrentSectorsPerTrack;
ULONG CurrentSectorCapacity;
UCHAR CurrentMultiSectorSetting;
UCHAR MultiSectorSettingValid :1;
UCHAR ReservedByte59 :7;
ULONG UserAddressableSectors;
USHORT ObsoleteWord62;
USHORT MultiWordDMASupport :8;
USHORT MultiWordDMAActive :8;
USHORT AdvancedPIOModes :8;
USHORT ReservedByte64 :8;
USHORT MinimumMWXferCycleTime;
USHORT RecommendedMWXferCycleTime;
USHORT MinimumPIOCycleTime;
USHORT MinimumPIOCycleTimeIORDY;
USHORT ReservedWords69[6];
USHORT QueueDepth :5;
USHORT ReservedWord75 :11;
USHORT ReservedWords76[4];
USHORT MajorRevision;
USHORT MinorRevision;
struct {
USHORT SmartCommands :1;
USHORT SecurityMode :1;
USHORT RemovableMediaFeature :1;
USHORT PowerManagement :1;
USHORT Reserved1 :1;
USHORT WriteCache :1;
USHORT LookAhead :1;
USHORT ReleaseInterrupt :1;
USHORT ServiceInterrupt :1;
USHORT DeviceReset :1;
USHORT HostProtectedArea :1;
USHORT Obsolete1 :1;
USHORT WriteBuffer :1;
USHORT ReadBuffer :1;
USHORT Nop :1;
USHORT Obsolete2 :1;
USHORT DownloadMicrocode :1;
USHORT DmaQueued :1;
USHORT Cfa :1;
USHORT AdvancedPm :1;
USHORT Msn :1;
USHORT PowerUpInStandby :1;
USHORT ManualPowerUp :1;
USHORT Reserved2 :1;
USHORT SetMax :1;
USHORT Acoustics :1;
USHORT BigLba :1;
USHORT DeviceConfigOverlay :1;
USHORT FlushCache :1;
USHORT FlushCacheExt :1;
USHORT Resrved3 :2;
USHORT SmartErrorLog :1;
USHORT SmartSelfTest :1;
USHORT MediaSerialNumber :1;
USHORT MediaCardPassThrough :1;
USHORT StreamingFeature :1;
USHORT GpLogging :1;
USHORT WriteFua :1;
USHORT WriteQueuedFua :1;
USHORT WWN64Bit :1;
USHORT URGReadStream :1;
USHORT URGWriteStream :1;
USHORT ReservedForTechReport :2;
USHORT IdleWithUnloadFeature :1;
USHORT Reserved4 :2;
} CommandSetSupport;
struct {
USHORT SmartCommands :1;
USHORT SecurityMode :1;
USHORT RemovableMediaFeature :1;
USHORT PowerManagement :1;
USHORT Reserved1 :1;
USHORT WriteCache :1;
USHORT LookAhead :1;
USHORT ReleaseInterrupt :1;
USHORT ServiceInterrupt :1;
USHORT DeviceReset :1;
USHORT HostProtectedArea :1;
USHORT Obsolete1 :1;
USHORT WriteBuffer :1;
USHORT ReadBuffer :1;
USHORT Nop :1;
USHORT Obsolete2 :1;
USHORT DownloadMicrocode :1;
USHORT DmaQueued :1;
USHORT Cfa :1;
USHORT AdvancedPm :1;
USHORT Msn :1;
USHORT PowerUpInStandby :1;
USHORT ManualPowerUp :1;
USHORT Reserved2 :1;
USHORT SetMax :1;
USHORT Acoustics :1;
USHORT BigLba :1;
USHORT DeviceConfigOverlay :1;
USHORT FlushCache :1;
USHORT FlushCacheExt :1;
USHORT Resrved3 :2;
USHORT SmartErrorLog :1;
USHORT SmartSelfTest :1;
USHORT MediaSerialNumber :1;
USHORT MediaCardPassThrough :1;
USHORT StreamingFeature :1;
USHORT GpLogging :1;
USHORT WriteFua :1;
USHORT WriteQueuedFua :1;
USHORT WWN64Bit :1;
USHORT URGReadStream :1;
USHORT URGWriteStream :1;
USHORT ReservedForTechReport :2;
USHORT IdleWithUnloadFeature :1;
USHORT Reserved4 :2;
} CommandSetActive;
USHORT UltraDMASupport :8;
USHORT UltraDMAActive :8;
USHORT ReservedWord89[4];
USHORT HardwareResetResult;
USHORT CurrentAcousticValue :8;
USHORT RecommendedAcousticValue :8;
USHORT ReservedWord95[5];
ULONG Max48BitLBA[2];
USHORT StreamingTransferTime;
USHORT ReservedWord105;
struct {
USHORT LogicalSectorsPerPhysicalSector :4;
USHORT Reserved0 :8;
USHORT LogicalSectorLongerThan256Words :1;
USHORT MultipleLogicalSectorsPerPhysicalSector :1;
USHORT Reserved1 :2;
} PhysicalLogicalSectorSize;
USHORT InterSeekDelay;
USHORT WorldWideName[4];
USHORT ReservedForWorldWideName128[4];
USHORT ReservedForTlcTechnicalReport;
USHORT WordsPerLogicalSector[2];
struct {
USHORT ReservedForDrqTechnicalReport :1;
USHORT WriteReadVerifySupported :1;
USHORT Reserved01 :11;
USHORT Reserved1 :2;
} CommandSetSupportExt;
struct {
USHORT ReservedForDrqTechnicalReport :1;
USHORT WriteReadVerifyEnabled :1;
USHORT Reserved01 :11;
USHORT Reserved1 :2;
} CommandSetActiveExt;
USHORT ReservedForExpandedSupportandActive[6];
USHORT MsnSupport :2;
USHORT ReservedWord1274 :14;
struct {
USHORT SecuritySupported :1;
USHORT SecurityEnabled :1;
USHORT SecurityLocked :1;
USHORT SecurityFrozen :1;
USHORT SecurityCountExpired :1;
USHORT EnhancedSecurityEraseSupported :1;
USHORT Reserved0 :2;
USHORT SecurityLevel :1;
USHORT Reserved1 :7;
} SecurityStatus;
USHORT ReservedWord129[31];
struct {
USHORT MaximumCurrentInMA2 :12;
USHORT CfaPowerMode1Disabled :1;
USHORT CfaPowerMode1Required :1;
USHORT Reserved0 :1;
USHORT Word160Supported :1;
} CfaPowerModel;
USHORT ReservedForCfaWord161[8];
struct {
USHORT SupportsTrim :1;
USHORT Reserved0 :15;
} DataSetManagementFeature;
USHORT ReservedForCfaWord170[6];
USHORT CurrentMediaSerialNumber[30];
USHORT ReservedWord206;
USHORT ReservedWord207[2];
struct {
USHORT AlignmentOfLogicalWithinPhysical :14;
USHORT Word209Supported :1;
USHORT Reserved0 :1;
} BlockAlignment;
USHORT WriteReadVerifySectorCountMode3Only[2];
USHORT WriteReadVerifySectorCountMode2Only[2];
struct {
USHORT NVCachePowerModeEnabled :1;
USHORT Reserved0 :3;
USHORT NVCacheFeatureSetEnabled :1;
USHORT Reserved1 :3;
USHORT NVCachePowerModeVersion :4;
USHORT NVCacheFeatureSetVersion :4;
} NVCacheCapabilities;
USHORT NVCacheSizeLSW;
USHORT NVCacheSizeMSW;
USHORT NominalMediaRotationRate;
USHORT ReservedWord218;
struct {
UCHAR NVCacheEstimatedTimeToSpinUpInSeconds;
UCHAR Reserved;
} NVCacheOptions;
USHORT ReservedWord220[35];
USHORT Signature :8;
USHORT CheckSum :8;
} IDENTIFY_DEVICE_DATA, *PIDENTIFY_DEVICE_DATA;
// Taken from smartmontools
// Copies n bytes (or n-1 if n is odd) from in to out, but swaps adjacents
// bytes.
static void swapbytes(char * out, const char * in, size_t n)
{
for (size_t i = 0; i < n; i += 2) {
out[i] = in[i+1];
out[i+1] = in[i];
}
}
// Taken from smartmontools
// Copies in to out, but removes leading and trailing whitespace.
static void trim(char * out, const char * in)
{
// Find the first non-space character (maybe none).
int first = -1;
int i;
for (i = 0; in[i]; i++)
if (!isspace((int)in[i])) {
first = i;
break;
}
if (first == -1) {
// There are no non-space characters.
out[0] = '';
return;
}
// Find the last non-space character.
for (i = strlen(in)-1; i >= first && isspace((int)in[i]); i--)
;
int last = i;
strncpy(out, in+first, last-first+1);
out[last-first+1] = '';
}
// Taken from smartmontools
// Convenience function for formatting strings from ata_identify_device
void ata_format_id_string(char * out, const unsigned char * in, int n)
{
bool must_swap = true;
#ifdef __NetBSD__
/* NetBSD kernel delivers IDENTIFY data in host byte order (but all else is LE) */
// TODO: Handle NetBSD case in os_netbsd.cpp
if (isbigendian())
must_swap = !must_swap;
#endif
char tmp[65];
n = n > 64 ? 64 : n;
if (!must_swap)
strncpy(tmp, (const char *)in, n);
else
swapbytes(tmp, (const char *)in, n);
tmp[n] = '';
trim(out, tmp);
}
int main(int argc, char* argv[])
{
HANDLE handle = ::CreateFileA(
"\\.\PhysicalDrive0",
GENERIC_READ | GENERIC_WRITE, //IOCTL_ATA_PASS_THROUGH requires read-write
FILE_SHARE_READ,
0, //no security attributes
OPEN_EXISTING,
0, //flags and attributes
0 //no template file
);
if ( handle == INVALID_HANDLE_VALUE ) {
std::cout << "Invalid handle\n";
}
// IDENTIFY command requires a 512 byte buffer for data:
const unsigned int IDENTIFY_buffer_size = 512;
const BYTE IDENTIFY_command_ID = 0xEC;
unsigned char Buffer[IDENTIFY_buffer_size + sizeof(ATA_PASS_THROUGH_EX)] = { 0 };
ATA_PASS_THROUGH_EX & PTE = *(ATA_PASS_THROUGH_EX *) Buffer;
PTE.Length = sizeof(PTE);
PTE.TimeOutValue = 10;
PTE.DataTransferLength = 512;
PTE.DataBufferOffset = sizeof(ATA_PASS_THROUGH_EX);
// Set up the IDE registers as specified in ATA spec.
IDEREGS * ir = (IDEREGS *) PTE.CurrentTaskFile;
ir->bCommandReg = IDENTIFY_command_ID;
ir->bSectorCountReg = 1;
// IDENTIFY is neither 48-bit nor DMA, it reads from the device:
PTE.AtaFlags = ATA_FLAGS_DATA_IN | ATA_FLAGS_DRDY_REQUIRED;
DWORD BR = 0;
BOOL b = ::DeviceIoControl(handle, IOCTL_ATA_PASS_THROUGH, &PTE, sizeof(Buffer), &PTE, sizeof(Buffer), &BR, 0);
if ( b == 0 ) {
std::cout << "Invalid call\n";
}
IDENTIFY_DEVICE_DATA * data = (IDENTIFY_DEVICE_DATA *) (Buffer + sizeof(ATA_PASS_THROUGH_EX));
// Nota Bene: I think some endianness control is needed
char model[40+1];
ata_format_id_string(model, data->ModelNumber, sizeof(model)-1);
char serial[20+1];
ata_format_id_string(serial, data->SerialNumber, sizeof(serial)-1);
char firmware[8+1];
ata_format_id_string(firmware, data->FirmwareRevision, sizeof(firmware)-1);
std::cout << "ModelNumber: " << model << "\n";
std::cout << "SerialNumber: " << serial << "\n";
std::cout << "FirmwareRevision: " << firmware << "\n";
return 0;
}