Reading and Writing using ATA-PASS-THROUGH Command

In this blog, we will see the use of ATA-PASS-THROUGH, a SCSI Command for reading and writing an SATA Drive. Here I will only explain the fields of ATA-PASS-THROUGH CDB (command descriptor block) other parts of code structure would remain same as the SCSI READ and WRITE command [see my previous blogs or just see the code, it’s simple].

SCSI READ and WRITE commands are more easy to handle than ATA-PASS-THROUGH command – as there are fewer fields in SCSI IO commands’ CDB than ATA-PASS-THROUGH’s CDB. When we send a SCSI IO command, SCSI drive itself translates it into ATA Commands or FIS.

Here we will form the ATA-PASS-THROUGH CDB for following ATA commands “READ DMA EXT – 25h, DMA” and “WRITE DMA EXT – 35h, DMA”.

Below figure shows ATA-PASS-THROUGH (16) Command CDB Format:

ata-pass-throug

BYTE 0: 0x85;
OPERATION CODE: it is constant for ATA PASS THROUGH (16) Command
BYTE 1: 0x0D;
MULTIPLE_COUNT: does not required here = 000 ;
PROTOCOL: For DMA Command it would be 6;
EXTEND: For our commands, it should be 1 – to make the LBA 48 bits long;
BYTE 2: for Reading =  0x2E and for Writing = 0x26;
OFF_LINE: it would be 0 here.
CK_COND: it can be 0 or 1, we will keep it 1, keeping 1 returns sense data, filled with CDB, even when the command is successful.
T_DIR: for Writing it is 0 and for Reading it is 1.
BYT_BLOK: it decides whether the value in FEATURES or SECTOR_COUNT will be Number-of-Bytes to transfer or Number-of-Blocks to transfer. If BYT_BLOK is 0 then transfer length would be in bytes and for 1 it would be in Blocks. Transfer length will go in FEATURES or SECTOR_COUNT is decided by T_LENGTH’s value. Here it would be 1.
T_LENGTH: DMA commands support SECTOR_COUNT for storing the transfer length. So it would be 0x02.
BYTE 3 and BYTE 4: 0
FEATURES: it would be 0.
BYTE 5 and BYTE 6:
SECTOR_COUNT: it would be initialized by Number of Blocks (sectors or LBAs) we want to read.
BYTE 7 to BYTE 12:
LBAs: initialized to start LBA.
BYTE 13: 0x40h;
DEVICE: command specific – constant for DMA commands.
BYTE 14: for Reading = 0x25 and for Writting = 0x35;
COMMAND: constant.
BYTE 15: 0
CONTROL: unknown.

FOR DETAILED INFORMATION ABOUT THE FIELDS ONE CAN READ THIS DOCUMENT.

Below is the INPUTS TABLE for READ DMA EXT -25h COMMAND:

read-dma-ext-25h

Below is the INPUTS TABLE for WRITE DMA EXT -35h COMMAND:

writeinputs

 

THEREFORE CDB FOR READING BECOMES:
{ 0x85, 0x0D, 0x2E, 0, 0, (no_of_blocks >> 8), no_of_blocks, (lba >> 8), lba, (lba>>24),(lba>>16), 0, 0, 0x40, 0x25, 0 };
AND FOR WRITING IT BECOMES:
{ 0x85, 0x0D, 0x26, 0, 0, (no_of_blocks >> 8), no_of_blocks,(lba >> 8), lba, (lba>>24),  (lba>>16), 0, 0, 0x40, 0x35, 0 };

Below is the COMPLETE code for reading with “READ DMA EXT – 25h” command using “ATA_PASS-THROUGH”:

GITHUB Link for downloading the Code.

To comprehend the following code you must have some acquaintance with sg_io_hdr Structure and SCSI IOCTL call. You can read my Blogs on Read and Write to understand that.

#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<error.h>
#include<sys/ioctl.h>
#include<sys/types.h>
#include<sys/mman.h>
#include<scsi/scsi_ioctl.h>
#include<scsi/sg.h>
/* reads the LBA entered from the terminal */
/* compile: gcc file.c*/
/* execute: sudo ./a.out /dev/sg<> */

#define DISPLAY
#define LBA_SIZE 512
#define CMD_LEN 16
#define BLOCK_MAX 65535
#define LBA_MAX (1<<30)

