/* sane - Scanner Access Now Easy.

   Copyright (C) 2000-2001 Kazuya Fukuda, based on sharp.c, which is 
   based on canon.c.

   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 implements a SANE backend for NEC flatbed scanners.  */

/*
   Version 0.12
   - Remove references to sharp backend (grep for "JX").
   - Check for HAVE_SYS_SHM_H before including sys/shm.h and
     disable shared memory support if necessary.
   - free devlist allocated in sane_get_devices() in sane_exit()
   - resolution setting bug fixed(PC-IN500/4C 10dpi step)
   - remove resolution list
   Version 0.11
   - get_data_buffer_status is not called in sane_get_parameter and 
     sane_read_direct, sane_read_shuffled.
   - change some #include <> to ""
   Version 0.10
   - First release!
   - suppoted scanner
     PC-IN500/4C                    available
     MultiReder 300U/300S series    not available
     MultiReder 600U/600S series    not available
     MultiReader PetiScan series    not available
*/
#include "../include/sane/config.h"

#include <limits.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>

#include "../include/sane/sane.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_scsi.h"

/* QUEUEDEBUG should be undefined unless you want to play
   with the sanei_scsi.c under Linux and/or with the Linux's SG driver,
   or your suspect problems with command queueing
*/
#define QUEUEDEBUG
/*#define DEBUG*/
#ifdef DEBUG
#include <unistd.h>
#include <sys/time.h>
#endif

/* USE_FORK: fork a special reader process
   disable shared memory support.
*/
#if 0
#ifdef HAVE_SYS_SHM_H
#define USE_FORK
#endif
#endif

#ifdef USE_FORK
#include <signal.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <sys/ipc.h>
#include <sys/shm.h>

#endif /* USE_FORK */

#ifndef USE_CUSTOM_GAMMA
#define USE_CUSTOM_GAMMA
#endif
#ifndef USE_COLOR_THRESHOLD
#define USE_COLOR_THRESHOLD
#endif
/* enable a short list of some standard resolutions. XSane provides 
   its own resolution list; therefore its is generally not reasonable
   to enable this list, if you mainly using XSane. But it might be handy
   if you are working with xscanimage
*/
/* #define USE_RESOLUTION_LIST */ 

#define BACKEND_NAME nec
#include "../include/sane/sanei_backend.h"

#ifndef PATH_MAX
#define PATH_MAX	1024
#endif

#define DEFAULT_MUD_1200 1200

#define PIX_TO_MM(x, mud) ((x) * 25.4 / mud)
#define MM_TO_PIX(x, mud) ((x) * mud / 25.4)

#include "../include/sane/sanei_config.h"
#define NEC_CONFIG_FILE "nec.conf"

#include "nec.h"

static int num_devices = 0;
static NEC_Device *first_dev = NULL;
static NEC_Scanner *first_handle = NULL;
static const SANE_Device **devlist = 0;

typedef enum
  {
    MODES_LINEART  = 0,
    MODES_GRAY,
    MODES_COLOR,
    MODES_LINEART_COLOR
  }
Modes;

#define M_LINEART            SANE_VALUE_SCAN_MODE_LINEART
#define M_GRAY               SANE_VALUE_SCAN_MODE_GRAY
#define M_LINEART_COLOR      "Lineart Color"
#define M_COLOR              SANE_VALUE_SCAN_MODE_COLOR
static const SANE_String_Const mode_list[] =
{
#if 0
  M_LINEART, M_GRAY, M_LINEART_COLOR, M_COLOR,
#endif
  M_LINEART, M_GRAY, M_COLOR,
  0
};

#define M_BILEVEL        "none"
#define M_BAYER          "Dither Bayer"
#define M_SPIRAL         "Dither Spiral"
#define M_DISPERSED      "Dither Dispersed"
#define M_ERRDIFFUSION   "Error Diffusion"

#define M_DITHER1        "Dither 1"
#define M_DITHER2        "Dither 2"
#define M_DITHER3        "Dither 3"
#define M_DITHERUSER     "User defined"

static const SANE_String_Const halftone_list[] =
{
  M_BILEVEL, M_DITHER1, M_DITHER2, M_DITHER3,
  0
};

#define LIGHT_GREEN "green"
#define LIGHT_RED   "red"
#define LIGHT_BLUE  "blue"
#define LIGHT_NONE  "none"
#define LIGHT_WHITE "white"

static const SANE_String_Const light_color_list[] =
{
  LIGHT_GREEN, LIGHT_RED, LIGHT_BLUE, LIGHT_NONE,
  0
};

/* possible values for ADF/FSU selection */
static SANE_String use_adf = "Automatic Document Feeder";
static SANE_String use_fsu = "Transparency Adapter";
static SANE_String use_simple = "Flatbed";

#define HAVE_FSU 1
#define HAVE_ADF 2

/* The follow #defines are used in NEC_Scanner.adf_fsu_mode 
   and as indexes for the arrays x_ranges, y_ranges in NEC_Device 
*/
#define SCAN_SIMPLE 0
#define SCAN_WITH_FSU 1
#define SCAN_WITH_ADF 2

#define LOAD_PAPER 1
#define UNLOAD_PAPER 0

#define PAPER_MAX  10
#define W_LETTER "11\"x17\""
#define INVOICE  "8.5\"x5.5\""
static const SANE_String_Const paper_list_pcinxxx[] =
{
  "A3", "A4", "A5", "A6", "B4", "B5",
  W_LETTER, "Legal", "Letter", INVOICE,
  0
};

static const SANE_String_Const paper_list_pcin500[] =
{
  "A4", "A5", "A6", "B5",
  0
};


#define CRT1    "CRT1"
#define CRT2    "CRT2"
#define PRINTER1 "PRINTER1"
#define PRINTER2 "PRINTER2"
#define NONE    "NONE"
/* #define CUSTOM  "CUSTOM" */
static const SANE_String_Const gamma_list[] =
{
  CRT1, CRT2, PRINTER1, PRINTER2, NONE,
  0
};

#if 0
#define SPEED_NORMAL    "Normal"
#define SPEED_FAST      "Fast"
static const SANE_String_Const speed_list[] =
{
  SPEED_NORMAL, SPEED_FAST,
  0
};
#endif

#ifdef USE_RESOLUTION_LIST
#define RESOLUTION_MAX_PCINXXX 8
static const SANE_String_Const resolution_list_pcinxxx[] =
{
  "50", "75", "100", "150", "200", "300", "400", "600", "Select",
  0
};

#define RESOLUTION_MAX_PCIN500 8
static const SANE_String_Const resolution_list_pcin500[] =
{
  "50", "75", "100", "150", "200", "300", "400", "480", "Select",
  0
};
#endif

#define EDGE_NONE    "None"
#define EDGE_MIDDLE  "Middle"
#define EDGE_STRONG  "Strong"
#define EDGE_BLUR    "Blur"
static const SANE_String_Const edge_emphasis_list[] =
{
  EDGE_NONE, EDGE_MIDDLE, EDGE_STRONG, EDGE_BLUR,
  0
};

#ifdef USE_CUSTOM_GAMMA
static const SANE_Range u8_range =
  {
      0,				/* minimum */
    255,				/* maximum */
      0				/* quantization */
  };
#endif

static SANE_Status
sense_handler(int fd, u_char *sense_buffer, void *ss)
{
  int sense_key;
  NEC_Sense_Data *sdat = (NEC_Sense_Data *) ss;
  
  fd = fd; /* silence compilation warnings */

  #define add_sense_code sense_buffer[12]
  #define add_sense_qual sense_buffer[13]

  memcpy(sdat->sb, sense_buffer, 16);
  
  DBG(10, "sense code: %02x %02x %02x %02x %02x %02x %02x %02x "
          "%02x %02x %02x %02x %02x %02x %02x %02x\n",
          sense_buffer[0], sense_buffer[1], sense_buffer[2], sense_buffer[3], 
          sense_buffer[4], sense_buffer[5], sense_buffer[6], sense_buffer[7], 
          sense_buffer[8], sense_buffer[9], sense_buffer[10], sense_buffer[11], 
          sense_buffer[12], sense_buffer[13], sense_buffer[14], sense_buffer[15]);

  sense_key = sense_buffer[1] & 0x0F;
  /* do we have additional information ? */
  if (sense_buffer[7] >= 5)
    {
      if (sdat->model == PCIN500)
        {
          switch (sense_key)
            {
              case 0x02: /* not ready */
                switch (add_sense_code)
                  {
                    case 0x80:
                      switch (add_sense_qual & 0xf0)
                        {
                          case 0x10:
                            DBG(1, "Scanner not ready: memory error\n");
                            return SANE_STATUS_IO_ERROR;
                          case 0x20:
                            DBG(1, "Scanner not ready: hardware error\n");
                            return SANE_STATUS_IO_ERROR;
                          case 0x30:
                            DBG(1, "Scanner not ready: optical error\n");
                            return SANE_STATUS_IO_ERROR;
                          case 0x40:
                            DBG(1, "Scanner not ready: optical error\n");
                            return SANE_STATUS_IO_ERROR;
                          case 0x50:
                            DBG(1, "Scanner not ready: marker error\n");
                            return SANE_STATUS_IO_ERROR;
                          case 0x60:
                            DBG(1, "Scanner not ready: mechanical error\n");
                            return SANE_STATUS_IO_ERROR;
                          case 0x70:
                            DBG(1, "Scanner not ready: hardware error\n");
                            return SANE_STATUS_IO_ERROR;
                          case 0x80:
                            DBG(1, "Scanner not ready: hardware error\n");
                            return SANE_STATUS_IO_ERROR;
                          case 0x90:
                          default:
                            DBG(5, "Scanner not ready: undocumented reason\n");
                            return SANE_STATUS_IO_ERROR;
                        }
                  }
              case 0x03: /* medium error */
		DBG(5, "medium error: undocumented reason\n");
		return SANE_STATUS_IO_ERROR;
              case 0x04: /* hardware error */
		DBG(1, "general hardware error\n");
		return SANE_STATUS_IO_ERROR;
              case 0x05: /* illegal request */
                DBG(10, "error: illegal request\n");
                return SANE_STATUS_IO_ERROR;
              case 0x06: /* unit attention */
		DBG(5, "unit attention: exact reason not documented\n");
		return SANE_STATUS_IO_ERROR;
              case 0x0B: /* data remains */
                DBG(5, "error: aborted command\n");
                return SANE_STATUS_IO_ERROR;
              default:
                DBG(5, "error: sense code not documented\n");
                return SANE_STATUS_IO_ERROR;
            }
        }
    }
  return SANE_STATUS_IO_ERROR;
}

static SANE_Status
test_unit_ready (int fd)
{
  static u_char cmd[] = {TEST_UNIT_READY, 0, 0, 0, 0, 0};
  SANE_Status status;
  DBG (11, "<< test_unit_ready ");

  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), 0, 0);

  DBG (11, ">>\n");
  return (status);
}

#if 0
static SANE_Status
request_sense (int fd, void *sense_buf, size_t *sense_size)
{
  static u_char cmd[] = {REQUEST_SENSE, 0, 0, 0, SENSE_LEN, 0};
  SANE_Status status;
  DBG (11, "<< request_sense ");

  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), sense_buf, sense_size);

  DBG (11, ">>\n");
  return (status);
}
#endif

static SANE_Status
inquiry (int fd, void *inq_buf, size_t *inq_size)
{
  static u_char cmd[] = {INQUIRY, 0, 0, 0, INQUIRY_LEN, 0};
  SANE_Status status;
  DBG (11, "<< inquiry ");

  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), inq_buf, inq_size);

  DBG (11, ">>\n");
  return (status);
}

static SANE_Status
mode_select_mud (int fd, int mud)
{
  static u_char cmd[6 + MODEPARAM_LEN] = 
  {MODE_SELECT6, 0x10, 0, 0, MODEPARAM_LEN, 0};
  mode_select_param *mp;
  SANE_Status status;
  DBG (11, "<< mode_select_mud ");

  mp = (mode_select_param *)(cmd + 6);
  memset (mp, 0, MODEPARAM_LEN);
  mp->mode_param_header1 = 11;
  mp->page_code = 3;
  mp->page_length = 6;
  mp->mud[0] = mud >> 8;
  mp->mud[1] = mud & 0xFF;

  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), 0, 0);

  DBG (11, ">>\n");
  return (status);
}

#if 0
static SANE_Status
mode_select_adf_fsu (int fd, int mode)
{
  static u_char cmd[6 + MODE_SUBDEV_LEN] = 
                        {MODE_SELECT6, 0x10, 0, 0, MODE_SUBDEV_LEN, 0};
  mode_select_subdevice *mp;
  SANE_Status status;
  DBG (11, "<< mode_select_adf_fsu ");

  mp = (mode_select_subdevice *)(cmd + 6);
  memset (mp, 0, MODE_SUBDEV_LEN);
  mp->page_code = 0x20;
  mp->page_length = 26;
  switch (mode)
    {
      case SCAN_SIMPLE:
        mp->a_mode = 0x40;
        mp->f_mode = 0x40;
        break;
      case SCAN_WITH_FSU:
        mp->a_mode = 0;
        mp->f_mode = 0x40;
        break;
      case SCAN_WITH_ADF:
        mp->a_mode = 0x40;
        mp->f_mode = 0;
        break;
    }

  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), 0, 0);

  DBG (11, ">>\n");
  return (status);
}
#endif

static SANE_Status wait_ready(int fd);

static SANE_Status
mode_sense (int fd, void *modeparam_buf, size_t * modeparam_size, 
            int page)
{
  static u_char cmd[6] = {MODE_SENSE6, 0, 0, 0, 0, 0};
  SANE_Status status;
  DBG (11, "<< mode_sense ");
  cmd[0] = 0x1a;
  cmd[2] = page;
  cmd[4] = *modeparam_size;
  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), modeparam_buf, 
			   modeparam_size);

  DBG (11, ">>\n");
  return (status);
}

static SANE_Status
scan (int fd)
{
  static u_char cmd[] = {SCAN, 0, 0, 0, 0, 0};
  SANE_Status status;
  DBG (11, "<< scan ");

  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), 0, 0);

  DBG (11, ">>\n");
  return (status);
}

#if 0
static SANE_Status
send_diagnostics (int fd)
{
  static u_char cmd[] = {SEND_DIAGNOSTIC, 0x04, 0, 0, 0, 0};
  SANE_Status status;
  DBG (11, "<< send_diagnostics ");

  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), 0, 0);

  DBG (11, ">>\n");
  return (status);
}
#endif

static SANE_Status
set_window (int fd, window_param *wp, int len)
{
  static u_char cmd[10 + WINDOW_LEN] = 
                        {SET_WINDOW, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  window_param *winp;
  SANE_Status status;
  DBG (11, "<< set_window ");

  cmd[8] = len;
  winp = (window_param *)(cmd + 10);
  memset (winp, 0, WINDOW_LEN);
  memcpy (winp, wp, len);
  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), 0, 0);

  DBG (11, ">>\n");
  return (status);

}

static SANE_Status
get_window (int fd, void *buf, size_t * buf_size)
{
  static u_char cmd[10] = {GET_WINDOW, 0, 0, 0, 0, 0, 0, 0, WINDOW_LEN, 0};
  SANE_Status status;
  DBG (11, "<< get_window ");

  cmd[8] = *buf_size;
  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), buf, buf_size);

  DBG (11, ">>\n");
  return (status);
}

