/* sane - Scanner Access Now Easy.
   Copyright (C) 1997 Geoffrey T. Dairiki
   This file is part of the SANE package.

   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., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.

   As a special exception, the authors of SANE give permission for
   additional uses of the libraries contained in this release of SANE.

   The exception is that, if you link a SANE library with other files
   to produce an executable, this does not by itself cause the
   resulting executable to be covered by the GNU General Public
   License.  Your use of that executable is in no way restricted on
   account of linking the SANE library code into it.

   This exception does not, however, invalidate any other reasons why
   the executable file might be covered by the GNU General Public
   License.

   If you submit changes to SANE to the maintainers to be included in
   a subsequent release, you agree by submitting the changes that
   those changes may be distributed with this exception intact.

   If you write modifications of your own for SANE, it is your choice
   whether to permit this exception to apply to your modifications.
   If you do not wish that, delete this exception notice.

   This file is part of a SANE backend for HP Scanners supporting
   HP Scanner Control Language (SCL).
*/

/*
   $Log: hp-scl.c,v $
   Revision 1.14  2004/10/04 18:09:05  kig-guest
   Rename global function hp_init_openfd to sanei_hp_init_openfd

   Revision 1.13  2004/03/27 13:52:39  kig-guest
   Keep USB-connection open (was problem with Linux 2.6.x)

   Revision 1.12  2003/10/09 19:34:57  kig-guest
   Redo when TEST UNIT READY failed
   Redo when read returns with 0 bytes (non-SCSI only)


*/

/*
#define STUBS
extern int sanei_debug_hp;*/
#define DEBUG_DECLARE_ONLY
#include "sane/config.h"
#include "../include/lalloca.h"		/* Must be first */

#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
#include <stdlib.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include "../include/lassert.h"
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "sane/sanei_scsi.h"
#include "sane/sanei_usb.h"
#include "sane/sanei_pio.h"

#include "hp.h"

#include "sane/sanei_backend.h"

#include "hp-option.h"
#include "hp-scsi.h"
#include "hp-scl.h"
#include "hp-device.h"

#define HP_SCSI_INQ_LEN		(36)
#define HP_SCSI_CMD_LEN		(6)
#define HP_SCSI_BUFSIZ	(HP_SCSI_MAX_WRITE + HP_SCSI_CMD_LEN)

#define HP_MAX_OPEN_FD 16
static struct hp_open_fd_s  /* structure to save info about open file descriptor */
{
    char *devname;
    HpConnect connect;
    int fd;
} asHpOpenFd[HP_MAX_OPEN_FD];


/*
 *
 */
struct hp_scsi_s
{
    int		fd;
    char      * devname;

    /* Output buffering */
    hp_byte_t	buf[HP_SCSI_BUFSIZ];
    hp_byte_t *	bufp;

    hp_byte_t	inq_data[HP_SCSI_INQ_LEN];
};

#define HP_TMP_BUF_SIZE (1024*4)
#define HP_WR_BUF_SIZE (1024*4)

typedef struct
{
  HpProcessData procdata;

  int outfd;
  const unsigned char *map;

  unsigned char *image_buf; /* Buffer to store complete image (if req.) */
  unsigned char *image_ptr;
  int image_buf_size;

  unsigned char *tmp_buf; /* Buffer for scan data to get even number of bytes */
  int tmp_buf_size;
  int tmp_buf_len;

  unsigned char wr_buf[HP_WR_BUF_SIZE];
  unsigned char *wr_ptr;
  int wr_buf_size;
  int wr_left;
} PROCDATA_HANDLE;


/* Initialize structure where we remember out open file descriptors */
void
sanei_hp_init_openfd ()
{int iCount;
 memset (asHpOpenFd, 0, sizeof (asHpOpenFd));

 for (iCount = 0; iCount < HP_MAX_OPEN_FD; iCount++)
     asHpOpenFd[iCount].fd = -1;
}


/* Look if the device is still open */
static SANE_Status
hp_GetOpenDevice (const char *devname, HpConnect connect, int *pfd)

{int iCount;

 for (iCount = 0; iCount < HP_MAX_OPEN_FD; iCount++)
     {
     if (!asHpOpenFd[iCount].devname) continue;
     if (   (strcmp (asHpOpenFd[iCount].devname, devname) == 0)
         && (asHpOpenFd[iCount].connect == connect) )
         {
         if (pfd) *pfd = asHpOpenFd[iCount].fd;
         DBG(3, "hp_GetOpenDevice: device %s is open with fd=%d\n", devname,
             asHpOpenFd[iCount].fd);
         return SANE_STATUS_GOOD;
         }
     }
 DBG(3, "hp_GetOpenDevice: device %s not open\n", devname);
 return SANE_STATUS_INVAL;
}

/* Add an open file descriptor. This also decides */
/* if we keep a connection open or not. */
static SANE_Status
hp_AddOpenDevice (const char *devname, HpConnect connect, int fd)

{int iCount, iKeepOpen;
 static int iInitKeepFlags = 1;

 /* The default values which connections to keep open or not */
 static int iKeepOpenSCSI = 0;
 static int iKeepOpenUSB = 1;
 static int iKeepOpenDevice = 0;
 static int iKeepOpenPIO = 0;

 if (iInitKeepFlags) /* Change the defaults by environment */
     {char *eptr;

     iInitKeepFlags = 0;

     eptr = getenv ("SANE_HP_KEEPOPEN_SCSI");
     if ( (eptr != NULL) && ((*eptr == '0') || (*eptr == '1')) )
         iKeepOpenSCSI = (*eptr == '1');

     eptr = getenv ("SANE_HP_KEEPOPEN_USB");
     if ( (eptr != NULL) && ((*eptr == '0') || (*eptr == '1')) )
         iKeepOpenUSB = (*eptr == '1');

     eptr = getenv ("SANE_HP_KEEPOPEN_DEVICE");
     if ( (eptr != NULL) && ((*eptr == '0') || (*eptr == '1')) )
         iKeepOpenDevice = (*eptr == '1');

     eptr = getenv ("SANE_HP_KEEPOPEN_PIO");
     if ( (eptr != NULL) && ((*eptr == '0') || (*eptr == '1')) )
         iKeepOpenPIO = (*eptr == '1');
     }

 /* Look if we should keep it open or not */
 iKeepOpen = 0;
 switch (connect)
     {
     case HP_CONNECT_SCSI: iKeepOpen = iKeepOpenSCSI;
                           break;
     case HP_CONNECT_PIO : iKeepOpen = iKeepOpenPIO;
                           break;
     case HP_CONNECT_USB : iKeepOpen = iKeepOpenUSB;
                           break;
     case HP_CONNECT_DEVICE : iKeepOpen = iKeepOpenDevice;
                           break;
     case HP_CONNECT_RESERVE:
                           break;
     }
 if (!iKeepOpen)
     {
     DBG(3, "hp_AddOpenDevice: %s should not be kept open\n", devname);
     return SANE_STATUS_INVAL;
     }

 for (iCount = 0; iCount < HP_MAX_OPEN_FD; iCount++)
     {
     if (!asHpOpenFd[iCount].devname)  /* Is this entry free ? */
         {
         asHpOpenFd[iCount].devname = sanei_hp_strdup (devname);
         if (!asHpOpenFd[iCount].devname) return SANE_STATUS_NO_MEM;
         DBG(3, "hp_AddOpenDevice: added device %s with fd=%d\n", devname, fd);
         asHpOpenFd[iCount].connect = connect;
         asHpOpenFd[iCount].fd = fd;
         return SANE_STATUS_GOOD;
         }
     }
 DBG(3, "hp_AddOpenDevice: %s not added\n", devname);
 return SANE_STATUS_NO_MEM;
}


/* Check if we have remembered an open file descriptor */
static SANE_Status
hp_IsOpenFd (int fd, HpConnect connect)

{int iCount;

 for (iCount = 0; iCount < HP_MAX_OPEN_FD; iCount++)
     {
     if (   (asHpOpenFd[iCount].devname != NULL)
         && (asHpOpenFd[iCount].fd == fd)
         && (asHpOpenFd[iCount].connect == connect) )
         {
         DBG(3, "hp_IsOpenFd: %d is open\n", fd);
         return SANE_STATUS_GOOD;
         }
     }
 DBG(3, "hp_IsOpenFd: %d not open\n", fd);
 return SANE_STATUS_INVAL;
}


static SANE_Status
hp_RemoveOpenFd (int fd, HpConnect connect)

