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

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