#if 0
static SANE_Status
get_data_buffer_status (int fd, void *buf, size_t *buf_size)
{
  static u_char cmd[10] = 
                        {GET_DATA_BUFFER_STATUS, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  SANE_Status status;
  DBG (11, "<< get_data_buffer_status ");

  cmd[8] = *buf_size;
  status = sanei_scsi_cmd (fd, cmd, sizeof (cmd), buf, buf_size);

  DBG (11, ">>\n");
  return (status);
}
#endif

#ifdef USE_FORK

/* the following four functions serve simply the purpose
   to avoid "over-optimised" code when reader_process and 
   read_data wait for the buffer to become ready. The simple 
   while-loops in these functions which check the buffer 
   status may be optimised so that the machine code only 
   operates with registers instead of using the variable 
   values stored in memory. (This is only a workaround - 
   it would be better to set a compiler pragma, which ensures
   that the program looks into the RAM in these while loops --
   but unfortunately I could not find appropriate information
   about this at least for gcc, not to speak about other 
   compilers... 
   Abel)
*/

static int
cancel_requested(NEC_Scanner *s)
{
  return s->rdr_ctl->cancel;
}

static SANE_Status 
rdr_status(NEC_Scanner *s)
{
  return s->rdr_ctl->status;
}

static int
buf_status(NEC_shmem_ctl *s)
{
  return s->shm_status;
}

static int
reader_running(NEC_Scanner *s)
{
  return s->rdr_ctl->running;
}

static int
reader_process(NEC_Scanner *s)
{
  SANE_Status status;
  sigset_t sigterm_set;
  static u_char cmd[] = {READ, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  int full_count = 0, counted;
  size_t waitindex, cmdindex;
  size_t bytes_to_queue;
  size_t nread;
  size_t max_bytes_per_read;
  int max_queue;
  int i;
  NEC_shmem_ctl *bc;

  s->rdr_ctl->running = 1;
  DBG(11, "<< reader_process\n");

  sigemptyset (&sigterm_set);
  
  bytes_to_queue = s->bytes_to_read;
  
  max_bytes_per_read = s->dev->info.bufsize / s->params.bytes_per_line;
  if (max_bytes_per_read)
    max_bytes_per_read *= s->params.bytes_per_line;
  else
    /* this is a really tiny buffer..*/
    max_bytes_per_read = s->dev->info.bufsize;
  
  /*  wait_ready(s->fd); */
  
  if (s->dev->info.queued_reads <= s->dev->info.buffers)
    max_queue = s->dev->info.queued_reads;
  else
    max_queue = s->dev->info.buffers;
  for (i = 0; i < max_queue; i++)
    {
      bc = &s->rdr_ctl->buf_ctl[i];
      if (bytes_to_queue)
        {
          nread = bytes_to_queue;
          if (nread > max_bytes_per_read)
            nread = max_bytes_per_read;
          bc->used = nread;
          cmd[6] = nread >> 16;
          cmd[7] = nread >> 8;
          cmd[8] = nread;
#ifdef QUEUEDEBUG
          DBG(2, "reader: req_enter...\n");
#endif
          status = sanei_scsi_req_enter (s->fd, cmd, sizeof (cmd), 
                     bc->buffer, 
                    &bc->used, 
                    &bc->qid);
#ifdef QUEUEDEBUG
          DBG(2, "reader: req_enter ok\n");
#endif
          if (status != SANE_STATUS_GOOD)
            {
              DBG(1, "reader_process: read command failed: %s", 
                  sane_strstatus(status));
#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
              sanei_scsi_req_flush_all_extended(s->fd);
#else
               sanei_scsi_req_flush_all();
#endif
              s->rdr_ctl->status = status;
              s->rdr_ctl->running = 0;
              return 2;
            }
          bc->shm_status = SHM_BUSY;
          bc->nreq = bc->used;
          bytes_to_queue -= bc->nreq;
        }
      else
        {
          bc->used = 0;
          bc->shm_status = SHM_EMPTY;
        }
    }
  waitindex = 0;
  cmdindex = i % s->dev->info.buffers;

  while(s->bytes_to_read > 0) 
    {
      if (cancel_requested(s)) 
        {
#ifdef QUEUEDEBUG
          DBG(2, "reader: flushing requests...\n");
#endif
#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
          sanei_scsi_req_flush_all_extended(s->fd);
#else
          sanei_scsi_req_flush_all();
#endif
#ifdef QUEUEDEBUG
          DBG(2, "reader: flushing requests ok\n");
#endif
          s->rdr_ctl->cancel = 0;
          s->rdr_ctl->status = SANE_STATUS_CANCELLED;
          s->rdr_ctl->running = 0;
          DBG(11, " reader_process (cancelled) >>\n");
          return 1;
        }

      bc = &s->rdr_ctl->buf_ctl[waitindex];
      if (bc->shm_status == SHM_BUSY) 
        {
#ifdef DEBUG
          {
            struct timeval t;
            gettimeofday(&t, 0);
            DBG(2, "rd: waiting for data %li.%06li\n", t.tv_sec, t.tv_usec);
          }
#endif
#ifdef QUEUEDEBUG
          DBG(2, "reader: req_wait...\n");
#endif
          status = sanei_scsi_req_wait(bc->qid);
#ifdef QUEUEDEBUG
          DBG(2, "reader: req_wait ok\n");
#endif
#ifdef DEBUG
          {
            struct timeval t;
            gettimeofday(&t, 0);
            DBG(2, "rd: data received    %li.%06li\n", t.tv_sec, t.tv_usec);
          }
#endif
          if (status != SANE_STATUS_GOOD)
            {
              DBG(1, "reader_process: read command failed: %s", 
                  sane_strstatus(status));
#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
              sanei_scsi_req_flush_all_extended(s->fd);
#else
              sanei_scsi_req_flush_all();
#endif
              s->rdr_ctl->status = status;
              s->rdr_ctl->running = 0;
              return 2;
            }
          s->bytes_to_read -= bc->used;
          bytes_to_queue += bc->nreq - bc->used;
          bc->start = 0;
          bc->shm_status = SHM_FULL;

          waitindex++;
          if (waitindex == s->dev->info.buffers)
            waitindex = 0;

        }
      
      if (bytes_to_queue)
        {
          /* wait until the next buffer is completely read via read_data */
          bc = &s->rdr_ctl->buf_ctl[cmdindex];
          counted = 0;
          while (buf_status(bc) != SHM_EMPTY)
            {
              if (!counted)
                {
                  counted = 1;
                  full_count++;
                }
              if (cancel_requested(s))
                {
#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
                  sanei_scsi_req_flush_all_extended(s->fd);
#else
                  sanei_scsi_req_flush_all();
#endif
                  s->rdr_ctl->cancel = 0;
                  s->rdr_ctl->status = SANE_STATUS_CANCELLED;
                  s->rdr_ctl->running = 0;
                  DBG(11, " reader_process (cancelled) >>\n");
                  return 1;
                }
            }

          nread = bytes_to_queue;
          if (nread > max_bytes_per_read)
            nread = max_bytes_per_read;
          bc->used = nread;
          cmd[6] = nread >> 16;
          cmd[7] = nread >> 8;
          cmd[8] = nread;
          status = sanei_scsi_req_enter (s->fd, cmd, sizeof (cmd), 
                    bc->buffer, &bc->used, &bc->qid);
          if (status != SANE_STATUS_GOOD)
            {
              DBG(1, "reader_process: read command failed: %s", 
                  sane_strstatus(status));
#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
              sanei_scsi_req_flush_all_extended(s->fd);
#else
              sanei_scsi_req_flush_all();
#endif
              s->rdr_ctl->status = status;
              s->rdr_ctl->running = 0;
              return 2;
            }
          bc->shm_status = SHM_BUSY;
          bc->nreq = nread;
          bytes_to_queue -= nread;
          
          cmdindex++;
          if (cmdindex == s->dev->info.buffers)
            cmdindex = 0;
        }
      
      if (cancel_requested(s)) 
        {
#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
          sanei_scsi_req_flush_all_extended(s->fd);
#else
          sanei_scsi_req_flush_all();
#endif
          s->rdr_ctl->cancel = 0;
          s->rdr_ctl->status = SANE_STATUS_CANCELLED;
          s->rdr_ctl->running = 0;
          DBG(11, " reader_process (cancelled) >>\n");
          return 1;
        }
    }
      
  DBG(1, "buffer full conditions: %i\n", full_count);
  DBG(11, " reader_process>>\n");

  s->rdr_ctl->running = 0;
  return 0;
}

static SANE_Status
read_data (NEC_Scanner *s, SANE_Byte *buf, size_t * buf_size)
{
  size_t copysize, copied = 0;
  NEC_shmem_ctl *bc;
  
  DBG(11, "<< read_data ");

  bc = &s->rdr_ctl->buf_ctl[s->read_buff];

  while (copied < *buf_size)
    {
      /* wait until the reader process delivers data or a scanner error occurs: */
      while (   buf_status(bc) != SHM_FULL
             && rdr_status(s) == SANE_STATUS_GOOD) 
        {
          usleep(10); /* could perhaps be longer. make this user configurable?? */
        }

      if (rdr_status(s) != SANE_STATUS_GOOD)
        {
          return rdr_status(s);
          DBG(11, ">>\n");
        }

      copysize = bc->used - bc->start;
      
      if (copysize > *buf_size - copied )
        copysize = *buf_size - copied;
        
      memcpy(buf, &(bc->buffer[bc->start]), copysize);

      copied += copysize;
      buf = &buf[copysize];

      bc->start += copysize;
      if (bc->start >= bc->used)
        {
          bc->start = 0;
          bc->shm_status = SHM_EMPTY;
          s->read_buff++;
          if (s->read_buff == s->dev->info.buffers)
            s->read_buff = 0;
          bc = &s->rdr_ctl->buf_ctl[s->read_buff];
        }
    }

  DBG(11, ">>\n");
  return SANE_STATUS_GOOD;
}

#else /* don't USE_FORK: */

static SANE_Status
read_data (NEC_Scanner *s, SANE_Byte *buf, size_t * buf_size)
{
  static u_char cmd[] = {READ, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  SANE_Status status = SANE_STATUS_GOOD;
  size_t remain = *buf_size;
  size_t nread;
  DBG (11, "<< read_data ");

  /* sane_read_shuffled requires that read_data returns
     exactly *buf_size bytes, so it must be guaranteed here.
     Further make sure that not more bytes are read in than 
     sanei_scsi_max_request_size allows, to avoid a failure 
     of the read command
  */
  while (remain > 0)
    {
      nread = remain;
      if (nread > s->dev->info.bufsize)
        nread = s->dev->info.bufsize;
      cmd[6] = nread >> 16;
      cmd[7] = nread >> 8;
      cmd[8] = nread;
      status = sanei_scsi_cmd (s->fd, cmd, sizeof (cmd), 
                 &buf[*buf_size - remain], &nread);
      if (status != SANE_STATUS_GOOD)
        {
          DBG(11, ">>\n");
          return(status);
        }
      remain -= nread;
    }
  DBG (11, ">>\n");
  return (status);
}
#endif

static size_t
max_string_size (const SANE_String_Const strings[])
{
  size_t size, max_size = 0;
  int i;
  DBG (10, "<< max_string_size ");

  for (i = 0; strings[i]; ++i)
    {
      size = strlen (strings[i]) + 1;
      if (size > max_size)
	max_size = size;
    }

  DBG (10, ">>\n");
  return max_size;
}

static SANE_Status
wait_ready(int fd)
{
  SANE_Status status;
  int retry = 0;

  while ((status = test_unit_ready (fd)) != SANE_STATUS_GOOD)
  {
    DBG (5, "wait_ready failed (%d)\n", retry);
    DBG (5, "wait_ready status = (%d)\n", status);
    if (retry++ > 15){
	return SANE_STATUS_IO_ERROR;
    }
    sleep(3);
  }
  return (status);
    
}

static SANE_Status
attach (const char *devnam, NEC_Device ** devp)
{
  SANE_Status status;
  NEC_Device *dev;
  NEC_Sense_Data sensedat;

  int fd;
  unsigned char inquiry_data[INQUIRY_LEN];
  const unsigned char *model_name;
  mode_sense_param msp;
  size_t buf_size;
  DBG (10, "<< attach ");

  for (dev = first_dev; dev; dev = dev->next)
    {
      if (strcmp (dev->sane.name, devnam) == 0)
	{
	  if (devp)
	    *devp = dev;
	  return (SANE_STATUS_GOOD);
	}
    }

  sensedat.model = unknown;
  sensedat.complain_on_adf_error = 0;
  DBG (3, "attach: opening %s\n", devnam);
#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
  {
    int bufsize = 4096;
    status = sanei_scsi_open_extended (devnam, &fd, &sense_handler, &sensedat, &bufsize);
    if (status != SANE_STATUS_GOOD)
      {
        DBG (1, "attach: open failed: %s\n", sane_strstatus (status));
        return (status);
      }
    if (bufsize < 4096)
      {
        DBG(1, "attach: open failed. no memory\n");
        sanei_scsi_close(fd);
        return SANE_STATUS_NO_MEM;
      }
  }
#else
  status = sanei_scsi_open (devnam, &fd, &sense_handler, &sensedat);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: open failed: %s\n", sane_strstatus (status));
      return (status);
    }
#endif

  DBG (3, "attach: sending INQUIRY\n");
  memset (inquiry_data, 0, sizeof (inquiry_data));
  buf_size = sizeof (inquiry_data);
  status = inquiry (fd, inquiry_data, &buf_size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: inquiry failed: %s\n", sane_strstatus (status));
      sanei_scsi_close (fd);
      return (status);
    }

  if (inquiry_data[0] == 6 && strncmp ((char *)inquiry_data + 8, "NEC", 3) == 0)
    {
      if (strncmp ((char *)inquiry_data + 16, "PC-IN500/4C", 11) == 0)
        sensedat.model = PCIN500;
      else
        sensedat.model = unknown;
    }

  if (sensedat.model == unknown)
    {
      DBG (1, "attach: device doesn't look like a NEC scanner\n");
      DBG (1, "      : Only PC-IN500/4C is supported.\n");
      sanei_scsi_close (fd);
      return (SANE_STATUS_INVAL);
    }

  DBG (3, "attach: sending TEST_UNIT_READY\n");
  status = test_unit_ready (fd);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: test unit ready failed (%s)\n",
	   sane_strstatus (status));
      sanei_scsi_close (fd);
      return (status);
    }
  DBG (3, "attach: sending MODE SELECT\n");

  if (sensedat.model == PCIN500)
    status = mode_select_mud (fd, DEFAULT_MUD_1200);

  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: MODE_SELECT_MUD failed\n");
      sanei_scsi_close (fd);
      return (SANE_STATUS_INVAL);
    }

  DBG (3, "attach: sending MODE SENSE/MUP page\n");
  memset (&msp, 0, sizeof (msp));
  buf_size = sizeof (msp);
  status = mode_sense (fd, &msp, &buf_size, 3);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "attach: MODE_SENSE/MUP page failed\n");
      sanei_scsi_close (fd);
      return (SANE_STATUS_INVAL);
    }
#ifdef DEBUG_NEC
  DBG (3,"attach: MODE SENSE parameter\n");
      DBG(11, "%02x %02x %02x %02x "
              "%02x %02x %02x %02x %02x %02x %02x %02x\n",
              msp.mode_data_length,
	      msp.mode_param_header2,
	      msp.mode_param_header3,
	      msp.mode_desciptor_length,
	      msp.page_code,
	      msp.page_length,
	      msp.bmu,
	      msp.res2,
	      msp.mud[0],
	      msp.mud[1],
	      msp.res3,
	      msp.res4);