{int iCount;

 for (iCount = 0; iCount < HP_MAX_OPEN_FD; iCount++)
     {
     if (   (asHpOpenFd[iCount].devname != NULL)
         && (asHpOpenFd[iCount].fd == fd)
         && (asHpOpenFd[iCount].connect == connect) )
         {
         sanei_hp_free (asHpOpenFd[iCount].devname);
         asHpOpenFd[iCount].devname = NULL;
         DBG(3, "hp_RemoveOpenFd: removed %d\n", asHpOpenFd[iCount].fd);
         asHpOpenFd[iCount].fd = -1;
         return SANE_STATUS_GOOD;
         }
     }
 DBG(3, "hp_RemoveOpenFd: %d not removed\n", fd);
 return SANE_STATUS_INVAL;
}


static SANE_Status
hp_nonscsi_write (HpScsi this, hp_byte_t *data, size_t len, HpConnect connect)

{int n = -1;
 size_t loc_len;
 SANE_Status status = SANE_STATUS_GOOD;

 if (len <= 0) return SANE_STATUS_GOOD;

 switch (connect)
 {
   case HP_CONNECT_DEVICE:   /* direct device-io */
     n = write (this->fd, data, len);
     break;

   case HP_CONNECT_PIO:      /* Use sanepio interface */
     n = sanei_pio_write (this->fd, data, len);
     break;

   case HP_CONNECT_USB:      /* Not supported */
     loc_len = len;
     status = sanei_usb_write_bulk ((SANE_Int)this->fd, data, &loc_len);
     n = loc_len;
     break;

   case HP_CONNECT_RESERVE:
     n = -1;
     break;

   default:
     n = -1;
     break;
 }

 if (n == 0) return SANE_STATUS_EOF;
 else if (n < 0) return SANE_STATUS_IO_ERROR;

 return status;
}

static SANE_Status
hp_nonscsi_read (HpScsi this, hp_byte_t *data, size_t *len, HpConnect connect,
  int UNUSEDARG isResponse)

{int n = -1;
 static int retries = -1;
 size_t save_len = *len;
 SANE_Status status = SANE_STATUS_GOOD;

 if (*len <= 0) return SANE_STATUS_GOOD;

 if (retries < 0)  /* Read environment */
 {char *eptr = getenv ("SANE_HP_RDREDO");

   retries = 1;       /* Set default value */
   if (eptr != NULL)
   {
     if (sscanf (eptr, "%d", &retries) != 1) retries = 1; /* Restore default */
     else if (retries < 0) retries = 0; /* Allow no retries here */
   }
 }

 for (;;) /* Retry on EOF */
 {
   switch (connect)
   {
     case HP_CONNECT_DEVICE:
       n = read (this->fd, data, *len);
       break;

     case HP_CONNECT_PIO:
       n = sanei_pio_read (this->fd, data, *len);
       break;

     case HP_CONNECT_USB:
       status = sanei_usb_read_bulk((SANE_Int)this->fd, (SANE_Byte *)data, len);
       n = *len;
       break;

     case HP_CONNECT_RESERVE:
       n = -1;
       break;

     default:
       n = -1;
       break;
   }
   if ((n != 0) || (retries <= 0)) break;
   retries--;
   usleep (100*1000);  /* sleep 0.1 seconds */
   *len = save_len;    /* Restore value */
 }

 if (n == 0) return SANE_STATUS_EOF;
 else if (n < 0) return SANE_STATUS_IO_ERROR;

 *len = n;
 return status;
}

static SANE_Status
hp_nonscsi_open (const char *devname, int *fd, HpConnect connect)

{int lfd, flags;
 SANE_Int dn;
 SANE_Status status = SANE_STATUS_INVAL;

#ifdef _O_RDWR
 flags = _O_RDWR;
#else
 flags = O_RDWR;
#endif
#ifdef _O_EXCL
 flags |= _O_EXCL;
#else
 flags |= O_EXCL;
#endif
#ifdef _O_BINARY
 flags |= _O_BINARY;
#endif
#ifdef O_BINARY
 flags |= O_BINARY;
#endif

 switch (connect)
 {
   case HP_CONNECT_DEVICE:
     lfd = open (devname, flags);
     if (lfd < 0)
     {
        DBG(1, "hp_nonscsi_open: open device %s failed (%s)\n", devname,
            strerror (errno) );
       status = (errno == EACCES) ? SANE_STATUS_ACCESS_DENIED : SANE_STATUS_INVAL;
     }
     else
       status = SANE_STATUS_GOOD;
     break;

   case HP_CONNECT_PIO:
     status = sanei_pio_open (devname, &lfd);
     break;

   case HP_CONNECT_USB:
     DBG(17, "hp_nonscsi_open: open usb with \"%s\"\n", devname);
     status = sanei_usb_open (devname, &dn);
     lfd = (int)dn;
     break;

   case HP_CONNECT_RESERVE:
     status = SANE_STATUS_INVAL;
     break;

   default:
     status = SANE_STATUS_INVAL;
     break;
 }

 if (status != SANE_STATUS_GOOD)
 {
    DBG(1, "hp_nonscsi_open: open device %s failed\n", devname);
 }
 else
 {
    DBG(17,"hp_nonscsi_open: device %s opened, fd=%d\n", devname, lfd);
 }

 if (fd) *fd = lfd;

 return status;
}

static void
hp_nonscsi_close (int fd, HpConnect connect)

{
 switch (connect)
 {
   case HP_CONNECT_DEVICE:
     close (fd);
     break;

   case HP_CONNECT_PIO:
     sanei_pio_close (fd);
     break;

   case HP_CONNECT_USB:
     sanei_usb_close (fd);
     break;

   case HP_CONNECT_RESERVE:
     break;

   default:
     break;
 }
 DBG(17,"hp_nonscsi_close: closed fd=%d\n", fd);
}

SANE_Status
sanei_hp_nonscsi_new (HpScsi * newp, const char * devname, HpConnect connect)
{
 HpScsi new;
 SANE_Status status;
 int iAlreadyOpen = 0;

  new = sanei_hp_allocz(sizeof(*new));
  if (!new)
    return SANE_STATUS_NO_MEM;

  /* Is the device already open ? */
  if ( hp_GetOpenDevice (devname, connect, &new->fd) == SANE_STATUS_GOOD )
  {
    iAlreadyOpen = 1;
  }
  else
  {
    status = hp_nonscsi_open(devname, &new->fd, connect);
    if (FAILED(status))
    {
      DBG(1, "nonscsi_new: open failed (%s)\n", sane_strstatus(status));
      sanei_hp_free(new);
      return SANE_STATUS_IO_ERROR;
    }
  }

  /* For SCSI-devices we would have the inquire command here */
  strncpy ((char *)new->inq_data, "\003zzzzzzzHP      ------          R000",
           sizeof (new->inq_data));

  new->bufp = new->buf + HP_SCSI_CMD_LEN;
  new->devname = sanei_hp_alloc ( strlen ( devname ) + 1 );
  if ( new->devname ) strcpy (new->devname, devname);

  *newp = new;

  /* Remember the open device */
  if (!iAlreadyOpen) hp_AddOpenDevice (devname, connect, new->fd);

  return SANE_STATUS_GOOD;
}

static void
hp_scsi_close (HpScsi this, int completely)
{HpConnect connect;

 DBG(3, "scsi_close: closing fd %ld\n", (long)this->fd);

 connect = sanei_hp_scsi_get_connect (this);

 if (!completely)  /* May we keep the device open ? */
 {
   if ( hp_IsOpenFd (this->fd, connect) == SANE_STATUS_GOOD )
   {
     DBG(3, "scsi_close: not closing. Keep open\n");
     return;
   }
   
 }
 assert(this->fd >= 0);

 if (connect != HP_CONNECT_SCSI)
   hp_nonscsi_close (this->fd, connect);
 else
   sanei_scsi_close (this->fd);

 DBG(3,"scsi_close: really closed\n");

 /* Remove a remembered open device */
 hp_RemoveOpenFd (this->fd, connect);
}