int main(int argc, char* argv[]){
int fd,i;
int lba;
int no_of_blocks = 1;

printf(“enter the LBA you want to read: “);
scanf(“%d”,&lba);

if(lba > LBA_MAX){
printf(“LBA larger than %d not allowed with this version\n”,LBA_MAX);
return 1;
}

printf(“Enter the NUMBER OF BLOCK you want to read: “);
scanf(“%d”,&no_of_blocks);

if((no_of_blocks > BLOCK_MAX)||(no_of_blocks <= 0) ){
printf(“it is not possible to read more than %d BLOCKS\n”,BLOCK_MAX);
printf(“we are reading 1 BLOCK for you\n”);
no_of_blocks = 1;
}

unsigned char cmd_blk[CMD_LEN] =
{0x85,0x0D,0x2E,0,0,(no_of_blocks >> 8),no_of_blocks,(lba >> 8),lba,(lba>>24),(lba>>16),0,0,0×40,0x25,0};
sg_io_hdr_t io_hdr;
char* file_name = 0;
unsigned char buffer[LBA_SIZE * no_of_blocks];
unsigned char sense_buffer[32];

if(argc < 2){
printf(“please enter a device file\n”);
return 1;
}else{
file_name = argv[1];
}

/////////opening the device file/////////////

if((fd = open(file_name,O_RDWR))<0){
printf(“device file opening failed\n”);
return 1;
}

/////////// data buffer ///////////
// printf(“********data buffer initially***********\n”);
for(i=0 ; i< (LBA_SIZE * no_of_blocks) ; i++){
buffer[i] = 0;
// printf(“%hx “,buffer[i]);
}
printf(“\n”);

////////////////prepare sg header for read//////
memset(&io_hdr,0,sizeof(sg_io_hdr_t));
io_hdr.interface_id = ‘S’;
io_hdr.cmd_len = sizeof(cmd_blk);
io_hdr.mx_sb_len = sizeof(sense_buffer);
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;
io_hdr.dxfer_len = (LBA_SIZE * no_of_blocks);
io_hdr.dxferp = buffer;
io_hdr.cmdp = cmd_blk;
io_hdr.sbp = sense_buffer;
io_hdr.timeout = 20000;

if(ioctl(fd,SG_IO,&io_hdr)<0){
printf(“ioctl failed\n”);
for(i = 0 ; i<32;i++)
printf(“%hx “,sense_buffer[i]);
return 1;
}
close(fd);

#ifdef DISPLAY
printf(“********data buffer after ioctl***********\n”);
for(i=0; i<(LBA_SIZE * no_of_blocks) ; i++){
if(i%512 == 0) printf(“\n NEW BLOCK \n”);
printf(“%hx “,buffer[i]);
}
#endif
printf(“\n”);

printf(“\n*********duration = %d\n”, io_hdr.duration);

return 1;
}

Below is the COMPLETE code for writing with “WRITE DMA EXT – 35h” command using “ATA_PASS-THROUGH”:

#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<error.h>
#include<sys/ioctl.h>
#include<sys/types.h>
#include<sys/mman.h>
#include<scsi/scsi_ioctl.h>
#include<scsi/sg.h>

/* writes the LBA entered from the terminal */
/* compile: gcc file.c*/
/* execute: sudo ./a.out /dev/sg<> */

#define LBA_SIZE 512
#define CMD_LEN 16
#define LBA_MAX (1<<30)