#endif
  dev = malloc (sizeof (*dev));
  if (!dev)
    return (SANE_STATUS_NO_MEM);
  memset (dev, 0, sizeof (*dev));

  dev->sane.name = (SANE_String) strdup (devnam);
  dev->sane.vendor = "NEC";
  model_name = inquiry_data + 16;
  dev->sane.model  = strndup ((const char *)model_name, 10);
  dev->sane.type = "flatbed scanner";
  
  dev->sensedat.model = sensedat.model;

  DBG (5, "dev->sane.name = %s\n", dev->sane.name);
  DBG (5, "dev->sane.vendor = %s\n", dev->sane.vendor);
  DBG (5, "dev->sane.model = %s\n", dev->sane.model);
  DBG (5, "dev->sane.type = %s\n", dev->sane.type);

  if (sensedat.model == PCIN500)
    dev->info.res_range.quant = 10;
  else
    dev->info.res_range.quant = 0;

  dev->info.tl_x_ranges[SCAN_SIMPLE].min = SANE_FIX(0);
  dev->info.br_x_ranges[SCAN_SIMPLE].min = SANE_FIX(1);
  dev->info.tl_y_ranges[SCAN_SIMPLE].min = SANE_FIX(0);
  dev->info.br_y_ranges[SCAN_SIMPLE].min = SANE_FIX(1);
  dev->info.tl_x_ranges[SCAN_SIMPLE].quant = SANE_FIX(0);
  dev->info.br_x_ranges[SCAN_SIMPLE].quant = SANE_FIX(0);
  dev->info.tl_y_ranges[SCAN_SIMPLE].quant = SANE_FIX(0);
  dev->info.br_y_ranges[SCAN_SIMPLE].quant = SANE_FIX(0);

  if (sensedat.model == PCIN500)
    dev->info.res_default = 15;
  else
    dev->info.res_default = 150;
  dev->info.tl_x_ranges[SCAN_SIMPLE].max = SANE_FIX(209);
  dev->info.br_x_ranges[SCAN_SIMPLE].max = SANE_FIX(210);
  dev->info.tl_y_ranges[SCAN_SIMPLE].max = SANE_FIX(296);
  dev->info.br_y_ranges[SCAN_SIMPLE].max = SANE_FIX(297);

  dev->info.bmu = msp.bmu;
  dev->info.mud = (msp.mud[0] << 8) + msp.mud[1];
  
  dev->info.adf_fsu_installed = 0;
  if (dev->sensedat.model == PCIN500)
    {
      dev->info.res_range.max = 48;
      dev->info.res_range.min = 5;

      dev->info.x_default = SANE_FIX(210);
      dev->info.tl_x_ranges[SCAN_SIMPLE].max = SANE_FIX(210); /* 304.8mm is the real max */
      dev->info.br_x_ranges[SCAN_SIMPLE].max = SANE_FIX(210); /* 304.8mm is the real max */

      dev->info.y_default = SANE_FIX(297);
      dev->info.tl_y_ranges[SCAN_SIMPLE].max = SANE_FIX(297); /* 431.8 is the real max */
      dev->info.br_y_ranges[SCAN_SIMPLE].max = SANE_FIX(297); /* 431.8 is the real max */
    }
  else
    {
      dev->info.res_range.max = 400;
      dev->info.res_range.min = 50;

      dev->info.x_default = SANE_FIX(210);
      dev->info.tl_x_ranges[SCAN_SIMPLE].max = SANE_FIX(210); /* 304.8mm is the real max */
      dev->info.br_x_ranges[SCAN_SIMPLE].max = SANE_FIX(210); /* 304.8mm is the real max */

      dev->info.y_default = SANE_FIX(297);
      dev->info.tl_y_ranges[SCAN_SIMPLE].max = SANE_FIX(297); /* 431.8 is the real max */
      dev->info.br_y_ranges[SCAN_SIMPLE].max = SANE_FIX(297); /* 431.8 is the real max */
    }
  sanei_scsi_close (fd);
  
  dev->info.threshold_range.min = 1;
  dev->info.threshold_range.max = 255;
  dev->info.threshold_range.quant = 0;

  dev->info.tint_range.min = 1;
  dev->info.tint_range.max = 255;
  dev->info.tint_range.quant = 0;

  dev->info.color_range.min = 1;
  dev->info.color_range.max = 255;
  dev->info.color_range.quant = 0;

  DBG (5, "res_default=%d\n", dev->info.res_default);
  DBG (5, "res_range.max=%d\n", dev->info.res_range.max);
  DBG (5, "res_range.min=%d\n", dev->info.res_range.min);
  DBG (5, "res_range.quant=%d\n", dev->info.res_range.quant);

  DBG (5, "x_default=%f\n", SANE_UNFIX(dev->info.x_default));
  DBG (5, "tl_x_range[0].max=%f\n", SANE_UNFIX(dev->info.tl_x_ranges[SCAN_SIMPLE].max));
  DBG (5, "tl_x_range[0].min=%f\n", SANE_UNFIX(dev->info.tl_x_ranges[SCAN_SIMPLE].min));
  DBG (5, "tl_x_range[0].quant=%d\n", dev->info.tl_x_ranges[SCAN_SIMPLE].quant);
  DBG (5, "br_x_range[0].max=%f\n", SANE_UNFIX(dev->info.br_x_ranges[SCAN_SIMPLE].max));
  DBG (5, "br_x_range[0].min=%f\n", SANE_UNFIX(dev->info.br_x_ranges[SCAN_SIMPLE].min));
  DBG (5, "br_x_range[0].quant=%d\n", dev->info.br_x_ranges[SCAN_SIMPLE].quant);
  DBG (5, "y_default=%f\n", SANE_UNFIX(dev->info.y_default));
  DBG (5, "tl_y_range[0].max=%f\n", SANE_UNFIX(dev->info.tl_y_ranges[SCAN_SIMPLE].max));
  DBG (5, "tl_y_range[0].min=%f\n", SANE_UNFIX(dev->info.tl_y_ranges[SCAN_SIMPLE].min));
  DBG (5, "tl_y_range[0].quant=%d\n", dev->info.tl_y_ranges[SCAN_SIMPLE].quant);
  DBG (5, "br_y_range[0].max=%f\n", SANE_UNFIX(dev->info.br_y_ranges[SCAN_SIMPLE].max));
  DBG (5, "br_y_range[0].min=%f\n", SANE_UNFIX(dev->info.br_y_ranges[SCAN_SIMPLE].min));
  DBG (5, "br_y_range[0].quant=%d\n", dev->info.br_y_ranges[SCAN_SIMPLE].quant);

  if (dev->info.adf_fsu_installed & HAVE_FSU)
    {
      DBG (5, "tl_x_range[1].max=%f\n", SANE_UNFIX(dev->info.tl_x_ranges[SCAN_WITH_FSU].max));
      DBG (5, "tl_x_range[1].min=%f\n", SANE_UNFIX(dev->info.tl_x_ranges[SCAN_WITH_FSU].min));
      DBG (5, "tl_x_range[1].quant=%d\n", dev->info.tl_x_ranges[SCAN_WITH_FSU].quant);
      DBG (5, "br_x_range[1].max=%f\n", SANE_UNFIX(dev->info.br_x_ranges[SCAN_WITH_FSU].max));
      DBG (5, "br_x_range[1].min=%f\n", SANE_UNFIX(dev->info.br_x_ranges[SCAN_WITH_FSU].min));
      DBG (5, "br_x_range[1].quant=%d\n", dev->info.br_x_ranges[SCAN_WITH_FSU].quant);
      DBG (5, "tl_y_range[1].max=%f\n", SANE_UNFIX(dev->info.tl_y_ranges[SCAN_WITH_FSU].max));
      DBG (5, "tl_y_range[1].min=%f\n", SANE_UNFIX(dev->info.tl_y_ranges[SCAN_WITH_FSU].min));
      DBG (5, "tl_y_range[1].quant=%d\n", dev->info.tl_y_ranges[SCAN_WITH_FSU].quant);
      DBG (5, "br_y_range[1].max=%f\n", SANE_UNFIX(dev->info.br_y_ranges[SCAN_WITH_FSU].max));
      DBG (5, "br_y_range[1].min=%f\n", SANE_UNFIX(dev->info.br_y_ranges[SCAN_WITH_FSU].min));
      DBG (5, "br_y_range[1].quant=%d\n", dev->info.br_y_ranges[SCAN_WITH_FSU].quant);
    }

  if (dev->info.adf_fsu_installed & HAVE_ADF)
    {
      DBG (5, "tl_x_range[2].max=%f\n", SANE_UNFIX(dev->info.tl_x_ranges[SCAN_WITH_ADF].max));
      DBG (5, "tl_x_range[2].min=%f\n", SANE_UNFIX(dev->info.tl_x_ranges[SCAN_WITH_ADF].min));
      DBG (5, "tl_x_range[2].quant=%d\n", dev->info.tl_x_ranges[SCAN_WITH_ADF].quant);
      DBG (5, "br_x_range[2].max=%f\n", SANE_UNFIX(dev->info.br_x_ranges[SCAN_WITH_ADF].max));
      DBG (5, "br_x_range[2].min=%f\n", SANE_UNFIX(dev->info.br_x_ranges[SCAN_WITH_ADF].min));
      DBG (5, "br_x_range[2].quant=%d\n", dev->info.br_x_ranges[SCAN_WITH_ADF].quant);
      DBG (5, "tl_y_range[2].max=%f\n", SANE_UNFIX(dev->info.tl_y_ranges[SCAN_WITH_ADF].max));
      DBG (5, "tl_y_range[2].min=%f\n", SANE_UNFIX(dev->info.tl_y_ranges[SCAN_WITH_ADF].min));
      DBG (5, "tl_y_range[2].quant=%d\n", dev->info.tl_y_ranges[SCAN_WITH_ADF].quant);
      DBG (5, "br_y_range[2].max=%f\n", SANE_UNFIX(dev->info.br_y_ranges[SCAN_WITH_ADF].max));
      DBG (5, "br_y_range[2].min=%f\n", SANE_UNFIX(dev->info.br_y_ranges[SCAN_WITH_ADF].min));
      DBG (5, "br_y_range[2].quant=%d\n", dev->info.br_y_ranges[SCAN_WITH_ADF].quant);
    }

  DBG (5, "bmu=%d\n", dev->info.bmu);
  DBG (5, "mud=%d\n", dev->info.mud);

  ++num_devices;
  dev->next = first_dev;
  first_dev = dev;

  if (devp)
    *devp = dev;

  DBG (10, ">>\n");
  return (SANE_STATUS_GOOD);
}

/* Enabling / disabling of gamma options.
   Depends on many user settable options, so lets put it into 
   one function to be called by init_options and by sane_control_option

*/
#ifdef USE_CUSTOM_GAMMA
static void 
set_gamma_caps(NEC_Scanner *s)
{
  /* neither fixed nor custom gamma for line art modes */
  if (   strcmp(s->val[OPT_MODE].s, M_LINEART) == 0
      || strcmp(s->val[OPT_MODE].s, M_LINEART_COLOR) == 0)
    {
      s->opt[OPT_GAMMA].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_CUSTOM_GAMMA].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
    }
  else if (strcmp(s->val[OPT_MODE].s, M_GRAY) == 0)
    {
      s->opt[OPT_CUSTOM_GAMMA].cap &= ~SANE_CAP_INACTIVE;
      if (s->val[OPT_CUSTOM_GAMMA].w == SANE_FALSE)
        {
          s->opt[OPT_GAMMA].cap &= ~SANE_CAP_INACTIVE;
          s->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
        }
      else
        {
          s->opt[OPT_GAMMA].cap |= SANE_CAP_INACTIVE;
          s->opt[OPT_GAMMA_VECTOR].cap &= ~SANE_CAP_INACTIVE;
        }
      s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
    }
  else
    {
      /* color mode */
      s->opt[OPT_CUSTOM_GAMMA].cap &= ~SANE_CAP_INACTIVE;
      if (s->val[OPT_CUSTOM_GAMMA].w == SANE_FALSE)
        {
          s->opt[OPT_GAMMA].cap &= ~SANE_CAP_INACTIVE;
          s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
          s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
          s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
        }
      else
        {
          s->opt[OPT_GAMMA].cap |= SANE_CAP_INACTIVE;
          s->opt[OPT_GAMMA_VECTOR_R].cap &= ~SANE_CAP_INACTIVE;
          s->opt[OPT_GAMMA_VECTOR_G].cap &= ~SANE_CAP_INACTIVE;
          s->opt[OPT_GAMMA_VECTOR_B].cap &= ~SANE_CAP_INACTIVE;
        }
      s->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
    }
}
#endif /* USE_CUSTOM_GAMMA */

/* The next function is a slightly modified version of sanei_constrain_value
   Instead of returning status information like STATUS_INVAL, it adjusts
   an invaild value to the nearest allowed one.
*/
static void
clip_value (const SANE_Option_Descriptor * opt, void * value)
{
  const SANE_String_Const * string_list;
  const SANE_Word * word_list;
  int i, num_matches, match;
  const SANE_Range * range;
  SANE_Word w, v;
  size_t len;

  switch (opt->constraint_type)
    {
    case SANE_CONSTRAINT_RANGE:
      w = *(SANE_Word *) value;
      range = opt->constraint.range;

      if (w < range->min)
        w = range->min;
      else if (w > range->max)
	w = range->max;

      if (range->quant)
	{
	  v = (w - range->min + range->quant/2) / range->quant;
	  w = v * range->quant + range->min;
	  *(SANE_Word*) value = w;
	}
      break;

    case SANE_CONSTRAINT_WORD_LIST:
      w = *(SANE_Word *) value;
      word_list = opt->constraint.word_list;
      for (i = 1; w != word_list[i]; ++i)
	if (i >= word_list[0])
	  /* somewhat arbitrary... Would be better to have a default value
	     explicitly defined.
	  */
	  *(SANE_Word*) value = word_list[1];
      break;

    case SANE_CONSTRAINT_STRING_LIST:
      /* Matching algorithm: take the longest unique match ignoring
	 case.  If there is an exact match, it is admissible even if
	 the same string is a prefix of a longer option name. */
      string_list = opt->constraint.string_list;
      len = strlen (value);

      /* count how many matches of length LEN characters we have: */
      num_matches = 0;
      match = -1;
      for (i = 0; string_list[i]; ++i)
	if (strncasecmp (value, string_list[i], len) == 0
	    && len <= strlen (string_list[i]))
	  {
	    match = i;
	    if (len == strlen (string_list[i]))
	      {
		/* exact match... */
		if (strcmp (value, string_list[i]) != 0)
		  /* ...but case differs */
		  strcpy (value, string_list[match]);
	      }
	    ++num_matches;
	  }

      if (num_matches > 1)
        /* xxx quite arbitrary... We could also choose the first match
        */
        strcpy(value, string_list[match]);
      else if (num_matches == 1)
        strcpy (value, string_list[match]);
      else
        strcpy (value, string_list[0]);

    default:
      break;
    }
}

/* make sure that enough memory is allocated for each string,
   so that the strcpy in sane_control_option / set value cannot
   write behind the end of the allocated memory. 
*/
static SANE_Status
init_string_option(NEC_Scanner *s, SANE_String_Const name, 
   SANE_String_Const title, SANE_String_Const desc, 
   const SANE_String_Const *string_list, int option, int default_index)
{
  int i;

  s->opt[option].name = name;
  s->opt[option].title = title;
  s->opt[option].desc = desc;
  s->opt[option].type = SANE_TYPE_STRING;
  s->opt[option].size = max_string_size (string_list);
  s->opt[option].constraint_type = SANE_CONSTRAINT_STRING_LIST;
  s->opt[option].constraint.string_list = string_list;
  s->val[option].s = malloc(s->opt[option].size);
  if (s->val[option].s == 0)
    {
      for (i = 1; i < NUM_OPTIONS; i++)
        {
          if (s->val[i].s && s->opt[i].type == SANE_TYPE_STRING)
            free(s->val[i].s);
        }
      return SANE_STATUS_NO_MEM;
    }
  strcpy(s->val[option].s, string_list[default_index]);
  return SANE_STATUS_GOOD;
}