SANE_Status
sanei_hp_scsi_new (HpScsi * newp, const char * devname)
{
  static hp_byte_t inq_cmd[] = { 0x12, 0, 0, 0, HP_SCSI_INQ_LEN, 0};
  static hp_byte_t tur_cmd[] = { 0x00, 0, 0, 0, 0, 0};
  size_t	inq_len		= HP_SCSI_INQ_LEN;
  HpScsi	new;
  HpConnect     connect;
  SANE_Status	status;
  int iAlreadyOpen = 0;

  connect = sanei_hp_get_connect (devname);

  if (connect != HP_CONNECT_SCSI)
    return sanei_hp_nonscsi_new (newp, devname, connect);

  new = sanei_hp_allocz(sizeof(*new));
  if (!new)
      return SANE_STATUS_NO_MEM;

  /* Is the device still open ? */
  if ( hp_GetOpenDevice (devname, connect, &new->fd) == SANE_STATUS_GOOD )
  {
    iAlreadyOpen = 1;
  }
  else
  {
    status = sanei_scsi_open(devname, &new->fd, 0, 0);
    if (FAILED(status))
      {
        DBG(1, "scsi_new: open failed (%s)\n", sane_strstatus(status));
        sanei_hp_free(new);
        return SANE_STATUS_IO_ERROR;
      }
  }

  DBG(3, "scsi_inquire: sending INQUIRE\n");
  status = sanei_scsi_cmd(new->fd, inq_cmd, 6, new->inq_data, &inq_len);
  if (FAILED(status))
    {
      DBG(1, "scsi_inquire: inquiry failed: %s\n", sane_strstatus(status));
      sanei_scsi_close(new->fd);
      sanei_hp_free(new);
      return status;
    }

  {char vendor[9], model[17], rev[5];
   memset (vendor, 0, sizeof (vendor));
   memset (model, 0, sizeof (model));
   memset (rev, 0, sizeof (rev));
   memcpy (vendor, new->inq_data + 8, 8);
   memcpy (model, new->inq_data + 16, 16);
   memcpy (rev, new->inq_data + 32, 4);

   DBG(3, "vendor=%s, model=%s, rev=%s\n", vendor, model, rev);
  }

  DBG(3, "scsi_new: sending TEST_UNIT_READY\n");
  status = sanei_scsi_cmd(new->fd, tur_cmd, 6, 0, 0);
  if (FAILED(status))
    {
      DBG(1, "hp_scsi_open: test unit ready failed (%s)\n",
	  sane_strstatus(status));
      usleep (500*1000); /* Wait 0.5 seconds */
      DBG(3, "scsi_new: sending TEST_UNIT_READY second time\n");
      status = sanei_scsi_cmd(new->fd, tur_cmd, 6, 0, 0);
    }

  if (FAILED(status))
    {
      DBG(1, "hp_scsi_open: test unit ready failed (%s)\n",
	  sane_strstatus(status));

      sanei_scsi_close(new->fd);
      sanei_hp_free(new);
      return status; /* Fix problem with non-scanner devices */
    }

  new->bufp = new->buf + HP_SCSI_CMD_LEN;
  new->devname = sanei_hp_alloc ( strlen ( devname ) + 1 );
  if ( new->devname ) strcpy (new->devname, devname);

  *newp = new;

  /* Remember the open device */
  if (!iAlreadyOpen) hp_AddOpenDevice (devname, connect, new->fd);

  return SANE_STATUS_GOOD;
}



/* The "completely" parameter was added for OfficeJet support.
 * For JetDirect connections, closing and re-opening the scan
 * channel is very time consuming.  Also, the OfficeJet G85
 * unloads a loaded document in the ADF when the scan channel
 * gets closed.  The solution is to "completely" destroy the
 * connection, including closing and deallocating the PTAL
 * channel, when initially probing the device in hp-device.c,
 * but leave it open while the frontend is actually using the
 * device (from hp-handle.c), and "completely" destroy it when
 * the frontend closes its handle. */
void
sanei_hp_scsi_destroy (HpScsi this,int completely)
{
  /* Moved to hp_scsi_close():
   * assert(this->fd >= 0);
   * DBG(3, "scsi_close: closing fd %d\n", this->fd);
   */

  hp_scsi_close (this, completely);
  if ( this->devname ) sanei_hp_free (this->devname);
  sanei_hp_free(this);
}

hp_byte_t *
sanei_hp_scsi_inq (HpScsi this)
{
  return this->inq_data;
}

const char *
sanei_hp_scsi_vendor (HpScsi this)
{
  static char buf[9];
  memcpy(buf, sanei_hp_scsi_inq(this) + 8, 8);
  buf[8] = '\0';
  return buf;
}

const char *
sanei_hp_scsi_model (HpScsi this)
{

  static char buf[17];
  memcpy(buf, sanei_hp_scsi_inq(this) + 16, 16);
  buf[16] = '\0';
  return buf;
}

const char *
sanei_hp_scsi_devicename (HpScsi this)
{
  return this->devname;
}

hp_bool_t
sanei_hp_is_active_xpa (HpScsi scsi)
{HpDeviceInfo *info;
 int model_num;

 info = sanei_hp_device_info_get ( sanei_hp_scsi_devicename  (scsi) );
 if (info->active_xpa < 0)
 {
   model_num = sanei_hp_get_max_model (scsi);
   info->active_xpa = (model_num >= 17);
   DBG(5,"sanei_hp_is_active_xpa: model=%d, active_xpa=%d\n",
       model_num, info->active_xpa);
 }
 return info->active_xpa;
}

int
sanei_hp_get_max_model (HpScsi scsi)

{HpDeviceInfo *info;

 info = sanei_hp_device_info_get ( sanei_hp_scsi_devicename  (scsi) );
 if (info->max_model < 0)
 {enum hp_device_compat_e compat;
  int model_num;

   if ( sanei_hp_device_probe_model ( &compat, scsi, &model_num, 0)
            == SANE_STATUS_GOOD )
     info->max_model = model_num;
 }
 return info->max_model;
}


int
sanei_hp_is_flatbed_adf (HpScsi scsi)

{int model = sanei_hp_get_max_model (scsi);

 return ((model == 2) || (model == 4) || (model == 5) || (model == 8));
}


HpConnect
sanei_hp_get_connect (const char *devname)

{const HpDeviceInfo *info;
 HpConnect connect = HP_CONNECT_SCSI;
 int got_connect_type = 0;

 info = sanei_hp_device_info_get (devname);
 if (!info)
 {
   DBG(1, "sanei_hp_get_connect: Could not get info for %s. Assume SCSI\n",
       devname);
   connect = HP_CONNECT_SCSI;
 }
 else
 if ( !(info->config_is_up) )
 {
   DBG(1, "sanei_hp_get_connect: Config not initialized for %s. Assume SCSI\n",
       devname);
   connect = HP_CONNECT_SCSI;
 }
 else
 {
   connect = info->config.connect;
   got_connect_type = info->config.got_connect_type;
 }

 /* Beware of using a USB-device as a SCSI-device (not 100% perfect) */
 if ((connect == HP_CONNECT_SCSI) && !got_connect_type)
 {int maybe_usb;

   maybe_usb = (   strstr (devname, "usb")
                || strstr (devname, "uscanner")
                || strstr (devname, "ugen"));
   if (maybe_usb)
   {static int print_warning = 1;

     if (print_warning)
     {
       print_warning = 0;
       DBG(1,"sanei_hp_get_connect: WARNING\n");
       DBG(1,"  Device %s assumed to be SCSI, but device name\n",devname);
       DBG(1,"  looks like USB. Will continue with USB.\n");
       DBG(1,"  If you really want it as SCSI, add the following\n");
       DBG(1,"  to your file .../etc/sane.d/hp.conf:\n");
       DBG(1,"    %s\n", devname);
       DBG(1,"      option connect-scsi\n");
       DBG(1,"  The same warning applies to other device names containing\n");
       DBG(1,"  \"usb\", \"uscanner\" or \"ugen\".\n");
     }
     connect = HP_CONNECT_DEVICE;
   }
 }
 return connect;
}

HpConnect
sanei_hp_scsi_get_connect (HpScsi this)

{
 return sanei_hp_get_connect (sanei_hp_scsi_devicename (this));
}


static SANE_Status
hp_scsi_flush (HpScsi this)
{
  hp_byte_t *	data	= this->buf + HP_SCSI_CMD_LEN;
  size_t 	len 	= this->bufp - data;
  HpConnect     connect;

  assert(len < HP_SCSI_MAX_WRITE);
  if (len == 0)
      return SANE_STATUS_GOOD;

  this->bufp = this->buf;

  DBG(16, "scsi_flush: writing %lu bytes:\n", (unsigned long) len);
  DBGDUMP(16, data, len);

  *this->bufp++ = 0x0A;
  *this->bufp++ = 0;
  *this->bufp++ = len >> 16;
  *this->bufp++ = len >> 8;
  *this->bufp++ = len;
  *this->bufp++ = 0;

  connect = sanei_hp_scsi_get_connect (this);
  if (connect == HP_CONNECT_SCSI)
    return sanei_scsi_cmd (this->fd, this->buf, HP_SCSI_CMD_LEN + len, 0, 0);
  else
    return hp_nonscsi_write (this, this->buf+HP_SCSI_CMD_LEN, len, connect);
}

