Отправка команд ATA непосредственно на устройство в Windows?

я пытаюсь отправить команды ATA на физический диск в Windows и получить ответ от устройства.

Примечание: в этом случае я хочу отправить IDENTIFY DEVICE (0xEC) команда. Прибор ответит с в 512-байтовый блок данных. (В особенно меня интересует бит 0 слово 119 - прибора поддержка the TRIM команда).

я знаю, что мне нужно использовать 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 и прочитать его ответ?

посмотреть также


ответ штук

откройте диск с доступом 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;
}