static SANE_Status
init_options (NEC_Scanner * s)
{
  int i, default_source;
  SANE_Word scalar;
  DBG (10, "<< init_options ");

  memset (s->opt, 0, sizeof (s->opt));
  memset (s->val, 0, sizeof (s->val));

  for (i = 0; i < NUM_OPTIONS; ++i)
    {
      s->opt[i].size = sizeof (SANE_Word);
      s->opt[i].cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
      s->val[i].s = 0;
    }

  s->opt[OPT_NUM_OPTS].title = SANE_TITLE_NUM_OPTIONS;
  s->opt[OPT_NUM_OPTS].desc = SANE_DESC_NUM_OPTIONS;
  s->opt[OPT_NUM_OPTS].type = SANE_TYPE_INT;
  s->opt[OPT_NUM_OPTS].cap = SANE_CAP_SOFT_DETECT;
  s->val[OPT_NUM_OPTS].w = NUM_OPTIONS;

  /* Mode group: */
  s->opt[OPT_MODE_GROUP].title = "Scan Mode";
  s->opt[OPT_MODE_GROUP].desc = "";
  s->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_MODE_GROUP].cap = 0;
  s->opt[OPT_MODE_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* scan mode */
  init_string_option(s, SANE_NAME_SCAN_MODE, SANE_TITLE_SCAN_MODE,
    SANE_DESC_SCAN_MODE, mode_list, OPT_MODE, MODES_COLOR);

  /* half tone */
  init_string_option(s, SANE_NAME_HALFTONE_PATTERN, SANE_TITLE_HALFTONE_PATTERN,
    SANE_DESC_HALFTONE " (not support)", halftone_list, OPT_HALFTONE, 0);

  if (s->dev->sensedat.model == PCIN500)
    s->opt[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE;

  i = 0;
  default_source = -1;

  if (s->dev->info.adf_fsu_installed & HAVE_ADF)
    {
      s->dev->info.scansources[i++] = use_adf;
      default_source = SCAN_WITH_ADF;
    }
  if (s->dev->info.adf_fsu_installed & HAVE_FSU)
    {
      s->dev->info.scansources[i++] = use_fsu;
      if (default_source < 0)
        default_source = SCAN_WITH_FSU;
    }
  s->dev->info.scansources[i++] = use_simple;
    if (default_source < 0)
      default_source = SCAN_SIMPLE;
  s->dev->info.scansources[i] = 0;
  
  init_string_option(s, SANE_NAME_SCAN_SOURCE, SANE_TITLE_SCAN_SOURCE,
    SANE_DESC_SCAN_SOURCE, (SANE_String_Const*)s->dev->info.scansources,
    OPT_SCANSOURCE, 0);

  if (i < 2)
    s->opt[OPT_SCANSOURCE].cap |= SANE_CAP_INACTIVE;

  if (s->dev->sensedat.model == PCIN500)
    init_string_option(s, "Paper size", "Paper size",
      "Paper size", paper_list_pcin500, OPT_PAPER, 0);
  else
    init_string_option(s, "Paper size", "Paper size",
      "Paper size", paper_list_pcinxxx, OPT_PAPER, 1);

  /* gamma */
  init_string_option(s, "Gamma", "Gamma", "Gamma", gamma_list, OPT_GAMMA, 0);

  /* Resolution Group */
  s->opt[OPT_RESOLUTION_GROUP].title = "Resolution";
  s->opt[OPT_RESOLUTION_GROUP].desc = "";
  s->opt[OPT_RESOLUTION_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_RESOLUTION_GROUP].cap = 0;
  s->opt[OPT_RESOLUTION_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

#ifdef USE_RESOLUTION_LIST
  /* select resolution */
  if (s->dev->sensedat.model == PCIN500)
    init_string_option(s, "Resolution", "Resolution", "Resolution", 
      resolution_list_pcin500, OPT_RESOLUTION_LIST, RESOLUTION_MAX_PCIN500);
  else
    init_string_option(s, "Resolution", "Resolution", "Resolution", 
      resolution_list_pcinxxx, OPT_RESOLUTION_LIST, RESOLUTION_MAX_PCINXXX);
#endif
  
  /* x & y resolution */
  s->opt[OPT_RESOLUTION].name = SANE_NAME_SCAN_RESOLUTION;
  if (s->dev->sensedat.model == PCIN500)
      s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION"(x 10)";
  else
      s->opt[OPT_RESOLUTION].title = SANE_TITLE_SCAN_RESOLUTION;
  s->opt[OPT_RESOLUTION].desc = SANE_DESC_SCAN_RESOLUTION;
  s->opt[OPT_RESOLUTION].type = SANE_TYPE_INT;
  s->opt[OPT_RESOLUTION].unit = SANE_UNIT_DPI;
  s->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_RESOLUTION].constraint.range = &s->dev->info.res_range;
  s->val[OPT_RESOLUTION].w = s->dev->info.res_default;

  /* "Geometry" group: */
  s->opt[OPT_GEOMETRY_GROUP].title = "Geometry";
  s->opt[OPT_GEOMETRY_GROUP].desc = "";
  s->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_GEOMETRY_GROUP].cap = SANE_CAP_ADVANCED;
  s->opt[OPT_GEOMETRY_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* top-left x */
  s->opt[OPT_TL_X].name = SANE_NAME_SCAN_TL_X;
  s->opt[OPT_TL_X].title = SANE_TITLE_SCAN_TL_X;
  s->opt[OPT_TL_X].desc = SANE_DESC_SCAN_TL_X;
  s->opt[OPT_TL_X].type = SANE_TYPE_FIXED;
  s->opt[OPT_TL_X].unit = SANE_UNIT_MM;
  s->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_TL_X].constraint.range = &s->dev->info.tl_x_ranges[default_source];
  s->val[OPT_TL_X].w = s->dev->info.tl_x_ranges[default_source].min;

  /* top-left y */
  s->opt[OPT_TL_Y].name = SANE_NAME_SCAN_TL_Y;
  s->opt[OPT_TL_Y].title = SANE_TITLE_SCAN_TL_Y;
  s->opt[OPT_TL_Y].desc = SANE_DESC_SCAN_TL_Y;
  s->opt[OPT_TL_Y].type = SANE_TYPE_FIXED;
  s->opt[OPT_TL_Y].unit = SANE_UNIT_MM;
  s->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_TL_Y].constraint.range = &s->dev->info.tl_y_ranges[default_source];
  s->val[OPT_TL_Y].w = s->dev->info.tl_y_ranges[default_source].min;

  /* bottom-right x */
  s->opt[OPT_BR_X].name = SANE_NAME_SCAN_BR_X;
  s->opt[OPT_BR_X].title = SANE_TITLE_SCAN_BR_X;
  s->opt[OPT_BR_X].desc = SANE_DESC_SCAN_BR_X;
  s->opt[OPT_BR_X].type = SANE_TYPE_FIXED;
  s->opt[OPT_BR_X].unit = SANE_UNIT_MM;
  s->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BR_X].constraint.range = &s->dev->info.br_x_ranges[default_source];
  scalar = s->dev->info.x_default;
  clip_value (&s->opt[OPT_BR_X], &scalar);
  s->val[OPT_BR_X].w = scalar;

  /* bottom-right y */
  s->opt[OPT_BR_Y].name = SANE_NAME_SCAN_BR_Y;
  s->opt[OPT_BR_Y].title = SANE_TITLE_SCAN_BR_Y;
  s->opt[OPT_BR_Y].desc = SANE_DESC_SCAN_BR_Y;
  s->opt[OPT_BR_Y].type = SANE_TYPE_FIXED;
  s->opt[OPT_BR_Y].unit = SANE_UNIT_MM;
  s->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_BR_Y].constraint.range = &s->dev->info.br_y_ranges[default_source];
  scalar = s->dev->info.y_default;
  clip_value (&s->opt[OPT_BR_X], &scalar);
  s->val[OPT_BR_Y].w = scalar;

  /* "Enhancement" group: */
  s->opt[OPT_ENHANCEMENT_GROUP].title = "Enhancement";
  s->opt[OPT_ENHANCEMENT_GROUP].desc = "";
  s->opt[OPT_ENHANCEMENT_GROUP].type = SANE_TYPE_GROUP;
  s->opt[OPT_ENHANCEMENT_GROUP].cap = 0;
  s->opt[OPT_ENHANCEMENT_GROUP].constraint_type = SANE_CONSTRAINT_NONE;

  /* edge emphasis */
  init_string_option(s, "Edge emphasis", "Edge emphasis",
    "Edge emphasis", edge_emphasis_list,
    OPT_EDGE_EMPHASIS, 0);

  if (s->dev->sensedat.model == PCIN500)
    s->opt[OPT_EDGE_EMPHASIS].cap |= SANE_CAP_INACTIVE;

  /* OR */
  s->opt[OPT_OR].name = "OR";
  s->opt[OPT_OR].title = "OR";
  s->opt[OPT_OR].desc = "Select OR emphancement";
  s->opt[OPT_OR].type = SANE_TYPE_BOOL;
  s->val[OPT_OR].w = SANE_FALSE;

  /* EDGE */
  s->opt[OPT_EDGE].name = "edge";
  s->opt[OPT_EDGE].title = "Edge";
  s->opt[OPT_EDGE].desc = "Select Edge emphancement";
  s->opt[OPT_EDGE].type = SANE_TYPE_BOOL;
  s->val[OPT_EDGE].w = SANE_FALSE;

  /* NR */
  s->opt[OPT_NR].name = "NR";
  s->opt[OPT_NR].title = "NR";
  s->opt[OPT_NR].desc = "Select noise reduction";
  s->opt[OPT_NR].type = SANE_TYPE_BOOL;
  s->val[OPT_NR].w = SANE_FALSE;

  if (s->dev->sensedat.model != PCIN500)
    {
      s->opt[OPT_EDGE].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_NR].cap |= SANE_CAP_INACTIVE;
      s->opt[OPT_OR].cap |= SANE_CAP_INACTIVE;
    }
  /* tint */
  s->opt[OPT_TINT].name = "tint";
  s->opt[OPT_TINT].title = "Tint";
  s->opt[OPT_TINT].desc = "Select tint";
  s->opt[OPT_TINT].type = SANE_TYPE_INT;
  s->opt[OPT_TINT].unit = SANE_UNIT_NONE;
  s->opt[OPT_TINT].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_TINT].constraint.range = &s->dev->info.tint_range;
  s->val[OPT_TINT].w = 128;
  if (s->dev->sensedat.model != PCIN500)
    s->opt[OPT_TINT].cap |= SANE_CAP_INACTIVE;

  /* color */
  s->opt[OPT_COLOR].name = "color";
  s->opt[OPT_COLOR].title = "Color";
  s->opt[OPT_COLOR].desc = "Select color";
  s->opt[OPT_COLOR].type = SANE_TYPE_INT;
  s->opt[OPT_COLOR].unit = SANE_UNIT_NONE;
  s->opt[OPT_COLOR].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_COLOR].constraint.range = &s->dev->info.color_range;
  s->val[OPT_COLOR].w = 128;
  if (s->dev->sensedat.model != PCIN500)
    s->opt[OPT_COLOR].cap |= SANE_CAP_INACTIVE;

  /* threshold */
  s->opt[OPT_THRESHOLD].name = SANE_NAME_THRESHOLD;
  s->opt[OPT_THRESHOLD].title = SANE_TITLE_THRESHOLD;
  s->opt[OPT_THRESHOLD].desc = SANE_DESC_THRESHOLD;
  s->opt[OPT_THRESHOLD].type = SANE_TYPE_INT;
  s->opt[OPT_THRESHOLD].unit = SANE_UNIT_NONE;
  s->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_THRESHOLD].constraint.range = &s->dev->info.threshold_range;
  s->val[OPT_THRESHOLD].w = 128;
  s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;

#ifdef USE_COLOR_THRESHOLD
  s->opt[OPT_THRESHOLD_R].name = SANE_NAME_THRESHOLD "-red";
  /* xxx the titles and decriptions are confusing:
     "set white point (red)" 
     Any idea? maybe "threshold to get the red component on"
  */
  s->opt[OPT_THRESHOLD_R].title = SANE_TITLE_THRESHOLD " (red)";
  s->opt[OPT_THRESHOLD_R].desc = SANE_DESC_THRESHOLD " (red)";
  s->opt[OPT_THRESHOLD_R].type = SANE_TYPE_INT;
  s->opt[OPT_THRESHOLD_R].unit = SANE_UNIT_NONE;
  s->opt[OPT_THRESHOLD_R].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_THRESHOLD_R].constraint.range = &s->dev->info.threshold_range;
  s->val[OPT_THRESHOLD_R].w = 128;
  s->opt[OPT_THRESHOLD_R].cap |= SANE_CAP_INACTIVE;

  s->opt[OPT_THRESHOLD_G].name = SANE_NAME_THRESHOLD "-green";
  s->opt[OPT_THRESHOLD_G].title = SANE_TITLE_THRESHOLD " (green)";
  s->opt[OPT_THRESHOLD_G].desc = SANE_DESC_THRESHOLD " (green)";
  s->opt[OPT_THRESHOLD_G].type = SANE_TYPE_INT;
  s->opt[OPT_THRESHOLD_G].unit = SANE_UNIT_NONE;
  s->opt[OPT_THRESHOLD_G].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_THRESHOLD_G].constraint.range = &s->dev->info.threshold_range;
  s->val[OPT_THRESHOLD_G].w = 128;
  s->opt[OPT_THRESHOLD_G].cap |= SANE_CAP_INACTIVE;

  s->opt[OPT_THRESHOLD_B].name = SANE_NAME_THRESHOLD "-blue";
  s->opt[OPT_THRESHOLD_B].title = SANE_TITLE_THRESHOLD " (blue)";
  s->opt[OPT_THRESHOLD_B].desc = SANE_DESC_THRESHOLD " (blue)";
  s->opt[OPT_THRESHOLD_B].type = SANE_TYPE_INT;
  s->opt[OPT_THRESHOLD_B].unit = SANE_UNIT_NONE;
  s->opt[OPT_THRESHOLD_B].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_THRESHOLD_B].constraint.range = &s->dev->info.threshold_range;
  s->val[OPT_THRESHOLD_B].w = 128;
  s->opt[OPT_THRESHOLD_B].cap |= SANE_CAP_INACTIVE;

#endif

  /* light color (for gray scale and line art scans) */
  init_string_option(s, "LightColor", "LightColor", "LightColor",
    light_color_list, OPT_LIGHTCOLOR, 3);
  s->opt[OPT_LIGHTCOLOR].cap |= SANE_CAP_INACTIVE;

  s->opt[OPT_PREVIEW].name  = SANE_NAME_PREVIEW;
  s->opt[OPT_PREVIEW].title = SANE_TITLE_PREVIEW;
  s->opt[OPT_PREVIEW].desc  = SANE_DESC_PREVIEW;
  s->opt[OPT_PREVIEW].type  = SANE_TYPE_BOOL;
  s->opt[OPT_PREVIEW].cap = SANE_CAP_SOFT_DETECT | SANE_CAP_SOFT_SELECT;
  s->val[OPT_PREVIEW].w     = SANE_FALSE;
          

#ifdef USE_CUSTOM_GAMMA
  /* custom-gamma table */
  s->opt[OPT_CUSTOM_GAMMA].name = SANE_NAME_CUSTOM_GAMMA;
  s->opt[OPT_CUSTOM_GAMMA].title = SANE_TITLE_CUSTOM_GAMMA;
  s->opt[OPT_CUSTOM_GAMMA].desc = SANE_DESC_CUSTOM_GAMMA;
  s->opt[OPT_CUSTOM_GAMMA].type = SANE_TYPE_BOOL;
  s->val[OPT_CUSTOM_GAMMA].w = SANE_FALSE;

  /* grayscale gamma vector */
  s->opt[OPT_GAMMA_VECTOR].name = SANE_NAME_GAMMA_VECTOR;
  s->opt[OPT_GAMMA_VECTOR].title = SANE_TITLE_GAMMA_VECTOR;
  s->opt[OPT_GAMMA_VECTOR].desc = SANE_DESC_GAMMA_VECTOR;
  s->opt[OPT_GAMMA_VECTOR].type = SANE_TYPE_INT;
#if 0
  s->opt[OPT_GAMMA_VECTOR].cap |= SANE_CAP_INACTIVE;
#endif
  s->opt[OPT_GAMMA_VECTOR].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR].size = 256 * sizeof (SANE_Word);
  s->opt[OPT_GAMMA_VECTOR].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR].constraint.range = &u8_range;
  s->val[OPT_GAMMA_VECTOR].wa = &s->gamma_table[0][0];

  /* red gamma vector */
  s->opt[OPT_GAMMA_VECTOR_R].name = SANE_NAME_GAMMA_VECTOR_R;
  s->opt[OPT_GAMMA_VECTOR_R].title = SANE_TITLE_GAMMA_VECTOR_R;
  s->opt[OPT_GAMMA_VECTOR_R].desc = SANE_DESC_GAMMA_VECTOR_R;
  s->opt[OPT_GAMMA_VECTOR_R].type = SANE_TYPE_INT;
#if 0
  s->opt[OPT_GAMMA_VECTOR_R].cap |= SANE_CAP_INACTIVE;
#endif
  s->opt[OPT_GAMMA_VECTOR_R].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_R].size = 256 * sizeof (SANE_Word);
  s->opt[OPT_GAMMA_VECTOR_R].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_R].constraint.range = &u8_range;
  s->val[OPT_GAMMA_VECTOR_R].wa = &s->gamma_table[1][0];

  /* green gamma vector */
  s->opt[OPT_GAMMA_VECTOR_G].name = SANE_NAME_GAMMA_VECTOR_G;
  s->opt[OPT_GAMMA_VECTOR_G].title = SANE_TITLE_GAMMA_VECTOR_G;
  s->opt[OPT_GAMMA_VECTOR_G].desc = SANE_DESC_GAMMA_VECTOR_G;
  s->opt[OPT_GAMMA_VECTOR_G].type = SANE_TYPE_INT;