static size_t
hp_scsi_room (HpScsi this)
{
  return this->buf + HP_SCSI_BUFSIZ - this->bufp;
}

static SANE_Status
hp_scsi_need (HpScsi this, size_t need)
{
  assert(need < HP_SCSI_MAX_WRITE);

  if (need > hp_scsi_room(this))
      RETURN_IF_FAIL( hp_scsi_flush(this) );

  return SANE_STATUS_GOOD;
}

static SANE_Status
hp_scsi_write (HpScsi this, const void *data, size_t len)
{
  if ( len < HP_SCSI_MAX_WRITE )
    {
      RETURN_IF_FAIL( hp_scsi_need(this, len) );
      memcpy(this->bufp, data, len);
      this->bufp += len;
    }
  else
    {size_t maxwrite = HP_SCSI_MAX_WRITE - 16;
     const char *c_data = (const char *)data;

      while ( len > 0 )
        {
          if ( maxwrite > len ) maxwrite = len;
          RETURN_IF_FAIL( hp_scsi_write(this, c_data, maxwrite) );
          c_data += maxwrite;
          len -= maxwrite;
        }
    }
  return SANE_STATUS_GOOD;
}

static SANE_Status
hp_scsi_scl(HpScsi this, HpScl scl, int val)
{
  char	group	= tolower(SCL_GROUP_CHAR(scl));
  char	param	= toupper(SCL_PARAM_CHAR(scl));
  int	count;

  assert(IS_SCL_CONTROL(scl) || IS_SCL_COMMAND(scl));
  assert(isprint(group) && isprint(param));

  RETURN_IF_FAIL( hp_scsi_need(this, 10) );

  /* Dont try to optimize SCL-commands like using <ESC>*a1b0c5T */
  /* Some scanners have problems with it (e.g. HP Photosmart Photoscanner */
  /* with window position/extent, resolution) */
  count = sprintf((char *)this->bufp, "\033*%c%d%c", group, val, param);
  this->bufp += count;

  assert(count > 0 && this->bufp < this->buf + HP_SCSI_BUFSIZ);

  return hp_scsi_flush(this);
}

/* Read it bytewise */
static SANE_Status
hp_scsi_read_slow (HpScsi this, void * dest, size_t *len)
{static hp_byte_t read_cmd[6] = { 0x08, 0, 0, 0, 0, 0 };
 size_t leftover = *len;
 SANE_Status status = SANE_STATUS_GOOD;
 unsigned char *start_dest = (unsigned char *)dest;
 unsigned char *next_dest = start_dest;

 DBG(16, "hp_scsi_read_slow: Start reading %d bytes bytewise\n", (int)*len);

 while (leftover > 0)  /* Until we got all the bytes */
 {size_t one = 1;

   read_cmd[2] = 0;
   read_cmd[3] = 0;
   read_cmd[4] = 1;   /* Read one byte */

   status = sanei_scsi_cmd (this->fd, read_cmd, sizeof(read_cmd),
                            next_dest, &one);
   if ((status != SANE_STATUS_GOOD) || (one != 1))
   {
     DBG(250,"hp_scsi_read_slow: Reading byte %d: status=%s, len=%d\n",
         (int)(next_dest-start_dest), sane_strstatus(status), (int)one);
   }

   if (status != SANE_STATUS_GOOD) break;  /* Finish on error */

   next_dest++;
   leftover--;
 }

 *len = next_dest-start_dest; /* This is the number of bytes we got */

 DBG(16, "hp_scsi_read_slow: Got %d bytes\n", (int)*len);

 if ((status != SANE_STATUS_GOOD) && (*len > 0))
 {
   DBG(16, "We got some data. Ignore the error \"%s\"\n",
       sane_strstatus(status));
   status = SANE_STATUS_GOOD;
 }
 return status;
}

/* The OfficeJets tend to return inquiry responses containing array
 * data in two packets.  The added "isResponse" parameter tells
 * whether we should keep reading until we get
 * a well-formed response.  Naturally, this parameter would be zero
 * when reading scan data. */
static SANE_Status
hp_scsi_read (HpScsi this, void * dest, size_t *len, int isResponse)
{
  HpConnect connect;

  RETURN_IF_FAIL( hp_scsi_flush(this) );

  connect = sanei_hp_scsi_get_connect (this);
  if (connect == HP_CONNECT_SCSI)
  {int read_bytewise = 0;

    if (*len <= 32)   /* Is it a candidate for reading bytewise ? */
    {const HpDeviceInfo *info;

      info = sanei_hp_device_info_get (sanei_hp_scsi_devicename (this));
      if ((info != NULL) && (info->config_is_up) && info->config.dumb_read)
        read_bytewise = 1;
    }

    if ( ! read_bytewise )
    {static hp_byte_t read_cmd[6] = { 0x08, 0, 0, 0, 0, 0 };
      read_cmd[2] = *len >> 16;
      read_cmd[3] = *len >> 8;
      read_cmd[4] = *len;

      RETURN_IF_FAIL( sanei_scsi_cmd (this->fd, read_cmd,
                                      sizeof(read_cmd), dest, len) );
    }
    else
    {
      RETURN_IF_FAIL (hp_scsi_read_slow (this, dest, len));
    }
  }
  else
  {
    RETURN_IF_FAIL( hp_nonscsi_read (this, dest, len, connect, isResponse) );
  }
  DBG(16, "scsi_read:  %lu bytes:\n", (unsigned long) *len);
  DBGDUMP(16, dest, *len);
  return SANE_STATUS_GOOD;
}


static int signal_caught = 0;

static RETSIGTYPE
signal_catcher (int sig)
{
  DBG(1,"signal_catcher(sig=%d): old signal_caught=%d\n",sig,signal_caught);
  if (!signal_caught)
      signal_caught = sig;
}

static void
hp_data_map (register const unsigned char *map, register int count,
             register unsigned char *data)
{
  if (count <= 0) return;
  while (count--)
  {
    *data = map[*data];
    data++;
  }
}

static const unsigned char *
hp_get_simulation_map (const char *devname, const HpDeviceInfo *info)
{
 hp_bool_t     sim_gamma, sim_brightness, sim_contrast;
 int           k, ind;
 const unsigned char *map = NULL;
 static unsigned char map8x8[256];

  sim_gamma = info->simulate.gamma_simulate;
  sim_brightness = sanei_hp_device_simulate_get (devname, SCL_BRIGHTNESS);
  sim_contrast = sanei_hp_device_simulate_get (devname, SCL_CONTRAST);

  if ( sim_gamma )
  {
    map = &(info->simulate.gamma_map[0]);
  }
  else if ( sim_brightness && sim_contrast )
  {
    for (k = 0; k < 256; k++)
    {
      ind = info->simulate.contrast_map[k];
      map8x8[k] = info->simulate.brightness_map[ind];
    }
    map = &(map8x8[0]);
  }
  else if ( sim_brightness )
    map = &(info->simulate.brightness_map[0]);
  else if ( sim_contrast )
    map = &(info->simulate.contrast_map[0]);

  return map;
}


/* Check the native byte order on the local machine */
static hp_bool_t
is_lowbyte_first_byteorder (void)

{unsigned short testvar = 1;
 unsigned char *testptr = (unsigned char *)&testvar;

 if (sizeof (unsigned short) == 2)
   return (testptr[0] == 1);
 else if (sizeof (unsigned short) == 4)
   return ((testptr[0] == 1) || (testptr[2] == 1));
 else
   return (   (testptr[0] == 1) || (testptr[2] == 1)
           || (testptr[4] == 1) || (testptr[6] == 1));
}

/* The SANE standard defines that 2-byte data must use the full 16 bit range.
 * Byte order returned by the backend must be native byte order.
 * Scaling to 16 bit and byte order is achived by hp_scale_to_16bit.
 * for >8 bits data, take the two data bytes and scale their content
 * to the full 16 bit range, using
 *     scaled = unscaled << (newlen - oldlen) +
 *              unscaled >> (oldlen - (newlen - oldlen)),
 * with newlen=16 and oldlen the original bit depth.
 */
