/**********************************************************************
 *     EPSCAN, an EPSON ES-1200 scanner driver     
 *     Adam P. Jenkins <ajenkins@cs.umass.edu>
 *     
 *     This program is free software; you can redistribute it and/or
 *     modify it under the terms of the GNU General Public License
 *     as published by the Free Software Foundation; either version 2
 *     of the License, or (at your option) any later version.
 *     
 *     This program is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *     
 *     You should have received a copy of the GNU General Public License
 *     along with this program; if not, write to the Free Software
 *     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 **********************************************************************/

#include <errno.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <iostream.h>

#include "Scsi.h"
#include "debug.h"

// Function: Scsi::error
// Parameters: 
//   char *format - a printf style format string
//   ...          - arguments to format
//   
// Description: This function is called whenever an error message
// should be printed. 
// Return value: void
void
ScsiDevice::error(char *format, ...)
{
   va_list args;
   va_start(args, format);
   vsnprintf(errStr, sizeof(errStr), format, args);
}

// Function: ScsiDevice::ScsiDevice
// Parameters:
//   const char *device - name of a SCSI device to initialize
// Description: Constructor creates object with device open.  Use ok
// method to see if it was successful
// 
// Return value: void
ScsiDevice::ScsiDevice(const char *device)
{
   dprintf("initialized with %s\n", device);
   Fd = -1;
   open(device);
}

// Function: ScsiDevice::ScsiDevice
// Parameters: 
//   void - 
// Description: constructor which doesn't create object with open
// device.  In this case use the open method to open a device.
// 
// Return value: void
ScsiDevice::ScsiDevice(void)
{
   dprintf("Initialized\n");
   Fd = -1;
}

// Function: ScsiDevice
// Parameters: 
//   void - 
// Description: The destructor.  Closes device.
// Return value: void
ScsiDevice::~ScsiDevice(void)
{
   dprintf("Destroyed!!\n");
   close();
}

// Function: ScsiDevice::scsiXfer
// Parameters: 
//   const int cmd_len   - length of the SCSI command
//
//   const u_char *src   - buffer with space at the beginning for a
//   struct sg_header, followed by the SCSI command, followed by any
//   arguments to the command and/or additional data.
//   
//   const int send_len  - length of the arguments to the command
//   and/or additional data.
//   
//   const u_char *dest  - buffer with space for a struct sg_header,
//   followed by any data to be read
//   
//   const int reply_len - length of data to be read.  Must fit in
//   dest, following the sg_header.
//
// Description: A generic function to execute any SCSI command, and
// return the result in dest.
// 
// Return value: Returns 0 on success, nonzero on failure
int
ScsiDevice::scsiXfer(const int cmd_len,
		     const u_char *src, const int send_len,
		     const u_char *dest, const int reply_len)
{
   int status = 0;
   struct sg_header *sg_p;

   dprintf("cmd_len = %d, cmd = %#x, send_len = %d, reply_len = %d\n",
	   cmd_len, (int)src[sizeof(struct sg_header)], send_len, reply_len);
   // safety checks 
   if (!cmd_len)
   {
      error("scsiXfer called with cmd_len = 0");
      return -1;			// need a cmd_len != 0
   }
   if (!src)
   {
      error("scsiXfer called with NULL src buf");
      return -1;			// need an input buffer != NULL
   }

   // generic SCSI device header construction
   sg_p = (struct sg_header *) src;
   sg_p->reply_len   = sizeof(struct sg_header) + reply_len;
   sg_p->twelve_byte = (cmd_len == 12);
   sg_p->result = 0;


   // send command
   status = write(Fd, src, sizeof(struct sg_header) + cmd_len + send_len );

   if ( status < 0 ||
       status != (int)(sizeof(struct sg_header) + cmd_len + send_len) || 
       sg_p->result )
   {
      error("write(generic) result = %#x, cmd = %#x, "
	    "status = %d: %s",
	    sg_p->result, (int)src[sizeof(struct sg_header)], status,
	    (status < 0) ? strerror(errno) : "unknown error");
      return status;
   }
   
   // retrieve result
   int rx = sizeof(struct sg_header) + reply_len;
   status = read(Fd, (u_char *)dest, rx);
   if (status < 0 ||
       status != rx ||
       sg_p->result )
   {
      sg_p = (struct sg_header *) dest;
      // some error happened 
      error("read(generic) status = %d, result = %#x, "
	    "cmd = %#x: %s", 
	    status, sg_p->result, src[sizeof(struct sg_header)],
	    (status < 0) ? strerror(errno) : "unknown error");
      handleError(sg_p->sense_buffer);
   }
   // Look if we got what we expected to get 
   if (status == (int)(sizeof(struct sg_header) + reply_len))
     status = 0;		// got them all 

   dprintf("Returned status = %d\n", status);
   return status;		// 0 means no error 
}