#if 0
  s->opt[OPT_GAMMA_VECTOR_G].cap |= SANE_CAP_INACTIVE;
#endif
  s->opt[OPT_GAMMA_VECTOR_G].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_G].size = 256 * sizeof (SANE_Word);
  s->opt[OPT_GAMMA_VECTOR_G].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_G].constraint.range = &u8_range;
  s->val[OPT_GAMMA_VECTOR_G].wa = &s->gamma_table[2][0];

  /* blue gamma vector */
  s->opt[OPT_GAMMA_VECTOR_B].name = SANE_NAME_GAMMA_VECTOR_B;
  s->opt[OPT_GAMMA_VECTOR_B].title = SANE_TITLE_GAMMA_VECTOR_B;
  s->opt[OPT_GAMMA_VECTOR_B].desc = SANE_DESC_GAMMA_VECTOR_B;
  s->opt[OPT_GAMMA_VECTOR_B].type = SANE_TYPE_INT;
#if 0
  s->opt[OPT_GAMMA_VECTOR_B].cap |= SANE_CAP_INACTIVE;
#endif
  s->opt[OPT_GAMMA_VECTOR_B].unit = SANE_UNIT_NONE;
  s->opt[OPT_GAMMA_VECTOR_B].size = 256 * sizeof (SANE_Word);
  s->opt[OPT_GAMMA_VECTOR_B].constraint_type = SANE_CONSTRAINT_RANGE;
  s->opt[OPT_GAMMA_VECTOR_B].constraint.range = &u8_range;
  s->val[OPT_GAMMA_VECTOR_B].wa = &s->gamma_table[3][0];
  set_gamma_caps(s);
#endif

  DBG (10, ">>\n");
  return SANE_STATUS_GOOD;
}

static SANE_Status
do_cancel (NEC_Scanner * s)
{
  DBG (10, "<< do_cancel ");

#ifdef USE_FORK
  if (s->reader_pid > 0)
    {
      int exit_status;
      int count = 0;
      /* ensure child knows it's time to stop: */

      DBG(11, "stopping reader process\n");
      s->rdr_ctl->cancel = 1;
      while(reader_running(s) && count < 100) 
        {
          usleep(100000);
          count++;
        };
      if (reader_running(s))
        {
          kill(s->reader_pid, SIGKILL);
        }
      wait(&exit_status);
      DBG(11, "reader process stopped\n");

      s->reader_pid = 0;
    }

#endif
  s->scanning = SANE_FALSE;

  if (s->fd >= 0)
    {
      sanei_scsi_close (s->fd);
      s->fd = -1;
    }
#ifdef USE_FORK
  {
    struct shmid_ds ds;
    if (s->shmid != -1)
      shmctl(s->shmid, IPC_RMID, &ds);
    s->shmid = -1;
  }
#endif
  if (s->buffer)
    free(s->buffer);
  s->buffer = 0;

  DBG (10, ">>\n");
  return (SANE_STATUS_CANCELLED);
}

static NEC_New_Device *new_devs = 0;
static NEC_New_Device *new_dev_pool = 0;

static SANE_Status 
attach_and_list(const char *devnam)
{
  SANE_Status res;
  NEC_Device *devp;
  NEC_New_Device *np;
  
  res = attach(devnam, &devp);
  if (res == SANE_STATUS_GOOD)
    {
      if (new_dev_pool)
        {
          np = new_dev_pool;
          new_dev_pool = np->next;
        }
      else
        {
          np = malloc(sizeof(NEC_New_Device));
          if (np == 0)
            return SANE_STATUS_NO_MEM;
        }
      np->next =new_devs;
      np->dev = devp;
      new_devs = np;
    }
  return res;
}

SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  char devnam[PATH_MAX] = "/dev/scanner";
  char line[PATH_MAX];
  const char *lp;
  char *word;
  char *end;
  FILE *fp;
  int opt_index = 0;
  int buffers[2] = {DEFAULT_BUFFERS, DEFAULT_BUFFERS};
  int bufsize[2] = {DEFAULT_BUFSIZE, DEFAULT_BUFSIZE};
  int queued_reads[2] = {DEFAULT_QUEUED_READS, DEFAULT_QUEUED_READS};
  int linecount = 0;
#if 1
  NEC_Device nd;
  NEC_Device *dp = &nd;
#else
  NEC_Device *dp;
#endif
  NEC_New_Device *np;
  int i;

  authorize = authorize; /* silence compilation warnings */

  DBG_INIT ();
  DBG (10, "<< sane_init ");

  DBG (1, "sane_init: NEC (Ver %d.%d)\n", NEC_MAJOR, NEC_MINOR);

  if (version_code)
    *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, V_MINOR, 0);

  fp = sanei_config_open (NEC_CONFIG_FILE);
  if (!fp)
    {
      /* use "/dev/scanner" as the default device name if no
         config file is available
      */
      attach (devnam, &dp);
      /* make sure that there are at least two buffers */
      if (DEFAULT_BUFFERS < 2)
        dp->info.buffers = DEFAULT_BUFFERS;
      else
        dp->info.buffers = 2;
      dp->info.wanted_bufsize = DEFAULT_BUFSIZE;
      dp->info.queued_reads = DEFAULT_QUEUED_READS;
      return SANE_STATUS_GOOD;
    }

  while (fgets(line, PATH_MAX, fp))
    {
      linecount++;
      word = 0;
      lp = sanei_config_get_string(line, &word);
      if (word)
        {
          if (word[0] != '#')
            {
              if (strcmp(word, "option") == 0)
                {
                  free(word);
                  word = 0;
                  lp = sanei_config_get_string(lp, &word);
                  if (strcmp(word, "buffers") == 0)
                    {
                      free(word);
                      word = 0;
                      sanei_config_get_string(lp, &word);
                      i = strtol(word, &end, 0);
                      if (end == word)
                        {
                          DBG(1, "error in config file, line %i: number expected:\n",
                              linecount);
                          DBG(1, "%s\n", line);
                        }
                      else
                        if (i > 2)
                          buffers[opt_index] = i;
                        else
                          buffers[opt_index] = 2;
                    }
                  else if (strcmp(word, "buffersize") == 0)
                    {
                      free(word);
                      word = 0;
                      sanei_config_get_string(lp, &word);
                      i = strtol(word, &end, 0);
                      if (word == end)
                        {
                          DBG(1, "error in config file, line %i: number expected:\n",
                              linecount);
                          DBG(1, "%s\n", line);
                        }
                      else
                        bufsize[opt_index] = i;
                    }
                  else if (strcmp(word, "readqueue") == 0)
                    {
                      free(word);
                      word = 0;
                      sanei_config_get_string(lp, &word);
                      i = strtol(word, &end, 0);
                      if (word == end)
                        {
                          DBG(1, "error in config file, line %i: number expected:\n",
                              linecount);
                          DBG(1, "%s\n", line);
                        }
                      else 
                        queued_reads[opt_index] = i;
                    }
                  else
                    {
                      DBG(1, "error in config file, line %i: unknown option\n",
                          linecount);
                      DBG(1, "%s\n", line);
                    }
                }
              else
                {
                  while (new_devs)
                    {
                      if (buffers[1] >= 2) 
                        new_devs->dev->info.buffers = buffers[1];
                      else
                        new_devs->dev->info.buffers = 2;
                      if (bufsize[1] > 0)
                        new_devs->dev->info.wanted_bufsize = bufsize[1];
                      else
                        new_devs->dev->info.wanted_bufsize = DEFAULT_BUFSIZE;
                      if (queued_reads[1] >= 0)
                        new_devs->dev->info.queued_reads = queued_reads[1];
                      else
                        new_devs->dev->info.queued_reads = 0;
                      np = new_devs->next;
                      new_devs->next = new_dev_pool;
                      new_dev_pool = new_devs;
                      new_devs = np;
                    }
                  if (line[strlen(line)-1] == '\n')
                    line[strlen(line)-1] = 0;
                  sanei_config_attach_matching_devices(line, &attach_and_list);
                  buffers[1] = buffers[0];
                  bufsize[1] = bufsize[0];
                  queued_reads[1] = queued_reads[0];
                  opt_index = 1;
                }
            }
          if (word) free(word);
        }
    }

  while (new_devs)
    {
      if (buffers[1] >= 2) 
        new_devs->dev->info.buffers = buffers[1];
      else
        new_devs->dev->info.buffers = 2;
      if (bufsize[1] > 0)
        new_devs->dev->info.wanted_bufsize = bufsize[1];
      else
        new_devs->dev->info.wanted_bufsize = DEFAULT_BUFSIZE;
      if (queued_reads[1] >= 0)
        new_devs->dev->info.queued_reads = queued_reads[1];
      else
        new_devs->dev->info.queued_reads = 0;
      if (line[strlen(line)-1] == '\n')
        line[strlen(line)-1] = 0;
      np = new_devs->next;
      free(new_devs);
      new_devs = np;
    }
  while (new_dev_pool)
    {
      np = new_dev_pool->next;
      free(new_dev_pool);
      new_dev_pool = np;
    }
  fclose(fp);
  DBG (10, ">>\n");
  return (SANE_STATUS_GOOD);
}

void
sane_exit (void)
{
  NEC_Device *dev, *next;
  DBG (10, "<< sane_exit ");

  for (dev = first_dev; dev; dev = next)
    {
      next = dev->next;
      free ((void *) dev->sane.name);
      free ((void *) dev->sane.model);
      free (dev);
    }
  first_dev = 0;

  if (devlist)
    free(devlist);

  DBG (10, ">>\n");
}

SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  NEC_Device *dev;
  int i;
  DBG (10, "<< sane_get_devices ");

  local_only = local_only; /* silence compilation warnings */

  if (devlist)
    free (devlist);
  devlist = malloc ((num_devices + 1) * sizeof (devlist[0]));
  if (!devlist)
    return (SANE_STATUS_NO_MEM);

  i = 0;
  for (dev = first_dev; dev; dev = dev->next)
    devlist[i++] = &dev->sane;
  devlist[i++] = 0;

  *device_list = devlist;

  DBG (10, ">>\n");
  return SANE_STATUS_GOOD;
}

SANE_Status
sane_open (SANE_String_Const devnam, SANE_Handle * handle)
{
  SANE_Status status;
  NEC_Device *dev;
  NEC_Scanner *s;
#ifdef USE_CUSTOM_GAMMA
  int i, j;
#endif

  DBG (10, "<< sane_open ");

  if (devnam[0])
    {
      for (dev = first_dev; dev; dev = dev->next)
	{
	  if (strcmp (dev->sane.name, devnam) == 0)
	    break;
	}

      if (!dev)
	{
	  status = attach (devnam, &dev);
	  if (status != SANE_STATUS_GOOD)
	    return (status);
	}
    }
  else
    {
      dev = first_dev;
    }

  if (!dev)
    return (SANE_STATUS_INVAL);

  s = malloc (sizeof (*s));
  if (!s)
    return SANE_STATUS_NO_MEM;
  memset (s, 0, sizeof (*s));

  s->fd = -1;
  s->dev = dev;
  
  s->buffer = 0;  
#ifdef USE_CUSTOM_GAMMA
  for (i = 0; i < 4; ++i)
    for (j = 0; j < 256; ++j)
      s->gamma_table[i][j] = j;
#endif
  status = init_options (s);
  if (status != SANE_STATUS_GOOD)
    {
      /* xxx clean up mallocs */
      return status;
    }

  s->next = first_handle;
  first_handle = s;

  *handle = s;

  DBG (10, ">>\n");
  return SANE_STATUS_GOOD;
}

void
sane_close (SANE_Handle handle)
{
  NEC_Scanner *s = (NEC_Scanner *) handle;
  DBG (10, "<< sane_close ");

  if (s->fd != -1)
    sanei_scsi_close (s->fd);
#ifdef USE_FORK
  {
    struct shmid_ds ds;
    if (s->shmid != -1)
      shmctl(s->shmid, IPC_RMID, &ds);
  }
#endif
  if (s->buffer)
    free(s->buffer);
  free (s);

  DBG (10, ">>\n");
}

const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  NEC_Scanner *s = handle;
  DBG (10, "<< sane_get_option_descriptor ");

  if ((unsigned) option >= NUM_OPTIONS)
    return (0);

  DBG (10, ">>\n");
  return (s->opt + option);
}

SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
		     SANE_Action action, void *val, SANE_Int * info)
{
  NEC_Scanner *s = handle;
  SANE_Status status;
#ifdef USE_CUSTOM_GAMMA
  SANE_Word w, cap;
#else
  SANE_Word cap;
#endif
  int range_index;
  DBG (10, "<< sane_control_option %i", option);

  if (info)
    *info = 0;

  if (s->scanning)
    return (SANE_STATUS_DEVICE_BUSY);
  if (option >= NUM_OPTIONS)
    return (SANE_STATUS_INVAL);

  cap = s->opt[option].cap;
  if (!SANE_OPTION_IS_ACTIVE (cap))
    return (SANE_STATUS_INVAL);

  if (action == SANE_ACTION_GET_VALUE)
    {
      switch (option)
	{
	  /* word options: */
	case OPT_RESOLUTION:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	case OPT_NUM_OPTS:
	case OPT_THRESHOLD:
	case OPT_TINT:
	case OPT_COLOR:
#ifdef USE_COLOR_THRESHOLD
	case OPT_THRESHOLD_R:
	case OPT_THRESHOLD_G:
	case OPT_THRESHOLD_B:
#endif
	case OPT_OR:
	case OPT_NR:
	case OPT_EDGE:
	case OPT_PREVIEW:
#ifdef USE_CUSTOM_GAMMA
	case OPT_CUSTOM_GAMMA:
#endif
	  *(SANE_Word *) val = s->val[option].w;
#if 0 /* here, values are read; reload should not be necessary */
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
#endif
	  return (SANE_STATUS_GOOD);

#ifdef USE_CUSTOM_GAMMA
	  /* word-array options: */
	case OPT_GAMMA_VECTOR:
	case OPT_GAMMA_VECTOR_R:
	case OPT_GAMMA_VECTOR_G:
	case OPT_GAMMA_VECTOR_B:
	  memcpy (val, s->val[option].wa, s->opt[option].size);
	  return SANE_STATUS_GOOD;
#endif

	  /* string options: */
	case OPT_MODE:
	case OPT_HALFTONE:
	case OPT_PAPER:
	case OPT_GAMMA:
#ifdef USE_RESOLUTION_LIST
	case OPT_RESOLUTION_LIST:
#endif
	case OPT_EDGE_EMPHASIS:
	case OPT_LIGHTCOLOR:
	case OPT_SCANSOURCE:
	  strcpy (val, s->val[option].s);
#if 0
	  if (info)
	    *info |= SANE_INFO_RELOAD_PARAMS;
#endif

	  return (SANE_STATUS_GOOD);

	}
    }
  else if (action == SANE_ACTION_SET_VALUE)
    {
      if (!SANE_OPTION_IS_SETTABLE (cap))
	return (SANE_STATUS_INVAL);

      status = sanei_constrain_value (s->opt + option, val, info);
      if (status != SANE_STATUS_GOOD)
	return status;

      switch (option)
	{
	  /* (mostly) side-effect-free word options: */
	case OPT_RESOLUTION:
	case OPT_TL_X:
	case OPT_TL_Y:
	case OPT_BR_X:
	case OPT_BR_Y:
	  if (info && s->val[option].w != *(SANE_Word *) val)
	    *info |= SANE_INFO_RELOAD_PARAMS;
	case OPT_NUM_OPTS:
	case OPT_THRESHOLD:
	  /* xxx theoretically, we could use OPT_THRESHOLD in 
	     bi-level color mode to adjust all three other
	     threshold together. But this would require to set
	     the bit SANE_INFO_RELOAD_OPTIONS in *info, and that
	     would unfortunately cause a crash in both xscanimage
	     and xsane... Therefore, OPT_THRESHOLD is disabled
	     for bi-level color scan right now.
	  */
	case OPT_TINT:
	case OPT_COLOR:
#ifdef USE_COLOR_THRESHOLD
	case OPT_THRESHOLD_R:
	case OPT_THRESHOLD_G:
	case OPT_THRESHOLD_B:
#endif
	case OPT_OR:
	case OPT_NR:
	case OPT_EDGE:
	case OPT_PREVIEW:
	  s->val[option].w = *(SANE_Word *) val;
	  return (SANE_STATUS_GOOD);

	case OPT_MODE:
	  if (strcmp (val, M_LINEART) == 0)
	    {
	      s->opt[OPT_LIGHTCOLOR].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_TINT].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_COLOR].cap |= SANE_CAP_INACTIVE;
#ifdef USE_COLOR_THRESHOLD
	      s->opt[OPT_THRESHOLD_R].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD_G].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD_B].cap |= SANE_CAP_INACTIVE;