static void
hp_scale_to_16bit(int count, register unsigned char *data, int depth,
                  hp_bool_t invert)
{
    register unsigned int tmp;
    register unsigned int mask;
    register hp_bool_t lowbyte_first = is_lowbyte_first_byteorder ();
    unsigned int shift1 = 16 - depth;
    unsigned int shift2 = 2*depth - 16;
    int k;

    if (count <= 0) return;

    mask = 1;
    for (k = 1; k < depth; k++) mask |= (1 << k);

    if (lowbyte_first)
    {
      while (count--) {
         tmp = ((((unsigned int)data[0])<<8) | ((unsigned int)data[1])) & mask;
         tmp = (tmp << shift1) + (tmp >> shift2);
         if (invert) tmp = ~tmp;
         *data++ = tmp & 255U;
         *data++ = (tmp >> 8) & 255U;
      }
    }
    else  /* Highbyte first */
    {
      while (count--) {
         tmp = ((((unsigned int)data[0])<<8) | ((unsigned int)data[1])) & mask;
         tmp = (tmp << shift1) + (tmp >> shift2);
         if (invert) tmp = ~tmp;
         *data++ = (tmp >> 8) & 255U;
         *data++ = tmp & 255U;
      }
    }
}


static void
hp_scale_to_8bit(int count, register unsigned char *data, int depth,
                 hp_bool_t invert)
{
    register unsigned int tmp, mask;
    register hp_bool_t lowbyte_first = is_lowbyte_first_byteorder ();
    unsigned int shift1 = depth-8;
    int k;
    unsigned char *dataout = data;

    if ((count <= 0) || (shift1 <= 0)) return;

    mask = 1;
    for (k = 1; k < depth; k++) mask |= (1 << k);

    if (lowbyte_first)
    {
      while (count--) {
         tmp = ((((unsigned int)data[0])<<8) | ((unsigned int)data[1])) & mask;
         tmp >>= shift1;
         if (invert) tmp = ~tmp;
         *(dataout++) = tmp & 255U;
         data += 2;
      }
    }
    else  /* Highbyte first */
    {
      while (count--) {
         tmp = ((((unsigned int)data[0])<<8) | ((unsigned int)data[1])) & mask;
         tmp >>= shift1;
         if (invert) tmp = ~tmp;
         *(dataout++) = tmp & 255U;
         data += 2;
      }
    }
}

static void
hp_soft_invert(int count, register unsigned char *data) {
	while (count>0) {
		*data = ~(*data);
		data++;
		count--;
	}
}

static PROCDATA_HANDLE *
process_data_init (HpProcessData *procdata, const unsigned char *map,
                   int outfd, hp_bool_t use_imgbuf)

{PROCDATA_HANDLE *ph = sanei_hp_alloc (sizeof (PROCDATA_HANDLE));
 int tsz;

 if (ph == NULL) return NULL;

 memset (ph, 0, sizeof (*ph));
 memcpy (&(ph->procdata), procdata, sizeof (*procdata));
 procdata = &(ph->procdata);

 tsz = (HP_TMP_BUF_SIZE <= 0) ? procdata->bytes_per_line : HP_TMP_BUF_SIZE;
 ph->tmp_buf = sanei_hp_alloc (tsz);
 if (ph->tmp_buf == NULL)
 {
   sanei_hp_free (ph);
   return NULL;
 }
 ph->tmp_buf_size = tsz;
 ph->tmp_buf_len = 0;

 ph->map = map;
 ph->outfd = outfd;

 if ( procdata->mirror_vertical || use_imgbuf)
 {
   tsz = procdata->lines*procdata->bytes_per_line;
   if (procdata->out8) tsz /= 2;
   ph->image_ptr = ph->image_buf = sanei_hp_alloc (tsz);
   if ( !ph->image_buf )
   {
     procdata->mirror_vertical = 0;
     ph->image_buf_size = 0;
     DBG(1, "process_scanline_init: Not enough memory to mirror image\n");
   }
   else
     ph->image_buf_size = tsz;
 }

 ph->wr_ptr = ph->wr_buf;
 ph->wr_buf_size = ph->wr_left = sizeof (ph->wr_buf);

 return ph;
}


static SANE_Status
process_data_write (PROCDATA_HANDLE *ph, unsigned char *data, int nbytes)

{int ncopy;

 if (ph == NULL) return SANE_STATUS_INVAL;

 /* Fill up write buffer */
 ncopy = ph->wr_left;
 if (ncopy > nbytes) ncopy = nbytes;

 memcpy (ph->wr_ptr, data, ncopy);
 ph->wr_ptr += ncopy;
 ph->wr_left -= ncopy;
 data += ncopy;
 nbytes -= ncopy;

 if ( ph->wr_left > 0 )  /* Did not fill up the write buffer ? Finished */
   return SANE_STATUS_GOOD;

 DBG(12, "process_data_write: write %d bytes\n", ph->wr_buf_size);
 /* Don't write data if we got a signal in the meantime */
 if (   signal_caught
     || (write (ph->outfd, ph->wr_buf, ph->wr_buf_size) != ph->wr_buf_size))
 {
   DBG(1, "process_data_write: write failed: %s\n",
       signal_caught ? "signal caught" : strerror(errno));
   return SANE_STATUS_IO_ERROR;
 }
 ph->wr_ptr = ph->wr_buf;
 ph->wr_left = ph->wr_buf_size;

 /* For large amount of data write it from data-buffer */
 while ( nbytes > ph->wr_buf_size )
 {
   if (   signal_caught
       || (write (ph->outfd, data, ph->wr_buf_size) != ph->wr_buf_size))
   {
     DBG(1, "process_data_write: write failed: %s\n",
         signal_caught ? "signal caught" : strerror(errno));
     return SANE_STATUS_IO_ERROR;
   }
   nbytes -= ph->wr_buf_size;
   data += ph->wr_buf_size;
 }

 if ( nbytes > 0 ) /* Something left ? Save it to (empty) write buffer */
 {
   memcpy (ph->wr_ptr, data, nbytes);
   ph->wr_ptr += nbytes;
   ph->wr_left -= nbytes;
 }
 return SANE_STATUS_GOOD;
}

static SANE_Status
process_scanline (PROCDATA_HANDLE *ph, unsigned char *linebuf,
                  int bytes_per_line)

{int out_bytes_per_line = bytes_per_line;
 HpProcessData *procdata;

 if (ph == NULL) return SANE_STATUS_INVAL;
 procdata = &(ph->procdata);

 if ( ph->map )
   hp_data_map (ph->map, bytes_per_line, linebuf);

 if (procdata->bits_per_channel > 8)
 {
   if (procdata->out8)
   {
     hp_scale_to_8bit( bytes_per_line/2, linebuf,
                       procdata->bits_per_channel,
                       procdata->invert);
     out_bytes_per_line /= 2;
   }
   else
   {
     hp_scale_to_16bit( bytes_per_line/2, linebuf,
                        procdata->bits_per_channel,
                        procdata->invert);
   }
 } else if (procdata->invert) {
   hp_soft_invert(bytes_per_line,linebuf);
 }

 if ( ph->image_buf )
 {
   DBG(5, "process_scanline: save in memory\n");

   if (    ph->image_ptr+out_bytes_per_line-1
        <= ph->image_buf+ph->image_buf_size-1 )
   {
     memcpy(ph->image_ptr, linebuf, out_bytes_per_line);
     ph->image_ptr += out_bytes_per_line;
   }
   else
   {
     DBG(1, "process_scanline: would exceed image buffer\n");
   }
 }
 else /* Save scanlines in a bigger buffer. */
 {    /* Otherwise we will get performance problems */

   RETURN_IF_FAIL ( process_data_write (ph, linebuf, out_bytes_per_line) );
 }
 return SANE_STATUS_GOOD;
}


static SANE_Status
process_data (PROCDATA_HANDLE *ph, unsigned char *read_ptr, int nread)