int main(int argc, char* argv[]){
int fd,i;
int lba;
int no_of_blocks = 2;
printf(“Enter the LBA you want to write to: “);
scanf(“%d”,&lba);

if(lba> LBA_MAX){
printf(“LBA greater than (1<<30) is not allowed with this version: “);
return 1;
}
unsigned char cmd_blk[CMD_LEN] =
{0x85,0x0D,0x26,0,0,(no_of_blocks >> 8),no_of_blocks,(lba >> 8),lba,(lba>>24),(lba>>16),0,0,0×40,0x35,0};

sg_io_hdr_t io_hdr;
char* file_name = 0;
unsigned char buffer[LBA_SIZE * no_of_blocks];
unsigned char sense_buffer[32];

if(argc < 2){
printf(“please enter a device file\n”);
return 1;
}else{
file_name = argv[1];

}

/////////opening the device file/////////////

if((fd = open(file_name,O_RDWR))<0){
printf(“device file opening failed\n”);
return 1;
}

/////////// data buffer ///////////

printf(“********data buffer initializing***********\n”);
for(i=0;i<512*no_of_blocks;i++){
buffer[i] = 0;
}/* code will write my name */
buffer[2] = ‘A’;
buffer[3] = ‘M’;
buffer[4] = ‘A’;
buffer[5] = ‘R’;

printf(“\n”);

printf(“********data buffer after initialling***********\n”);
for(i=0;i<512*no_of_blocks;i++){
printf(“%hx “,buffer[i]);
}
printf(“\n”);
////////////////prepare sg header for write//////
memset(&io_hdr,0,sizeof(sg_io_hdr_t));
io_hdr.interface_id = ‘S’;
io_hdr.cmd_len = sizeof(cmd_blk);
io_hdr.mx_sb_len = sizeof(sense_buffer);
io_hdr.dxfer_direction = SG_DXFER_TO_DEV;
io_hdr.dxfer_len = LBA_SIZE*no_of_blocks;
io_hdr.dxferp = buffer;
io_hdr.cmdp = cmd_blk;
io_hdr.sbp = sense_buffer;
io_hdr.timeout = 20000;

 

if(ioctl(fd,SG_IO,&io_hdr)<0){
printf(“ioctl failed\n”);
for(i = 0;i<32;i++){
printf(“%hx “,sense_buffer[i]);
}
printf(“\n”);
return 1;
}else printf(“write successfull\n”);

printf(“duration: %d\n”,io_hdr.duration);

return 1;
}

Below is the screenshot of data at LBA 12345678/9 before writing i.e output of reading code:

readbeforwrite

Below is the output for writing code:

write

Below is the screenshot of data at LBA 12345678/9 after writing:

readafterwrite

Thus, we can successfully read and write with this code.

You can try the Writing-Code even if you do not have a separate drive. Search for an LBA that is empty using Read Code, use that LBA (empty LBA) for Writing, once done, make the LBA empty again (by writing only 0s). As OS is already loaded in RAM and with IOCTL you are bypassing the File System this method won’t cause any issues. But do it quickly. I have tried and it worked for me. And which is why I have chosen the LBA 12345678 above because it was empty.

GOOD DAY;

 

Advertisements

4 thoughts on “Reading and Writing using ATA-PASS-THROUGH Command

  1. I was wondering if this could work with sata optical drives?

    I’m having difficulty understanding how to send commands to a sata optical disc drive through a USB to SATA converter, and am having difficulty understanding how to send ATA/ATAPI-8 commands and when it is appropriate or possible to send MMC-5.

    Can everything be done just sending one byte at a time directly to the SATA drive over USB?

    I’m wanting to create a standalone reader/writer but don’t know how to send commands directly over USB.

    Could such a thing handle communication over USB CDC serial com port protocol?

    Thanks

    Like

  2. Does your Drive gets registered as SCSI GENRIC device file (sg :: in /dev ). If it gets then, you can send a SCSI READ and WRITE(and not SCSI ATA PASS-THROUGH) commands directly and SCSI will get is translated in any requisite format.
    Here is the link to my Blog explaining SCSI READ:
    https://wisesciencewise.wordpress.com/2017/01/26/program-to-read-an-lba-from-storage-drivesata-based-using-scsi-generic-driver/
    That is all I can suggest. I haven’t worked with OPTICAL Drives and USB interface. But IF your Device gets registered as SCSI drive then you can try SCSI READ & WRITE commands. Hope it helps.

    Like

    • Thanks! The USB to SATA cord is found as a USB to ATA/ATAPI Bridge.

      I am reading up on ATA8-ACS command set and the mmc-5 command set but it is confusing. One appears to control the drive and the other command set uses atapi to physically write bits onto media a certain way depending on the opcode command byte.

      The USB controller is a MAX3421E that supports USB mass storage devices and it might allow scsi commands directly, and be translated to atapi inside the USB to SATA cord.

      I just bough a USB Host Shield for an Arduino microcontroller that should be able to talk with the drive directly.

      I’ll try starting with the ata/atapi-8-acs commands first to try to get identify and eject working as a test. The multi-media commands appear to be in an extended type of ata packet container format that allow physical I/o at the 2048 byte sector level for packet writing.

      A goal would be to use disc media like a hard drive so I will research the UDF releases to figure out the file system byte structure and which version goes with each type of media along with what type of UDF to use in each version.

      I’m guessing developing firmware to control a sata disc drive would take about a year or more. The identify device would be tricky to decode with its 512 bytes taking the majority of MCU flash.

      Can start simple and work on more complex as it comes together.

      Thank you again.

      Thank you again.

      Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s