This document is designed to help other people avoid the huge amount of searching I had to do in order to figure out how to send a custom SCSI command to a device in Solaris. Ultimately, I figured it out by trial and error and the minimal documentation in the man pages. In this document, I'll cover how to create functions for Request Sense, Mode Sense, Mode Select, and a Write and Read command for Sequential access devices.
Why would you want to do that? Any number of reasons. If you want some more information on why a device is failing, if you want to test a device, or if you're just bored. This is my first HOWTO, so feedback is welcomed at foeclan@visi.com.
I've mainly worked with tapes, so those are what I'll use in my examples. The first thing you need to do is open the device, in this case your raw magnetic tape drive (/dev/rmt/X).
/* Open the tape drive */
deviceName = "/dev/rmt/0";
fd = open(deviceName, O_RDWR);
if(fd == -1)
{
printf("Couldn't open %s.\n", deviceName);
perror(NULL);
exit(-1);
}
The important structure in this is uscsi_cmd.
int uscsi_flags; /* read, write, etc. */
short uscsi_status; /* SCSI status */
short uscsi_timeout; /* Timeout (in seconds) */
caddr_t uscsi_cdb /* Command Descriptor Block (CDB) to send */
caddr_t uscsi_bufaddr; /* Data buffer */
size_t uscsi_buflen; /* Size (in bytes) of data buffer */
size_t uscsi_resid; /* Amount of residual data */
uchar_t uscsi_cdblen; /* Size (in bytes) of the CDB */
uchar_t uscsi_rqlen; /* Size (in bytes) of the Request buffer */
uchar_t uscsi_rqstatus; /* SCSI status of an automatic Request Sense */
uchar_t uscsi_rqresid; /* Amount of residual data from the Request Sense*/
caddr_t uscsi_rqbuf; /* Request Sense buffer */
void *uscsi_reserved_5; /* Reserved */
int uscsi_flags This will depend on what command you're sending. If you're not expecting to receive any data back (such as with a Write or Mode Select command), this will be set to USCSI_WRITE. If you're expecting to get data back, set it to USCSI_READ. It's generally a good idea to automatically issue a Request Sense command if the command returns a Check Condition. They've provided an easy means of doing this with the USCSI_RQENABLE flag. To set more than one flag, use the bitwise OR, e.g. USCSI_WRITE|USCSI_RQENABLE.
short uscsi_status This will be set to whatever the resulting SCSI status is (0x00 for success, 0x02 for Check Condition).
short uscsi_timeout We can also explicitly tell Solaris how long to wait before it considers the command a failure. To do this, set short uscsi_timeout to the number of seconds you'd like it to wait.
caddr_t uscsi_cdb This is a pointer to an array of characters that we use to generate our CDB. I'm going to assume that, if you're reading this, you probably already know how to do that. If not, check out the Linux SCSI Programming HOWTO for a good guide on this.
caddr_t uscsi_bufaddr This is a pointer to another buffer that will be used for the SCSI command we're issuing. If we're issuing a Read command, the data we read will be stored here; if we're issuing a Write, the data we're writing to the device will be stored here.
size_t uscsi_buflen This is a number telling how large, in bytes, the buffer pointed to by caddr_t uscsi_bufaddr is.
size_t uscsi_resid This tells you how many bytes of data weren't written to or read from the device in the event that something went wrong (not enough space on the disk/tape, no data where you told it to read, etc.).
uchar_t uscsi_cdblen This specifies the length in bytes of the CDB pointed to by caddr_t uscsi_cdb.
uchar_t uscsi_rqlen This is the size, in bytes, of the Sense buffer set up if you've got USCSI_RQENABLE enabled.
uchar_t uscsi_rqstatus This is the SCSI status of the Request Sense command issued when USCSI_RQENABLE is enabled (similar to short uscsi_status, above).
uchar_t uscsi_rqresid If the buffer defined for the automatic Request Sense is too small, this will be set to the number of bytes that weren't stored.
caddr_t uscsi_rqbuf This is a pointer to the request sense buffer described above.
void *uscsi_reserved_5 These aren't used yet.
To give a good overview, I'm going to do a function that requests data and one that sends it. Some fairly general ones are Mode Sense and Mode Select, so I'll use those.
/**********************************************************************
* Function: mode_sense(int fd, unsigned char *modepage)
*
* This function will issue a Mode Sense command, requesting all the
* data pages. We're assuming 100 bytes of data (all pages). For a more
* flexible version of the function, you'd want a Length field and a
* 'which pages' field (single byte).
*
* It will return a pointer to the page data buffer on success, or a
* pointer to NULL on failure.
*********************************************************************/
caddr_t mode_sense(int fd, caddr_t modepage)
{
int rc;
struct uscsi_cmd my_cmd;
unsigned char my_rq_buf[26];
unsigned char my_scsi_cdb[6];
/* Issue a Mode Sense(6) */
/* Initialize my_scsi_cdb as a Mode Sense(6) */
my_scsi_cdb[0] = 0x1A; /* Mode Select command */
my_scsi_cdb[1] = 0x00; /* Page formatted data follows block descriptor */
my_scsi_cdb[2] = 0x3F; /* Request all current page code values */
my_scsi_cdb[3] = NULL; /* Reserved */
my_scsi_cdb[4] = 0x64; /* 100b (100bytes of data follow) */
my_scsi_cdb[5] = 0x00; /* Control byte */
/* Initialize sense buffer */
my_rq_buf[0] = 0x00; /* Initialize byte 0 (will be 70h or 71h if information has been put there */
my_rq_buf[2] = 0x00; /* Initialize byte 2 (sense key and related data) */
/* Set up my_cmd for Mode Sense(6) */
my_cmd.uscsi_flags = (USCSI_READ|USCSI_RQENABLE); /* We're going to be receiving data */
my_cmd.uscsi_timeout = 15; /* Allow 15 seconds for completion */
my_cmd.uscsi_cdb = my_scsi_cdb; /* We'll be using the array above for the CDB */
my_cmd.uscsi_bufaddr = my_page_data; /* The block and page data is stored here */
my_cmd.uscsi_buflen = 100; /* There're 100 bytes of data */
my_cmd.uscsi_cdblen = 6; /* The CDB is 6 bytes long */
my_cmd.uscsi_rqlen = 26; /* The request sense buffer (only valid on a check condition) is 26 bytes long */
my_cmd.uscsi_rqbuf = my_rq_buf; /* Pointer to the request sense buffer */
/* Issue Mode Sense(6) */
rc = ioctl(fd, USCSICMD, &my_cmd);
if(rc != 0)
{
printf("Mode Sense Failed - Aborting\n");
perror(NULL);
/* Since things failed, print out some useful information gleaned from the request buffer */
printf("Sense Key: %0.2X ASC: %0.2X ASCQ: %0.2X\n", LOBYTE(my_rq_buf[2]), my_rq_buf[12], my_rq_buf[13]);
return NULL;
}
else
{
return my_page_data;
}
}
Usually after requesting mode pages, we'll want to do something to them. These functions will give you the mode pages so that you can manually change the bytes in it within your program. The Mode Select function below will let you reissue the mode pages once you've changed it.
/**********************************************************************
* Function: mode_select(int fd, unsigned char *modepage)
*
* This function will issue a Mode Sense command, requesting all the
* data pages. We're assuming 100 bytes of data (all pages). For a more
* flexible version of the function, you'd want a Length field and a
* 'which pages' field (single byte).
*
* It will return a pointer to the data buffer on success, or a
* pointer to NULL on failure.
*********************************************************************/
caddr_t mode_select(int fd, caddr_t modepage)
{
int rc;
struct uscsi_cmd my_cmd;
unsigned char my_rq_buf[26];
unsigned char my_scsi_cdb[6];
/* Initialize my_scsi_cdb as a Mode Select(6) */
my_scsi_cdb[0] = 0x15; /* Mode Select command */
my_scsi_cdb[1] = 0x10; /* Page formatted data follows block descriptor */
my_scsi_cdb[2] = NULL; /* Reserved */
my_scsi_cdb[3] = NULL; /* Reserved */
my_scsi_cdb[4] = 0x64; /* 100b (100bytes of data follow) */
my_scsi_cdb[5] = 0x00; /* Control byte */
/* Set up my_cmd for Mode Select(6) */
my_cmd.uscsi_flags = (USCSI_WRITE|USCSI_RQENABLE); /* We're going to be sending data */
my_cmd.uscsi_timeout = 15; /* Allow 15 seconds for completion */
my_cmd.uscsi_cdb = my_scsi_cdb; /* We'll be using the array above for the CDB */
my_cmd.uscsi_bufaddr = my_page_data; /* The block and page data is stored here */
my_cmd.uscsi_buflen = 100; /* There're 100 bytes of data */
my_cmd.uscsi_cdblen = 6; /* The CDB is 6 bytes long */
my_cmd.uscsi_rqlen = 26; /* The request sense buffer (only valid on a check condition) is 26 bytes long */
my_cmd.uscsi_rqbuf = my_rq_buf; /* Pointer to the request sense buffer */
/* Issue Mode Sense(6) */
rc = ioctl(fd, USCSICMD, &my_cmd);
if(rc != 0)
{
printf("Mode Select Failed - Aborting\n");
perror(NULL);
/* Since things failed, print out some useful information gleaned from the request buffer */
printf("Sense Key: %0.2X ASC: %0.2X ASCQ: %0.2X\n", LOBYTE(my_rq_buf[2]), my_rq_buf[12], my_rq_buf[13]);
return NULL;
}
else
{
return my_page_data;
}
}
The functions above should be a good starting point for working with USCSI. If you need more information, you might try looking into http://docs.sun.com and checking out the man pages there. Of particular relevence are ioctl() and USCSI.