{int bytes_left;
 HpProcessData *procdata;

 if (nread <= 0) return SANE_STATUS_GOOD;

 if (ph == NULL) return SANE_STATUS_INVAL;

 procdata = &(ph->procdata);
 if ( ph->tmp_buf_len > 0 )  /* Something left ? */
 {
   bytes_left = ph->tmp_buf_size - ph->tmp_buf_len;
   if (nread < bytes_left)  /* All to buffer ? */
   {
     memcpy (ph->tmp_buf+ph->tmp_buf_len, read_ptr, nread);
     ph->tmp_buf_len += nread;
     return SANE_STATUS_GOOD;
   }
   memcpy (ph->tmp_buf+ph->tmp_buf_len, read_ptr, bytes_left);
   read_ptr += bytes_left;
   nread -= bytes_left;
   RETURN_IF_FAIL ( process_scanline (ph, ph->tmp_buf, ph->tmp_buf_size) );
   ph->tmp_buf_len = 0;
 }
 while (nread > 0)
 {
   if (nread >= ph->tmp_buf_size)
   {
     RETURN_IF_FAIL ( process_scanline (ph, read_ptr, ph->tmp_buf_size) );
     read_ptr += ph->tmp_buf_size;
     nread -= ph->tmp_buf_size;
   }
   else
   {
     memcpy (ph->tmp_buf, read_ptr, nread);
     ph->tmp_buf_len = nread;
     nread = 0;
   }
 }
 return SANE_STATUS_GOOD;
}


static SANE_Status
process_data_flush (PROCDATA_HANDLE *ph)

{SANE_Status status = SANE_STATUS_GOOD;
 HpProcessData *procdata;
 unsigned char *image_data;
 size_t image_len;
 int num_lines, bytes_per_line;
 int nbytes;

 if (ph == NULL) return SANE_STATUS_INVAL;

 if ( ph->tmp_buf_len > 0 )
   process_scanline (ph, ph->tmp_buf, ph->tmp_buf_len);

 if ( ph->wr_left != ph->wr_buf_size ) /* Something in write buffer ? */
 {
   nbytes = ph->wr_buf_size - ph->wr_left;
   if ( signal_caught || (write (ph->outfd, ph->wr_buf, nbytes) != nbytes))
   {
     DBG(1, "process_data_flush: write failed: %s\n",
         signal_caught ? "signal caught" : strerror(errno));
     return SANE_STATUS_IO_ERROR;
   }
   ph->wr_ptr = ph->wr_buf;
   ph->wr_left = ph->wr_buf_size;
 }

 procdata = &(ph->procdata);
 if ( ph->image_buf )
 {
   bytes_per_line = procdata->bytes_per_line;
   if (procdata->out8) bytes_per_line /= 2;
   image_len = (size_t) (ph->image_ptr - ph->image_buf);
   num_lines = ((int)(image_len + bytes_per_line-1)) / bytes_per_line;

   DBG(3, "process_data_finish: write %d bytes from memory...\n",
       (int)image_len);

   if ( procdata->mirror_vertical )
   {
     image_data = ph->image_buf + (num_lines-1) * bytes_per_line;
     while (num_lines > 0 )
     {
       if (   signal_caught
           || (write(ph->outfd, image_data, bytes_per_line) != bytes_per_line))
       {
         DBG(1,"process_data_finish: write from memory failed: %s\n",
             signal_caught ? "signal caught" : strerror(errno));
         status = SANE_STATUS_IO_ERROR;
         break;
       }
       num_lines--;
       image_data -= bytes_per_line;
     }
   }
   else
   {
     image_data = ph->image_buf;
     while (num_lines > 0 )
     {
       if (   signal_caught
           || (write(ph->outfd, image_data, bytes_per_line) != bytes_per_line))
       {
         DBG(1,"process_data_finish: write from memory failed: %s\n",
             signal_caught ? "signal caught" : strerror(errno));
         status = SANE_STATUS_IO_ERROR;
         break;
       }
       num_lines--;
       image_data += bytes_per_line;
     }
   }
 }
 return status;
}


static void
process_data_finish (PROCDATA_HANDLE *ph)

{
 DBG(12, "process_data_finish called\n");

 if (ph == NULL) return;

 if (ph->image_buf != NULL) sanei_hp_free (ph->image_buf);

 sanei_hp_free (ph->tmp_buf);
 sanei_hp_free (ph);
}


SANE_Status
sanei_hp_scsi_pipeout (HpScsi this, int outfd, HpProcessData *procdata)
{
  /* We will catch these signals, and rethrow them after cleaning up,
   * anything not in this list, we will ignore. */
  static int kill_sig[] = {
      SIGHUP, SIGINT, SIGQUIT, SIGILL, SIGPIPE, SIGALRM, SIGTERM,
      SIGUSR1, SIGUSR2, SIGBUS,
#ifdef SIGSTKFLT
      SIGSTKFLT,
#endif
#ifdef SIGIO
      SIGIO,
#else
# ifdef SIGPOLL
      SIGPOLL,
# endif
#endif
#ifdef SIGXCPU
      SIGXCPU,
#endif
#ifdef SIGXFSZ
      SIGXFSZ,
#endif
#ifdef SIGVTALRM
      SIGVTALRM,
#endif
#ifdef SIGPWR
      SIGPWR,
#endif
  };
#define HP_NSIGS (sizeof(kill_sig)/sizeof(kill_sig[0]))
  struct SIGACTION old_handler[HP_NSIGS];
  struct SIGACTION sa;
  sigset_t	old_set, sig_set;
  int		i;
  int           bits_per_channel = procdata->bits_per_channel;

#define HP_PIPEBUF	32768
  SANE_Status	status	= SANE_STATUS_GOOD;
  struct {
      size_t	len;
      void *	id;
      hp_byte_t	cmd[6];
      hp_byte_t	data[HP_PIPEBUF];
  } 	buf[2], *req = NULL;

  int		reqs_completed = 0;
  int		reqs_issued = 0;
  char          *image_buf = 0;
  char          *read_buf = 0;
  const HpDeviceInfo *info;
  const char    *devname = sanei_hp_scsi_devicename (this);
  int           enable_requests = 1;
  int           enable_image_buffering = 0;
  const unsigned char *map = NULL;
  HpConnect     connect;
  PROCDATA_HANDLE *ph = NULL;
  size_t count = procdata->lines * procdata->bytes_per_line;

  RETURN_IF_FAIL( hp_scsi_flush(this) );

  connect = sanei_hp_get_connect (devname);
  info = sanei_hp_device_info_get (devname);

  assert (info);

  if ( info->config_is_up )
  {
    enable_requests = info->config.use_scsi_request;
    enable_image_buffering = info->config.use_image_buffering;
  }
  else
  {
    enable_requests = 0;
  }

  if (connect != HP_CONNECT_SCSI)
    enable_requests = 0;

  /* Currently we can only simulate 8 bits mapping */
  if (bits_per_channel == 8)
    map = hp_get_simulation_map (devname, info);

  sigfillset(&sig_set);
  sigprocmask(SIG_BLOCK, &sig_set, &old_set);

  memset(&sa, 0, sizeof(sa));
  sa.sa_handler = signal_catcher;
  sigfillset(&sa.sa_mask);

  sigemptyset(&sig_set);
  for (i = 0; i < (int)(HP_NSIGS); i++)
    {
      sigaction(kill_sig[i], &sa, &old_handler[i]);
      sigaddset(&sig_set, kill_sig[i]);
    }
  signal_caught = 0;
  sigprocmask(SIG_UNBLOCK, &sig_set, 0);

  /* Wait for front button push ? */
  if ( procdata->startscan )
  {
    for (;;)
    {int val = 0;

       if (signal_caught) goto quit;
       sanei_hp_scl_inquire (this, SCL_FRONT_BUTTON, &val, 0, 0);
       if (val) break;
       usleep ((unsigned long)333*1000); /* Wait 1/3 second */
    }
    status = sanei_hp_scl_startScan (this, procdata->startscan);
    if (status != SANE_STATUS_GOOD )
    {
      DBG(1, "do_read: Error starting scan in reader process\n");
      goto quit;
    }
  }
  ph = process_data_init (procdata, map, outfd, enable_image_buffering);

  if ( ph == NULL )
  {
    DBG(1, "do_read: Error with process_data_init()\n");
    goto quit;
  }

  DBG(1, "do_read: Start reading data from scanner\n");

  if (enable_requests)   /* Issue SCSI-requests ? */
  {
    while (count > 0 || reqs_completed < reqs_issued)
    {
      while (count > 0 && reqs_issued < reqs_completed + 2)
	{
	  req = buf + (reqs_issued++ % 2);

	  req->len = HP_PIPEBUF;
	  if (count < req->len)
	      req->len = count;
	  count -= req->len;

	  req->cmd[0] = 0x08;
	  req->cmd[1] = 0;
	  req->cmd[2] = req->len >> 16;
	  req->cmd[3] = req->len >> 8;
	  req->cmd[4] = req->len;
	  req->cmd[5] = 0;

	  DBG(3, "do_read: entering request to read %lu bytes\n",
	      (unsigned long) req->len);

	  status = sanei_scsi_req_enter(this->fd, req->cmd, 6,
				      req->data, &req->len, &req->id);
	  if (status != SANE_STATUS_GOOD)
	    {
	      DBG(1, "do_read: Error from scsi_req_enter: %s\n",
		  sane_strstatus(status));
	      goto quit;
	    }
	  if (signal_caught)
	      goto quit;
	}

      if (signal_caught)
	  goto quit;

      assert(reqs_completed < reqs_issued);
      req = buf + (reqs_completed++ % 2);

      DBG(3, "do_read: waiting for data\n");
      status = sanei_scsi_req_wait(req->id);
      if (status != SANE_STATUS_GOOD)
	{
	  DBG(1, "do_read: Error from scsi_req_wait: %s\n",
	      sane_strstatus(status));
	  goto quit;
	}
      if (signal_caught)
	  goto quit;

      status = process_data (ph, (unsigned char *)req->data, (int)req->len);
      if ( status != SANE_STATUS_GOOD )
      {
        DBG(1,"do_read: Error in process_data\n");
        goto quit;
      }
    }
  }
  else  /* Read directly */
  {
    read_buf = sanei_hp_alloc ( HP_PIPEBUF );
    if (!read_buf)
    {
      DBG(1, "do_read: not enough memory for read buffer\n");
      goto quit;
    }

    while (count > 0)
    {size_t nread;

      if (signal_caught)
	  goto quit;

      DBG(5, "do_read: %lu bytes left to read\n", (unsigned long)count);

      nread = HP_PIPEBUF;
      if (nread > count) nread = count;

      DBG(3, "do_read: try to read data (%lu bytes)\n", (unsigned long)nread);

      status = hp_scsi_read (this, read_buf, &nread, 0);
      if (status != SANE_STATUS_GOOD)
      {
        DBG(1, "do_read: Error from scsi_read: %s\n",sane_strstatus(status));
        goto quit;
      }

      DBG(3, "do_read: got %lu bytes\n", (unsigned long)nread);

      if (nread <= 0)
      {
        DBG(1, "do_read: Nothing read\n");
        continue;
      }

      status = process_data (ph, (unsigned char *)read_buf, (int)nread);
      if ( status != SANE_STATUS_GOOD )
      {
        DBG(1,"do_read: Error in process_data\n");
        goto quit;
      }
      count -= nread;
    }
  }

  process_data_flush (ph);

quit:

  process_data_finish (ph);

  if ( image_buf ) sanei_hp_free ( image_buf );
  if ( read_buf ) sanei_hp_free ( read_buf );

  if (enable_requests && (reqs_completed < reqs_issued))
    {
      DBG(1, "do_read: cleaning up leftover requests\n");
      while (reqs_completed < reqs_issued)
	{
	  req = buf + (reqs_completed++ % 2);
	  sanei_scsi_req_wait(req->id);
	}
    }

  sigfillset(&sig_set);
  sigprocmask(SIG_BLOCK, &sig_set, 0);
  for (i = 0; i < (int)(HP_NSIGS); i++)
      sigaction(kill_sig[i], &old_handler[i], 0);
  sigprocmask(SIG_SETMASK, &old_set, 0);

  if (signal_caught)
    {
      DBG(1, "do_read: caught signal %d\n", signal_caught);
      raise(signal_caught);
      return SANE_STATUS_CANCELLED;
    }

  return status;
}



