none
Issue ATA IOCTL commands to SSD drives in Vista and Windows 7/8 RRS feed

  • Question

  • I want to issue an IDENTIFY and TRIM command to SSD drive from a Windows application. IOCTL_ATA_PASS_THROUGH command works for IDE drives but not SSD.

    There is no answer available in any Microsoft or other forums. I want exact sample working code snippet.

    If I try the following for IDENTIFY to start with, I always get not supported (0x32) error.  I think there should be a proper sample code in MSDN or at least in this forum.

       HANDLE handle = ::CreateFileA(
          "\\\\.\\PhysicalDrive1", 
          GENERIC_READ | GENERIC_WRITE,  
          FILE_SHARE_READ | FILE_SHARE_WRITE, 
          0,          
          OPEN_EXISTING,
          0,               
          0           
          );

       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";
       }

    // Here b always returns false. And GetLastError shows 0x32. 

    // But if I use PhysicalDrive0 which is an IDE drive, I get proper result.

    Sunday, August 24, 2014 4:00 PM

Answers

  • You need to use FSCTL, not pass through. See here and here

     -Brian


    Azius Developer Training www.azius.com Windows device driver, internals, security, & forensics training and consulting. Blog at www.azius.com/blog

    Sunday, August 24, 2014 6:08 PM
    Moderator