// Function: ScsiDevice::testUnitReady
// Parameters: 
//   void -
//   
// Description: Calls TEST UNIT READY (SCSI2 8.2.16).
// 
// Return value: Returns the value of Sense Keys.  See (SCSI2
// 8.2.14.3) for description of sense keys.  This seemed to be the
// only field relevant for the Epson Scanners.
// -1 -> command failed
// 0  -> good
// 5  -> Illegal request
// 2  -> not ready/check condition
int
ScsiDevice::testUnitReady(void)
{
   u_char cmdblk[6 + sizeof(struct sg_header)];
   u_char *cmd = cmdblk + sizeof(struct sg_header);
   
   cmd[0] = TESTUNITREADY_CMD;	// command
   cmd[1] = 0;			// lun/reserved
   cmd[2] = 0;			// reserved
   cmd[3] = 0;			// reserved
   cmd[4] = 0;			// reserved
   cmd[5] = 0;			// control

   if (setBufSize(sizeof(struct sg_header)) )
     return -1;   
   struct sg_header *sg_p = (struct sg_header *) receiveBuf;

   dprintf("Executing command\n");
   int result = scsiXfer(6, cmdblk, 0, receiveBuf, 0);
   if (result)
     return -1;			// scsiXfer sets error code
   else
     return sg_p->sense_buffer[2] & 0x0f; // Sense Keys (SCSI2 8.2.14.3)
}


// Function: ScsiDevice::requestSense
// Parameters: 
//   void - 
// Description: SCSI REQUEST SENSE command (SCSI2 8.2.14).  Returns 8 bytes of
// sense data, since that is what EPSON specifies. (Currently this
// crashes the system. Not sure why.)
// 
// Return value: buffer containing the sense data, or NULL if not successful.
const u_char *
ScsiDevice::requestSense(void)
{
   u_char sensecmd[6 + sizeof(struct sg_header)];
   u_char *cmd = sensecmd + sizeof(struct sg_header);

   cmd[0] = REQUESTSENSE_CMD;	// command
   cmd[1] = 0;			// lun/reserved
   cmd[2] = 0;			// reserved
   cmd[3] = 0;			// reserved
   cmd[4] = 8;			// allocation length
   cmd[5] = 0;			// control

   dprintf("Executing command\n");
   if (setBufSize(sizeof(struct sg_header)) )
     return NULL;
   int result = scsiXfer(6, sensecmd, 0, receiveBuf, 0);
   if (result != 0)
     return NULL;
   else
   {
      struct sg_header *sg_p = (struct sg_header *) receiveBuf;
      return sg_p->sense_buffer;
   }
}
      