/*
 *
 */

static SANE_Status
_hp_scl_inq (HpScsi scsi, HpScl scl, HpScl inq_cmnd,
	     void *valp, size_t *lengthp)
{
  size_t	bufsize	= 16 + (lengthp ? *lengthp: 0);
  char *	buf	= alloca(bufsize);
  char		expect[16], expect_char;
  int		val, count;
  SANE_Status	status;

  if (!buf)
      return SANE_STATUS_NO_MEM;

  /* Flush data before sending inquiry. */
  /* Otherwise scanner might not generate a response. */
  RETURN_IF_FAIL( hp_scsi_flush (scsi)) ;

  RETURN_IF_FAIL( hp_scsi_scl(scsi, inq_cmnd, SCL_INQ_ID(scl)) );

  status =  hp_scsi_read(scsi, buf, &bufsize, 1);
  if (FAILED(status))
    {
      DBG(1, "scl_inq: read failed (%s)\n", sane_strstatus(status));
      return status;
    }

  if (SCL_PARAM_CHAR(inq_cmnd) == 'R')
      expect_char = 'p';
  else
      expect_char = tolower(SCL_PARAM_CHAR(inq_cmnd) - 1);

  count = sprintf(expect, "\033*s%d%c", SCL_INQ_ID(scl), expect_char);
  if (memcmp(buf, expect, count) != 0)
    {
      DBG(1, "scl_inq: malformed response: expected '%s', got '%.*s'\n",
	  expect, count, buf);
      return SANE_STATUS_IO_ERROR;
    }
  buf += count;

  if (buf[0] == 'N')
    {				/* null response */
      DBG(3, "scl_inq: parameter %d unsupported\n", SCL_INQ_ID(scl));
      return SANE_STATUS_UNSUPPORTED;
    }

  if (sscanf(buf, "%d%n", &val, &count) != 1)
    {
      DBG(1, "scl_inq: malformed response: expected int, got '%.8s'\n", buf);
      return SANE_STATUS_IO_ERROR;
    }
  buf += count;

  expect_char = lengthp ? 'W' : 'V';
  if (*buf++ != expect_char)
    {
      DBG(1, "scl_inq: malformed response: expected '%c', got '%.4s'\n",
	  expect_char, buf - 1);
      return SANE_STATUS_IO_ERROR;
    }

  if (!lengthp)
      *(int *)valp = val; /* Get integer value */
  else
    {
      if (val > (int)*lengthp)
	{
	  DBG(1, "scl_inq: inquiry returned %d bytes, expected <= %lu\n",
	      val, (unsigned long) *lengthp);
	  return SANE_STATUS_IO_ERROR;
	}
      *lengthp = val;
      memcpy(valp, buf , *lengthp); /* Get binary data */
    }

  return SANE_STATUS_GOOD;
}


SANE_Status
sanei_hp_scl_upload_binary (HpScsi scsi, HpScl scl, size_t *lengthhp,
                            char **bufhp)
{
  size_t	bufsize	= 16, sv;
  char *	buf	= alloca(bufsize);
  char *        bufstart = buf;
  char *        hpdata;
  char		expect[16], expect_char;
  int		n, val, count;
  SANE_Status	status;

  if (!buf)
      return SANE_STATUS_NO_MEM;

  assert ( IS_SCL_DATA_TYPE (scl) );

  /* Flush data before sending inquiry. */
  /* Otherwise scanner might not generate a response. */
  RETURN_IF_FAIL( hp_scsi_flush (scsi)) ;

  RETURN_IF_FAIL( hp_scsi_scl(scsi, SCL_UPLOAD_BINARY_DATA, SCL_INQ_ID(scl)) );

  status =  hp_scsi_read(scsi, buf, &bufsize, 0);
  if (FAILED(status))
    {
      DBG(1, "scl_upload_binary: read failed (%s)\n", sane_strstatus(status));
      return status;
    }

  expect_char = 't';
  count = sprintf(expect, "\033*s%d%c", SCL_INQ_ID(scl), expect_char);
  if (memcmp(buf, expect, count) != 0)
    {
      DBG(1, "scl_upload_binary: malformed response: expected '%s', got '%.*s'\n",
	  expect, count, buf);
      return SANE_STATUS_IO_ERROR;
    }
  buf += count;

  if (buf[0] == 'N')
    {				/* null response */
      DBG(1, "scl_upload_binary: parameter %d unsupported\n", SCL_INQ_ID(scl));
      return SANE_STATUS_UNSUPPORTED;
    }

  if (sscanf(buf, "%d%n", &val, &count) != 1)
    {
      DBG(1, "scl_inq: malformed response: expected int, got '%.8s'\n", buf);
      return SANE_STATUS_IO_ERROR;
    }
  buf += count;

  expect_char = 'W';
  if (*buf++ != expect_char)
    {
      DBG(1, "scl_inq: malformed response: expected '%c', got '%.4s'\n",
	  expect_char, buf - 1);
      return SANE_STATUS_IO_ERROR;
    }

  *lengthhp = val;
  *bufhp = hpdata = sanei_hp_alloc ( val );
  if (!hpdata)
      return SANE_STATUS_NO_MEM;

  if (buf < bufstart + bufsize)
    {
       n = bufsize - (buf - bufstart);
       if (n > val) n = val;
       memcpy (hpdata, buf, n);
       hpdata += n;
       val -= n;
    }

  status = SANE_STATUS_GOOD;
  if ( val > 0 )
    {
      sv = val;
      status = hp_scsi_read(scsi, hpdata, &sv, 0);
      if (status != SANE_STATUS_GOOD)
        sanei_hp_free ( *bufhp );
    }

  return status;
}