#endif
	      if (s->dev->sensedat.model == PCIN500)
                s->opt[OPT_HALFTONE].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else if (strcmp (val, M_LINEART_COLOR) == 0)
	    {
	      s->opt[OPT_LIGHTCOLOR].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_TINT].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_COLOR].cap |= SANE_CAP_INACTIVE;
#ifdef USE_COLOR_THRESHOLD
	      s->opt[OPT_THRESHOLD_R].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD_G].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD_B].cap &= ~SANE_CAP_INACTIVE;
#endif
	      if (s->dev->sensedat.model == PCIN500)
                s->opt[OPT_HALFTONE].cap &= ~SANE_CAP_INACTIVE;
	    }
	  else if (strcmp (val, M_GRAY) == 0)
	    {
	      s->opt[OPT_LIGHTCOLOR].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_TINT].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_COLOR].cap |= SANE_CAP_INACTIVE;
#ifdef USE_COLOR_THRESHOLD
	      s->opt[OPT_THRESHOLD_R].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD_G].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD_B].cap |= SANE_CAP_INACTIVE;
#endif
              s->opt[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE;
	    }
	  else
	    {
	      s->opt[OPT_LIGHTCOLOR].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_TINT].cap &= ~SANE_CAP_INACTIVE;
	      s->opt[OPT_COLOR].cap &= ~SANE_CAP_INACTIVE;
#ifdef USE_COLOR_THRESHOLD
	      s->opt[OPT_THRESHOLD_R].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD_G].cap |= SANE_CAP_INACTIVE;
	      s->opt[OPT_THRESHOLD_B].cap |= SANE_CAP_INACTIVE;
#endif
              s->opt[OPT_HALFTONE].cap |= SANE_CAP_INACTIVE;
            }
#if 0          
	  if (   strcmp (val, M_LINEART) == 0 
	      || strcmp (val, M_GRAY) == 0)
            {
	      s->opt[OPT_LIGHTCOLOR].cap &= ~SANE_CAP_INACTIVE;
            }
          else
            {
	      s->opt[OPT_LIGHTCOLOR].cap |= SANE_CAP_INACTIVE;
            }
#endif            
          strcpy(s->val[option].s, val);
#ifdef USE_CUSTOM_GAMMA
          set_gamma_caps(s);
#endif
          if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
	  return (SANE_STATUS_GOOD);

	case OPT_GAMMA:
	case OPT_HALFTONE:
	case OPT_EDGE_EMPHASIS:
	case OPT_LIGHTCOLOR:
#if 0
	  if (s->val[option].s)
	    free (s->val[option].s);
	  s->val[option].s = strdup (val);
#endif
          strcpy(s->val[option].s, val);
	  return (SANE_STATUS_GOOD);

	case OPT_SCANSOURCE:
	  if (info && strcmp (s->val[option].s, (SANE_String) val))
	    *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
#if 0
	  if (s->val[option].s)
	    free (s->val[option].s);
	  s->val[option].s = strdup (val);
#endif
          strcpy(s->val[option].s, val);
	  if (strcmp(val, use_fsu) == 0)
	    range_index = SCAN_WITH_FSU;
	  else if (strcmp(val, use_adf) == 0)
	    range_index = SCAN_WITH_ADF;
	  else
	    range_index = SCAN_SIMPLE;

          s->opt[OPT_TL_X].constraint.range 
            = &s->dev->info.tl_x_ranges[range_index];
          clip_value (&s->opt[OPT_TL_X], &s->val[OPT_TL_X].w);

          s->opt[OPT_TL_Y].constraint.range 
            = &s->dev->info.tl_y_ranges[range_index];
          clip_value (&s->opt[OPT_TL_Y], &s->val[OPT_TL_Y].w);

          s->opt[OPT_BR_X].constraint.range 
            = &s->dev->info.br_x_ranges[range_index];
          clip_value (&s->opt[OPT_BR_X], &s->val[OPT_BR_X].w);

          s->opt[OPT_BR_Y].constraint.range 
            = &s->dev->info.br_y_ranges[range_index];
          clip_value (&s->opt[OPT_BR_Y], &s->val[OPT_BR_Y].w);

	  return (SANE_STATUS_GOOD);

	case OPT_PAPER:
          if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
#if 0
	  if (s->val[option].s)
	    free (s->val[option].s);
	  s->val[option].s = strdup (val);
#endif
          strcpy(s->val[option].s, val);
	  s->val[OPT_TL_X].w = SANE_FIX(0);
	  s->val[OPT_TL_Y].w = SANE_FIX(0);
	  if (strcmp (s->val[option].s, "A3") == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(297);
	      s->val[OPT_BR_Y].w = SANE_FIX(420);
	  }else if (strcmp (s->val[option].s, "A4") == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(210);
	      s->val[OPT_BR_Y].w = SANE_FIX(297);
	  }else if (strcmp (s->val[option].s, "A5") == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(148.5);
	      s->val[OPT_BR_Y].w = SANE_FIX(210);
	  }else if (strcmp (s->val[option].s, "A6") == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(105);
	      s->val[OPT_BR_Y].w = SANE_FIX(148.5);
	  }else if (strcmp (s->val[option].s, "B4") == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(250);
	      s->val[OPT_BR_Y].w = SANE_FIX(353);
	  }else if (strcmp (s->val[option].s, "B5") == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(182);
	      s->val[OPT_BR_Y].w = SANE_FIX(257);
	  }else if (strcmp (s->val[option].s, W_LETTER) == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(279.4);
	      s->val[OPT_BR_Y].w = SANE_FIX(431.8);
	  }else if (strcmp (s->val[option].s, "Legal") == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(215.9);
	      s->val[OPT_BR_Y].w = SANE_FIX(355.6);
	  }else if (strcmp (s->val[option].s, "Letter") == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(215.9);
	      s->val[OPT_BR_Y].w = SANE_FIX(279.4);
	  }else if (strcmp (s->val[option].s, INVOICE) == 0){
	      s->val[OPT_BR_X].w = SANE_FIX(215.9);
	      s->val[OPT_BR_Y].w = SANE_FIX(139.7);
	  }else{
	  }
	  return (SANE_STATUS_GOOD);

#ifdef USE_RESOLUTION_LIST
	case OPT_RESOLUTION_LIST:
	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS | SANE_INFO_RELOAD_PARAMS;
#if 0
	  if (s->val[option].s)
	    free (s->val[option].s);
	  s->val[option].s = strdup (val);
#endif
	  for (i = 0; s->opt[OPT_RESOLUTION_LIST].constraint.string_list[i]; i++) {
	    if (strcmp (val, 
	          s->opt[OPT_RESOLUTION_LIST].constraint.string_list[i]) == 0){
	      s->val[OPT_RESOLUTION].w 
	        = atoi(s->opt[OPT_RESOLUTION_LIST].constraint.string_list[i]);
	      if (info)
	        *info |= SANE_INFO_RELOAD_PARAMS;
	      break;
	    }
	  }
	  return (SANE_STATUS_GOOD);
#endif

#ifdef USE_CUSTOM_GAMMA
	  /* side-effect-free word-array options: */
	case OPT_GAMMA_VECTOR:
	case OPT_GAMMA_VECTOR_R:
	case OPT_GAMMA_VECTOR_G:
	case OPT_GAMMA_VECTOR_B:
	  memcpy (s->val[option].wa, val, s->opt[option].size);
	  return SANE_STATUS_GOOD;

	case OPT_CUSTOM_GAMMA:
	  w = *(SANE_Word *) val;

	  if (w == s->val[OPT_CUSTOM_GAMMA].w)
	    return SANE_STATUS_GOOD;		/* no change */

	  if (info)
	    *info |= SANE_INFO_RELOAD_OPTIONS;
	  s->val[OPT_CUSTOM_GAMMA].w = w;
          set_gamma_caps(s);
	  return SANE_STATUS_GOOD;
#endif
	}
    }

  DBG (10, ">>\n");
  return (SANE_STATUS_INVAL);
}

SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
  int width, length, res;
  const char *mode;
  NEC_Scanner *s = handle;
  DBG (10, "<< sane_get_parameters ");

  res = s->val[OPT_RESOLUTION].w * s->dev->info.res_range.quant;
  if (!s->scanning)
    {
      /* make best-effort guess at what parameters will look like once
         scanning starts.  */
      memset (&s->params, 0, sizeof (s->params));

      width = MM_TO_PIX(  SANE_UNFIX(s->val[OPT_BR_X].w) 
                        - SANE_UNFIX(s->val[OPT_TL_X].w),
			s->dev->info.mud);
      length = MM_TO_PIX(  SANE_UNFIX(s->val[OPT_BR_Y].w)
                          - SANE_UNFIX(s->val[OPT_TL_Y].w),
			 s->dev->info.mud);

      s->width = width;
      s->length = length;
      s->params.pixels_per_line = width * res / s->dev->info.mud;
      s->params.lines = length * res / s->dev->info.mud;

      if (s->dev->sensedat.model == PCIN500)
	{
	  s->params.pixels_per_line += 1;
	  s->params.lines += 1;
	}
      s->unscanned_lines = s->params.lines;
    }
#if 0
  else
    {
      buffer_status bs;
      SANE_Status status;
      size_t len = sizeof (buffer_status);
      status = get_data_buffer_status (s->fd, &bs, &len);
      DBG (11, "<< get_data_buffer_status ");

      if (status != SANE_STATUS_GOOD)
	{
	  do_cancel(s);
	  return (status);
	}
      DBG (11, ">>\n ");
      {
#ifdef DEBUG_NEC
	int i;
	u_char *buf = &bs;
	DBG(11, "get data buffer status(debug):\n");
	for (i = 0; i < len; i += 16)
	  {
	    DBG(1, "%2x %2x %2x %2x %2x %2x %2x %2x - %2x %2x %2x %2x \n",
		buf[i], buf[i+1], buf[i+2], buf[i+3], buf[i+4], buf[i+5], buf[i+6], buf[i+7], buf[i+8],
		buf[i+9], buf[i+10], buf[i+11]);
	  }
#endif
      }
    }
#endif
  res = s->val[OPT_RESOLUTION].w * s->dev->info.res_range.quant;

  mode = s->val[OPT_MODE].s;

  if (strcmp (mode, M_LINEART) == 0)
     {
       s->params.format = SANE_FRAME_GRAY;
       s->params.bytes_per_line = (s->params.pixels_per_line + 7) / 8;
       s->params.depth = 1;
       s->modes = MODES_LINEART;
     }
  else if (strcmp (mode, M_GRAY) == 0)
     {
       s->params.format = SANE_FRAME_GRAY;
       s->params.bytes_per_line = s->params.pixels_per_line;
       s->params.depth = 8;
       s->modes = MODES_GRAY;
     }
  else if (strcmp (mode, M_LINEART_COLOR) == 0)
    {
       s->params.format = SANE_FRAME_RGB;
       s->params.bytes_per_line = 3 * (s->params.pixels_per_line + 7) / 8;
       s->params.depth = 8;
       s->modes = MODES_LINEART_COLOR;
    }
  else
     {
       s->params.format = SANE_FRAME_RGB;
       s->params.bytes_per_line = 3 * s->params.pixels_per_line;
       s->params.depth = 8;
       s->modes = MODES_COLOR;
     }
  s->params.last_frame = SANE_TRUE;

  if (params)
    *params = s->params;

  DBG (10, ">>\n");
  return (SANE_STATUS_GOOD);
}

#ifdef USE_CUSTOM_GAMMA

static int
sprint_gamma(Option_Value val, SANE_Byte *dst)
{
  int i;
  SANE_Byte *p = dst;
  
  p += sprintf((char *) p, "%i", val.wa[0]);
  for (i = 1; i < 256; i++)
    p += sprintf((char *) p, ",%i", val.wa[i] > 255 ? 255 : val.wa[i]);
  return p - dst;
}

static SANE_Status
send_ascii_gamma_tables (NEC_Scanner *s)
{
  SANE_Status status;
  int i;
  
  DBG(11, "<< send_ascii_gamma_tables ");
    
  /* we need: 4 bytes for each gamma value (3 digits + delimiter)
             + 10 bytes for the command header
     i.e. 4 * 4 * 256 + 10 = 4106 bytes
  */
  
  if (s->dev->info.bufsize < 4106)
    return SANE_STATUS_NO_MEM;
  
  memset(s->buffer, 0, 4106);

  i = sprint_gamma(s->val[OPT_GAMMA_VECTOR_R], &s->buffer[10]);
  s->buffer[10+i++] = '/';
  i += sprint_gamma(s->val[OPT_GAMMA_VECTOR_G], &s->buffer[10+i]);
  s->buffer[10+i++] = '/';
  i += sprint_gamma(s->val[OPT_GAMMA_VECTOR_B], &s->buffer[10+i]);
  s->buffer[10+i++] = '/';
  i += sprint_gamma(s->val[OPT_GAMMA_VECTOR], &s->buffer[10+i]);
  
  DBG(12, "%s\n", &s->buffer[10]);

  s->buffer[0] = SEND;
  s->buffer[2] = 0x03;
  s->buffer[7] = i >> 8;
  s->buffer[8] = i & 0xff;
  
  wait_ready(s->fd);
  status = sanei_scsi_cmd (s->fd, s->buffer, i+10, 0, 0);

  DBG(11, ">>\n");
  
  return status;
}
#endif

static SANE_Status
send_binary_g_table(NEC_Scanner *s, SANE_Word *a, int dtq)
{
  SANE_Status status;
  unsigned int i, j;
  
  dtq = dtq; /* silence compilation warnings */
  
  DBG(11, "<< send_binary_g_table\n");

  i = 256;
  if (s->dev->info.bufsize < i)
    return SANE_STATUS_NO_MEM;
  memset(s->buffer, 0, i+10);

  s->buffer[0] = SEND;
  s->buffer[2] = 0x03;
  s->buffer[7] = i >> 8;
  s->buffer[8] = i & 0xff;
  
  for (i = 0; i < 256; i++)
    {
      s->buffer[i+11] = a[i&0xff] & 0xff;
    }
  
  for (j = 0; j < 256; j += 16)
    {
      DBG(11, "%02x %02x %02x %02x %02x %02x %02x %02x "
              "%02x %02x %02x %02x %02x %02x %02x %02x\n",
              a[j  ], a[j+1], a[j+2], a[j+3], 
              a[j+4], a[j+5], a[j+6], a[j+7],
              a[j+8], a[j+9], a[j+10], a[j+11], 
              a[j+12], a[j+13], a[j+14], a[j+15]);
    }
  DBG(12, "transfer length = %d\n", i);
  DBG(12, "buffer[7] = %d\n", s->buffer[7]);
  DBG(12, "buffer[8] = %d\n", s->buffer[8]);

  /*  wait_ready(s->fd); */
  status = sanei_scsi_cmd (s->fd, s->buffer, i+10, 0, 0);

  DBG(11, ">>\n");

  return status;
}

#ifdef USE_CUSTOM_GAMMA
static SANE_Status
send_binary_gamma_tables (NEC_Scanner *s)
{
  SANE_Status status;
  
  status = send_binary_g_table(s, s->val[OPT_GAMMA_VECTOR].wa, 0x10);
  if (status != SANE_STATUS_GOOD)
    return status;
  DBG(11, "send_binary_gamma_tables\n");
#if 0  
  status = send_binary_g_table(s, s->val[OPT_GAMMA_VECTOR_R].wa, 0x11);
  if (status != SANE_STATUS_GOOD)
    return status;
  
  status = send_binary_g_table(s, s->val[OPT_GAMMA_VECTOR_G].wa, 0x12);
  if (status != SANE_STATUS_GOOD)
    return status;
  
  status = send_binary_g_table(s, s->val[OPT_GAMMA_VECTOR_B].wa, 0x13);
#endif
  return status;
}