All replies

  • You need to use FSCTL, not pass through. See here and here

     -Brian


    Azius Developer Training www.azius.com Windows device driver, internals, security, & forensics training and consulting. Blog at www.azius.com/blog

    Sunday, August 24, 2014 6:08 PM
    Moderator
  • Thank you Brian. But how do I get the IDENTIFY data for SSD drive?  I do not understand how I can do that using FSCTL.  

    I want to communicate with the device directly and send IDENTIFY and TRIM etc. I can do it for IDE. For SSD, Windows fails the command. 

    How do I do that?

    Basudeb

    Monday, August 25, 2014 2:34 AM
  • FSCTL is for TRIM and UNMAP. You can send the IDENTIFY in the passthru IOCTL. Also, you need to be running as Admin to issue these requests from an application.

     -Brian


    Azius Developer Training www.azius.com Windows device driver, internals, security, & forensics training and consulting. Blog at www.azius.com/blog

    Tuesday, August 26, 2014 11:44 PM
    Moderator
  • You are right. This was my original question. I need a code sample that sends IOCTL pass thru to SSD disks. My code successfully reads IDE IDENTIFY but for SSD, it returns 0x32 not supported error. Of course I am doing it as admin. 

    It is strange that such simple things are neither documented  nor responded to in Microsoft forum. How does one develop for Microsoft platform?

    Basudeb

    Wednesday, August 27, 2014 1:32 AM
  • Sorry, my bad. You cannot send IOCTL_ATA_PASS_THROUGH to SSDs. What information from the IDENTIFY do you want?

     -Brian


    Azius Developer Training www.azius.com Windows device driver, internals, security, & forensics training and consulting. Blog at www.azius.com/blog

    Wednesday, August 27, 2014 2:41 AM
    Moderator
  • I need the full IDENTIFY 512 byte information. 

    There are programs which are able to do that: an example is TxBENCH.exe. It can display and also send commands. It does not have any driver or other DLLs. This tells me that there are ways to access SSD using standard commands. All msdn documents say we can use IOCTL_ATA_PASS_THROUGH  and no where it is documented how to send it to SSD. 

    So, I need someone to tell me how I can send ATA commands to SSD devices directly from Windows. 

    Thanks

    Basudeb

    Wednesday, August 27, 2014 3:00 AM
  • I wrote the following program, which sends an IDENTIFY command to each drive on my system (2 of which are SSDs), and it works fine. The header ATA.H is in C:\Program Files (x86)\Windows Kits\8.1\Include\km\ata.h
    The above image is a capture of the output for my Corsair SSD

    //++
    //
    // FACILITY:	SENDID - Send IDENTIFY to disks
    //
    // DESCRIPTION:	This program will send an ATA IDENTIFY command to the first 10 physical disks
    //
    // VERSION:		1.0
    //
    // AUTHOR:		Brian Catlin
    //
    // CREATED:		2014-08-27
    //
    // MODIFICATION HISTORY:
    //
    //	1.0		2014-08-27	Brian Catlin
    //			Original version
    //
    //--
    
    
    //+
    // INCLUDE FILES:
    //-
    
    #include <windows.h>
    #include <stdlib.h>
    #include <conio.h>
    #include <stdio.h>
    #include <tchar.h>
    #include <strsafe.h>
    #include <ntdddisk.h>
    #include <ntddscsi.h>
    #include <ata.h>
    
    //+
    // CONSTANTS:
    //-
    
    #define	ATA_IDENTIFY_DATA_SIZE		512
    
    //+
    // DATA STRUCTURES:
    //-
    
    static CHAR							nibble_to_hex [] = {'0', '1', '2', '3', '4', '5', '6', '7',
    														 '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    
    typedef struct
    	{
    	ATA_PASS_THROUGH_EX			ata_pass_thru;
    	ULONG						filler [1];
    	UCHAR						buffer [2048];
    	} ATA_PT, *pATA_PT;
    
    //+
    // TABLE OF CONTENTS:
    //
    //-
    
    int main												// Main entry point into test program 
    	(
    	int		argc,										// Number of arguments passed
    	char*	argv[]										// Array of arguments
    	);
    
    VOID TEST_display_message								// Lookup the error message for an error code
    	(
    	IN ULONG	Error_code								// Error code to lookup
    	);
    
    void Dump												// Dump a buffer (in hex) to the console
    	(
    	IN PVOID	Address,								// Address of buffer
    	IN DWORD	Length,									// Length of buffer
    	IN DWORD	Line_width,								// Maximum width of output line
    	IN DWORD	Show_offset,							// Display the offset on each line
    	IN DWORD	Show_ascii								// Display ASCII representation of data
    	);
    
    
    
    int main												// Main entry point into test program 
    	(
    	int		argc,										// Number of arguments passed
    	char*	argv[]										// Array of arguments
    	)
    
    //++
    //
    // DESCRIPTION:		Main entry point
    //
    // ASSUMPTIONS:		User mode
    //
    // RETURN VALUES:
    //
    //		ERROR_SUCCESS						Normal, successful completion
    //
    // SIDE EFFECTS:
    //
    //--
    
    {
    DWORD					status;
    DWORD					disk_num;
    WCHAR					dev_name [100];
    HANDLE					hdl;
    ATA_PT					pass_thru;
    DWORD					length = sizeof (pass_thru);
    PIDEREGS				ide_regs = (PIDEREGS) &pass_thru.ata_pass_thru.CurrentTaskFile;
    DWORD					bytes_ret;
    
    
    	//+
    	// Loop through all the disks
    	//-
    
    	for (disk_num = 0; disk_num < 10; disk_num++)
    		{
    
    		//+
    		// Open a handle to the disk
    		//-
    
    		StringCbPrintf (dev_name, sizeof (dev_name), L"\\\\.\\PhysicalDrive%d", disk_num);
    
    		if ((hdl = CreateFile (dev_name, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0)) == INVALID_HANDLE_VALUE)
    			{
    			continue;
    			}
    
    		//+
    		// Create the IDENTIFY request
    		//-
    
    		ZeroMemory (pass_thru.buffer, sizeof (pass_thru.buffer));
    
    		pass_thru.ata_pass_thru.Length = sizeof (ATA_PASS_THROUGH_EX);
    		pass_thru.ata_pass_thru.AtaFlags = ATA_FLAGS_DRDY_REQUIRED | ATA_FLAGS_DATA_IN;
    		pass_thru.ata_pass_thru.DataTransferLength = ATA_IDENTIFY_DATA_SIZE;
    		pass_thru.ata_pass_thru.TimeOutValue = 10;
    		pass_thru.ata_pass_thru.DataBufferOffset = FIELD_OFFSET (ATA_PT, buffer);
    		ide_regs->bCommandReg = IDE_COMMAND_IDENTIFY;
    
    		//+
    		// Send the request to the disk
    		//-
    
    		if (DeviceIoControl (hdl, IOCTL_ATA_PASS_THROUGH, &pass_thru, sizeof (ATA_PT), &pass_thru, sizeof (ATA_PT), &bytes_ret, 0))
    			{
    			printf ("IDENTIFY successful for %S\n\n", dev_name);
    			Dump (pass_thru.buffer, ATA_IDENTIFY_DATA_SIZE, 80, TRUE, TRUE);
    			}
    		else
    			{
    			status = GetLastError ();
    			TEST_display_message (status);
    			}
    
    		CloseHandle (hdl);
    		}	// End for i
    
    }		// End main
    
    void Dump												// Dump a buffer (in hex) to the console
    	(
    	IN PVOID	Address,								// Address of buffer
    	IN DWORD	Length,									// Length of buffer
    	IN DWORD	Line_width,								// Maximum width of output line
    	IN DWORD	Show_offset,							// Display the offset on each line
    	IN DWORD	Show_ascii								// Display ASCII representation of data
    	)
    
    //++
    //
    // DESCRIPTION:		Dump the buffer to the console, in the proper little-endian format.
    //
    // ASSUMPTIONS:		
    //
    // RETURN VALUES:
    //
    //	STATUS_SUCCESS
    //	Return status from Win32 APIs
    //
    // SIDE EFFECTS:
    //
    //	None.
    //
    //--
    
    {
    DWORD		i;
    DWORD		chars_per_byte;
    PUCHAR		buffer = (PUCHAR)Address;
    DWORD		overhead;
    DWORD		bytes_per_line;
    DWORD		num_groups;
    DWORD		offset_index;
    DWORD		ascii_index;
    PUCHAR		line;
    DWORD		bytes_this_line;
    DWORD		j;
    DWORD		cur;
    DWORD		num_lines;
    DWORD		group_size;
    DWORD		first_hex_index;
    DWORD		next_column;
    
    
    	//+
    	// Calculate how many bytes can be dumped per line.  A line consists of:
    	// some number of longword groups, separated by spaces, optionally the offset into the buffer,
    	// and optionally the ASCII representation of each of the bytes in the longwords.
    	//-
    
    	if (Show_offset)
    		{
    		overhead = 3 + 8;								// 3 spaces + 8 characters for the offset value
    		}
    	else
    		{
    		overhead = 0;									// Without the offset, there isn't any fixed overhead
    		}
    
    	if (Show_ascii)
    		{
    		chars_per_byte = 3;								// 2 hex characters per byte + 1 ASCII character per byte
    		overhead = overhead + 1;						// Space before the ASCII
    		}
    	else
    		{
    		chars_per_byte = 2;								// 2 hex characters per byte
    		}
    
    	group_size = 8 + 1;									// 8 characters per longword + 1 space
    	num_groups = (Line_width - overhead) / ((chars_per_byte * 4) + 1);
    	bytes_per_line = num_groups * 4;					// num_groups * 4 bytes per group
    	first_hex_index = (num_groups * group_size);
    	next_column = first_hex_index;
    
    	if (Show_offset)
    		{
    		offset_index = next_column + 3;					// Calculate where the offset appears on the line
    		next_column = offset_index + 8;					// 8 hex characters in offset
    		}
    	else
    		{
    		offset_index = 0;
    		}
    
    	if (Show_ascii)
    		{
    		ascii_index = next_column + 1;					// Space before ASCII
    		}
    	else
    		{
    		ascii_index = 0;
    		}
    
    	//+
    	// Allocate the line buffer
    	//-
    
    	if ((line = (PUCHAR) malloc (Line_width)) != 0)
    		{
    
    		//+
    		// Walk through the buffer one line at a time
    		//-
    
    		num_lines = ((Length + bytes_per_line - 1) / bytes_per_line);
    
    		for (i = 0; i < num_lines; i++)
    			{
    			bytes_this_line = Length - (i * bytes_per_line);
    
    			if (bytes_this_line > bytes_per_line)
    				{
    				bytes_this_line = bytes_per_line;
    				}
    
    			//+
    			// Erase the line
    			//-
    
    			memset (line, ' ', Line_width);
    
    			//+
    			// Write the current offset
    			//-
    
    			if (Show_offset)
    				{
    				sprintf_s ((char *)&line [offset_index], 9, "%08X", i * bytes_per_line);
    				line [offset_index + 8] = ' ';		// sprintf_s zero-terminates the buffer; replace the space
    				}
    
    			cur = first_hex_index;
    
    			//+
    			// Display the bytes on the current line
    			//-
    
    			for (j = 0; j < bytes_this_line; j++)
    				{
    
    				if ((j % 4) == 0)
    					{
    					cur = cur - 1;
    					}
    
    				//+
    				// Write the little-endian hex representation of the byte
    				//-
    
    				line [cur] = nibble_to_hex [(buffer [(i * bytes_per_line) + j] >> 0) & 0xf];
    				cur = cur - 1;
    				line [cur] = nibble_to_hex [(buffer [(i * bytes_per_line) + j] >> 4) & 0xf];
    				cur = cur - 1;
    
    				//+
    				// Write the ASCII representation of the byte
    				//-
    
    				if (Show_ascii)
    					{
    
    					if (isprint (buffer [(i * bytes_per_line) + j]))
    						{
    						line [ascii_index + j] = buffer [(i * bytes_per_line) + j];
    						}
    					else
    						{
    						line [ascii_index + j] = '.';
    						}
    
    					}
    
    				}	// End for j
    
    			printf ("%.*s", Line_width, line);
    			}	// End for i
    
    		}
    	else
    		{
    
    		//+
    		// Couldn't allocate line buffer
    		//-
    
    		printf ("Error allocating %d bytes for line buffer\n", Line_width);
    		}
    
    	free (line);
    }											// End of function dump
    


    Azius Developer Training www.azius.com Windows device driver, internals, security, & forensics training and consulting. Blog at www.azius.com/blog

    Wednesday, August 27, 2014 5:49 AM
    Moderator
  • Thanks Brian. I have a few problems with your code:

    1. I have no ..\8.1\include\km folder only shared and um and winrt. and there is no ata.h

    2. There is no definition of IDE_COMMAND_IDENTIFY so I just put it as 0xEC. I also pointed my include path to the 8.1\include\shared. Then the program compiled

    3. The compiled program always comes to GetLastError() in Vista/Win7/Win8.1  And the error is 0x32 that is "not supported"

    4.  I have connected the SSD KINGSTON 60 gb drive thru USB  port. TxBench.exe has no problem with that, it is showing identify info, and also can send other ATA commands. 

    So???  May be I could run your compiled program on my machines and see what happens. Then  I would know, there is some problem with my compile environment. 

    Thanks in advance, for any further help. 

    Basudeb

    Wednesday, August 27, 2014 8:16 AM
  • Since this is a device driver forum, I assumed you had the WDK installed; which is where the ATA.H file comes from.

    Send me your email address and I'll send you the program

     -Brian


    Azius Developer Training www.azius.com Windows device driver, internals, security, & forensics training and consulting. Blog at www.azius.com/blog

    Wednesday, August 27, 2014 7:46 PM
    Moderator
  • Hello Brian,

    Actually I did not find any appropriate forum for this. 

    Can  you please send your program to bgupta@bluestsoft.com?

    Thanks

    Basudeb

    Thursday, August 28, 2014 1:24 AM
  • What about the reset of the world that is looking for this same solution? "I'll send you the program" leave us at a dead-end and a waste of time!
    Wednesday, December 10, 2014 1:44 PM