SANE_Status
sanei_hp_scl_set(HpScsi scsi, HpScl scl, int val)
{
  RETURN_IF_FAIL( hp_scsi_scl(scsi, scl, val) );


#ifdef PARANOID
  RETURN_IF_FAIL( sanei_hp_scl_errcheck(scsi) );
#endif

  return SANE_STATUS_GOOD;
}

SANE_Status
sanei_hp_scl_inquire(HpScsi scsi, HpScl scl, int * valp, int * minp, int * maxp)
{
  HpScl	inquiry = ( IS_SCL_CONTROL(scl)
		    ? SCL_INQUIRE_PRESENT_VALUE
		    : SCL_INQUIRE_DEVICE_PARAMETER );

  assert(IS_SCL_CONTROL(scl) || IS_SCL_PARAMETER(scl));
  assert(IS_SCL_CONTROL(scl) || (!minp && !maxp));

  if (valp)
      RETURN_IF_FAIL( _hp_scl_inq(scsi, scl, inquiry, valp, 0) );
  if (minp)
      RETURN_IF_FAIL( _hp_scl_inq(scsi, scl,
				  SCL_INQUIRE_MINIMUM_VALUE, minp, 0) );
  if (maxp)
      RETURN_IF_FAIL( _hp_scl_inq(scsi, scl,
				  SCL_INQUIRE_MAXIMUM_VALUE, maxp, 0) );
  return SANE_STATUS_GOOD;
}

#ifdef _HP_NOT_USED
static SANE_Status
hp_scl_get_bounds(HpScsi scsi, HpScl scl, int * minp, int * maxp)
{
  assert(IS_SCL_CONTROL(scl));
  RETURN_IF_FAIL( _hp_scl_inq(scsi, scl, SCL_INQUIRE_MINIMUM_VALUE, minp, 0) );
  return _hp_scl_inq(scsi, scl, SCL_INQUIRE_MAXIMUM_VALUE, maxp, 0);
}
#endif

#ifdef _HP_NOT_USED
static SANE_Status
hp_scl_get_bounds_and_val(HpScsi scsi, HpScl scl,
			  int * minp, int * maxp, int * valp)
{
  assert(IS_SCL_CONTROL(scl));
  RETURN_IF_FAIL( _hp_scl_inq(scsi, scl, SCL_INQUIRE_MINIMUM_VALUE, minp, 0) );
  RETURN_IF_FAIL( _hp_scl_inq(scsi, scl, SCL_INQUIRE_MAXIMUM_VALUE, maxp, 0) );
  return    _hp_scl_inq(scsi, scl, SCL_INQUIRE_PRESENT_VALUE, valp, 0);
}
#endif

SANE_Status
sanei_hp_scl_download(HpScsi scsi, HpScl scl, const void * valp, size_t len)
{
  assert(IS_SCL_DATA_TYPE(scl));

  sanei_hp_scl_clearErrors ( scsi );
  RETURN_IF_FAIL( hp_scsi_need(scsi, 16) );
  RETURN_IF_FAIL( hp_scsi_scl(scsi, SCL_DOWNLOAD_TYPE, SCL_INQ_ID(scl)) );
                            /* Download type not supported ? */
  RETURN_IF_FAIL( sanei_hp_scl_errcheck(scsi) );
  RETURN_IF_FAIL( hp_scsi_scl(scsi, SCL_DOWNLOAD_LENGTH, len) );
  RETURN_IF_FAIL( hp_scsi_write(scsi, valp, len) );

#ifdef PARANOID
  RETURN_IF_FAIL( sanei_hp_scl_errcheck(scsi) );
#endif

  return SANE_STATUS_GOOD;
}

SANE_Status
sanei_hp_scl_upload(HpScsi scsi, HpScl scl, void * valp, size_t len)
{
  size_t	nread = len;
  HpScl		inquiry = ( IS_SCL_DATA_TYPE(scl)
			    ? SCL_UPLOAD_BINARY_DATA
			    : SCL_INQUIRE_DEVICE_PARAMETER );

  assert(IS_SCL_DATA_TYPE(scl) || IS_SCL_PARAMETER(scl));

  RETURN_IF_FAIL( _hp_scl_inq(scsi, scl, inquiry, valp, &nread) );
  if (IS_SCL_PARAMETER(scl) && nread < len)
      ((char *)valp)[nread] = '\0';
  else if (len != nread)
    {
      DBG(1, "scl_upload: requested %lu bytes, got %lu\n",
	  (unsigned long) len, (unsigned long) nread);
      return SANE_STATUS_IO_ERROR;
    }
  return SANE_STATUS_GOOD;
}

SANE_Status
sanei_hp_scl_calibrate(HpScsi scsi)
{
  RETURN_IF_FAIL( hp_scsi_scl(scsi, SCL_CALIBRATE, 0) );
  return hp_scsi_flush(scsi);
}

SANE_Status
sanei_hp_scl_startScan(HpScsi scsi, HpScl scl)
{
  char *msg = "";

  if (scl == SCL_ADF_SCAN) msg = " (ADF)";
  else if (scl == SCL_XPA_SCAN) msg = " (XPA)";
  else scl = SCL_START_SCAN;

  DBG(1, "sanei_hp_scl_startScan: Start scan%s\n", msg);

  /* For active XPA we must not use XPA scan */
  if ((scl == SCL_XPA_SCAN) && sanei_hp_is_active_xpa (scsi))
  {
    DBG(3,"Map XPA scan to scan because of active XPA\n");
    scl = SCL_START_SCAN;
  }

  RETURN_IF_FAIL( hp_scsi_scl(scsi, scl, 0) );
  return hp_scsi_flush(scsi);
}

SANE_Status
sanei_hp_scl_reset(HpScsi scsi)
{
  RETURN_IF_FAIL( hp_scsi_write(scsi, "\033E", 2) );
  RETURN_IF_FAIL( hp_scsi_flush(scsi) );
  return sanei_hp_scl_errcheck(scsi);
}

SANE_Status
sanei_hp_scl_clearErrors(HpScsi scsi)
{
  RETURN_IF_FAIL( hp_scsi_flush(scsi) );
  RETURN_IF_FAIL( hp_scsi_write(scsi, "\033*oE", 4) );
  return hp_scsi_flush(scsi);
}

static const char *
hp_scl_strerror (int errnum)
{
  static const char * errlist[] = {
      "Command Format Error",
      "Unrecognized Command",
      "Parameter Error",
      "Illegal Window",
      "Scaling Error",
      "Dither ID Error",
      "Tone Map ID Error",
      "Lamp Error",
      "Matrix ID Error",
      "Cal Strip Param Error",
      "Gross Calibration Error"
  };

  if (errnum >= 0 && errnum < (int)(sizeof(errlist)/sizeof(errlist[0])))
      return errlist[errnum];
  else
      switch(errnum) {
      case 1024: return "ADF Paper Jam";
      case 1025: return "Home Position Missing";
      case 1026: return "Paper Not Loaded";
      default: return "??Unkown Error??";
      }
}

/* Check for SCL errors */
SANE_Status
sanei_hp_scl_errcheck (HpScsi scsi)
{
  int		errnum;
  int		nerrors;
  SANE_Status	status;

  status = sanei_hp_scl_inquire(scsi, SCL_CURRENT_ERROR_STACK, &nerrors,0,0);
  if (!FAILED(status) && nerrors)
      status = sanei_hp_scl_inquire(scsi, SCL_OLDEST_ERROR, &errnum,0,0);
  if (FAILED(status))
    {
      DBG(1, "scl_errcheck: Can't read SCL error stack: %s\n",
	  sane_strstatus(status));
      return SANE_STATUS_IO_ERROR;
    }

  if (nerrors)
    {
      DBG(1, "Scanner issued SCL error: (%d) %s\n",
	  errnum, hp_scl_strerror(errnum));

      sanei_hp_scl_clearErrors (scsi);
      return SANE_STATUS_IO_ERROR;
    }

  return SANE_STATUS_GOOD;
}