static SANE_Status
send_gamma_tables (NEC_Scanner *s)
{
  if (s->dev->sensedat.model == PCIN500)
    {
      return send_binary_gamma_tables(s);
    }
  else
    {
      return send_ascii_gamma_tables(s);
    }
  
}
#endif

#ifdef USE_COLOR_THRESHOLD
/* not used? */
#if 0
static SANE_Status
send_threshold_data(NEC_Scanner *s)
{
  SANE_Status status;
  SANE_Byte cmd[26] = {SEND, 0, 0x82, 0, 0, 0, 0, 0, 0, 0};
  int len;
  
  memset(cmd, 0, sizeof(cmd));
  /* maximum string length: 3 bytes for each number (they are 
     restricted to the range 0..255), 3 '/' and the null-byte,
     total: 16 bytes.
  */
  len = sprintf((char *) &cmd[10], "%i/%i/%i/%i", 
                s->val[OPT_THRESHOLD_R].w,
                s->val[OPT_THRESHOLD_G].w,
                s->val[OPT_THRESHOLD_B].w,
                s->val[OPT_THRESHOLD].w);
  cmd[8] = len;
  
  wait_ready(s->fd);
  status = sanei_scsi_cmd(s->fd, cmd, len + 10, 0, 0);
  return status;
}
#endif
#endif

SANE_Status
sane_start (SANE_Handle handle)
{
  char *mode, *halftone, *gamma, *edge, *lightcolor, *adf_fsu;
  NEC_Scanner *s = handle;
  SANE_Status status;
  size_t buf_size;
  window_param wp;

  DBG (10, "<< sane_start ");

  /* First make sure we have a current parameter set.  Some of the
     parameters will be overwritten below, but that's OK.  */
  status = sane_get_parameters (s, 0);
  if (status != SANE_STATUS_GOOD)
    return status;

  s->dev->sensedat.complain_on_adf_error = 1;

#ifdef HAVE_SANEI_SCSI_OPEN_EXTENDED
  s->dev->info.bufsize = s->dev->info.wanted_bufsize;
  if (s->dev->info.bufsize < 32 * 1024)
    s->dev->info.bufsize = 32 * 1024;
  {
    int bsize = s->dev->info.bufsize;
    status = sanei_scsi_open_extended (s->dev->sane.name, &s->fd, 
              &sense_handler, &s->dev->sensedat, &bsize);
    s->dev->info.bufsize = bsize;
  }

  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "open of %s failed: %s\n",
         s->dev->sane.name, sane_strstatus (status));
      return (status);
    }

  /* make sure that we got at least 32 kB. Even then, the scan will be
     awfully slow. 
     
  */
  if (s->dev->info.bufsize < 32 * 1024)
    {
      sanei_scsi_close(s->fd);
      s->fd = -1;
      return SANE_STATUS_NO_MEM;
    }
#else
  status = sanei_scsi_open(s->dev->sane.name, &s->fd, &sense_handler, 
              &s->dev->sensedat);
  if (s->dev->info.wanted_bufsize < sanei_scsi_max_request_size)
    s->dev->info.bufsize = s->dev->info.wanted_bufsize;
  else
    s->dev->info.bufsize = sanei_scsi_max_request_size;
    
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "open of %s failed: %s\n",
         s->dev->sane.name, sane_strstatus (status));
      return (status);
    }
#endif

  s->buffer = malloc(s->dev->info.bufsize);
  if (!s->buffer) {
    sanei_scsi_close(s->fd);
    s->fd = -1;
    free(s);
    return SANE_STATUS_NO_MEM;
  }

#ifdef USE_FORK
  {
    struct shmid_ds ds;
    size_t n;

    s->shmid = shmget(IPC_PRIVATE,
       sizeof(NEC_rdr_ctl)
       + s->dev->info.buffers *
         (sizeof(NEC_shmem_ctl) + s->dev->info.bufsize),
       IPC_CREAT | 0600);
    if (s->shmid == -1)
      {
        free(s->buffer);
        s->buffer = 0;
        sanei_scsi_close(s->fd);
        s->fd = -1;
        return SANE_STATUS_NO_MEM;
      }
    s->rdr_ctl = (NEC_rdr_ctl*) shmat(s->shmid, 0, 0);
    if ((int)s->rdr_ctl == -1)
     {
       shmctl(s->shmid, IPC_RMID, &ds);
       free(s->buffer);
       s->buffer = 0;
       sanei_scsi_close(s->fd);
       s->fd = -1;
       return SANE_STATUS_NO_MEM;
     }

    s->rdr_ctl->buf_ctl = (NEC_shmem_ctl*) &s->rdr_ctl[1];
    for (n = 0; n < s->dev->info.buffers; n++)
      {
        s->rdr_ctl->buf_ctl[n].buffer =
          (SANE_Byte*) &s->rdr_ctl->buf_ctl[s->dev->info.buffers]
            + n * s->dev->info.bufsize;
      }
  }
#endif /* USE_FORK */

  DBG (5, "start: TEST_UNIT_READY\n");
  status = test_unit_ready (s->fd);
  
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "TEST UNIT READY failed: %s\n", sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }

  DBG (3, "start: sending MODE SELECT\n");
  status = mode_select_mud (s->fd, s->dev->info.mud);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "start: MODE_SELECT6 failed\n");
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }

  mode = s->val[OPT_MODE].s;
  halftone = s->val[OPT_HALFTONE].s;
  gamma = s->val[OPT_GAMMA].s;
  edge = s->val[OPT_EDGE_EMPHASIS].s;
  lightcolor = s->val[OPT_LIGHTCOLOR].s;
  adf_fsu = s->val[OPT_SCANSOURCE].s;

  if (s->val[OPT_PREVIEW].w == SANE_FALSE)
    {
      s->res = s->val[OPT_RESOLUTION].w * s->dev->info.res_range.quant;
    }
  else
    {
      s->res = 75;
    }
  s->ulx = MM_TO_PIX(SANE_UNFIX(s->val[OPT_TL_X].w), s->dev->info.mud);
  s->uly = MM_TO_PIX(SANE_UNFIX(s->val[OPT_TL_Y].w), s->dev->info.mud);
  s->threshold = s->val[OPT_THRESHOLD].w;

  if (strcmp (mode, M_LINEART_COLOR) == 0)
    s->bpp = 1;
  else
    s->bpp = s->params.depth;

  s->adf_fsu_mode = SCAN_SIMPLE; /* default: scan without ADF and FSU */

  if (strcmp(adf_fsu, use_fsu) == 0)
    s->adf_fsu_mode = SCAN_WITH_FSU;
  else if (strcmp(adf_fsu, use_adf) == 0)
    s->adf_fsu_mode = SCAN_WITH_ADF;
  else if (strcmp(adf_fsu, use_adf) == 0)
    s->adf_fsu_mode = SCAN_SIMPLE;

  /* halftone must not set to be zero PC-IN500 */
  s->halftone = 0x01;
  if (strcmp (mode, M_LINEART) == 0)
    {
      s->reverse = 0;
      s->image_composition = 1;
      if (strcmp(halftone, M_BILEVEL) == 0)
	{
	  s->image_composition = 0;
	  s->halftone = 0x01;
	}
      else if (strcmp(halftone, M_DITHER1) == 0)
	s->halftone = 0x01;
      else if (strcmp(halftone, M_DITHER2) == 0)
	s->halftone = 0x10;
      else if (strcmp(halftone, M_DITHER3) == 0)
	s->halftone = 0x20;
    }
  else if (strcmp (mode, M_GRAY) == 0)
    {
      s->image_composition = 2;
      s->reverse = 1;
    }
  else if (strcmp (mode, M_LINEART_COLOR) == 0)
    {
      s->reverse = 1;
      s->image_composition = 4;
      if (strcmp(halftone, M_BILEVEL) == 0)
	{
	  s->image_composition = 3;
	  s->halftone = 0x01;
	}
      else if (strcmp(halftone, M_DITHER1) == 0)
	s->halftone = 0x01;
      else if (strcmp(halftone, M_DITHER2) == 0)
	s->halftone = 0x10;
      else if (strcmp(halftone, M_DITHER3) == 0)
	s->halftone = 0x20;
    }
  else if (strcmp (mode, M_COLOR) == 0)
    {
      s->image_composition = 5;
      s->reverse = 1;
    }

  if (s->dev->sensedat.model == PCIN500)
    {
      s->or = s->val[OPT_OR].w;
      s->nr = s->val[OPT_NR].w;
      s->edge = s->val[OPT_EDGE].w;
    }
  else
    {
      if (strcmp (edge, EDGE_NONE) == 0)
	s->edge = 0;
      else if (strcmp (edge, EDGE_MIDDLE) == 0)
	s->edge = 1;
      else if (strcmp (edge, EDGE_STRONG) == 0)
	s->edge = 2;
      else if (strcmp (edge, EDGE_BLUR) == 0)
	s->edge = 3;
    }
  
  s->lightcolor = 3;
  if (strcmp(lightcolor, LIGHT_GREEN) == 0)
    s->lightcolor = 0;
  else if (strcmp(lightcolor, LIGHT_RED) == 0)
    s->lightcolor = 1;
  else if (strcmp(lightcolor, LIGHT_BLUE) == 0)
    s->lightcolor = 2;
  else if (strcmp(lightcolor, LIGHT_NONE) == 0)
    s->lightcolor = 3;

  s->adf_scan = 0;
  
#ifdef USE_CUSTOM_GAMMA
  if (s->val[OPT_CUSTOM_GAMMA].w == SANE_FALSE)
    {
#endif
      if (s->dev->sensedat.model == PCIN500)
	{
	  if (strcmp (gamma, CRT1))
	    s->gamma = 1;
	  else if (strcmp (gamma, CRT2))
	    s->gamma = 2;
	  else if (strcmp (gamma, PRINTER1))
	    s->gamma = 3;
	  else if (strcmp (gamma, PRINTER2))
	    s->gamma = 4;
	  else if (strcmp (gamma, NONE))
	    s->gamma = 5;
	}
#if 0
      if (s->dev->sensedat.model != PCIN500)
        {
          ss.dtc = 0x03;
          if (strcmp (gamma, GAMMA10) == 0)
	    ss.dtq = 0x01;
          else
	    ss.dtq = 0x02;
          ss.length = 0;
          DBG (5, "start: SEND\n");
          status = send (s->fd,  &ss);
          if (status != SANE_STATUS_GOOD)
            {
              DBG (1, "send failed: %s\n", sane_strstatus (status));
              sanei_scsi_close (s->fd);
              s->fd = -1;
              return (status);
            }
       }
     else
#else
       {
         int i;
         SANE_Word gtbl[256];
#if 0
         if (strcmp (gamma, GAMMA10) == 0)
           for (i = 0; i < 256; i++)
             gtbl[i] = i;
         else
#endif
           {
             gtbl[0] = 0;
             for (i = 1; i < 256; i++)
               gtbl[i] = 255 * exp(0.45 * log(i/255.0));
           }
         send_binary_g_table(s, gtbl, 0x10);
         send_binary_g_table(s, gtbl, 0x11);
         send_binary_g_table(s, gtbl, 0x12);
         send_binary_g_table(s, gtbl, 0x13);
       }
#endif /* DEBUG_NEC */
#ifdef USE_CUSTOM_GAMMA
    }
  else
    {
      s->gamma = 9;
      status = send_gamma_tables(s);
    }
  if (status != SANE_STATUS_GOOD)
    {
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }
#endif

  s->tint = s->val[OPT_TINT].w;
  s->color = s->val[OPT_COLOR].w;

  memset (&wp, 0, sizeof (wp));
  /* every NEC scanner seems to have a different 
     window descriptor block...
  */
  if (s->dev->sensedat.model == PCIN500)
    buf_size = sizeof(WDB) + sizeof(WDBX500);
  else
    buf_size = sizeof(WDB);
    
  wp.wpdh.wdl[0] = buf_size >> 8;
  wp.wpdh.wdl[1] = buf_size;
  wp.wdb.x_res[0] = s->res >> 8;
  wp.wdb.x_res[1] = s->res;
  wp.wdb.y_res[0] = s->res >> 8;
  wp.wdb.y_res[1] = s->res;
  wp.wdb.x_ul[0] = s->ulx >> 24;
  wp.wdb.x_ul[1] = s->ulx >> 16;
  wp.wdb.x_ul[2] = s->ulx >> 8;
  wp.wdb.x_ul[3] = s->ulx;
  wp.wdb.y_ul[0] = s->uly >> 24;
  wp.wdb.y_ul[1] = s->uly >> 16;
  wp.wdb.y_ul[2] = s->uly >> 8;
  wp.wdb.y_ul[3] = s->uly;
  wp.wdb.width[0] = s->width >> 24;
  wp.wdb.width[1] = s->width >> 16;
  wp.wdb.width[2] = s->width >> 8;
  wp.wdb.width[3] = s->width;
  wp.wdb.length[0] = s->length >> 24;
  wp.wdb.length[1] = s->length >> 16;
  wp.wdb.length[2] = s->length >> 8;
  wp.wdb.length[3] = s->length;
  wp.wdb.brightness = 0;
  wp.wdb.threshold = s->threshold;
  wp.wdb.brightness = 128;
  wp.wdb.contrast = 128;
  wp.wdb.image_composition = s->image_composition;
  if (s->image_composition <= 2 || s->image_composition >= 5)
    wp.wdb.bpp = s->bpp;
  else
    wp.wdb.bpp = 1;
  wp.wdb.ht_pattern[0] = 0;
  wp.wdb.ht_pattern[1] = 0;
  if (s->dev->sensedat.model == PCIN500)
    wp.wdb.ht_pattern[1] = s->halftone;
  wp.wdb.rif_padding = (s->reverse * 128) + 0;

  if (s->dev->sensedat.model == PCIN500)
    {
      wp.wdbx500.data_length = 0x07;
      wp.wdbx500.control = 0 | (s->or << 7) | (s->edge << 6) | (s->nr << 5) |
	                   (s->lightcolor << 2);
      wp.wdbx500.format = 0;
      wp.wdbx500.gamma = s->gamma;
      wp.wdbx500.tint = s->tint;
      wp.wdbx500.color = s->color;
      wp.wdbx500.reserved1 = 0;
      wp.wdbx500.reserved2 = 0;
    }

  DBG (5, "wdl=%d\n", (wp.wpdh.wdl[0] << 8) + wp.wpdh.wdl[1]);
  DBG (5, "xres=%d\n", (wp.wdb.x_res[0] << 8) + wp.wdb.x_res[1]);
  DBG (5, "yres=%d\n", (wp.wdb.y_res[0] << 8) + wp.wdb.y_res[1]);
  DBG (5, "ulx=%d\n", (wp.wdb.x_ul[0] << 24) + (wp.wdb.x_ul[1] << 16) +
                      (wp.wdb.x_ul[2] << 8) + wp.wdb.x_ul[3]);
  DBG (5, "uly=%d\n", (wp.wdb.y_ul[0] << 24) + (wp.wdb.y_ul[1] << 16) +
                      (wp.wdb.y_ul[2] << 8) + wp.wdb.y_ul[3]);
  DBG (5, "width=%d\n", (wp.wdb.width[0] << 8) + (wp.wdb.width[1] << 16) +
                        (wp.wdb.width[2] << 8) + wp.wdb.width[3]);
  DBG (5, "length=%d\n", (wp.wdb.length[0] << 16) + (wp.wdb.length[1] << 16) +
                         (wp.wdb.length[2] << 8) + wp.wdb.length[3]);

  DBG (5, "threshold=%d\n", wp.wdb.threshold);
  DBG (5, "image_composition=%d\n", wp.wdb.image_composition);
  DBG (5, "bpp=%d\n", wp.wdb.bpp);
  DBG (5, "rif_padding=%d\n", wp.wdb.rif_padding);

#ifdef DEBUG_NEC
  {
    window_param foo;
    size_t len = buf_size;
    len += sizeof(WPDH);
  DBG (5, "start: GET WINDOW\n");
  status = get_window (s->fd, &foo, &len);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "GET WINDOW failed: %s\n", sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }
