C-Program to Read an LBA from Storage Drives(ATA-based) using SCSI GENERIC driver

Let me first answer the question why are we using SCSI driver to read a drive which uses ATA protocol for communication. We will employ the environment of Linux to read an LBA from the drive. In Linux, even the ATA drives register itself as SCSI drives. So SCSI is used to communicate with ATA drives. This is because over the time SCSI has evolved so much and have got quite stabilised in Linux. Thus ATA drivers itself has started using some of the layers of SCSI.

NOTE: All SCSI device uses a common driver called SCSI GENERIC(sg).

NOTE: have you tried sending SCSI Inquiry Command. NO, then please do that first.

There are two ways to send the READ command to ATA drives.

  1. either send SCSI read command and let SCSI layer translate it into ATA command
  2. or use ATA PASS-THROUGH command provided by SCSI

I will Post the method to use ATA PASS-THROUGH command provided by SCSI in my later Blogs. In this post we will learn the easy method to send the read-LBA command to ATA-based drives, and that method is directly sending the SCSI read command and leaving the conversion onto SCSI driver.

We will use IOCTL command call system to send the command. IOCTL lets us send the command other than Read, Write, Poll and Mmap to a device. Please note, we are gonna do this on Linux and in Linux, everything is a file, even the devices. Read, Write, Poll and Mmap are the general operations that we do on files and when our devices have some other commands to support, it is done via IOCTL calls to the files.

Steps involved in sending SCSI Read command:

  1. prepare the SCSI READ (16 Byte) cdb(command descriptor block). there are various sized commands in SCSI for the same purpose.
  2. declare two char arrays; one of 512-byte size for data and another of 32 bytes for sense data. sense data is data that come in the response to IOCTL command failure.
  3. open the device file in O_RDWR mode.
  4. prepare the sg_io_hdr structure. read about the structure from “SCSI generic HOW TO
  5. call the IOCTL function on your device file. when successful, your data buffer will have the LBA’s data that you wanted to read.
  6. display the received data.

STEP 1: preparing the SCSI READ CDB.

unsigned char read16cmd[16] =
                                    {0x88,0,0,0,0,0,0,0,0,lba,0,0,1,0,0};

STEP 2: declaring the buffers to store the data.

unsigned char data_buffer[512];
unsigned char sense_buffer[32];

STEP 3: opening the device file.

int fd ;
    if((fd = open( “/dev/sda” , O_RDWR))<0){
        printf(“device file opening failed\n”);
        return  1; //exiting the code if opening fails
    }

STEP 4: preparing the sg_io_hdr structure.
there are many fields in sg_io_hdr structure but the following are sufficient. If you can understand the other fields of structure then you must not hesitate from using them.

sg_io_hdr_t  io_hdr; //creating a object of sg_io_hdr structure

    memset(&io_hdr,0,sizeof(sg_io_hdr_t)); // initializing the other fields
    io_hdr.interface_id = ‘S’;
    io_hdr.cmd_len = sizeof(read16cmd);    // 16 bytes
    io_hdr.mx_sb_len = sizeof(sense_buffer);   // 32 bytes
    io_hdr.dxfer_direction = SG_DXFER_FROM_DEV; //direction of data transfer
    io_hdr.dxfer_len = 512;
    io_hdr.dxferp = data_buffer; //pointer of data buffer where data will come
    io_hdr.cmdp = read16cmd; // pointer to the cdb
    io_hdr.sbp = sense_buffer;  // pointer to the sense buffer
    io_hdr.timeout = 20000;  // command time out duration

STEP 5: calling the IOCTL function on our device file.

if( ioctl ( fd , SG_IO , &io_hdr ) < 0 ) {
        printf(“ioctl failed\n”);
        return 1;
    }

“fd ” is your device file pointer, “SG_IO” is IOCTL command, and “&io_hdr” points to the relevant data required for SG_IO command to execute.

I have attached a working code with this post, try and run it. You can increase the LBA range by understanding the SCSI READ(16) cdb. We can even ask for more than 1 block(sectors). Study the SCSI cdb and sg_io_hdr structure to learn more.

CODE to READ an LBA from ATA drive using SCSI:

GITHUB Link for downloading the CODE:

#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>

////////////////this program takes a LBA from user and reads that
////////////////from the device-file passed from command line
///////////////compil:   gcc read.c
///////////////execute: sudo ./a.out /dev/sda
///////////////”/dev/sda” is the device file name

#define REPLY_LEN 512  // length of the reply from drive
#define CMD_LEN 16     // cdb length

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

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

if(lba>512){
printf(“lba is greater than 512\n”);
return 1;
}

unsigned char read16cmd[CMD_LEN] =
{0x88,0,0,0,0,0,0,0,0,lba,0,0,1,0,0};

sg_io_hdr_t io_hdr;  //object of structure

char* file_name = 0; // to hold the device name passed from terminal
unsigned char data_buffer[REPLY_LEN];
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<512;i++){
printf(“%hx “,data_buffer[i]);
}
printf(“\n”);

////////////////prepare read///////////////
memset(&io_hdr,0,sizeof(sg_io_hdr_t));
io_hdr.interface_id = ‘S’;
io_hdr.cmd_len = sizeof(read16cmd);
io_hdr.mx_sb_len = sizeof(sense_buffer);
io_hdr.dxfer_direction = SG_DXFER_FROM_DEV;// macro is defined in scsi.h
io_hdr.dxfer_len = REPLY_LEN;
io_hdr.dxferp = data_buffer;
io_hdr.cmdp = read16cmd;
io_hdr.sbp = sense_buffer;
io_hdr.timeout = 20000;

if(ioctl(fd,SG_IO,&io_hdr)<0){
printf(“ioctl failed\n”);
return 1;
}

printf(“********data buffer after ioctl***********\n”);
for(i=0;i<512;i++){
printf(“%hx “,data_buffer[i]);
}
printf(“\n”);

return 1;
}

thanks for coming this far. I hope this post was useful to you.

GOOD DAY.

Advertisements

4 thoughts on “C-Program to Read an LBA from Storage Drives(ATA-based) using SCSI GENERIC driver

  1. Pingback: Asynchronous method to send a command using SCSI Genric Driver | WISESCIENCEWISE

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