// Function: ScsiDevice::inquiry
// Parameters: 
//   void -
//   
// Description: Calls the SCSI INQUIRY command on device. (SCSI2
// 8.2.5). See (SCSI2 8.2.5.1) for the format of the returned block of
// data. At offset 8, is a product identification string.
// 
// Return value: A pointer to the INQUIRY data, or NULL if command
// failed. 
const u_char *
ScsiDevice::inquiry(void)
{
   const int reply_len = 40;	// reply length specified by epson
   u_char inquirycmd[6 + sizeof(struct sg_header)];
   u_char *cmd = inquirycmd + sizeof(struct sg_header);

   cmd[0] = INQUIRY_CMD;	// command
   cmd[1] = 0;			// lun/reserved
   cmd[2] = 0;			// page code
   cmd[3] = 0;			// reserved
   cmd[4] = reply_len;		// reply length
   cmd[5] = 0;			// control


   dprintf("Executing command\n");
   if (setBufSize(sizeof(struct sg_header) + reply_len))
     return NULL;
   int result = scsiXfer(6, inquirycmd, 0, receiveBuf, reply_len);
   if (result)
     return NULL;		// scsiXfer sets error code
   else
     return receiveBuf + sizeof(sg_header);
}


// Function: ScsiDevice::receive
// Parameters: 
//   const int len - length of the data to be read
//   
// Description: SCSI RECEIVE command (SCSI2 12.2.1).  Reads data from
// a SCSI device.  The contents of the read can be accessed with
// getRbuf(). 
// 
// Return value: 0 on success, -1 on error
int
ScsiDevice::receive(const int len)
{
   u_char receivecmd[6 + sizeof(struct sg_header)];
   u_char *cmd = receivecmd + sizeof(struct sg_header);
   
   cmd[0] = RECEIVE_CMD;	// command
   cmd[1] = 0;			// lun/reserved
   cmd[2] = len >> 16;		// MSB
   cmd[3] = len >> 8;		// middle byte
   cmd[4] = len;		// LSB
   cmd[5] = 0;			// control

   dprintf("Called with len = %d\n", len);
   if (setBufSize(sizeof(struct sg_header) + len))
     return -1;
   int result = scsiXfer(6, receivecmd, 0, receiveBuf, len);
   if (result != 0)
     return -1;
   else
     return 0;
}

// Function: ScsiDevice::receive
// Parameters: 
//   const int len     - number of bytes expected
//   const u_char *buf - buffer to hold them
//   
// Description: This is just a version of receive that stores the data
// read into buf instead of just leaving it in receiveBuf.  If reading
// large amounts of data, this is less efficient because of the extra
// copying, but it's more convenient for small amounts of data.
// 
// Return value: 0 on success, -1 on failure
int
ScsiDevice::receive(const int len, u_char *buf)
{
   dprintf("len = %d, buf = %p\n", len, buf);
   if (receive(len))
     return -1;
   else
   {
      memcpy(buf, receiveBuf + sizeof(struct sg_header), len);
      return 0;
   }
}

// Function: ScsiDevice::getRbuf
// Parameters: 
//   void - 
// Description: Returns a pointer to the receiveBuf, to access the
// contents of a previous read.  It hides the fact that at the
// beginning of every read, there is a sg_header.
// 
// Return value: Pointer into receiveBuf, or NULL if receiveBuf is NULL
const u_char *
ScsiDevice::getRbuf(void)
{
   if (receiveBuf)
     return receiveBuf + sizeof(struct sg_header);
   else
     return NULL;
}

// Function: ScsiDevice::send
// Parameters: 
//   const char *src - Data to be sent to device
//   int len         - length of data to be sent
//   
// Description: SCSI SEND command (SCSI2 12.2.2). Sends data in src to the
// SCSI device
// 
// Return value: 0 on success, -1 on failure
int
ScsiDevice::send(const u_char *src, const int len)
{
   // gcc extension lets me use non-constant in array decl.
   u_char sendcmd[6 + sizeof(struct sg_header) + len];
   u_char *cmd = sendcmd + sizeof(struct sg_header);
   
   cmd[0] = SEND_CMD;		// command
   cmd[1] = 0;			// lun/reserved
   cmd[2] = len >> 16;		// MSB
   cmd[3] = len >> 8;		// middle
   cmd[4] = len;		// LSB
   cmd[5] = 0;			// control

   memcpy(sendcmd + sizeof(struct sg_header) + 6, src, len);

   dprintf("Called with src = %p, len = %d\n", src, len);
   if (setBufSize(sizeof(struct sg_header)) )
     return -1;
   int result = scsiXfer(6, sendcmd, len, receiveBuf, 0);
   if (result != 0)
     return -1;
   else
     return 0;
}