#if 1
  {
    unsigned char *p = (unsigned char*) &foo;
    int i;
    DBG(11, "get window(debug):\n");
    for (i = 0; i < len; i += 16)
     {
      DBG(1, "%2x %2x %2x %2x %2x %2x %2x %2x - %2x %2x %2x %2x %2x %2x %2x %2x\n",
      p[i], p[i+1], p[i+2], p[i+3], p[i+4], p[i+5], p[i+6], p[i+7], p[i+8],
      p[i+9], p[i+10], p[i+11], p[i+12], p[i+13], p[i+14], p[i+15]);
     }
  }
#endif
  foo.wpdh.wpdh[1] = 0;
  status = set_window (s->fd, &foo, len);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "SET WINDOW failed: %s\n", sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }
#if 1
  {
    unsigned char *p = (unsigned char*) &foo;
    int i;
    DBG(11, "set window(debug):\n");
    for (i = 0; i < len; i += 16)
     {
      DBG(1, "%2x %2x %2x %2x %2x %2x %2x %2x - %2x %2x %2x %2x %2x %2x %2x %2x\n",
      p[i], p[i+1], p[i+2], p[i+3], p[i+4], p[i+5], p[i+6], p[i+7], p[i+8],
      p[i+9], p[i+10], p[i+11], p[i+12], p[i+13], p[i+14], p[i+15]);
     }
  }
#endif
  }
#endif /* debug_nec */

#ifdef DEBUG_NEC
  {
    unsigned char *p = (unsigned char*) &wp;
    int i;
    DBG(11, "set window(debug):\n");
    for (i = 0; i < sizeof(wp.wdb) + sizeof(wp.wdbx500); i += 16)
     {
      DBG(1, "%2x %2x %2x %2x %2x %2x %2x %2x - %2x %2x %2x %2x %2x %2x %2x %2x\n",
      p[i], p[i+1], p[i+2], p[i+3], p[i+4], p[i+5], p[i+6], p[i+7], p[i+8],
      p[i+9], p[i+10], p[i+11], p[i+12], p[i+13], p[i+14], p[i+15]);
     }
  }
#endif /* debug_nec */

  buf_size += sizeof(WPDH);
  DBG (5, "start: SET WINDOW\n");
  status = set_window (s->fd, &wp, buf_size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "SET WINDOW failed: %s\n", sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }

  memset (&wp, 0, buf_size);
  DBG (5, "start: GET WINDOW\n");
  status = get_window (s->fd, &wp, &buf_size);
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "GET WINDOW failed: %s\n", sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      return (status);
    }
#ifdef DEBUG_NEC
  {
    unsigned char *p = (unsigned char*) &wp;
    int i;
    DBG(11, "get window(debug):\n");
    for (i = 0; i < buf_size; i += 16)
     {
      DBG(1, "%2x %2x %2x %2x %2x %2x %2x %2x - %2x %2x %2x %2x %2x %2x %2x %2x\n",
      p[i], p[i+1], p[i+2], p[i+3], p[i+4], p[i+5], p[i+6], p[i+7], p[i+8],
      p[i+9], p[i+10], p[i+11], p[i+12], p[i+13], p[i+14], p[i+15]);
     }
  }
#endif
  DBG (5, "xres=%d\n", (wp.wdb.x_res[0] << 8) + wp.wdb.x_res[1]);
  DBG (5, "yres=%d\n", (wp.wdb.y_res[0] << 8) + wp.wdb.y_res[1]);
  DBG (5, "ulx=%d\n", (wp.wdb.x_ul[0] << 24) + (wp.wdb.x_ul[1] << 16) +
                      (wp.wdb.x_ul[2] << 8) + wp.wdb.x_ul[3]);
  DBG (5, "uly=%d\n", (wp.wdb.y_ul[0] << 24) + (wp.wdb.y_ul[1] << 16) +
       (wp.wdb.y_ul[2] << 8) + wp.wdb.y_ul[3]);
  DBG (5, "width=%d\n", (wp.wdb.width[0] << 24) + (wp.wdb.width[1] << 16) +
                        (wp.wdb.width[2] << 8) + wp.wdb.width[3]);
  DBG (5, "length=%d\n", (wp.wdb.length[0] << 24) + (wp.wdb.length[1] << 16) +
                         (wp.wdb.length[2] << 8) + wp.wdb.length[3]);
  DBG (5, "start: SCAN\n");
  s->scanning = SANE_TRUE;
  s->busy = SANE_TRUE;
  s->cancel = SANE_FALSE;
  s->get_params_called = 0;

  status = scan (s->fd);
#ifdef DEBUG
          {
            struct timeval t;
            gettimeofday(&t, 0);
            DBG(2, "rd: scan started        %li.%06li\n", t.tv_sec, t.tv_usec);
          }
#endif
  if (status != SANE_STATUS_GOOD)
    {
      DBG (1, "start of scan failed: %s\n", sane_strstatus (status));
      sanei_scsi_close (s->fd);
      s->fd = -1;
      s->busy = SANE_FALSE;
      s->cancel = SANE_FALSE;
      return (status);
    }

  /* ask the scanner for the scan size */
  /* wait_ready(s->fd); */
#ifdef DEBUG
          {
            struct timeval t;
            gettimeofday(&t, 0);
            DBG(2, "rd: wait_ready ok       %li.%06li\n", t.tv_sec, t.tv_usec);
          }
#endif
  sane_get_parameters(s, 0);
#ifdef DEBUG
          {
            struct timeval t;
            gettimeofday(&t, 0);
            DBG(2, "rd: get_params ok       %li.%06li\n", t.tv_sec, t.tv_usec);
          }
#endif
  if (strcmp (mode, M_LINEART_COLOR) != 0)
    s->bytes_to_read = s->params.bytes_per_line * s->params.lines;
  else
    {
      s->bytes_to_read = s->params.bytes_per_line * s->params.lines;
    }

#ifdef USE_FORK
  {
    size_t i;
    for (i = 0; i < s->dev->info.buffers; i++) 
      s->rdr_ctl->buf_ctl[i].shm_status = SHM_EMPTY;
    s->read_buff = 0;
    s->rdr_ctl->cancel = 0;
    s->rdr_ctl->running = 0;
    s->rdr_ctl->status  = SANE_STATUS_GOOD;
  }
  s->reader_pid = fork();
#ifdef DEBUG
          {
            struct timeval t;
            gettimeofday(&t, 0);
            DBG(2, "rd: forked              %li.%06li %i\n", t.tv_sec, t.tv_usec,
              s->reader_pid);
          }
#endif
  if (s->reader_pid == 0) 
    {
      sigset_t ignore_set;
      struct SIGACTION act;

      sigfillset (&ignore_set);
      sigdelset (&ignore_set, SIGTERM);
      sigprocmask (SIG_SETMASK, &ignore_set, 0);

      memset (&act, 0, sizeof (act));
      sigaction (SIGTERM, &act, 0);

      /* don't use exit() since that would run the atexit() handlers... */
      _exit (reader_process (s));
    }
  else if (s->reader_pid == -1)
    {
      s->busy = SANE_FALSE;
      do_cancel(s);
      return SANE_STATUS_NO_MEM;
    }
   
#endif /* USE_FORK */
  

  DBG (1, "%d pixels per line, %d bytes, %d lines high, total %lu bytes, "
       "dpi=%d\n", s->params.pixels_per_line, s->params.bytes_per_line,
       s->params.lines, (u_long) s->bytes_to_read, s->val[OPT_RESOLUTION].w);

  s->busy = SANE_FALSE;
  s->buf_used = 0;
  s->buf_pos = 0;

  if (s->cancel == SANE_TRUE) 
    {
      do_cancel(s);
      DBG (10, ">>\n");
      return(SANE_STATUS_CANCELLED);
    }

  DBG (10, ">>\n");
  return (SANE_STATUS_GOOD);

}

static SANE_Status
sane_read_direct (SANE_Handle handle, SANE_Byte *dst_buf, SANE_Int max_len,
	   SANE_Int * len)
{
  NEC_Scanner *s = handle;
  SANE_Status status;
  size_t nread;
  DBG (10, "<< sane_read_direct ");

#if 0
  {
    buffer_status bs;
    size_t len = sizeof (buffer_status);
    get_data_buffer_status (s->fd, &bs, &len);
    DBG (20, "buffer_status: %i ", bs.fdb[0]*256*256 + bs.fdb[1]*256 + bs.fdb[2]); 
  }
#endif
  DBG (20, "remaining: %lu ", (u_long)  s->bytes_to_read);
  *len = 0;

  if (s->bytes_to_read == 0)
    {
      do_cancel (s);
      return (SANE_STATUS_EOF);
    }

  if (!s->scanning)
    return (do_cancel (s));
  nread = max_len;
  if (nread > s->bytes_to_read)
    nread = s->bytes_to_read;
  if (nread > s->dev->info.bufsize)
    nread = s->dev->info.bufsize;

#ifdef USE_FORK
  status = read_data(s, dst_buf, &nread);
#else
#ifdef NOTUSE_PCIN500
  wait_ready(s->fd);
#endif
  status = read_data (s, dst_buf, &nread);
#endif
  if (status != SANE_STATUS_GOOD)
    {
      do_cancel (s);
      return (SANE_STATUS_IO_ERROR);
    }
  *len = nread;
  s->bytes_to_read -= nread;
  DBG (20, "remaining: %lu ", (u_long) s->bytes_to_read);

  DBG (10, ">>\n");
  return (SANE_STATUS_GOOD);
}

static SANE_Status
sane_read_shuffled (SANE_Handle handle, SANE_Byte *dst_buf, SANE_Int max_len,
	   SANE_Int * len, int eight_bit_data)
{
  NEC_Scanner *s = handle;
  SANE_Status status;
  SANE_Byte *dest, *red, *green, *blue, mask;
  SANE_Int transfer;
  size_t nread, ntest, pixel, max_pixel, line, max_line;
  size_t start_input, bytes_per_line_in;
  DBG (10, "<< sane_read_shuffled ");

#if 0
  {
    buffer_status bs;
    size_t len = sizeof (buffer_status);
    get_data_buffer_status (s->fd, &bs, &len);
    DBG (20, "buffer_status: %i ", bs.fdb[0]*256*256 + bs.fdb[1]*256 + bs.fdb[2]); 
  }
#endif
  *len = 0;
  if (s->bytes_to_read == 0 && s->buf_pos == s->buf_used) 
    {
      do_cancel (s);
      DBG (10, ">>\n");
      return (SANE_STATUS_EOF);
    }
    
  if (!s->scanning) 
    {
      DBG (10, ">>\n");
      return(do_cancel(s));
    }

  if (s->buf_pos < s->buf_used)
    {
      transfer = s->buf_used - s->buf_pos;
      if (transfer > max_len)
        transfer = max_len;
      
      memcpy(dst_buf, &(s->buffer[s->buf_pos]), transfer);
      s->buf_pos += transfer;
      max_len -= transfer;
      *len = transfer;
    }

  while (max_len > 0 && s->bytes_to_read > 0) 
    {
      if (eight_bit_data)
        {
          nread = s->dev->info.bufsize / s->params.bytes_per_line - 1;
          nread *= s->params.bytes_per_line;
          if (nread > s->bytes_to_read)
            nread = s->bytes_to_read;
          max_line = nread / s->params.bytes_per_line;
          start_input = s->params.bytes_per_line;
          bytes_per_line_in = s->params.bytes_per_line;
        }
      else
        {
          bytes_per_line_in = (s->params.pixels_per_line + 7) / 8;
          bytes_per_line_in *= 3;
          max_line = s->params.bytes_per_line + bytes_per_line_in;
          max_line = s->dev->info.bufsize / max_line;
          nread = max_line * bytes_per_line_in;
          if (nread > s->bytes_to_read)
            {
              nread = s->bytes_to_read;
              max_line = nread / bytes_per_line_in;
            }
          start_input = s->dev->info.bufsize - nread;
        }
      ntest = nread;
      
#ifdef USE_FORK
      status = read_data (s, &(s->buffer[start_input]), &nread);
#else
      status = read_data (s, &(s->buffer[start_input]), &nread);
#endif
      if (status != SANE_STATUS_GOOD)
        {
          do_cancel (s);
          DBG (10, ">>\n");
          return (SANE_STATUS_IO_ERROR);
        }
      
      if (nread != ntest) 
        {
          /* if this happens, something is wrong in the input buffer
             management...
          */
          DBG(1, "Warning: could not read an integral number of scan lines\n");
          DBG(1, "         image will be scrambled\n");
        }
      
      
      s->buf_used = max_line * s->params.bytes_per_line;
      s->buf_pos = 0;
      s->bytes_to_read -= nread;
      dest = s->buffer;
      max_pixel = s->params.pixels_per_line;

      if (eight_bit_data)
        for (line = 1; line <= max_line; line++)
          {
            red = &(s->buffer[line * s->params.bytes_per_line]);
            green = &(red[max_pixel]);
            blue = &(green[max_pixel]);
            for (pixel = 0; pixel < max_pixel; pixel++)
              {
                *dest++ = *red++;
                *dest++ = *green++;
                *dest++ = *blue++;
              }
          }
      else
        for (line = 0; line < max_line; line++)
          {
            red = &(s->buffer[start_input + line * bytes_per_line_in]);
            green = &(red[(max_pixel+7)/8]);
            blue = &(green[(max_pixel+7)/8]);
            mask = 0x80;
            for (pixel = 0; pixel < max_pixel; pixel++)
              {
                *dest++ = (*red & mask)   ? 0xff : 0;
                *dest++ = (*green & mask) ? 0xff : 0;
                *dest++ = (*blue & mask)  ? 0xff : 0;
                mask = mask >> 1;
                if (mask == 0)
                  {
                    mask = 0x80;
                    red++;
                    green++;
                    blue++;
                  }
              }
          }
      
      transfer = max_len;
      if (transfer > s->buf_used)
        transfer = s->buf_used;
      memcpy(&(dst_buf[*len]), s->buffer, transfer);
      
      max_len -= transfer;
      s->buf_pos += transfer;
      *len += transfer;
    }

  if (s->bytes_to_read == 0 && s->buf_pos == s->buf_used)
    do_cancel (s);
  DBG (10, ">>\n");
  return (SANE_STATUS_GOOD);
}

SANE_Status
sane_read (SANE_Handle handle, SANE_Byte *dst_buf, SANE_Int max_len,
	   SANE_Int * len)
{
  NEC_Scanner *s = handle;
  SANE_Status status;

  DBG (10, "<< sane_read ");
  s->busy = SANE_TRUE;
  if (s->cancel == SANE_TRUE) 
    {
      do_cancel(s);
      *len = 0;
      return (SANE_STATUS_CANCELLED);
    }
  
  if (s->image_composition <= 2)
    status = sane_read_direct(handle, dst_buf, max_len, len);
  else if (s->image_composition <= 4)
    status = sane_read_shuffled(handle, dst_buf, max_len, len, 0);
  else if (s->dev->sensedat.model == PCIN500)
    status = sane_read_direct(handle, dst_buf, max_len, len);
  else
    status = sane_read_shuffled(handle, dst_buf, max_len, len, 1);

  s->busy = SANE_FALSE;
  if (s->cancel == SANE_TRUE)
    {
      do_cancel(s);
      return (SANE_STATUS_CANCELLED);
    }

  DBG (10, ">> \n");
  return (status);
}

void
sane_cancel (SANE_Handle handle)
{
  NEC_Scanner *s = handle;
  DBG (10, "<< sane_cancel ");

  s->cancel = SANE_TRUE;
  if (s->busy == SANE_FALSE)
      do_cancel(s);

  DBG (10, ">>\n");
}

SANE_Status
sane_set_io_mode (SANE_Handle handle, SANE_Bool non_blocking)
{
  handle = handle;
  non_blocking = non_blocking; /* silence compilation warnings */

  DBG (10, "<< sane_set_io_mode");
  DBG (10, ">>\n");

  return SANE_STATUS_UNSUPPORTED;
}

SANE_Status
sane_get_select_fd (SANE_Handle handle, SANE_Int * fd)
{
  handle = handle;
  fd = fd; /* silence compilation warnings */
  
  DBG (10, "<< sane_get_select_fd");
  DBG (10, ">>\n");

  return SANE_STATUS_UNSUPPORTED;
}