// Function: ScsiDevice::sendf
// Parameters: 
//   const char *format - a printf style format string
//   ...                - arguments for the format string
//   
// Description: For sending a formatted string to the SCSI device.  Calls send
// to do the actual work.  See send comment.
// 
// Return value: 0 if successful, -1 on failure
int
ScsiDevice::sendf(const char *format, ...)
{
   va_list args;
   char buf[1024];

   va_start(args, format);
   vsnprintf(buf, sizeof(buf), format, args);
   
   dprintf("Sending %s\n", buf);
   return send((u_char *)buf, strlen(buf));
}


// Function: ScsiDevice::open
// Parameters: 
//   const char *device - The name of a SCSI device
// Description: opens the specified device
// Return value: 0 if successful, -1 on failure
int
ScsiDevice::open(const char *device)
{
   dprintf("Called with device = %s\n", device);
   Fd = ::open(device, O_RDWR);
   if (Fd == -1)
   {
      error("%s: %s", device, strerror(errno));
      return -1;
   }
   else				// see if it's a device
   {
      if (ioctl(Fd, SG_GET_TIMEOUT, NULL) >= 0)
	return 0;		// it's a SG device
      else
      {
	 ::close(Fd);
	 Fd = -1;		// so ok() will return false
	 error("%s: %s", device, strerror(errno));
	 return -1;
      }
   }
}

// Function: ScsiDevice::close
// Parameters: 
//   void - 
// Description: Closes the SCSI device
// Return value: 0 on success, -1 on failure
int
ScsiDevice::close(void)
{
   dprintf("\n");
   if (Fd >= 0 && ::close(Fd) < 0)
   {
      error("%s: %s", __PRETTY_FUNCTION__, strerror(errno));
      return -1;
   }
   else
   {
      Fd = -1;
      return 0;
   }
}

// Function: ScsiDevice::initialize
// Parameters: 
//   void - 
// Description: Initializes the SCSI device
// Return value: 0 if successful, non-zero otherwise.
int
ScsiDevice::initialize(void)
{
   return testUnitReady();
}

// Function: ScsiDevice::handleError
// Parameters: 
//   u_char *sense_data - sense_buffer after executing a SCSI command.  
// Description: If an error is indicated, prints a slightly informative
// message describing the error.
// 
// Return value: Returns -1 if an error condition is found, 0 otherwise.
int
ScsiDevice::handleError(const u_char *sense_data)
{
   int status = 0;
   int sense_key = sense_data[2] & 0x0f;
   if (sense_key != 0) // error condition
   {
      char *errstr;
      switch (sense_key)	// see (SCSI2 8.2.14.3)
      {
       case 1:
	 errstr = "Recovered error";
	 break;
       case 2:
	 errstr = "Not ready";
	 break;
       case 3:
	 errstr = "Medium error";
	 break;
       case 4:
	 errstr = "Hardware error";
	 break;
       case 5:
	 errstr = "Illegal request";
	 break;
       case 6:
	 errstr = "Unit attention conditon";
	 break;
       case 7:
	 errstr = "Data protect error";
	 break;
       case 8:
	 errstr = "Blank check";
	 break;
       case 11:
	 errstr = "Aborted command";
	 break;
       case 13:
	 errstr = "Volume overflow";
	 break;
       default:
	 errstr = "Unknown error";
      }
      cerr.form("SCSI Error, sense key = %d: %s\n", sense_key, errstr);
      status = -1;
   }
   
   if (!(sense_data[0] & 0x80))	// test Valid bit
   {
      int len = (sense_data[3] << 24) + (sense_data[4] << 16) +
	(sense_data[5] << 8) + sense_data[6];
      cerr.form("Invalid data length, information byte = %d\n", len);
      status = -1;
   }

   dprintf("Returning status = %d\n", status);
   return status;
}
