/* sane - Scanner Access Now Easy.

   This file is part of the SANE package, and implements a SANE backend
   for various Canon DR-series scanners.

   Copyright (C) 2008-2022 m. allan noah

   Yabarana Corp. www.yabarana.com provided significant funding
   EvriChart, Inc. www.evrichart.com provided funding and loaned equipment
   Canon, USA. www.usa.canon.com loaned equipment
   HPrint hprint.com.br provided funding and testing for DR-2510 support
   Stone-IT www.stone-it.com provided funding for DR-2010 and DR-2050 support
   Smartmatic www.smartmatic.com provided testing and changes for DR-X10C support

   --------------------------------------------------------------------------

   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, see <https://www.gnu.org/licenses/>.

   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.

   --------------------------------------------------------------------------

   The source code is divided in sections which you can easily find by
   searching for the tag "@@".

   Section 1 - Init & static stuff
   Section 2 - sane_init, _get_devices, _open & friends
   Section 3 - sane_*_option functions
   Section 4 - sane_start, _get_param, _read & friends
   Section 5 - calibration functions
   Section 6 - sane_close functions
   Section 7 - misc functions
   Section 8 - image processing functions

   Changes:
      v1 2008-10-29, MAN
         - initial version
      v2 2008-11-04, MAN
         - round scanlines to even bytes
	 - spin RS and usb_clear_halt code into new function
	 - update various scsi payloads
	 - calloc out block so it gets set to 0 initially
      v3 2008-11-07, MAN
         - back window uses id 1
         - add option and functions to read/send page counter
         - add rif option
      v4 2008-11-11, MAN
         - eject document when sane_read() returns EOF
      v5 2008-11-25, MAN
         - remove EOF ejection code
         - add SSM and GSM commands
         - add dropout, doublefeed, and jpeg compression options
         - disable adf backside
         - fix adf duplex
         - read two extra lines (ignore errors) at end of image
         - only send scan command at beginning of batch
	 - fix bug in hexdump with 0 length string
         - DR-7580 support
      v6 2008-11-29, MAN
         - fix adf simplex
         - rename ssm_duplex to ssm_buffer
         - add --buffer option
         - reduce inter-page commands when buffering is enabled
         - improve sense_handler output
         - enable counter option
         - drop unused code
      v7 2008-11-29, MAN
         - jpeg support (size rounding and header overwrite)
         - call object_position(load) between pages even if buffering is on
         - use request sense info bytes on short scsi reads
         - byte swap color BGR to RGB
         - round image width down, not up
         - round image height down to even # of lines
         - always transfer even # of lines per block
         - scsi and jpeg don't require reading extra lines to reach EOF
         - rename buffer option to buffermode to avoid conflict with scanimage
         - send ssm_do and ssm_df during sane_start
         - improve sense_handler output
      v8 2008-12-07, MAN
         - rename read/send_counter to read/send_panel
         - enable control panel during init
         - add options for all buttons
         - call TUR twice in wait_scanner(), even if first succeeds
         - disable rif
         - enable brightness/contrast/threshold options
      v9 2008-12-07, MAN
         - add rollerdeskew and stapledetect options
         - add rollerdeskew and stapledetect bits to ssm_df()
      v10 2008-12-10, MAN
         - add all documented request sense codes to sense_handler()
         - fix color jpeg (remove unneeded BGR to RGB swapping code)
         - add macros for LUT data
      v11 2009-01-10, MAN
         - send_panel() can disable too
         - add cancel() to send d8 command
         - call cancel() only after final read from scanner
         - stop button requests cancel
      v12 2009-01-21, MAN
         - don't export private symbols
      v13 2009-03-06, MAN
         - new vendor ID for recent machines
         - add usb ids for several new machines
      v14 2009-03-07, MAN
         - remove HARD_SELECT from counter (Legitimate, but API violation)
         - attach to CR-series scanners as well
      v15 2009-03-15, MAN
         - add byte-oriented duplex interlace code
         - add RRGGBB color interlace code
         - add basic support for DR-2580C
      v16 2009-03-20, MAN
         - add more unknown setwindow bits
         - add support for 16 byte status packets
         - clean do_usb_cmd error handling (call reset more often)
         - add basic support for DR-2050C, DR-2080C, DR-2510C
      v17 2009-03-20, MAN
         - set status packet size from config file
      v18 2009-03-21, MAN
         - rewrite config file parsing to reset options after each scanner
         - add config options for vendor, model, version
         - don't call inquiry if those 3 options are set
         - remove default config file from code
         - add initial gray deinterlacing code for DR-2510C
         - rename do_usb_reset to do_usb_clear
      v19 2009-03-22, MAN
         - pad gray deinterlacing area for DR-2510C
         - override tl_x and br_x for fixed width scanners
      v20 2009-03-23, MAN
         - improved macros for inquiry and set window
         - shorten inquiry vpd length to match windows driver
         - remove status-length config option
         - add padded-read config option
         - rewrite do_usb_cmd to pad reads and calloc/copy buffers
      v21 2009-03-24, MAN
         - correct rgb padding macro
         - skip send_panel and ssm_df commands for DR-20xx scanners
      v22 2009-03-25, MAN
         - add deinterlacing code for DR-2510C in duplex and color
      v23 2009-03-27, MAN
         - rewrite all image data processing code
         - handle more image interlacing formats
         - re-enable binary mode on some scanners
         - limit some machines to full-width scanning
      v24 2009-04-02, MAN
         - fix DR-2510C duplex deinterlacing code
         - rewrite sane_read helpers to read until EOF
         - update sane_start for scanners that don't use object_position
         - don't call sanei_usb_clear_halt() if device is not open
         - increase default buffer size to 4 megs
         - set buffermode on by default
         - hide modes and resolutions that DR-2510C lies about
         - read_panel() logs front-end access to sensors instead of timing
         - rewrite do_usb_cmd() to use remainder from RS info
      v25 2009-04-12, MAN
         - disable SANE_FRAME_JPEG
      v26 2009-04-14, MAN (SANE 1.0.20)
         - return cmd status for reads on sensors
         - allow rs to adjust read length for all bad status responses
      v27 2009-05-08, MAN
         - bug fix in read_panel()
         - initialize vars in do_usb_cmd()
         - set buffermode off by default
         - clear page counter during init and sane_start()
         - eject previous page during init and sane_start()
         - improved SSM_BUFF macros
         - moved set_window() to after ssm-*()
         - add coarse calibration (AFE offset/gain & per-channel exposure)
         - add fine calibration (per-cell offset/gain)
         - free image and fine cal buffers in sane_close()
         - compare page counter of small scanners only in non-buffered mode
         - add back-side gray mirroring code for DR-2580C
      v28 2009-05-20, MAN
         - use average instead of min/max for fine offset and gain
         - rewrite supported resolution list as x and y arrays
         - merge x and y resolution options into single option
         - move scan params into two new structs, s->u and s->s
         - sane_get_parameters() just returns values from s->u
         - don't call wait_scanner() in object_position()
         - don't call ssm_*() from option handler
         - refactor sane_start()
         - read_from_buffer() can workaround missing res, modes and cropping
         - set most DR-2xxx machines to use the read_from_buffer workarounds
         - set default threshold to 90
         - add option for button #3 of some machines
         - don't eject paper during init
         - add DR-2010 quirks
         - switch counter to HARD_SELECT, not SOFT
      v29 2009-06-01, MAN
         - split coarse and fine cal to run independently
         - add side option
         - reset scan params to user request if calibration fails
         - better handling of sane_cancel
         - better handling of errors during sane_start and sane_read
      v30 2009-06-17, MAN
         - add fine cal support for machines with internal buffer (2050/2080)
         - support fixed-width machines that require even bytes per scanline
         - pad end of scan with gray if scanner stops prematurely
         - better handling of errors during calibration
         - cleanup canceling debug messages
         - remove old cancel() prototype
         - small sleep before clearing usb halt condition
      v31 2009-06-29, MAN
         - reduce default buffer size to 2 megs
      v32 2009-07-21, MAN
         - crop/resample image data before buffering, not after
         - shink image buffers to size of output image, not input
         - correct some debug message
         - better handling of EOF
         - add intermediate param struct to existing user and scan versions
      v33 2009-07-23, MAN
         - add software brightness/contrast for dumb scanners
         - add blocking mode to allow full-page manipulation options to run
         - add swdespeck option and support code
         - add swdeskew and swcrop options (disabled)
      v34 2009-07-28, MAN
         - add simplified Hough transform based deskewing code
         - add extremity detecting cropping code
         - use per-model background color to fill corners after deskew
         - request and chop extra scanlines instead of rounding down
         - remove padding dumb scanners add to top of front side
         - sane_get_params uses intermediate struct instead of user struct
         - if scanner stops, clone the last line until the end of buffer
         - reset some intermediate params between duplex sides
      v35 2010-02-09, MAN (SANE 1.0.21)
         - cleanup #includes and copyright
         - add SANE_I18N to static strings
         - don't fail if scsi buffer is too small
      v36 2011-01-03, MAN
         - initial support for DR-3080 and DR-5060
         - add code to clamp scan width to an arbitrary byte width boundary
         - add code to prevent setting of brightness/threshold/contrast
         - don't send dropout color command on non-color scanners
         - initial support for DR-7090C
         - update credits
      v37 2011-01-26, MAN (SANE 1.0.22)
         - don't center window when using flatbed
         - improve request sense error messages
         - enable flatbed for all known models
      v38 2011-07-06, MAN
	 - initial support for DR-5020
	 - use ppl_mod instead of Bpl_mod, apply to all modes
	 - invert logic of read_panel tracking
	 - add ability to disable read_panel()
	 - automatically disable read/send_panel if unsupported
      v39 2011-11-01, MAN
         - DR-2580C pads the backside of duplex scans
      v40 2012-11-01, MAN
         - initial DR-9050C, DR-7550C, DR-6050C and DR-3010C support
      v41 2013-07-31, MAN (SANE 1.0.24)
         - initial P-208 and P-215 support
         - bug fix for calibration of scanners with duplex_offset
         - allow duplex_offset to be controlled from config file
      v42 2013-12-09, MAN
         - initial DR-G1100 support
         - add support for paper sensors (P-215 & P-208)
         - add initial support for card reader (P-215)
         - removed unused var from do_scsi_cmd()
      v43 2014-03-13, MAN
         - initial DR-M140 support
         - add extra_status config and code
         - split status code into do_usb_status
         - fix copy_line margin offset
         - add new color interlacing modes and code
         - comment out ssm2
         - add timestamp to do_usb_cmd
      v44 2014-03-26, MAN
         - buffermode support for machines with ssm2 command
         - DR-M140 needs always_op=0
      v45 2014-03-29, MAN
         - dropout support for machines with ssm2 command
         - doublefeed support for machines with ssm2 command
      v46 2014-04-09, MAN
         - split debug level 30 into two levels
         - simplify jpeg ifdefs
         - add support for DR-M160
      v47 2014-07-07, MAN
         - initial DR-G1130 support
      v48 2014-08-06, MAN
         - set another unknown byte in buffermode for ssm2
         - add another gettimeofday call at end of do_usb_cmd
         - don't print 0 length line in hexdump
      v49 2015-03-18, MAN
         - initial support for DR-C125
      v50 2015-08-23, MAN
         - DR-C125 adds duplex padding on back side
         - initial support for DR-C225
      v51 2015-08-25, MAN (SANE 1.0.25)
         - DR-C125 does not invert_tly, does need sw_lut
      v52 2015-11-03, MAN
         - set can_color=1 by default (recent models don't have 'C' in name)
         - enable jpeg for DR-6080
         - add must_downsample and must_fully_buffer
         - improve dropout option handling
         - add software dropout implementation for downsampled modes
      v53 2015-11-06, MAN
         - replace image processing methods with sanei_magic
         - add swskip option
         - reorder geometry group options
         - use bg_color to fill missing image data
      v54 2015-11-21, MAN
         - br_x and br_y locked to page_width/height until changed
      v55 2016-03-19, MAN
         - fixed-width scanners were calculating left-side offset incorrectly in color
         - initial support for DR-F120
         - rename all DUPLEX_INTERLACE_* to indicate start and end of line
      v56 2016-08-23, MAN
         - initial support for P-150
      v57 2019-02-24, manuarg
         - complete support for X-10, including hardware cropping
      v58 2019-11-10, MAN
         - adjust wait_scanner to set runRS only as a last resort, bug #154
      v59 2020-09-23, MAN
         - restructure fine calibration code
         - initial support for uploading fine calibration payloads
         - improve DR-C225 support
      v60 2020-11-28, MAN
         - add new gray and color interlacing options for DR-C120
         - initial support for DR-C120 and C130
         - enable fine calibration for P-208 (per @sashacmc in !546)
      v61 2021-02-13, MAN
         - treat DR-P208 like P-208 (#356)
         - treat DR-P215 like P-215 (#356)
         - adjust wait_scanner to try one TUR with a long timeout (#142)
      v62 2021-02-13, MAN
         - allow config file to set inq and vpd lengths for DR-M1060 (#263)
         - rewrite do_cmd() timeout handling
         - remove long timeout TUR from v61 (did not help)
         - allow config file to set initial tur timeout for DR-X10C (#142)
      v63 2022-11-18, CQ, MAN
         - add support for reading the total and roller counters
      v64 2022-11-18, CQ, MAN
         - add complete support for imprinters on X10C (#585)
      v65 2023-06-06, MAN
         - fix imprinter support (#672)
         - update attach_one and other init functions

   SANE FLOW DIAGRAM

   - sane_init() : initialize backend
   . - sane_get_devices() : query list of scanner devices
   . - sane_open() : open a particular scanner device
   . . - sane_set_io_mode : set blocking mode
   . . - sane_get_select_fd : get scanner fd
   . .
   . . - sane_get_option_descriptor() : get option information
   . . - sane_control_option() : change option values
   . . - sane_get_parameters() : returns estimated scan parameters
   . . - (repeat previous 3 functions)
   . .
   . . - sane_start() : start image acquisition
   . .   - sane_get_parameters() : returns actual scan parameters
   . .   - sane_read() : read image data (from pipe)
   . . (sane_read called multiple times; after sane_read returns EOF,
   . . loop may continue with sane_start which may return a 2nd page
   . . when doing duplex scans, or load the next page from the ADF)
   . .
   . . - sane_cancel() : cancel operation
   . - sane_close() : close opened scanner device
   - sane_exit() : terminate use of backend

*/

/*
 * @@ Section 1 - Init
 */

#include "../include/sane/config.h"

#include <string.h> /*memcpy...*/
#include <ctype.h> /*isspace*/
#include <math.h> /*tan*/
#include <unistd.h> /*usleep*/
#include <sys/time.h> /*gettimeofday*/
#include <time.h> /*localtime*/
#include <stdlib.h> /*strtol*/

#include "../include/sane/sanei_backend.h"
#include "../include/sane/sanei_scsi.h"
#include "../include/sane/sanei_usb.h"
#include "../include/sane/saneopts.h"
#include "../include/sane/sanei_config.h"
#include "../include/sane/sanei_magic.h"

#include "canon_dr-cmd.h"
#include "canon_dr.h"

#define DEBUG 1
#define BUILD 65

/* values for SANE_DEBUG_CANON_DR env var:
 - errors           5
 - function trace  10
 - function detail 15
 - get/setopt cmds 20
 - scsi/usb trace  25
 - scsi/usb writes 30
 - scsi/usb reads  31
 - useless noise   35
*/

/* ------------------------------------------------------------------------- */
/* if JPEG support is not enabled in sane.h, we setup our own defines */
#ifndef SANE_FRAME_JPEG
#define SANE_FRAME_JPEG 0x0B
#define SANE_JPEG_DISABLED 1
#endif
/* ------------------------------------------------------------------------- */
#define STRING_FLATBED SANE_I18N("Flatbed")
#define STRING_ADFFRONT SANE_I18N("ADF Front")
#define STRING_ADFBACK SANE_I18N("ADF Back")
#define STRING_ADFDUPLEX SANE_I18N("ADF Duplex")
#define STRING_CARDFRONT SANE_I18N("Card Front")
#define STRING_CARDBACK SANE_I18N("Card Back")
#define STRING_CARDDUPLEX SANE_I18N("Card Duplex")

#define STRING_LINEART SANE_VALUE_SCAN_MODE_LINEART
#define STRING_HALFTONE SANE_VALUE_SCAN_MODE_HALFTONE
#define STRING_GRAYSCALE SANE_VALUE_SCAN_MODE_GRAY
#define STRING_COLOR SANE_VALUE_SCAN_MODE_COLOR

#define STRING_RED SANE_I18N("Red")
#define STRING_GREEN SANE_I18N("Green")
#define STRING_BLUE SANE_I18N("Blue")
#define STRING_EN_RED SANE_I18N("Enhance Red")
#define STRING_EN_GREEN SANE_I18N("Enhance Green")
#define STRING_EN_BLUE SANE_I18N("Enhance Blue")

#define STRING_NONE SANE_I18N("None")
#define STRING_JPEG SANE_I18N("JPEG")

#define STRING_IMPRINTER_8x12_FONT SANE_I18N("8x12")
#define STRING_IMPRINTER_12x12_FONT SANE_I18N("12x12")

#define STRING_IMPRINTER_ADDON_BoW SANE_I18N("Black-on-White")
#define STRING_IMPRINTER_ADDON_BoI SANE_I18N("Black-on-Image")
#define STRING_IMPRINTER_ADDON_WoB SANE_I18N("White-on-Black")


/* Also set via config file. */
static int global_buffer_size;
static int global_buffer_size_default = 2 * 1024 * 1024;
static int global_padded_read;
static int global_padded_read_default = 0;
static int global_extra_status;
static int global_extra_status_default = 0;
static int global_duplex_offset;
static int global_duplex_offset_default = 0;
static int global_inquiry_length;
static int global_vpd_length;
static int global_tur_timeout;
static int global_tur_timeout_default = USB_PACKET_TIMEOUT/60; /* half second */
static char global_vendor_name[9];
static char global_model_name[17];
static char global_version_name[5];

/*
 * used by attach* and sane_get_devices
 * a ptr to a null term array of ptrs to SANE_Device structs
 * a ptr to a single-linked list of scanner structs
 */
static const SANE_Device **sane_devArray = NULL;
static struct scanner *scanner_devList = NULL;

/*
 * @@ Section 2 - SANE & scanner init code
 */

/*
 * Called by SANE initially.
 *
 * From the SANE spec:
 * This function must be called before any other SANE function can be
 * called. The behavior of a SANE backend is undefined if this
 * function is not called first. The version code of the backend is
 * returned in the value pointed to by version_code. If that pointer
 * is NULL, no version code is returned. Argument authorize is either
 * a pointer to a function that is invoked when the backend requires
 * authentication for a specific resource or NULL if the frontend does
 * not support authentication.
 */
SANE_Status
sane_init (SANE_Int * version_code, SANE_Auth_Callback authorize)
{
  (void) authorize;             /* get rid of compiler warning */

  DBG_INIT ();
  DBG (10, "sane_init: start\n");

  if (version_code)
    *version_code = SANE_VERSION_CODE (SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, BUILD);

  DBG (5, "sane_init: canon_dr backend %d.%d.%d, from %s\n",
    SANE_CURRENT_MAJOR, SANE_CURRENT_MINOR, BUILD, PACKAGE_STRING);

  DBG (10, "sane_init: finish\n");

  return SANE_STATUS_GOOD;
}

/*
 * Called by SANE to find out about supported devices.
 *
 * From the SANE spec:
 * This function can be used to query the list of devices that are
 * available. If the function executes successfully, it stores a
 * pointer to a NULL terminated array of pointers to SANE_Device
 * structures in *device_list. The returned list is guaranteed to
 * remain unchanged and valid until (a) another call to this function
 * is performed or (b) a call to sane_exit() is performed. This
 * function can be called repeatedly to detect when new devices become
 * available. If argument local_only is true, only local devices are
 * returned (devices directly attached to the machine that SANE is
 * running on). If it is false, the device list includes all remote
 * devices that are accessible to the SANE library.
 *
 * SANE does not require that this function is called before a
 * sane_open() call is performed. A device name may be specified
 * explicitly by a user which would make it unnecessary and
 * undesirable to call this function first.
 */
/*
 * Read the config file, find scanners with help from sanei_*
 * and store in global device structs
 */
SANE_Status
sane_get_devices (const SANE_Device *** device_list, SANE_Bool local_only)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  struct scanner * s;
  struct scanner * prev = NULL;
  char line[PATH_MAX];
  const char *lp;
  FILE *fp;
  int num_devices=0;
  int i=0;

  (void) local_only;            /* get rid of compiler warning */

  DBG (10, "sane_get_devices: start\n");

  /* mark all existing scanners as missing, attach_one will remove mark */
  for (s = scanner_devList; s; s = s->next) {
    s->missing = 1;
  }

  sanei_usb_init();

  /* reset globals before reading the file */
  default_globals();

  fp = sanei_config_open (CANON_DR_CONFIG_FILE);

  if (fp) {

      DBG (15, "sane_get_devices: reading config file %s\n",
        CANON_DR_CONFIG_FILE);

      while (sanei_config_read (line, PATH_MAX, fp)) {

          lp = line;

          /* ignore comments */
          if (*lp == '#')
            continue;

          /* skip empty lines */
          if (*lp == 0)
            continue;

          if (!strncmp ("option", lp, 6) && isspace (lp[6])) {

              lp += 6;
              lp = sanei_config_skip_whitespace (lp);

              /* BUFFERSIZE: > 4K */
              if (!strncmp (lp, "buffer-size", 11) && isspace (lp[11])) {

                  int buf;
                  lp += 11;
                  lp = sanei_config_skip_whitespace (lp);
                  buf = atoi (lp);

                  if (buf < 4096) {
                    DBG (5, "sane_get_devices: config option \"buffer-size\" "
                      "(%d) is < 4096, ignoring!\n", buf);
                    continue;
                  }

                  if (buf > global_buffer_size_default) {
                    DBG (5, "sane_get_devices: config option \"buffer-size\" "
                      "(%d) is > %d, scanning problems may result\n", buf,
                      global_buffer_size_default);
                  }

                  DBG (15, "sane_get_devices: setting \"buffer-size\" to %d\n",
                    buf);

                  global_buffer_size = buf;
              }

              /* PADDED READ: we clamp to 0 or 1 */
              else if (!strncmp (lp, "padded-read", 11) && isspace (lp[11])) {

                  int buf;
                  lp += 11;
                  lp = sanei_config_skip_whitespace (lp);
                  buf = atoi (lp);

                  if (buf < 0) {
                    DBG (5, "sane_get_devices: config option \"padded-read\" "
                      "(%d) is < 0, ignoring!\n", buf);
                    continue;
                  }

                  if (buf > 1) {
                    DBG (5, "sane_get_devices: config option \"padded-read\" "
                      "(%d) is > 1, ignoring!\n", buf);
                    continue;
                  }

                  DBG (15, "sane_get_devices: setting \"padded-read\" to %d\n",
                    buf);

                  global_padded_read = buf;
              }

              /* EXTRA STATUS: we clamp to 0 or 1 */
              else if (!strncmp (lp, "extra-status", 12) && isspace (lp[12])) {

                  int buf;
                  lp += 12;
                  lp = sanei_config_skip_whitespace (lp);
                  buf = atoi (lp);

                  if (buf < 0) {
                    DBG (5, "sane_get_devices: config option \"extra-status\" "
                      "(%d) is < 0, ignoring!\n", buf);
                    continue;
                  }

                  if (buf > 1) {
                    DBG (5, "sane_get_devices: config option \"extra-status\" "
                      "(%d) is > 1, ignoring!\n", buf);
                    continue;
                  }

                  DBG (15, "sane_get_devices: setting \"extra-status\" to %d\n",
                    buf);

                  global_extra_status = buf;
              }

              /* DUPLEXOFFSET: < 2400 */
              else if (!strncmp (lp, "duplex-offset", 13) && isspace (lp[13])) {

                  int buf;
                  lp += 13;
                  lp = sanei_config_skip_whitespace (lp);
                  buf = atoi (lp);

                  if (buf > 2400) {
                    DBG (5, "sane_get_devices: config option \"duplex-offset\" "
                      "(%d) is > 2400, ignoring!\n", buf);
                    continue;
                  }

                  if (buf < 0) {
                    DBG (5, "sane_get_devices: config option \"duplex-offset\" "
                      "(%d) is < 0, ignoring!\n", buf);
                    continue;
                  }

                  DBG (15, "sane_get_devices: setting \"duplex-offset\" to %d\n",
                    buf);

                  global_duplex_offset = buf;
              }

              /* INQUIRY_LENGTH: <= 0x30 */
              else if (!strncmp (lp, "inquiry-length", 14) && isspace (lp[14])) {

                  int buf;
                  lp += 14;
                  lp = sanei_config_skip_whitespace (lp);
                  buf = (int) strtol (lp,NULL,16);

                  if (buf > INQUIRY_std_max_len) {
                    DBG (5, "sane_get_devices: config option \"inquiry-length\" "
                      "(%#04x) is > %#04x, ignoring!\n", buf, INQUIRY_std_max_len);
                    continue;
                  }

                  if (buf < 0) {
                    DBG (5, "sane_get_devices: config option \"inquiry-length\" "
                      "(%#04x) is < 0, ignoring!\n", buf);
                    continue;
                  }

                  DBG (15, "sane_get_devices: setting \"inquiry-length\" to %#04x\n",
                    buf);

                  global_inquiry_length = buf;
              }

              /* VPD_LENGTH: <= 0x30 */
              else if (!strncmp (lp, "vpd-length", 10) && isspace (lp[10])) {

                  int buf;
                  lp += 10;
                  lp = sanei_config_skip_whitespace (lp);
                  buf = (int) strtol (lp,NULL,16);

                  if (buf > INQUIRY_vpd_max_len) {
                    DBG (5, "sane_get_devices: config option \"vpd-length\" "
                      "(%#04x) is > %#04x, ignoring!\n", buf, INQUIRY_vpd_max_len);
                    continue;
                  }

                  if (buf < 0) {
                    DBG (5, "sane_get_devices: config option \"vpd-length\" "
                      "(%#04x) is < 0, ignoring!\n", buf);
                    continue;
                  }

                  DBG (15, "sane_get_devices: setting \"vpd-length\" to %#04x\n",
                    buf);

                  global_vpd_length = buf;
              }

              /* TUR_TIMEOUT <= 60000 */
              else if (!strncmp (lp, "tur-timeout", 11) && isspace (lp[11])) {

                  int buf;
                  lp += 11;
                  lp = sanei_config_skip_whitespace (lp);
                  buf = atoi (lp);

                  if (buf > 60000) {
                    DBG (5, "sane_get_devices: config option \"tur-timeout\" "
                      "(%d) is > 60000, ignoring!\n", buf);
                    continue;
                  }

                  if (buf < 0) {
                    DBG (5, "sane_get_devices: config option \"tur-timeout\" "
                      "(%d) is < 0, ignoring!\n", buf);
                    continue;
                  }

                  DBG (15, "sane_get_devices: setting \"tur-timeout\" to %d\n",
                    buf);

                  global_tur_timeout = buf;
              }

              /* VENDOR: we ingest up to 8 bytes */
              else if (!strncmp (lp, "vendor-name", 11) && isspace (lp[11])) {

                  lp += 11;
                  lp = sanei_config_skip_whitespace (lp);
                  strncpy(global_vendor_name, lp, 8);
                  global_vendor_name[8] = 0;

                  DBG (15, "sane_get_devices: setting \"vendor-name\" to %s\n",
                    global_vendor_name);
              }

              /* MODEL: we ingest up to 16 bytes */
              else if (!strncmp (lp, "model-name", 10) && isspace (lp[10])) {

                  lp += 10;
                  lp = sanei_config_skip_whitespace (lp);
                  strncpy(global_model_name, lp, 16);
                  global_model_name[16] = 0;

                  DBG (15, "sane_get_devices: setting \"model-name\" to %s\n",
                    global_model_name);
              }

              /* VERSION: we ingest up to 4 bytes */
              else if (!strncmp (lp, "version-name", 12) && isspace (lp[12])) {

                  lp += 12;
                  lp = sanei_config_skip_whitespace (lp);
                  strncpy(global_version_name, lp, 4);
                  global_version_name[4] = 0;

                  DBG (15, "sane_get_devices: setting \"version-name\" to %s\n",
                    global_version_name);
              }

              else {
                  DBG (5, "sane_get_devices: config option \"%s\" unrecognized "
                  "- ignored.\n", lp);
              }
          }
          else if ((strncmp ("usb", lp, 3) == 0) && isspace (lp[3])) {
              DBG (15, "sane_get_devices: looking for '%s'\n", lp);
              sanei_usb_attach_matching_devices(lp, attach_one_usb);

              /* re-default these after reading the usb line */
              default_globals();
          }
          else if ((strncmp ("scsi", lp, 4) == 0) && isspace (lp[4])) {
              DBG (15, "sane_get_devices: looking for '%s'\n", lp);
              sanei_config_attach_matching_devices (lp, attach_one_scsi);

              /* re-default these after reading the scsi line */
              default_globals();
          }
          else{
              DBG (5, "sane_get_devices: config line \"%s\" unrecognized - "
              "ignored.\n", lp);
          }
      }
      fclose (fp);
  }

  else {
      DBG (5, "sane_get_devices: missing required config file '%s'!\n",
        CANON_DR_CONFIG_FILE);
  }

  /*delete missing scanners from list*/
  for (s = scanner_devList; s;) {
    if(s->missing){
      DBG (5, "sane_get_devices: missing scanner %s\n",s->device_name);

      /*splice s out of list by changing pointer in prev to next*/
      if(prev){
        prev->next = s->next;
        free(s);
        s=prev->next;
      }
      /*remove s from head of list, using prev to cache it*/
      else{
        prev = s;
        s = s->next;
        free(prev);
	prev=NULL;

	/*reset head to next s*/
	scanner_devList = s;
      }
    }
    else{
      prev = s;
      s=prev->next;
    }
  }

  for (s = scanner_devList; s; s=s->next) {
    DBG (15, "sane_get_devices: found scanner %s\n",s->device_name);
    num_devices++;
  }

  DBG (15, "sane_get_devices: found %d scanner(s)\n",num_devices);

  if (sane_devArray)
    free (sane_devArray);

  sane_devArray = calloc (num_devices + 1, sizeof (SANE_Device*));
  if (!sane_devArray)
    return SANE_STATUS_NO_MEM;

  for (s = scanner_devList; s; s=s->next) {
    sane_devArray[i++] = (SANE_Device *)&s->sane;
  }
  sane_devArray[i] = 0;

  if(device_list){
      *device_list = sane_devArray;
  }

  DBG (10, "sane_get_devices: finish\n");

  return ret;
}

/* callbacks used by sane_get_devices */
static SANE_Status
attach_one_scsi (const char *device_name)
{
  return attach_one(device_name,CONNECTION_SCSI);
}

static SANE_Status
attach_one_usb (const char *device_name)
{
  return attach_one(device_name,CONNECTION_USB);
}

/* build the scanner struct and link to global list
 * unless struct is already loaded, then pretend
 */
static SANE_Status
attach_one (const char *device_name, int connType)
{
  struct scanner *s;
  int ret;

  DBG (10, "attach_one: start\n");
  DBG (15, "attach_one: looking for '%s'\n", device_name);

  for (s = scanner_devList; s; s = s->next) {
    if (strcmp (s->device_name, device_name) == 0){
      DBG (10, "attach_one: already attached!\n");
      s->missing = 0;
      return SANE_STATUS_GOOD;
    }
  }

  /* build a scanner struct to hold it */
  if ((s = calloc (sizeof (*s), 1)) == NULL)
    return SANE_STATUS_NO_MEM;

  /* config file settings */
  s->buffer_size = global_buffer_size;
  s->padded_read = global_padded_read;
  s->extra_status = global_extra_status;
  s->duplex_offset = global_duplex_offset;
  s->inquiry_length = global_inquiry_length;
  s->vpd_length = global_vpd_length;
  s->tur_timeout = global_tur_timeout;

  /* copy the device name */
  strcpy (s->device_name, device_name);

  /* connect the fd */
  s->connection = connType;
  s->fd = -1;
  ret = connect_fd(s);
  if(ret != SANE_STATUS_GOOD){
    free (s);
    return ret;
  }

  /* query the device to load its vendor/model/version, */
  /* if config file doesn't give all three */
  if ( !strlen(global_vendor_name)
    || !strlen(global_model_name)
    || !strlen(global_version_name)
  ){
    ret = init_inquire (s);
    if (ret != SANE_STATUS_GOOD) {
      disconnect_fd(s);
      free (s);
      DBG (5, "attach_one: inquiry failed\n");
      return ret;
    }
  }

  /* override any inquiry settings with those from config file */
  if(strlen(global_vendor_name))
    strcpy(s->vendor_name, global_vendor_name);
  if(strlen(global_model_name))
    strcpy(s->model_name, global_model_name);
  if(strlen(global_version_name))
    strcpy(s->version_name, global_version_name);

  /* load detailed specs/capabilities from the device */
  /* if a model cannot support inquiry vpd, this function will die */
  ret = init_vpd (s);
  if (ret != SANE_STATUS_GOOD) {
    disconnect_fd(s);
    free (s);
    DBG (5, "attach_one: vpd failed\n");
    return ret;
  }

  /* clean up the scanner struct based on model */
  /* this is the big piece of model specific code */
  ret = init_model (s);
  if (ret != SANE_STATUS_GOOD) {
    disconnect_fd(s);
    free (s);
    DBG (5, "attach_one: model failed\n");
    return ret;
  }

  /* this detects imprinters if they are available */
  ret = init_imprinters (s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "attach_one: unable to init_imprinters, continuing\n");
  }

  /* enable/read the buttons */
  ret = init_panel (s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "attach_one: unable init_panel, continuing\n");
  }

  /* enable/read the lifecycle counters */
  ret = init_counters (s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "attach_one: unable to init_counters, continuing\n");
  }

  /* sets SANE option 'values' to good defaults */
  ret = init_user (s);
  if (ret != SANE_STATUS_GOOD) {
    disconnect_fd(s);
    free (s);
    DBG (5, "attach_one: user failed\n");
    return ret;
  }

  /* sets the s->opt array to blank */
  ret = init_options (s);
  if (ret != SANE_STATUS_GOOD) {
    disconnect_fd(s);
    free (s);
    DBG (5, "attach_one: options failed\n");
    return ret;
  }

  /* load strings into sane_device struct */
  s->sane.name = s->device_name;
  s->sane.vendor = s->vendor_name;
  s->sane.model = s->model_name;
  s->sane.type = "scanner";

  /* change name in sane_device struct if scanner has serial number
  ret = init_serial (s);
  if (ret == SANE_STATUS_GOOD) {
    s->sane.name = s->serial_name;
  }
  else{
    DBG (5, "attach_one: serial number unsupported?\n");
  }
  */

  /* we close the connection, so that another backend can talk to scanner */
  disconnect_fd(s);

  /* store this scanner in global vars */
  s->next = scanner_devList;
  scanner_devList = s;

  DBG (10, "attach_one: finish\n");

  return SANE_STATUS_GOOD;
}

/*
 * connect the fd in the scanner struct
 */
static SANE_Status
connect_fd (struct scanner *s)
{
  SANE_Status ret;
  int buffer_size = s->buffer_size;

  DBG (10, "connect_fd: start\n");

  if(s->fd > -1){
    DBG (5, "connect_fd: already open\n");
    ret = SANE_STATUS_GOOD;
  }
  else if (s->connection == CONNECTION_USB) {
    DBG (15, "connect_fd: opening USB device (%s)\n", s->device_name);
    ret = sanei_usb_open (s->device_name, &(s->fd));
    if(!ret){
      ret = sanei_usb_clear_halt(s->fd);
    }
  }
  else {
    DBG (15, "connect_fd: opening SCSI device (%s)\n", s->device_name);
    ret = sanei_scsi_open_extended (s->device_name, &(s->fd), sense_handler, s,
      &s->buffer_size);
    if(!ret && buffer_size != s->buffer_size){
      DBG (5, "connect_fd: cannot get requested buffer size (%d/%d)\n",
        buffer_size, s->buffer_size);
    }
  }

  if(ret == SANE_STATUS_GOOD){

    /* first generation usb scanners can get flaky if not closed
     * properly after last use. very first commands sent to device
     * must be prepared to correct this- see wait_scanner() */
    ret = wait_scanner(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "connect_fd: could not wait_scanner\n");
      disconnect_fd(s);
    }

  }
  else{
    DBG (5, "connect_fd: could not open device: %d\n", ret);
  }

  DBG (10, "connect_fd: finish\n");

  return ret;
}

/*
 * This routine will check if a certain device is a Canon scanner
 * It also copies interesting data from INQUIRY into the handle structure
 */
static SANE_Status
init_inquire (struct scanner *s)
{
  int i;
  SANE_Status ret;

  unsigned char cmd[INQUIRY_len];
  size_t cmdLen = INQUIRY_len;

  unsigned char in[INQUIRY_std_max_len];
  size_t inLen = s->inquiry_length;

  DBG (10, "init_inquire: start\n");

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, INQUIRY_code);
  set_IN_return_size (cmd, inLen);
  set_IN_evpd (cmd, 0);
  set_IN_page_code (cmd, 0);

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    in, &inLen
  );

  if (ret != SANE_STATUS_GOOD){
    DBG (10, "init_inquire: failed: %d\n", ret);
    return ret;
  }

  if (get_IN_periph_devtype (in) != IN_periph_devtype_scanner){
    DBG (5, "The device at '%s' is not a scanner.\n", s->device_name);
    return SANE_STATUS_INVAL;
  }

  get_IN_vendor (in, s->vendor_name);
  get_IN_product (in, s->model_name);
  get_IN_version (in, s->version_name);

  s->vendor_name[8] = 0;
  s->model_name[16] = 0;
  s->version_name[4] = 0;

  /* gobble trailing spaces */
  for (i = 7; s->vendor_name[i] == ' ' && i >= 0; i--)
    s->vendor_name[i] = 0;
  for (i = 15; s->model_name[i] == ' ' && i >= 0; i--)
    s->model_name[i] = 0;
  for (i = 3; s->version_name[i] == ' ' && i >= 0; i--)
    s->version_name[i] = 0;

  /*check for vendor name*/
  if (strcmp ("CANON", s->vendor_name)) {
    DBG (5, "The device at '%s' is reported to be made by '%s'\n",
      s->device_name, s->vendor_name);
    DBG (5, "This backend only supports Canon products.\n");
    return SANE_STATUS_INVAL;
  }

  /*check for model name*/
  if (strncmp ("DR", s->model_name, 2)
   && strncmp ("CR", s->model_name, 2)
   && strncmp ("P-", s->model_name, 2)
   && strncmp ("R", s->model_name, 1)
  ) {
    DBG (5, "The device at '%s' is reported to be a '%s'\n",
      s->device_name, s->model_name);
    DBG (5, "This backend only supports Canon P-, CR & DR-series products.\n");
    return SANE_STATUS_INVAL;
  }

  DBG (15, "init_inquire: Found %s scanner %s version %s at %s\n",
    s->vendor_name, s->model_name, s->version_name, s->device_name);

  DBG (10, "init_inquire: finish\n");

  return SANE_STATUS_GOOD;
}

/*
 * Use INQUIRY VPD to setup more detail about the scanner
 */
static SANE_Status
init_vpd (struct scanner *s)
{
  SANE_Status ret;

  unsigned char cmd[INQUIRY_len];
  size_t cmdLen = INQUIRY_len;

  unsigned char in[INQUIRY_vpd_max_len];
  size_t inLen = s->vpd_length;

  DBG (10, "init_vpd: start\n");

  /* get EVPD */
  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, INQUIRY_code);
  set_IN_return_size (cmd, inLen);
  set_IN_evpd (cmd, 1);
  set_IN_page_code (cmd, 0xf0);

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    in, &inLen
  );

  DBG (15, "init_vpd: length=%0x\n",get_IN_page_length (in));

  /* This scanner supports vital product data.
   * Use this data to set dpi-lists etc. */
  if (ret == SANE_STATUS_GOOD || ret == SANE_STATUS_EOF) {

      DBG (15, "standard options\n");

      s->basic_x_res = get_IN_basic_x_res (in);
      DBG (15, "  basic x res: %d dpi\n",s->basic_x_res);

      s->basic_y_res = get_IN_basic_y_res (in);
      DBG (15, "  basic y res: %d dpi\n",s->basic_y_res);

      s->step_x_res = get_IN_step_x_res (in);
      DBG (15, "  step x res: %d dpi\n", s->step_x_res);

      s->step_y_res = get_IN_step_y_res (in);
      DBG (15, "  step y res: %d dpi\n", s->step_y_res);

      s->max_x_res = get_IN_max_x_res (in);
      DBG (15, "  max x res: %d dpi\n", s->max_x_res);

      s->max_y_res = get_IN_max_y_res (in);
      DBG (15, "  max y res: %d dpi\n", s->max_y_res);

      s->min_x_res = get_IN_min_x_res (in);
      DBG (15, "  min x res: %d dpi\n", s->min_x_res);

      s->min_y_res = get_IN_min_y_res (in);
      DBG (15, "  min y res: %d dpi\n", s->min_y_res);

      /* some scanners list B&W resolutions. */
      s->std_res_x[DPI_60] = get_IN_std_res_60 (in);
      s->std_res_y[DPI_60] = s->std_res_x[DPI_60];
      DBG (15, "  60 dpi: %d\n", s->std_res_x[DPI_60]);

      s->std_res_x[DPI_75] = get_IN_std_res_75 (in);
      s->std_res_y[DPI_75] = s->std_res_x[DPI_75];
      DBG (15, "  75 dpi: %d\n", s->std_res_x[DPI_75]);

      s->std_res_x[DPI_100] = get_IN_std_res_100 (in);
      s->std_res_y[DPI_100] = s->std_res_x[DPI_100];
      DBG (15, "  100 dpi: %d\n", s->std_res_x[DPI_100]);

      s->std_res_x[DPI_120] = get_IN_std_res_120 (in);
      s->std_res_y[DPI_120] = s->std_res_x[DPI_120];
      DBG (15, "  120 dpi: %d\n", s->std_res_x[DPI_120]);

      s->std_res_x[DPI_150] = get_IN_std_res_150 (in);
      s->std_res_y[DPI_150] = s->std_res_x[DPI_150];
      DBG (15, "  150 dpi: %d\n", s->std_res_x[DPI_150]);

      s->std_res_x[DPI_160] = get_IN_std_res_160 (in);
      s->std_res_y[DPI_160] = s->std_res_x[DPI_160];
      DBG (15, "  160 dpi: %d\n", s->std_res_x[DPI_160]);

      s->std_res_x[DPI_180] = get_IN_std_res_180 (in);
      s->std_res_y[DPI_180] = s->std_res_x[DPI_180];
      DBG (15, "  180 dpi: %d\n", s->std_res_x[DPI_180]);

      s->std_res_x[DPI_200] = get_IN_std_res_200 (in);
      s->std_res_y[DPI_200] = s->std_res_x[DPI_200];
      DBG (15, "  200 dpi: %d\n", s->std_res_x[DPI_200]);

      s->std_res_x[DPI_240] = get_IN_std_res_240 (in);
      s->std_res_y[DPI_240] = s->std_res_x[DPI_240];
      DBG (15, "  240 dpi: %d\n", s->std_res_x[DPI_240]);

      s->std_res_x[DPI_300] = get_IN_std_res_300 (in);
      s->std_res_y[DPI_300] = s->std_res_x[DPI_300];
      DBG (15, "  300 dpi: %d\n", s->std_res_x[DPI_300]);

      s->std_res_x[DPI_320] = get_IN_std_res_320 (in);
      s->std_res_y[DPI_320] = s->std_res_x[DPI_320];
      DBG (15, "  320 dpi: %d\n", s->std_res_x[DPI_320]);

      s->std_res_x[DPI_400] = get_IN_std_res_400 (in);
      s->std_res_y[DPI_400] = s->std_res_x[DPI_400];
      DBG (15, "  400 dpi: %d\n", s->std_res_x[DPI_400]);

      s->std_res_x[DPI_480] = get_IN_std_res_480 (in);
      s->std_res_y[DPI_480] = s->std_res_x[DPI_480];
      DBG (15, "  480 dpi: %d\n", s->std_res_x[DPI_480]);

      s->std_res_x[DPI_600] = get_IN_std_res_600 (in);
      s->std_res_y[DPI_600] = s->std_res_x[DPI_600];
      DBG (15, "  600 dpi: %d\n", s->std_res_x[DPI_600]);

      s->std_res_x[DPI_800] = get_IN_std_res_800 (in);
      s->std_res_y[DPI_800] = s->std_res_x[DPI_800];
      DBG (15, "  800 dpi: %d\n", s->std_res_x[DPI_800]);

      s->std_res_x[DPI_1200] = get_IN_std_res_1200 (in);
      s->std_res_y[DPI_1200] = s->std_res_x[DPI_1200];
      DBG (15, "  1200 dpi: %d\n", s->std_res_x[DPI_1200]);

      /* maximum window width and length are reported in basic units.*/
      s->max_x = get_IN_window_width(in) * 1200 / s->basic_x_res;
      DBG(15, "  max width: %d (%2.2f in)\n",s->max_x,(float)s->max_x/1200);

      s->max_y = get_IN_window_length(in) * 1200 / s->basic_y_res;
      DBG(15, "  max length: %d (%2.2f in)\n",s->max_y,(float)s->max_y/1200);

      DBG (15, "  AWD: %d\n", get_IN_awd(in));
      DBG (15, "  CE Emphasis: %d\n", get_IN_ce_emphasis(in));
      DBG (15, "  C Emphasis: %d\n", get_IN_c_emphasis(in));
      DBG (15, "  High quality: %d\n", get_IN_high_quality(in));

      /* known modes FIXME more here? */
      s->can_grayscale = get_IN_multilevel (in);
      DBG (15, "  grayscale: %d\n", s->can_grayscale);

      s->can_halftone = get_IN_half_tone (in);
      DBG (15, "  halftone: %d\n", s->can_halftone);

      s->can_monochrome = get_IN_monochrome (in);
      DBG (15, "  monochrome: %d\n", s->can_monochrome);

      s->can_overflow = get_IN_overflow(in);
      DBG (15, "  overflow: %d\n", s->can_overflow);
  }
  /*FIXME no vpd, set some defaults? */
  else{
    DBG (5, "init_vpd: Your scanner does not support VPD?\n");
    DBG (5, "init_vpd: Please contact kitno455 at gmail dot com\n");
    DBG (5, "init_vpd: with details of your scanner model.\n");
  }

  DBG (10, "init_vpd: finish\n");

  return ret;
}

/*
 * get model specific info that is not in vpd, and correct
 * errors in vpd data. struct is already initialized to 0.
 */
static SANE_Status
init_model (struct scanner *s)
{

  DBG (10, "init_model: start\n");

  s->reverse_by_mode[MODE_LINEART] = 1;
  s->reverse_by_mode[MODE_HALFTONE] = 1;
  s->reverse_by_mode[MODE_GRAYSCALE] = 0;
  s->reverse_by_mode[MODE_COLOR] = 0;

  s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_RGB;
  s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_RGB;

  s->always_op = 1;
  s->has_df = 1;
  s->has_btc = 1;
  s->has_counter = 1;
  s->has_adf = 1;
  s->has_duplex = 1;
  s->has_buffer = 1;
  s->can_read_panel = 1;
  s->can_write_panel = 1;
  s->has_ssm = 1;

  s->brightness_steps = 255;
  s->contrast_steps = 255;
  s->threshold_steps = 255;

  s->ppl_mod = 1;
  s->bg_color = 0xee;

  /* assume these are same as adf, override below */
  s->valid_x = s->max_x;
  s->max_x_fb = s->max_x;
  s->max_y_fb = s->max_y;

  /* missing from vpd- we will unset these for some machines below */
  s->can_color = 1;
  s->can_read_lifecycle_counters = 1;

  /* specific settings missing from vpd */
  if (strstr (s->model_name,"DR-9080")){
    s->has_comp_JPEG = 1;
    s->rgb_format = 2;
  }

  else if (strstr (s->model_name,"DR-6080")
    || strstr (s->model_name,"DR-7580")){
    s->has_comp_JPEG = 1;
    s->can_color = 0;
  }

  else if (strstr (s->model_name,"DR-7090")){
    s->has_flatbed = 1;
  }

  else if (strstr (s->model_name,"DR-9050")
    || strstr (s->model_name,"DR-7550")
    || strstr (s->model_name,"DR-6050")
    || strstr (s->model_name,"DR-G1100")
    || strstr (s->model_name,"DR-G1130")
  ){

    /*missing*/
    s->std_res_x[DPI_100]=1;
    s->std_res_y[DPI_100]=1;
    s->std_res_x[DPI_150]=1;
    s->std_res_y[DPI_150]=1;
    s->std_res_x[DPI_200]=1;
    s->std_res_y[DPI_200]=1;
    s->std_res_x[DPI_240]=1;
    s->std_res_y[DPI_240]=1;
    s->std_res_x[DPI_300]=1;
    s->std_res_y[DPI_300]=1;
    s->std_res_x[DPI_400]=1;
    s->std_res_y[DPI_400]=1;
    s->std_res_x[DPI_600]=1;
    s->std_res_y[DPI_600]=1;

    /*weirdness*/
    s->has_ssm = 0;
    s->has_ssm2 = 1;
  }

  else if (strstr (s->model_name,"DR-4080")
    || strstr (s->model_name,"DR-4580")
    || strstr (s->model_name,"DR-7080")){
    s->has_flatbed = 1;
  }

  else if (strstr (s->model_name,"DR-2580")){
    s->invert_tly = 1;
    s->rgb_format = 1;
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_RRGGBB;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_rRgGbB;
    s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_gG;
    s->duplex_interlace = DUPLEX_INTERLACE_FBfb;
    s->need_ccal = 1;
    s->fcal_src = FCAL_SRC_SCAN;
    s->fcal_dest = FCAL_DEST_SW;
    /*s->duplex_offset = 432; now set in config file*/
    s->duplex_offset_side = SIDE_BACK;

    /*lies*/
    s->can_halftone=0;
    s->can_monochrome=0;
  }

  else if (strstr (s->model_name,"DR-2510")
   || strstr (s->model_name,"DR-2010")
  ){
    s->rgb_format = 1;
    s->always_op = 0;
    s->unknown_byte2 = 0x80;
    s->fixed_width = 1;
    s->valid_x = 8.5 * 1200;
    s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_2510;
    s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_2510;
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_2510;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_2510;
    s->duplex_interlace = DUPLEX_INTERLACE_2510;
    /*s->duplex_offset = 400; now set in config file*/
    s->need_ccal = 1;
    s->fcal_src = FCAL_SRC_SCAN;
    s->fcal_dest = FCAL_DEST_SW;
    s->sw_lut = 1;
    /*s->invert_tly = 1;*/

    /*only in Y direction, so we trash them in X*/
    s->std_res_x[DPI_100]=0;
    s->std_res_x[DPI_150]=0;
    s->std_res_x[DPI_200]=0;
    s->std_res_x[DPI_240]=0;
    s->std_res_x[DPI_400]=0;

    /*lies*/
    s->can_halftone=0;
    s->can_monochrome=0;
  }

  else if (strstr (s->model_name,"R40")
  ){
	/* confirmed */
    s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_C120;
    s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_C120;
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_C120;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_C120;
    s->duplex_interlace = DUPLEX_INTERLACE_2510;
    /*s->duplex_offset = 320; now set in config file*/
    s->fixed_width = 1;
    s->need_ccal = 1;
    s->fcal_src = FCAL_SRC_SCAN;
    s->fcal_dest = FCAL_DEST_SW;
    s->rgb_format = 1;
    s->sw_lut = 1;

    /*only in Y direction, so we trash them in X*/
    s->std_res_x[DPI_100]=0;
    s->std_res_x[DPI_150]=0;
    s->std_res_x[DPI_200]=0;
    s->std_res_x[DPI_240]=0;
    s->std_res_x[DPI_400]=0;

    /* suspected settings */
    s->ccal_version = 3;
    s->has_df_ultra = 1;
  }

  /* copied from 2510, possibly incorrect */
  else if (strstr (s->model_name,"DR-3010")){
    s->rgb_format = 1;
    s->always_op = 0;
    s->unknown_byte2 = 0x80;
    s->fixed_width = 1;
    s->valid_x = 8.5 * 1200;
    s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_2510;
    s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_2510;
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_2510;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_2510;
    s->duplex_interlace = DUPLEX_INTERLACE_2510;
    /*s->duplex_offset = 400; now set in config file*/
    s->need_ccal = 1;
    s->fcal_src = FCAL_SRC_SCAN;
    s->fcal_dest = FCAL_DEST_SW;
    s->sw_lut = 1;
    s->invert_tly = 1;
    s->has_function_number = 1;

    /*only in Y direction, so we trash them in X*/
    s->std_res_x[DPI_100]=0;
    s->std_res_x[DPI_150]=0;
    s->std_res_x[DPI_200]=0;
    s->std_res_x[DPI_240]=0;
    s->std_res_x[DPI_400]=0;

    /*lies*/
    s->can_halftone=0;
    s->can_monochrome=0;
  }

  else if (strstr (s->model_name,"DR-2050")
   || strstr (s->model_name,"DR-2080")){
    s->can_write_panel = 0;
    s->has_df = 0;
    s->fixed_width = 1;
    s->even_Bpl = 1;
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_RRGGBB;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_RRGGBB;
    s->duplex_interlace = DUPLEX_INTERLACE_FBfb;
    s->fcal_src = FCAL_SRC_HW;
    s->fcal_dest = FCAL_DEST_SW;
    s->bg_color = 0x08;
    /*s->duplex_offset = 840; now set in config file*/
    s->sw_lut = 1;

    /*lies*/
    s->can_halftone=0;
    s->can_monochrome=0;
  }

  else if (strstr (s->model_name,"DR-3080")){
    s->can_write_panel = 0;
    s->has_df = 0;
    s->has_btc = 0;
  }

  else if (strstr (s->model_name,"DR-5060F")){
    s->can_write_panel = 0;
    s->has_df = 0;
    s->has_btc = 0;
    s->ppl_mod = 32;
    s->reverse_by_mode[MODE_LINEART] = 0;
    s->reverse_by_mode[MODE_HALFTONE] = 0;
    s->can_color = 0;
  }

  else if (strstr (s->model_name,"DR-5020")){
    s->can_read_panel = 0;
    s->can_write_panel = 0;
    s->has_df = 0;
    s->has_btc = 0;
    s->ppl_mod = 32;
    s->reverse_by_mode[MODE_LINEART] = 0;
    s->reverse_by_mode[MODE_HALFTONE] = 0;
    s->can_color = 0;
  }

  /* all copied from P-215 */
  else if (strstr (s->model_name, "P-150")) {
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_rRgGbB;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_RRGGBB;
    s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_gG;
    s->duplex_interlace = DUPLEX_INTERLACE_FBfb;
    s->need_ccal = 1;
    s->invert_tly = 1;
    s->unknown_byte2 = 0x88;
    s->rgb_format = 1;
    s->has_ssm_pay_head_len = 1;
    s->ppl_mod = 8;
    s->ccal_version = 3;
    s->can_read_sensors = 1;
    s->has_card = 1;
  }

  else if (strstr (s->model_name, "P-208")
   || strstr (s->model_name,"DR-P208")){
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_RRGGBB;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_rRgGbB;
    s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_gG;
    s->duplex_interlace = DUPLEX_INTERLACE_FBfb;
    s->need_ccal = 1;
    s->fcal_src = FCAL_SRC_SCAN;
    s->fcal_dest = FCAL_DEST_SW;
    s->invert_tly = 1;
    s->unknown_byte2 = 0x88;
    s->rgb_format = 1;
    s->has_ssm_pay_head_len = 1;
    s->ppl_mod = 8;
    s->ccal_version = 3;
    s->can_read_sensors = 1;
  }

  else if (strstr (s->model_name, "P-215")
   || strstr (s->model_name,"DR-P215")){
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_rRgGbB;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_RRGGBB;
    s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_gG;
    s->duplex_interlace = DUPLEX_INTERLACE_FBfb;
    s->need_ccal = 1;
    s->invert_tly = 1;
    s->unknown_byte2 = 0x88;
    s->rgb_format = 1;
    s->has_ssm_pay_head_len = 1;
    s->ppl_mod = 8;
    s->ccal_version = 3;
    s->can_read_sensors = 1;
    s->has_card = 1;
  }

  else if (strstr (s->model_name,"DR-M160")){

    /*missing*/
    s->std_res_x[DPI_100]=1;
    s->std_res_y[DPI_100]=1;
    s->std_res_x[DPI_150]=1;
    s->std_res_y[DPI_150]=1;
    s->std_res_x[DPI_200]=1;
    s->std_res_y[DPI_200]=1;
    s->std_res_x[DPI_300]=1;
    s->std_res_y[DPI_300]=1;
    s->std_res_x[DPI_400]=1;
    s->std_res_y[DPI_400]=1;
    s->std_res_x[DPI_600]=1;
    s->std_res_y[DPI_600]=1;

    s->has_comp_JPEG = 1;
    s->rgb_format = 1;
    s->has_df_ultra = 1;

    s->color_inter_by_res[DPI_100] = COLOR_INTERLACE_GBR;
    s->color_inter_by_res[DPI_150] = COLOR_INTERLACE_GBR;
    s->color_inter_by_res[DPI_200] = COLOR_INTERLACE_BRG;
    s->color_inter_by_res[DPI_400] = COLOR_INTERLACE_GBR;

    /*weirdness*/
    s->always_op = 0;
    s->fixed_width = 1;
    s->invert_tly = 1;
    s->can_write_panel = 0;
    s->has_ssm = 0;
    s->has_ssm2 = 1;
    s->duplex_interlace = DUPLEX_INTERLACE_FfBb;
    s->duplex_offset_side = SIDE_FRONT;

    /*lies*/
    s->can_halftone=0;
    s->can_monochrome=0;
  }

  else if (strstr (s->model_name,"DR-M140")){

    /*missing*/
    s->std_res_x[DPI_100]=1;
    s->std_res_y[DPI_100]=1;
    s->std_res_x[DPI_150]=1;
    s->std_res_y[DPI_150]=1;
    s->std_res_x[DPI_200]=1;
    s->std_res_y[DPI_200]=1;
    s->std_res_x[DPI_300]=1;
    s->std_res_y[DPI_300]=1;
    s->std_res_x[DPI_400]=1;
    s->std_res_y[DPI_400]=1;
    s->std_res_x[DPI_600]=1;
    s->std_res_y[DPI_600]=1;

    s->has_comp_JPEG = 1;
    s->rgb_format = 1;
    s->has_df_ultra = 1;

    s->color_inter_by_res[DPI_100] = COLOR_INTERLACE_GBR;
    s->color_inter_by_res[DPI_150] = COLOR_INTERLACE_GBR;
    s->color_inter_by_res[DPI_200] = COLOR_INTERLACE_BRG;
    s->color_inter_by_res[DPI_400] = COLOR_INTERLACE_GBR;

    /*weirdness*/
    s->always_op = 0;
    s->fixed_width = 1;
    s->invert_tly = 1;
    s->can_write_panel = 0;
    s->has_ssm = 0;
    s->has_ssm2 = 1;
    s->duplex_interlace = DUPLEX_INTERLACE_FfBb;
    s->duplex_offset_side = SIDE_BACK;

    /*lies*/
    s->can_halftone=0;
    s->can_monochrome=0;
  }

  else if (strstr (s->model_name,"DR-C120")
    || strstr (s->model_name,"DR-C130")
  ){

    /*confirmed settings*/
    s->need_ccal = 1;
    s->ccal_version = 3;

    s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_C120;
    s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_C120;
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_C120;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_C120;
    s->duplex_interlace = DUPLEX_INTERLACE_2510;
    s->duplex_offset_side = SIDE_BACK;
    s->unknown_byte2 = 0x88;
    s->fcal_src = FCAL_SRC_SCAN;
    s->fcal_dest = FCAL_DEST_SW;
    s->sw_lut = 1;
    s->rgb_format = 1;
    /*s->duplex_offset = 400; now set in config file*/

    /*only in Y direction, so we trash them in X*/
    s->std_res_x[DPI_100]=0;
    s->std_res_x[DPI_150]=0;
    s->std_res_x[DPI_200]=0;
    s->std_res_x[DPI_240]=0;
    s->std_res_x[DPI_400]=0;

    /*suspected settings*/
    s->always_op = 0;
    s->fixed_width = 1;
    s->valid_x = 8.5 * 1200;
  }
  else if (strstr (s->model_name,"DR-C125")){

    /*confirmed settings*/
    s->gray_interlace[SIDE_FRONT] = GRAY_INTERLACE_2510;
    s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_2510;
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_2510;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_2510;
    s->duplex_interlace = DUPLEX_INTERLACE_2510;
    s->duplex_offset_side = SIDE_BACK;
    s->unknown_byte2 = 0x88;
    s->need_ccal = 1;
    s->ccal_version = 3;
    s->fcal_src = FCAL_SRC_SCAN;
    s->fcal_dest = FCAL_DEST_SW;
    s->sw_lut = 1;
    s->rgb_format = 1;
    /*s->duplex_offset = 400; now set in config file*/

    /*only in Y direction, so we trash them in X*/
    s->std_res_x[DPI_100]=0;
    s->std_res_x[DPI_150]=0;
    s->std_res_x[DPI_200]=0;
    s->std_res_x[DPI_240]=0;
    s->std_res_x[DPI_400]=0;

    /*suspected settings*/
    s->always_op = 0;
    s->fixed_width = 1;
    s->valid_x = 8.5 * 1200;
  }

  else if (strstr (s->model_name,"DR-C225")){

    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_RRGGBB;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_RRGGBB;
    s->gray_interlace[SIDE_BACK] = GRAY_INTERLACE_gG;
    s->duplex_interlace = DUPLEX_INTERLACE_PER_CHANNEL;

    s->unknown_byte2 = 0x88;
    s->need_ccal = 1;
    s->ccal_version = 3;
    s->fcal_src = FCAL_SRC_SCAN;
    s->fcal_dest = FCAL_DEST_HW;
    s->invert_tly = 1;
    s->rgb_format = 1;
    /*s->duplex_offset = 400; now set in config file*/

    /*only in Y direction, so we trash them in X*/
    s->std_res_x[DPI_100]=0;
    s->std_res_x[DPI_150]=0;
    s->std_res_x[DPI_200]=0;
    s->std_res_x[DPI_240]=0;
    s->std_res_x[DPI_400]=0;

    /*suspected settings*/
    s->always_op = 0;
    s->fixed_width = 1;
    s->valid_x = 8.5 * 1200;
  }

  else if (strstr (s->model_name,"DR-F120")){
    /* TODO items:
     * * has_rif = 0 ? is this correct
     * * has_comp_JPEG = 0 ? is this correct
     * * need_ccal = need_fcal = need_fcal_buffer = ccal_version = 0 ? is this correct
     */

    /* Required for USB coms */
    s->has_ssm = 0;
    s->has_ssm2 = 1;

    /*missing*/
    s->std_res_x[DPI_100] = 1;
    s->std_res_y[DPI_100] = 1;
    // DPI_150 not supported
    s->std_res_x[DPI_200] = 1;
    s->std_res_y[DPI_200] = 1;
    s->std_res_x[DPI_300] = 1;
    s->std_res_y[DPI_300] = 1;
    // DPI_400 not supported
    s->std_res_x[DPI_600]= 1;
    s->std_res_y[DPI_600] = 1;
    // DPI_1200 not supported
    // NOTE: This scanner supports higher resolutions
    // in the Y direction, but 600 is maximum in X

    // This is true however only the ADF is ever selected in hardware
    // FIXME: What extra option is needed to select this in the USB comms
    s->has_flatbed = 1;

    /* duplex */
    s->duplex_interlace = DUPLEX_INTERLACE_fFBb;
    s->color_interlace[SIDE_FRONT] = COLOR_INTERLACE_GBR;
    s->color_interlace[SIDE_BACK] = COLOR_INTERLACE_GBR;
    s->color_inter_by_res[DPI_100] = COLOR_INTERLACE_RGB;
    s->color_inter_by_res[DPI_600] = COLOR_INTERLACE_RGB;
    s->duplex_offset_side = SIDE_BACK;

    /* weirdness */
    s->fixed_width = 1;

    /* lies */
    s->can_halftone = 0;
  }

  else if (strstr (s->model_name,"DR-X10C")){
    int i = 0;

    /* Required for USB coms */
    s->has_ssm = 0;
    s->has_ssm2 = 1;

    /* missing */
    s->std_res_x[DPI_100]=1;
    s->std_res_y[DPI_100]=1;
    s->std_res_x[DPI_150]=1;
    s->std_res_y[DPI_150]=1;
    s->std_res_x[DPI_200]=1;
    s->std_res_y[DPI_200]=1;
    s->std_res_x[DPI_240]=1;
    s->std_res_y[DPI_240]=1;
    s->std_res_x[DPI_300]=1;
    s->std_res_y[DPI_300]=1;
    s->std_res_x[DPI_400]=1;
    s->std_res_y[DPI_400]=1;
    s->std_res_x[DPI_600]=1;
    s->std_res_y[DPI_600]=1;

    s->has_hwcrop = 1;

    /*valid horizontal offsets for post-imprinter*/
    s->post_imprinter_h_offset_list[++i] = 21;
    s->post_imprinter_h_offset_list[++i] = 35;
    s->post_imprinter_h_offset_list[++i] = 47;
    s->post_imprinter_h_offset_list[++i] = 59;
    s->post_imprinter_h_offset_list[++i] = 72;
    s->post_imprinter_h_offset_list[++i] = 99;
    s->post_imprinter_h_offset_list[++i] = 114;
    s->post_imprinter_h_offset_list[++i] = 143;
    s->post_imprinter_h_offset_list[++i] = 155;
    s->post_imprinter_h_offset_list[++i] = 167;
    s->post_imprinter_h_offset_list[++i] = 196;
    s->post_imprinter_h_offset_list[++i] = 211;
    s->post_imprinter_h_offset_list[++i] = 239;
    s->post_imprinter_h_offset_list[++i] = 251;
    s->post_imprinter_h_offset_list[++i] = 263;
    s->post_imprinter_h_offset_list[++i] = 275;
    s->post_imprinter_h_offset_list[++i] = 289;
    s->post_imprinter_h_offset_list[0] = i;

    i = 0;
    /*valid horizontal offsets for pre-imprinter*/
    s->pre_imprinter_h_offset_list[++i] = 14;
    s->pre_imprinter_h_offset_list[++i] = 28;
    s->pre_imprinter_h_offset_list[++i] = 41;
    s->pre_imprinter_h_offset_list[++i] = 53;
    s->pre_imprinter_h_offset_list[++i] = 65;
    s->pre_imprinter_h_offset_list[++i] = 106;
    s->pre_imprinter_h_offset_list[0] = i;

    /*valid vertical offsets for imprinters*/
    s->imprinter_v_offset_range.min = 0;
    s->imprinter_v_offset_range.max = 500;
    s->imprinter_v_offset_range.quant = 1;

    i = 0;
    /*valid font angles for imprinters*/
    s->imprinter_font_angle_list[++i] = 0;
    s->imprinter_font_angle_list[++i] = 90;
    s->imprinter_font_angle_list[++i] = 180;
    s->imprinter_font_angle_list[++i] = 270;
    s->imprinter_font_angle_list[0] = i;

    i = 0;
    s->imprint_font_size_list[i++] = STRING_IMPRINTER_8x12_FONT;
    s->imprint_font_size_list[i++] = STRING_IMPRINTER_12x12_FONT;
    s->imprint_font_size_list[i] = NULL;

    i = 0;
    s->imprint_addon_mode_list[i++] = STRING_IMPRINTER_ADDON_BoI;
    s->imprint_addon_mode_list[i++] = STRING_IMPRINTER_ADDON_BoW;
    s->imprint_addon_mode_list[i++] = STRING_IMPRINTER_ADDON_WoB;
    s->imprint_addon_mode_list[i++] = STRING_NONE;
    s->imprint_addon_mode_list[i] = NULL;
  }

  DBG (10, "init_model: finish\n");

  return SANE_STATUS_GOOD;
}

/*
 * try to detect imprinters.
 */
static SANE_Status
init_imprinters (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "init_imprinters: start\n");

  /* check the pre imprinter first */
  ret = detect_imprinter(s,R_PRE_IMPRINTER);
  if(ret == SANE_STATUS_GOOD){
    DBG (15, "init_imprinters: preimprinter found\n");
    s->has_pre_imprinter = 1;
  }

  /* these scanners only support one imprinter */
  /* so only ask for postimp if preimp not found */
  else if(ret == SANE_STATUS_UNSUPPORTED){
    ret = detect_imprinter(s,R_POST_IMPRINTER);
    if(ret == SANE_STATUS_GOOD){
      DBG (15, "init_imprinters: postimprinter found\n");
      s->has_post_imprinter = 1;
    }
  }

  DBG (10, "init_imprinters: finish\n");
  return ret;
}

/*
 * This function enables the buttons and preloads the current panel values
 */
static SANE_Status
init_panel (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "init_panel: start\n");

  ret = read_panel(s,0);
  if(ret){
    DBG (5, "init_panel: disabling read_panel\n");
    s->can_read_panel = 0;
  }

  s->panel_enable_led = 1;
  s->panel_counter = 0;
  ret = send_panel(s);
  if(ret){
    DBG (5, "init_panel: disabling send_panel\n");
    s->can_write_panel = 0;
  }

  DBG (10, "init_panel: finish\n");

  return ret;
}

/*
 * This function disables the lifecycle counters if not available
 */
static SANE_Status
init_counters (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "init_counters: start\n");

  ret = read_counters(s);
  if(ret){
    DBG (5, "init_counters: disabling lifecycle counters\n");
    s->can_read_lifecycle_counters = 0;
  }

  DBG (10, "init_counters: finish\n");

  return ret;
}

/*
 * set good default user values.
 * struct is already initialized to 0.
 */
static SANE_Status
init_user (struct scanner *s)
{

  DBG (10, "init_user: start\n");

  /* source */
  if(s->has_flatbed)
    s->u.source = SOURCE_FLATBED;
  else if(s->has_adf)
    s->u.source = SOURCE_ADF_FRONT;
  else if(s->has_card)
    s->u.source = SOURCE_CARD_FRONT;

  /* scan mode */
  if(s->can_monochrome)
    s->u.mode=MODE_LINEART;
  else if(s->can_halftone)
    s->u.mode=MODE_HALFTONE;
  else if(s->can_grayscale)
    s->u.mode=MODE_GRAYSCALE;
  else if(s->can_color)
    s->u.mode=MODE_COLOR;

  /*x and y res*/
  s->u.dpi_x = s->basic_x_res;
  s->u.dpi_y = s->basic_x_res;

  /* page width US-Letter */
  s->u.page_x = 8.5 * 1200;
  if(s->u.page_x > s->valid_x){
    s->u.page_x = s->valid_x;
  }

  /* page height US-Letter */
  s->u.page_y = 11 * 1200;
  if(s->u.page_y > s->max_y){
    s->u.page_y = s->max_y;
  }

  /* bottom-right x */
  s->u.br_x = s->u.page_x;

  /* bottom-right y */
  s->u.br_y = s->u.page_y;

  s->threshold = 90;
  s->compress_arg = 50;

  s->pre_imprint.h_offset = 65;
  s->post_imprint.h_offset = 155;
  s->post_imprint_addon_mode = ADDON_BoI;

  DBG (10, "init_user: finish\n");

  return SANE_STATUS_GOOD;
}

/*
 * This function presets the "option" array to blank
 */
static SANE_Status
init_options (struct scanner *s)
{
  int i;

  DBG (10, "init_options: start\n");

  memset (s->opt, 0, sizeof (s->opt));
  for (i = 0; i < NUM_OPTIONS; ++i) {
      s->opt[i].name = "filler";
      s->opt[i].size = sizeof (SANE_Word);
      s->opt[i].cap = SANE_CAP_INACTIVE;
  }

  /* go ahead and setup the first opt, because
   * frontend may call control_option on it
   * before calling get_option_descriptor
   */
  s->opt[OPT_NUM_OPTS].name = SANE_NAME_NUM_OPTIONS;
  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;

  DBG (10, "init_options: finish\n");

  return SANE_STATUS_GOOD;
}

/*
 * From the SANE spec:
 * This function is used to establish a connection to a particular
 * device. The name of the device to be opened is passed in argument
 * name. If the call completes successfully, a handle for the device
 * is returned in *h. As a special case, specifying a zero-length
 * string as the device requests opening the first available device
 * (if there is such a device).
 */
SANE_Status
sane_open (SANE_String_Const name, SANE_Handle * handle)
{
  struct scanner *dev = NULL;
  struct scanner *s = NULL;
  SANE_Status ret;

  DBG (10, "sane_open: start\n");

  if(scanner_devList){
    DBG (15, "sane_open: searching currently attached scanners\n");
  }
  else{
    DBG (15, "sane_open: no scanners currently attached, attaching\n");

    ret = sane_get_devices(NULL,0);
    if(ret != SANE_STATUS_GOOD){
      return ret;
    }
  }

  if(name[0] == 0){
    DBG (15, "sane_open: no device requested, using default\n");
    s = scanner_devList;
  }
  else{
    DBG (15, "sane_open: device %s requested\n", name);

    for (dev = scanner_devList; dev; dev = dev->next) {
      if (strcmp (dev->sane.name, name) == 0
       || strcmp (dev->device_name, name) == 0) { /*always allow sanei devname*/
        s = dev;
        break;
      }
    }
  }

  if (!s) {
    DBG (5, "sane_open: no device found\n");
    return SANE_STATUS_INVAL;
  }

  DBG (15, "sane_open: device %s found\n", s->sane.name);

  *handle = s;

  /* connect the fd so we can talk to scanner */
  ret = connect_fd(s);
  if(ret != SANE_STATUS_GOOD){
    return ret;
  }

  DBG (10, "sane_open: finish\n");

  return SANE_STATUS_GOOD;
}

/*
 * @@ Section 3 - SANE Options functions
 */

/*
 * Returns the options we know.
 *
 * From the SANE spec:
 * This function is used to access option descriptors. The function
 * returns the option descriptor for option number n of the device
 * represented by handle h. Option number 0 is guaranteed to be a
 * valid option. Its value is an integer that specifies the number of
 * options that are available for device handle h (the count includes
 * option 0). If n is not a valid option index, the function returns
 * NULL. The returned option descriptor is guaranteed to remain valid
 * (and at the returned address) until the device is closed.
 */
const SANE_Option_Descriptor *
sane_get_option_descriptor (SANE_Handle handle, SANE_Int option)
{
  struct scanner *s = handle;
  int i;
  SANE_Option_Descriptor *opt = &s->opt[option];

  DBG (20, "sane_get_option_descriptor: %d\n", option);

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

  /* "Mode" group -------------------------------------------------------- */
  if(option==OPT_STANDARD_GROUP){
    opt->name = SANE_NAME_STANDARD;
    opt->title = SANE_TITLE_STANDARD;
    opt->desc = SANE_DESC_STANDARD;
    opt->type = SANE_TYPE_GROUP;
    opt->constraint_type = SANE_CONSTRAINT_NONE;
  }

  /* source */
  if(option==OPT_SOURCE){
    i=0;
    if(s->has_flatbed){
      s->source_list[i++]=STRING_FLATBED;
    }
    if(s->has_adf){
      s->source_list[i++]=STRING_ADFFRONT;

      if(s->has_back){
        s->source_list[i++]=STRING_ADFBACK;
      }
      if(s->has_duplex){
        s->source_list[i++]=STRING_ADFDUPLEX;
      }
    }
    if(s->has_card){
      s->source_list[i++]=STRING_CARDFRONT;

      if(s->has_back){
        s->source_list[i++]=STRING_CARDBACK;
      }
      if(s->has_duplex){
        s->source_list[i++]=STRING_CARDDUPLEX;
      }
    }
    s->source_list[i]=NULL;

    opt->name = SANE_NAME_SCAN_SOURCE;
    opt->title = SANE_TITLE_SCAN_SOURCE;
    opt->desc = SANE_DESC_SCAN_SOURCE;
    opt->type = SANE_TYPE_STRING;
    opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    opt->constraint.string_list = s->source_list;
    opt->size = maxStringSize (opt->constraint.string_list);
    opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  /* scan mode */
  if(option==OPT_MODE){
    i=0;
    if(s->can_monochrome || s->can_grayscale || s->can_color){
      s->mode_list[i++]=STRING_LINEART;
    }
    if(s->can_halftone){
      s->mode_list[i++]=STRING_HALFTONE;
    }
    if(s->can_grayscale || s->can_color){
      s->mode_list[i++]=STRING_GRAYSCALE;
    }
    if(s->can_color){
      s->mode_list[i++]=STRING_COLOR;
    }
    s->mode_list[i]=NULL;

    opt->name = SANE_NAME_SCAN_MODE;
    opt->title = SANE_TITLE_SCAN_MODE;
    opt->desc = SANE_DESC_SCAN_MODE;
    opt->type = SANE_TYPE_STRING;
    opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    opt->constraint.string_list = s->mode_list;
    opt->size = maxStringSize (opt->constraint.string_list);
    opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  /* resolution */
  /* some scanners only support fixed res
   * build a list of possible choices */
  /* we actually only look at the y resolution choices,
   * and interpolate the image data as required for limited x resolutions */
  if(option==OPT_RES){
    i=0;
    if(s->std_res_y[DPI_60] && s->max_y_res >= 60 && s->min_y_res <= 60){
      s->res_list[++i] = 60;
    }
    if(s->std_res_y[DPI_75] && s->max_y_res >= 75 && s->min_y_res <= 75){
      s->res_list[++i] = 75;
    }
    if(s->std_res_y[DPI_100] && s->max_y_res >= 100 && s->min_y_res <= 100){
      s->res_list[++i] = 100;
    }
    if(s->std_res_y[DPI_120] && s->max_y_res >= 120 && s->min_y_res <= 120){
      s->res_list[++i] = 120;
    }
    if(s->std_res_y[DPI_150] && s->max_y_res >= 150 && s->min_y_res <= 150){
      s->res_list[++i] = 150;
    }
    if(s->std_res_y[DPI_160] && s->max_y_res >= 160 && s->min_y_res <= 160){
      s->res_list[++i] = 160;
    }
    if(s->std_res_y[DPI_180] && s->max_y_res >= 180 && s->min_y_res <= 180){
      s->res_list[++i] = 180;
    }
    if(s->std_res_y[DPI_200] && s->max_y_res >= 200 && s->min_y_res <= 200){
      s->res_list[++i] = 200;
    }
    if(s->std_res_y[DPI_240] && s->max_y_res >= 240 && s->min_y_res <= 240){
      s->res_list[++i] = 240;
    }
    if(s->std_res_y[DPI_300] && s->max_y_res >= 300 && s->min_y_res <= 300){
      s->res_list[++i] = 300;
    }
    if(s->std_res_y[DPI_320] && s->max_y_res >= 320 && s->min_y_res <= 320){
      s->res_list[++i] = 320;
    }
    if(s->std_res_y[DPI_400] && s->max_y_res >= 400 && s->min_y_res <= 400){
      s->res_list[++i] = 400;
    }
    if(s->std_res_y[DPI_480] && s->max_y_res >= 480 && s->min_y_res <= 480){
      s->res_list[++i] = 480;
    }
    if(s->std_res_y[DPI_600] && s->max_y_res >= 600 && s->min_y_res <= 600){
      s->res_list[++i] = 600;
    }
    if(s->std_res_y[DPI_800] && s->max_y_res >= 800 && s->min_y_res <= 800){
      s->res_list[++i] = 800;
    }
    if(s->std_res_y[DPI_1200] && s->max_y_res >= 1200 && s->min_y_res <= 1200){
      s->res_list[++i] = 1200;
    }
    s->res_list[0] = i;

    opt->name = SANE_NAME_SCAN_RESOLUTION;
    opt->title = SANE_TITLE_SCAN_RESOLUTION;
    opt->desc = SANE_DESC_SCAN_RESOLUTION;
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_DPI;
    opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

    if(s->step_y_res){
      s->res_range.min = s->min_y_res;
      s->res_range.max = s->max_y_res;
      s->res_range.quant = s->step_y_res;
      opt->constraint_type = SANE_CONSTRAINT_RANGE;
      opt->constraint.range = &s->res_range;
    }
    else{
      opt->constraint_type = SANE_CONSTRAINT_WORD_LIST;
      opt->constraint.word_list = s->res_list;
    }
  }

  /* "Geometry" group ---------------------------------------------------- */
  if(option==OPT_GEOMETRY_GROUP){
    opt->name = SANE_NAME_GEOMETRY;
    opt->title = SANE_TITLE_GEOMETRY;
    opt->desc = SANE_DESC_GEOMETRY;
    opt->type = SANE_TYPE_GROUP;
    opt->constraint_type = SANE_CONSTRAINT_NONE;
  }

  /* top-left x */
  if(option==OPT_TL_X){
    /* values stored in 1200 dpi units */
    /* must be converted to MM for sane */
    s->tl_x_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_x);
    s->tl_x_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_width(s));
    s->tl_x_range.quant = MM_PER_UNIT_FIX;

    opt->name = SANE_NAME_SCAN_TL_X;
    opt->title = SANE_TITLE_SCAN_TL_X;
    opt->desc = SANE_DESC_SCAN_TL_X;
    opt->type = SANE_TYPE_FIXED;
    opt->unit = SANE_UNIT_MM;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &(s->tl_x_range);
    opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  /* top-left y */
  if(option==OPT_TL_Y){
    /* values stored in 1200 dpi units */
    /* must be converted to MM for sane */
    s->tl_y_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_y);
    s->tl_y_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_height(s));
    s->tl_y_range.quant = MM_PER_UNIT_FIX;

    opt->name = SANE_NAME_SCAN_TL_Y;
    opt->title = SANE_TITLE_SCAN_TL_Y;
    opt->desc = SANE_DESC_SCAN_TL_Y;
    opt->type = SANE_TYPE_FIXED;
    opt->unit = SANE_UNIT_MM;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &(s->tl_y_range);
    opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  /* bottom-right x */
  if(option==OPT_BR_X){
    /* values stored in 1200 dpi units */
    /* must be converted to MM for sane */
    s->br_x_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_x);
    s->br_x_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_width(s));
    s->br_x_range.quant = MM_PER_UNIT_FIX;

    opt->name = SANE_NAME_SCAN_BR_X;
    opt->title = SANE_TITLE_SCAN_BR_X;
    opt->desc = SANE_DESC_SCAN_BR_X;
    opt->type = SANE_TYPE_FIXED;
    opt->unit = SANE_UNIT_MM;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &(s->br_x_range);
    opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  /* bottom-right y */
  if(option==OPT_BR_Y){
    /* values stored in 1200 dpi units */
    /* must be converted to MM for sane */
    s->br_y_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_y);
    s->br_y_range.max = SCANNER_UNIT_TO_FIXED_MM(get_page_height(s));
    s->br_y_range.quant = MM_PER_UNIT_FIX;

    opt->name = SANE_NAME_SCAN_BR_Y;
    opt->title = SANE_TITLE_SCAN_BR_Y;
    opt->desc = SANE_DESC_SCAN_BR_Y;
    opt->type = SANE_TYPE_FIXED;
    opt->unit = SANE_UNIT_MM;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &(s->br_y_range);
    opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  /* page width */
  if(option==OPT_PAGE_WIDTH){
    /* values stored in 1200 dpi units */
    /* must be converted to MM for sane */
    s->paper_x_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_x);
    s->paper_x_range.max = SCANNER_UNIT_TO_FIXED_MM(s->valid_x);
    s->paper_x_range.quant = MM_PER_UNIT_FIX;

    opt->name = SANE_NAME_PAGE_WIDTH;
    opt->title = SANE_TITLE_PAGE_WIDTH;
    opt->desc = SANE_DESC_PAGE_WIDTH;
    opt->type = SANE_TYPE_FIXED;
    opt->unit = SANE_UNIT_MM;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->paper_x_range;

    if(s->has_adf || s->has_card){
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
      if(s->u.source == SOURCE_FLATBED){
        opt->cap |= SANE_CAP_INACTIVE;
      }
    }
    else{
      opt->cap = SANE_CAP_INACTIVE;
    }
  }

  /* page height */
  if(option==OPT_PAGE_HEIGHT){
    /* values stored in 1200 dpi units */
    /* must be converted to MM for sane */
    s->paper_y_range.min = SCANNER_UNIT_TO_FIXED_MM(s->min_y);
    s->paper_y_range.max = SCANNER_UNIT_TO_FIXED_MM(s->max_y);
    s->paper_y_range.quant = MM_PER_UNIT_FIX;

    opt->name = SANE_NAME_PAGE_HEIGHT;
    opt->title = SANE_TITLE_PAGE_HEIGHT;
    opt->desc = SANE_DESC_PAGE_HEIGHT;
    opt->type = SANE_TYPE_FIXED;
    opt->unit = SANE_UNIT_MM;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->paper_y_range;

    if(s->has_adf || s->has_card){
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
      if(s->u.source == SOURCE_FLATBED){
        opt->cap |= SANE_CAP_INACTIVE;
      }
    }
    else{
      opt->cap = SANE_CAP_INACTIVE;
    }
  }

  /* "Enhancement" group ------------------------------------------------- */
  if(option==OPT_ENHANCEMENT_GROUP){
    opt->name = SANE_NAME_ENHANCEMENT;
    opt->title = SANE_TITLE_ENHANCEMENT;
    opt->desc = SANE_DESC_ENHANCEMENT;
    opt->type = SANE_TYPE_GROUP;
    opt->constraint_type = SANE_CONSTRAINT_NONE;
  }

  /* brightness */
  if(option==OPT_BRIGHTNESS){
    opt->name = SANE_NAME_BRIGHTNESS;
    opt->title = SANE_TITLE_BRIGHTNESS;
    opt->desc = SANE_DESC_BRIGHTNESS;
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->brightness_range;
    s->brightness_range.quant=1;

    /* some have hardware brightness (always 0 to 255?) */
    /* some use LUT or GT (-127 to +127)*/
    if (s->brightness_steps){
      s->brightness_range.min=-127;
      s->brightness_range.max=127;
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }
    else{
      opt->cap = SANE_CAP_INACTIVE;
    }
  }

  /* contrast */
  if(option==OPT_CONTRAST){
    opt->name = SANE_NAME_CONTRAST;
    opt->title = SANE_TITLE_CONTRAST;
    opt->desc = SANE_DESC_CONTRAST;
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->contrast_range;
    s->contrast_range.quant=1;

    /* some have hardware contrast (always 0 to 255?) */
    /* some use LUT or GT (-127 to +127)*/
    if (s->contrast_steps){
      s->contrast_range.min=-127;
      s->contrast_range.max=127;
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }
    else {
      opt->cap = SANE_CAP_INACTIVE;
    }
  }

  /*threshold*/
  if(option==OPT_THRESHOLD){
    opt->name = SANE_NAME_THRESHOLD;
    opt->title = SANE_TITLE_THRESHOLD;
    opt->desc = SANE_DESC_THRESHOLD;
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->threshold_range;
    s->threshold_range.min=0;
    s->threshold_range.max=s->threshold_steps;
    s->threshold_range.quant=1;

    if (s->threshold_steps){
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
      if(s->u.mode != MODE_LINEART){
        opt->cap |= SANE_CAP_INACTIVE;
      }
    }
    else {
      opt->cap = SANE_CAP_INACTIVE;
    }
  }

  if(option==OPT_RIF){
    opt->name = "rif";
    opt->title = "RIF";
    opt->desc = "Reverse image format";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    if (s->has_rif)
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  /* "Advanced" group ------------------------------------------------------ */
  if(option==OPT_ADVANCED_GROUP){
    opt->name = SANE_NAME_ADVANCED;
    opt->title = SANE_TITLE_ADVANCED;
    opt->desc = SANE_DESC_ADVANCED;
    opt->type = SANE_TYPE_GROUP;
    opt->constraint_type = SANE_CONSTRAINT_NONE;
  }

  /*image compression*/
  if(option==OPT_COMPRESS){
    i=0;
    s->compress_list[i++]=STRING_NONE;

    if(s->has_comp_JPEG){
#ifndef SANE_JPEG_DISABLED
      s->compress_list[i++]=STRING_JPEG;
#endif
    }

    s->compress_list[i]=NULL;

    opt->name = "compression";
    opt->title = "Compression";
    opt->desc = "Enable compressed data. May crash your front-end program";
    opt->type = SANE_TYPE_STRING;
    opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    opt->constraint.string_list = s->compress_list;
    opt->size = maxStringSize (opt->constraint.string_list);

    if (i > 1){
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
      if ( must_downsample(s) || s->s.mode < MODE_GRAYSCALE ){
        opt->cap |= SANE_CAP_INACTIVE;
      }
    }
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  /*image compression arg*/
  if(option==OPT_COMPRESS_ARG){

    opt->name = "compression-arg";
    opt->title = "Compression argument";
    opt->desc = "Level of JPEG compression. 1 is small file, 100 is large file.";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->compress_arg_range;
    s->compress_arg_range.quant=1;

    if(s->has_comp_JPEG){
      s->compress_arg_range.min=0;
      s->compress_arg_range.max=100;
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;

      if(s->compress != COMP_JPEG){
        opt->cap |= SANE_CAP_INACTIVE;
      }
    }
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  /*double feed by length*/
  if(option==OPT_DF_LENGTH){
    opt->name = "df-length";
    opt->title = "DF by length";
    opt->desc = "Detect double feeds by comparing document lengths";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_NONE;

    if (1)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  /*double feed by thickness */
  if(option==OPT_DF_THICKNESS){

    opt->name = "df-thickness";
    opt->title = "DF by thickness";
    opt->desc = "Detect double feeds using thickness sensor";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_NONE;

    if (1){
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    }
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  /*deskew by roller*/
  if(option==OPT_ROLLERDESKEW){
    opt->name = "rollerdeskew";
    opt->title = "Roller deskew";
    opt->desc = "Request scanner to correct skewed pages mechanically";
    opt->type = SANE_TYPE_BOOL;
    if (1)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  /*deskew by software*/
  if(option==OPT_SWDESKEW){
    opt->name = "swdeskew";
    opt->title = "Software deskew";
    opt->desc = "Request driver to rotate skewed pages digitally";
    opt->type = SANE_TYPE_BOOL;
    if (1)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  /*software despeckle radius*/
  if(option==OPT_SWDESPECK){

    opt->name = "swdespeck";
    opt->title = "Software despeckle diameter";
    opt->desc = "Maximum diameter of lone dots to remove from scan";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->swdespeck_range;
    s->swdespeck_range.quant=1;

    if(1){
      s->swdespeck_range.min=0;
      s->swdespeck_range.max=9;
      opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
    }
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  /*crop by software*/
  if(option==OPT_SWCROP){
    opt->name = "swcrop";
    opt->title = "Software crop";
    opt->desc = "Request driver to remove border from pages digitally";
    opt->type = SANE_TYPE_BOOL;
    if (1)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  /* Software blank page skip */
  if(option==OPT_SWSKIP){

    opt->name = "swskip";
    opt->title = SANE_I18N ("Software blank skip percentage");
    opt->desc = SANE_I18N("Request driver to discard pages with low percentage of dark pixels");
    opt->type = SANE_TYPE_FIXED;
    opt->unit = SANE_UNIT_PERCENT;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->swskip_range;

    s->swskip_range.quant=SANE_FIX(0.10001);
    s->swskip_range.min=SANE_FIX(0);
    s->swskip_range.max=SANE_FIX(100);

    opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT;
  }

  /*staple detection*/
  if(option==OPT_STAPLEDETECT){
    opt->name = "stapledetect";
    opt->title = "Staple detect";
    opt->desc = "Request scanner to halt if stapled pages are detected";
    opt->type = SANE_TYPE_BOOL;
    if (1)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  /*dropout color front*/
  if(option==OPT_DROPOUT_COLOR_F){
    s->do_color_list[0] = STRING_NONE;
    s->do_color_list[1] = STRING_RED;
    s->do_color_list[2] = STRING_GREEN;
    s->do_color_list[3] = STRING_BLUE;
    s->do_color_list[4] = STRING_EN_RED;
    s->do_color_list[5] = STRING_EN_GREEN;
    s->do_color_list[6] = STRING_EN_BLUE;
    s->do_color_list[7] = NULL;

    opt->name = "dropout-front";
    opt->title = "Dropout color front";
    opt->desc = "One-pass scanners use only one color during gray or binary scanning, useful for colored paper or ink";
    opt->type = SANE_TYPE_STRING;
    opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    opt->constraint.string_list = s->do_color_list;
    opt->size = maxStringSize (opt->constraint.string_list);

    if (1){
      opt->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED;
      if(s->u.mode == MODE_COLOR)
        opt->cap |= SANE_CAP_INACTIVE;
    }
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  /*dropout color back*/
  if(option==OPT_DROPOUT_COLOR_B){
    s->do_color_list[0] = STRING_NONE;
    s->do_color_list[1] = STRING_RED;
    s->do_color_list[2] = STRING_GREEN;
    s->do_color_list[3] = STRING_BLUE;
    s->do_color_list[4] = STRING_EN_RED;
    s->do_color_list[5] = STRING_EN_GREEN;
    s->do_color_list[6] = STRING_EN_BLUE;
    s->do_color_list[7] = NULL;

    opt->name = "dropout-back";
    opt->title = "Dropout color back";
    opt->desc = "One-pass scanners use only one color during gray or binary scanning, useful for colored paper or ink";
    opt->type = SANE_TYPE_STRING;
    opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    opt->constraint.string_list = s->do_color_list;
    opt->size = maxStringSize (opt->constraint.string_list);

    if (1){
      opt->cap = SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT|SANE_CAP_ADVANCED;
      if(s->u.mode == MODE_COLOR)
        opt->cap |= SANE_CAP_INACTIVE;
    }
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  /*buffer mode*/
  if(option==OPT_BUFFERMODE){
    opt->name = "buffermode";
    opt->title = "Buffer mode";
    opt->desc = "Request scanner to read pages async into internal memory";
    opt->type = SANE_TYPE_BOOL;
    if (s->has_buffer)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_SIDE){
    opt->name = "side";
    opt->title = "Duplex side";
    opt->desc = "Tells which side (0=front, 1=back) of a duplex scan the next call to sane_read will return.";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->size = sizeof(SANE_Word);
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    opt->constraint_type = SANE_CONSTRAINT_NONE;
  }

  /*hardware crop*/
  if(option==OPT_HW_CROP){
    opt->name = "hwcrop";
    opt->title = "Hardware crop";
    opt->desc = "Request scanner to crop image automatically";
    opt->type = SANE_TYPE_BOOL;
    if (s->has_hwcrop)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  /* "Imprinter" group --------------------------------------------------- */
  if(option==OPT_IMPRINT_GROUP){
    opt->name = "imprinter-options";
    opt->title = SANE_I18N ("Imprinter Options");
    opt->desc = SANE_I18N ("Controls for imprinter units");
    opt->type = SANE_TYPE_GROUP;
    opt->constraint_type = SANE_CONSTRAINT_NONE;

    /*flaming hack to get scanimage to hide group*/
    if ( !(s->has_pre_imprinter || s->has_post_imprinter) )
      opt->type = SANE_TYPE_BOOL;
  }

  if(option==OPT_PRE_IMPRINT_SPECSTRING){
    opt->name = "pre-imprint-string";
    opt->title = "Pre-Imprinter string";
    opt->desc = "String specifier for the pre-imprinter text";
    opt->type = SANE_TYPE_STRING;
    opt->size = IMPRINT_SPECSTRING_LEN;
    if (s->has_pre_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_PRE_IMPRINT_H_OFFSET){
    opt->name = "pre-imprint-h-offset";
    opt->title = "Pre-Imprinter horizontal offset";
    opt->desc = "Integer specifying the horizontal positioning of the pre-imprinter";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_MM;
    opt->size = sizeof(SANE_Word);
    opt->constraint_type = SANE_CONSTRAINT_WORD_LIST;
    opt->constraint.word_list = s->pre_imprinter_h_offset_list;
    if (s->has_pre_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_PRE_IMPRINT_V_OFFSET){
    opt->name = "pre-imprint-v-offset";
    opt->title = "Pre-Imprinter vertical offset";
    opt->desc = "Integer specifying the vertical positioning of the pre-imprinter";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_MM;
    opt->size = sizeof(SANE_Word);
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->imprinter_v_offset_range;
    if (s->has_pre_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_PRE_IMPRINT_FONT_SIZE){
    opt->name = "pre-imprint-font-size";
    opt->title = "Pre-Imprinter font size";
    opt->desc = "Integer specifying the pre-imprint font size";
    opt->type = SANE_TYPE_STRING;
    opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    opt->constraint.string_list = s->imprint_font_size_list;
    opt->size = maxStringSize (opt->constraint.string_list);
    if (s->has_pre_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_PRE_IMPRINT_FONT_ROT){
    opt->name = "pre-imprint-font-rot";
    opt->title = "Pre-Imprinter font rotation";
    opt->desc = "Integer specifying the pre-imprint font rotation";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->size = sizeof(SANE_Word);
    opt->constraint_type = SANE_CONSTRAINT_WORD_LIST;
    opt->constraint.word_list = s->imprinter_font_angle_list;
    if (s->has_pre_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_PRE_IMPRINT_SPACING){
    opt->name = "pre-imprint-spacing";
    opt->title = "Pre-Imprinter spacing";
    opt->desc = "Enables the pre-imprint extra spacing";
    opt->type = SANE_TYPE_BOOL;
    if (s->has_pre_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_POST_IMPRINT_SPECSTRING){
    opt->name = "post-imprint-string";
    opt->title = "Post-Imprinter string";
    opt->desc = "String specifier for the post-imprinter text";
    opt->type = SANE_TYPE_STRING;
    opt->size = IMPRINT_SPECSTRING_LEN;
    if (s->has_post_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_POST_IMPRINT_H_OFFSET){
    opt->name = "post-imprint-h-offset";
    opt->title = "Post-Imprinter horizontal offset";
    opt->desc = "Integer specifying the horizontal positioning of the post-imprinter";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_MM;
    opt->size = sizeof(SANE_Word);
    opt->constraint_type = SANE_CONSTRAINT_WORD_LIST;
    opt->constraint.word_list = s->post_imprinter_h_offset_list;
    if (s->has_post_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_POST_IMPRINT_V_OFFSET){
    opt->name = "post-imprint-v-offset";
    opt->title = "Post-Imprinter vertical offset";
    opt->desc = "Integer specifying the vertical positioning of the post-imprinter";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_MM;
    opt->size = sizeof(SANE_Word);
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->imprinter_v_offset_range;
    if (s->has_post_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_POST_IMPRINT_FONT_SIZE){
    opt->name = "post-imprint-font-size";
    opt->title = "Post-Imprinter font size";
    opt->desc = "Integer specifying the post-imprint font size";
    opt->type = SANE_TYPE_STRING;
    opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    opt->constraint.string_list = s->imprint_font_size_list;
    opt->size = maxStringSize (opt->constraint.string_list);
    if (s->has_post_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_POST_IMPRINT_FONT_ROT){
    opt->name = "post-imprint-font-rot";
    opt->title = "Post-Imprinter font rotation";
    opt->desc = "Integer specifying the post-imprint font rotation";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->size = sizeof(SANE_Word);
    opt->constraint_type = SANE_CONSTRAINT_WORD_LIST;
    opt->constraint.word_list = s->imprinter_font_angle_list;
    if (s->has_post_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_POST_IMPRINT_SPACING){
    opt->name = "post-imprint-spacing";
    opt->title = "Post-Imprinter spacing";
    opt->desc = "Enables the post-imprint extra spacing";
    opt->type = SANE_TYPE_BOOL;
    if (s->has_post_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_POST_IMPRINT_ADDON_MODE){
    opt->name = "post-imprint-addon-mode";
    opt->title = "Post-Imprinter addon mode";
    opt->desc = "Integer specifying the type of post-imprint addon rendered in the scanned image";
    opt->type = SANE_TYPE_STRING;
    opt->constraint_type = SANE_CONSTRAINT_STRING_LIST;
    opt->constraint.string_list = s->imprint_addon_mode_list;
    opt->size = maxStringSize (opt->constraint.string_list);
    if (s->has_post_imprinter)
     opt->cap = SANE_CAP_SOFT_SELECT | SANE_CAP_SOFT_DETECT | SANE_CAP_ADVANCED;
    else
     opt->cap = SANE_CAP_INACTIVE;
  }

  /* "Sensor" group ------------------------------------------------------ */
  if(option==OPT_SENSOR_GROUP){
    opt->name = SANE_NAME_SENSORS;
    opt->title = SANE_TITLE_SENSORS;
    opt->desc = SANE_DESC_SENSORS;
    opt->type = SANE_TYPE_GROUP;
    opt->constraint_type = SANE_CONSTRAINT_NONE;
  }

  if(option==OPT_START){
    opt->name = "start";
    opt->title = "Start/1 button";
    opt->desc = "Big green or small 1 button";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_panel)
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_STOP){
    opt->name = "stop";
    opt->title = "Stop/2 button";
    opt->desc = "Small orange or small 2 button";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_panel)
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_BUTT3){
    opt->name = "button-3";
    opt->title = "3 button";
    opt->desc = "Small 3 button";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_panel)
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_NEWFILE){
    opt->name = "newfile";
    opt->title = "New File button";
    opt->desc = "New File button";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_panel)
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_COUNTONLY){
    opt->name = "countonly";
    opt->title = "Count Only button";
    opt->desc = "Count Only button";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_panel)
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_BYPASSMODE){
    opt->name = "bypassmode";
    opt->title = "Bypass Mode button";
    opt->desc = "Bypass Mode button";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_panel)
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_COUNTER){
    opt->name = "counter";
    opt->title = "Counter";
    opt->desc = "Scan counter";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->counter_range;
    s->counter_range.min=0;
    s->counter_range.max=500;
    s->counter_range.quant=1;

    if (s->can_read_panel && s->has_counter)
     opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_ROLLERCOUNTER){
    opt->name = "roller-counter";
    opt->title = "Roller Counter";
    opt->desc = "Scans since last roller replacement";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_NONE;

    if (s->can_read_lifecycle_counters)
      opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_TOTALCOUNTER){
    opt->name = "total-counter";
    opt->title = "Total Counter";
    opt->desc = "Total scan count of the device";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_NONE;

    if (s->can_read_lifecycle_counters)
      opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    else
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_FUNCTION_NUMBER){
    opt->name = "function-number";
    opt->title = "Function number";
    opt->desc = "Function number set on panel";
    opt->type = SANE_TYPE_INT;
    opt->unit = SANE_UNIT_NONE;
    opt->constraint_type = SANE_CONSTRAINT_RANGE;
    opt->constraint.range = &s->counter_range;
    s->counter_range.min=1;
    s->counter_range.max=9;
    s->counter_range.quant=1;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_panel || !s->has_function_number)
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_ADF_LOADED){
    opt->name = "adf-loaded";
    opt->title = "ADF Loaded";
    opt->desc = "Paper available in ADF input hopper";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_sensors)
      opt->cap = SANE_CAP_INACTIVE;
  }

  if(option==OPT_CARD_LOADED){
    opt->name = "card-loaded";
    opt->title = "Card Loaded";
    opt->desc = "Paper available in card reader";
    opt->type = SANE_TYPE_BOOL;
    opt->unit = SANE_UNIT_NONE;
    opt->cap = SANE_CAP_SOFT_DETECT | SANE_CAP_HARD_SELECT | SANE_CAP_ADVANCED;
    if(!s->can_read_sensors || !s->has_card)
      opt->cap = SANE_CAP_INACTIVE;
  }

  return opt;
}

/**
 * Gets or sets an option value.
 *
 * From the SANE spec:
 * This function is used to set or inquire the current value of option
 * number n of the device represented by handle h. The manner in which
 * the option is controlled is specified by parameter action. The
 * possible values of this parameter are described in more detail
 * below.  The value of the option is passed through argument val. It
 * is a pointer to the memory that holds the option value. The memory
 * area pointed to by v must be big enough to hold the entire option
 * value (determined by member size in the corresponding option
 * descriptor).
 *
 * The only exception to this rule is that when setting the value of a
 * string option, the string pointed to by argument v may be shorter
 * since the backend will stop reading the option value upon
 * encountering the first NUL terminator in the string. If argument i
 * is not NULL, the value of *i will be set to provide details on how
 * well the request has been met.
 */
SANE_Status
sane_control_option (SANE_Handle handle, SANE_Int option,
                     SANE_Action action, void *val, SANE_Int * info)
{
  struct scanner *s = (struct scanner *) handle;
  SANE_Int dummy = 0;
  SANE_Status ret = SANE_STATUS_GOOD;

  /* Make sure that all those statements involving *info cannot break (better
   * than having to do "if (info) ..." everywhere!)
   */
  if (info == 0)
    info = &dummy;

  if (option >= NUM_OPTIONS) {
    DBG (5, "sane_control_option: %d too big\n", option);
    return SANE_STATUS_INVAL;
  }

  if (!SANE_OPTION_IS_ACTIVE (s->opt[option].cap)) {
    DBG (5, "sane_control_option: %d inactive\n", option);
    return SANE_STATUS_INVAL;
  }

  /*
   * SANE_ACTION_GET_VALUE: We have to find out the current setting and
   * return it in a human-readable form (often, text).
   */
  if (action == SANE_ACTION_GET_VALUE) {
      SANE_Word * val_p = (SANE_Word *) val;

      DBG (20, "sane_control_option: get value for '%s' (%d)\n", s->opt[option].name,option);

      switch (option) {

        case OPT_NUM_OPTS:
          *val_p = NUM_OPTIONS;
          return SANE_STATUS_GOOD;

        case OPT_SOURCE:
          if(s->u.source == SOURCE_FLATBED){
            strcpy (val, STRING_FLATBED);
          }
          else if(s->u.source == SOURCE_ADF_FRONT){
            strcpy (val, STRING_ADFFRONT);
          }
          else if(s->u.source == SOURCE_ADF_BACK){
            strcpy (val, STRING_ADFBACK);
          }
          else if(s->u.source == SOURCE_ADF_DUPLEX){
            strcpy (val, STRING_ADFDUPLEX);
          }
          else if(s->u.source == SOURCE_CARD_FRONT){
            strcpy (val, STRING_CARDFRONT);
          }
          else if(s->u.source == SOURCE_CARD_BACK){
            strcpy (val, STRING_CARDBACK);
          }
          else if(s->u.source == SOURCE_CARD_DUPLEX){
            strcpy (val, STRING_CARDDUPLEX);
          }
          return SANE_STATUS_GOOD;

        case OPT_MODE:
          if(s->u.mode == MODE_LINEART){
            strcpy (val, STRING_LINEART);
          }
          else if(s->u.mode == MODE_HALFTONE){
            strcpy (val, STRING_HALFTONE);
          }
          else if(s->u.mode == MODE_GRAYSCALE){
            strcpy (val, STRING_GRAYSCALE);
          }
          else if(s->u.mode == MODE_COLOR){
            strcpy (val, STRING_COLOR);
          }
          return SANE_STATUS_GOOD;

        case OPT_RES:
          *val_p = s->u.dpi_x;
          return SANE_STATUS_GOOD;

        case OPT_TL_X:
          *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.tl_x);
          return SANE_STATUS_GOOD;

        case OPT_TL_Y:
          *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.tl_y);
          return SANE_STATUS_GOOD;

        case OPT_BR_X:
          *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.br_x);
          return SANE_STATUS_GOOD;

        case OPT_BR_Y:
          *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.br_y);
          return SANE_STATUS_GOOD;

        case OPT_PAGE_WIDTH:
          *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.page_x);
          return SANE_STATUS_GOOD;

        case OPT_PAGE_HEIGHT:
          *val_p = SCANNER_UNIT_TO_FIXED_MM(s->u.page_y);
          return SANE_STATUS_GOOD;

        case OPT_BRIGHTNESS:
          *val_p = s->brightness;
          return SANE_STATUS_GOOD;

        case OPT_CONTRAST:
          *val_p = s->contrast;
          return SANE_STATUS_GOOD;

        case OPT_THRESHOLD:
          *val_p = s->threshold;
          return SANE_STATUS_GOOD;

        case OPT_RIF:
          *val_p = s->rif;
          return SANE_STATUS_GOOD;

        /* Advanced Group */
        case OPT_COMPRESS:
          if(s->compress == COMP_JPEG){
            strcpy (val, STRING_JPEG);
          }
          else{
            strcpy (val, STRING_NONE);
          }
          return SANE_STATUS_GOOD;

        case OPT_COMPRESS_ARG:
          *val_p = s->compress_arg;
          return SANE_STATUS_GOOD;

        case OPT_DF_LENGTH:
          *val_p = s->df_length;
          return SANE_STATUS_GOOD;

        case OPT_DF_THICKNESS:
          *val_p = s->df_thickness;
          return SANE_STATUS_GOOD;

        case OPT_ROLLERDESKEW:
          *val_p = s->rollerdeskew;
          return SANE_STATUS_GOOD;

        case OPT_SWDESKEW:
          *val_p = s->swdeskew;
          return SANE_STATUS_GOOD;

        case OPT_SWDESPECK:
          *val_p = s->swdespeck;
          return SANE_STATUS_GOOD;

        case OPT_SWCROP:
          *val_p = s->swcrop;
          return SANE_STATUS_GOOD;

        case OPT_SWSKIP:
          *val_p = SANE_FIX(s->swskip);
          return SANE_STATUS_GOOD;

        case OPT_STAPLEDETECT:
          *val_p = s->stapledetect;
          return SANE_STATUS_GOOD;

        case OPT_DROPOUT_COLOR_F:
          switch (s->dropout_color[SIDE_FRONT]) {
            case COLOR_NONE:
              strcpy (val, STRING_NONE);
              break;
            case COLOR_RED:
              strcpy (val, STRING_RED);
              break;
            case COLOR_GREEN:
              strcpy (val, STRING_GREEN);
              break;
            case COLOR_BLUE:
              strcpy (val, STRING_BLUE);
              break;
            case COLOR_EN_RED:
              strcpy (val, STRING_EN_RED);
              break;
            case COLOR_EN_GREEN:
              strcpy (val, STRING_EN_GREEN);
              break;
            case COLOR_EN_BLUE:
              strcpy (val, STRING_EN_BLUE);
              break;
          }
          return SANE_STATUS_GOOD;

        case OPT_DROPOUT_COLOR_B:
          switch (s->dropout_color[SIDE_BACK]) {
            case COLOR_NONE:
              strcpy (val, STRING_NONE);
              break;
            case COLOR_RED:
              strcpy (val, STRING_RED);
              break;
            case COLOR_GREEN:
              strcpy (val, STRING_GREEN);
              break;
            case COLOR_BLUE:
              strcpy (val, STRING_BLUE);
              break;
            case COLOR_EN_RED:
              strcpy (val, STRING_EN_RED);
              break;
            case COLOR_EN_GREEN:
              strcpy (val, STRING_EN_GREEN);
              break;
            case COLOR_EN_BLUE:
              strcpy (val, STRING_EN_BLUE);
              break;
          }
          return SANE_STATUS_GOOD;

        case OPT_BUFFERMODE:
          *val_p = s->buffermode;
          return SANE_STATUS_GOOD;

        case OPT_SIDE:
          *val_p = s->side;
          return SANE_STATUS_GOOD;

        case OPT_HW_CROP:
          *val_p = s->hwcrop;
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_SPECSTRING:
          strcpy(val, s->pre_imprint.specstring);
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_H_OFFSET:
          *val_p = s->pre_imprint.h_offset;
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_V_OFFSET:
          *val_p = s->pre_imprint.v_offset;
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_FONT_SIZE:
          switch (s->pre_imprint.font_size){
            case IMPRINTER_12x12_FONT:
              strcpy(val, STRING_IMPRINTER_12x12_FONT);
              break;

            case IMPRINTER_8x12_FONT:
              strcpy(val, STRING_IMPRINTER_8x12_FONT);
              break;
          }
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_FONT_ROT:
          switch (s->pre_imprint.font_rot){
            case IMPRINTER_0_FONT_ROT:
              *val_p = 0;
              break;

            case IMPRINTER_90_FONT_ROT:
              *val_p = 90;
              break;

            case IMPRINTER_180_FONT_ROT:
              *val_p = 180;
              break;

            case IMPRINTER_270_FONT_ROT:
              *val_p = 270;
              break;
          }
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_SPACING:
          *val_p = s->pre_imprint.spacing;
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_SPECSTRING:
          strcpy(val, s->post_imprint.specstring);
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_H_OFFSET:
          *val_p = s->post_imprint.h_offset;
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_V_OFFSET:
          *val_p = s->post_imprint.v_offset;
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_FONT_SIZE:
          switch (s->post_imprint.font_size){
            case IMPRINTER_12x12_FONT:
              strcpy(val, STRING_IMPRINTER_12x12_FONT);
              break;

            case IMPRINTER_8x12_FONT:
              strcpy(val, STRING_IMPRINTER_8x12_FONT);
              break;
          }
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_FONT_ROT:
          switch (s->post_imprint.font_rot){
            case IMPRINTER_0_FONT_ROT:
              *val_p = 0;
              break;

            case IMPRINTER_90_FONT_ROT:
              *val_p = 90;
              break;

            case IMPRINTER_180_FONT_ROT:
              *val_p = 180;
              break;

            case IMPRINTER_270_FONT_ROT:
              *val_p = 270;
              break;
          }
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_SPACING:
          *val_p = s->post_imprint.spacing;
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_ADDON_MODE:
          switch (s->post_imprint_addon_mode){
            case ADDON_BoW:
              strcpy(val, STRING_IMPRINTER_ADDON_BoW);
              break;

            case ADDON_BoI:
              strcpy(val, STRING_IMPRINTER_ADDON_BoI);
              break;

            case ADDON_WoB:
              strcpy(val, STRING_IMPRINTER_ADDON_WoB);
              break;

            case ADDON_DISABLED:
              strcpy(val, STRING_NONE);
              break;
          }
          return SANE_STATUS_GOOD;

        /* Sensor Group */
        case OPT_START:
          ret = read_panel(s,OPT_START);
          *val_p = s->panel_start;
          return ret;

        case OPT_STOP:
          ret = read_panel(s,OPT_STOP);
          *val_p = s->panel_stop;
          return ret;

        case OPT_BUTT3:
          ret = read_panel(s,OPT_BUTT3);
          *val_p = s->panel_butt3;
          return ret;

        case OPT_NEWFILE:
          ret = read_panel(s,OPT_NEWFILE);
          *val_p = s->panel_new_file;
          return ret;

        case OPT_COUNTONLY:
          ret = read_panel(s,OPT_COUNTONLY);
          *val_p = s->panel_count_only;
          return ret;

        case OPT_BYPASSMODE:
          ret = read_panel(s,OPT_BYPASSMODE);
          *val_p = s->panel_bypass_mode;
          return ret;

        case OPT_COUNTER:
          ret = read_panel(s,OPT_COUNTER);
          *val_p = s->panel_counter;
          return ret;

        case OPT_ROLLERCOUNTER:
          ret = read_counters(s);
          *val_p = s->roller_counter;
          return ret;

        case OPT_TOTALCOUNTER:
          ret = read_counters(s);
          *val_p = s->total_counter;
          return ret;

        case OPT_FUNCTION_NUMBER:
          ret = read_panel(s, OPT_FUNCTION_NUMBER);
          *val_p = s->panel_function_number;
          return ret;

        case OPT_ADF_LOADED:
          ret = read_sensors(s,OPT_ADF_LOADED);
          *val_p = s->sensor_adf_loaded;
          return ret;

        case OPT_CARD_LOADED:
          ret = read_sensors(s,OPT_CARD_LOADED);
          *val_p = s->sensor_card_loaded;
          return ret;
      }
  }
  else if (action == SANE_ACTION_SET_VALUE) {
      int tmp;
      SANE_Word val_c;
      SANE_Status status;

      DBG (20, "sane_control_option: set value for '%s' (%d)\n", s->opt[option].name,option);

      if ( s->started ) {
        DBG (5, "sane_control_option: can't set, device busy\n");
        return SANE_STATUS_DEVICE_BUSY;
      }

      if (!SANE_OPTION_IS_SETTABLE (s->opt[option].cap)) {
        DBG (5, "sane_control_option: not settable\n");
        return SANE_STATUS_INVAL;
      }

      status = sanei_constrain_value (s->opt + option, val, info);
      if (status != SANE_STATUS_GOOD) {
        DBG (5, "sane_control_option: bad value\n");
        return status;
      }

      /* may have been changed by constrain, so don't copy until now */
      val_c = *(SANE_Word *)val;

      /*
       * Note - for those options which can assume one of a list of
       * valid values, we can safely assume that they will have
       * exactly one of those values because that's what
       * sanei_constrain_value does. Hence no "else: invalid" branches
       * below.
       */
      switch (option) {

        /* Mode Group */
        case OPT_SOURCE:
          if (!strcmp (val, STRING_ADFFRONT)) {
            tmp = SOURCE_ADF_FRONT;
          }
          else if (!strcmp (val, STRING_ADFBACK)) {
            tmp = SOURCE_ADF_BACK;
          }
          else if (!strcmp (val, STRING_ADFDUPLEX)) {
            tmp = SOURCE_ADF_DUPLEX;
          }
          else if (!strcmp (val, STRING_CARDFRONT)) {
            tmp = SOURCE_CARD_FRONT;
          }
          else if (!strcmp (val, STRING_CARDBACK)) {
            tmp = SOURCE_CARD_BACK;
          }
          else if (!strcmp (val, STRING_CARDDUPLEX)) {
            tmp = SOURCE_CARD_DUPLEX;
          }
          else{
            tmp = SOURCE_FLATBED;
          }

          if (s->u.source == tmp)
              return SANE_STATUS_GOOD;

          s->u.source = tmp;

          *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        case OPT_MODE:
          if (!strcmp (val, STRING_LINEART)) {
            tmp = MODE_LINEART;
          }
          else if (!strcmp (val, STRING_HALFTONE)) {
            tmp = MODE_HALFTONE;
          }
          else if (!strcmp (val, STRING_GRAYSCALE)) {
            tmp = MODE_GRAYSCALE;
          }
          else{
            tmp = MODE_COLOR;
          }

          if (tmp == s->u.mode)
              return SANE_STATUS_GOOD;

          s->u.mode = tmp;

          *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        case OPT_RES:

          if (s->u.dpi_x == val_c && s->u.dpi_y == val_c)
              return SANE_STATUS_GOOD;

          s->u.dpi_x = val_c;
          s->u.dpi_y = val_c;

          *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        /* Geometry Group */
        case OPT_TL_X:
          if (s->u.tl_x == FIXED_MM_TO_SCANNER_UNIT(val_c))
              return SANE_STATUS_GOOD;

          s->u.tl_x = FIXED_MM_TO_SCANNER_UNIT(val_c);

          *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        case OPT_TL_Y:
          if (s->u.tl_y == FIXED_MM_TO_SCANNER_UNIT(val_c))
              return SANE_STATUS_GOOD;

          s->u.tl_y = FIXED_MM_TO_SCANNER_UNIT(val_c);

          *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        case OPT_BR_X:
          if (s->u.br_x == FIXED_MM_TO_SCANNER_UNIT(val_c))
              return SANE_STATUS_GOOD;

          s->u.br_x = FIXED_MM_TO_SCANNER_UNIT(val_c);

          *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        case OPT_BR_Y:
          if (s->u.br_y == FIXED_MM_TO_SCANNER_UNIT(val_c))
              return SANE_STATUS_GOOD;

          s->u.br_y = FIXED_MM_TO_SCANNER_UNIT(val_c);

          *info |= SANE_INFO_RELOAD_PARAMS | SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        case OPT_PAGE_WIDTH:
          if (s->u.page_x == FIXED_MM_TO_SCANNER_UNIT(val_c))
              return SANE_STATUS_GOOD;

          /* if full width image, and paper size is changed,
             change the image size to match new paper */
          if (s->u.tl_x == 0 && s->u.br_x == s->u.page_x){
              DBG (20, "sane_control_option: br_x tracking page_width\n");
              s->u.br_x = FIXED_MM_TO_SCANNER_UNIT(val_c);
              *info |= SANE_INFO_RELOAD_PARAMS;
          }

          s->u.page_x = FIXED_MM_TO_SCANNER_UNIT(val_c);

          *info |= SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        case OPT_PAGE_HEIGHT:
          if (s->u.page_y == FIXED_MM_TO_SCANNER_UNIT(val_c))
              return SANE_STATUS_GOOD;

          /* if full height image, and paper size is changed,
             change the image size to match new paper */
          if (s->u.tl_y == 0 && s->u.br_y == s->u.page_y){
              DBG (20, "sane_control_option: br_y tracking page_height\n");
              s->u.br_y = FIXED_MM_TO_SCANNER_UNIT(val_c);
              *info |= SANE_INFO_RELOAD_PARAMS;
          }

          s->u.page_y = FIXED_MM_TO_SCANNER_UNIT(val_c);

          *info |= SANE_INFO_RELOAD_OPTIONS;
          return SANE_STATUS_GOOD;

        /* Enhancement Group */
        case OPT_BRIGHTNESS:
          s->brightness = val_c;
          return SANE_STATUS_GOOD;

        case OPT_CONTRAST:
          s->contrast = val_c;
          return SANE_STATUS_GOOD;

        case OPT_THRESHOLD:
          s->threshold = val_c;
          return SANE_STATUS_GOOD;

        case OPT_RIF:
          s->rif = val_c;
          return SANE_STATUS_GOOD;

        /* Advanced Group */
        case OPT_COMPRESS:
          if (!strcmp (val, STRING_JPEG)) {
            s->compress = COMP_JPEG;
          }
          else{
            s->compress = COMP_NONE;
          }
          return SANE_STATUS_GOOD;

        case OPT_COMPRESS_ARG:
          s->compress_arg = val_c;
          return SANE_STATUS_GOOD;

        case OPT_DF_LENGTH:
          s->df_length = val_c;
          return SANE_STATUS_GOOD;

        case OPT_DF_THICKNESS:
          s->df_thickness = val_c;
          return SANE_STATUS_GOOD;

        case OPT_ROLLERDESKEW:
          s->rollerdeskew = val_c;
          return SANE_STATUS_GOOD;

        case OPT_SWDESKEW:
          s->swdeskew = val_c;
          return SANE_STATUS_GOOD;

        case OPT_SWDESPECK:
          s->swdespeck = val_c;
          return SANE_STATUS_GOOD;

        case OPT_SWCROP:
          s->swcrop = val_c;
          return SANE_STATUS_GOOD;

        case OPT_SWSKIP:
          s->swskip = SANE_UNFIX(val_c);
          return SANE_STATUS_GOOD;

        case OPT_STAPLEDETECT:
          s->stapledetect = val_c;
          return SANE_STATUS_GOOD;

        case OPT_DROPOUT_COLOR_F:
          if (!strcmp(val, STRING_NONE))
            s->dropout_color[SIDE_FRONT] = COLOR_NONE;
          else if (!strcmp(val, STRING_RED))
            s->dropout_color[SIDE_FRONT] = COLOR_RED;
          else if (!strcmp(val, STRING_GREEN))
            s->dropout_color[SIDE_FRONT] = COLOR_GREEN;
          else if (!strcmp(val, STRING_BLUE))
            s->dropout_color[SIDE_FRONT] = COLOR_BLUE;
          else if (!strcmp(val, STRING_EN_RED))
            s->dropout_color[SIDE_FRONT] = COLOR_EN_RED;
          else if (!strcmp(val, STRING_EN_GREEN))
            s->dropout_color[SIDE_FRONT] = COLOR_EN_GREEN;
          else if (!strcmp(val, STRING_EN_BLUE))
            s->dropout_color[SIDE_FRONT] = COLOR_EN_BLUE;
          return SANE_STATUS_GOOD;

        case OPT_DROPOUT_COLOR_B:
          if (!strcmp(val, STRING_NONE))
            s->dropout_color[SIDE_BACK] = COLOR_NONE;
          else if (!strcmp(val, STRING_RED))
            s->dropout_color[SIDE_BACK] = COLOR_RED;
          else if (!strcmp(val, STRING_GREEN))
            s->dropout_color[SIDE_BACK] = COLOR_GREEN;
          else if (!strcmp(val, STRING_BLUE))
            s->dropout_color[SIDE_BACK] = COLOR_BLUE;
          else if (!strcmp(val, STRING_EN_RED))
            s->dropout_color[SIDE_BACK] = COLOR_EN_RED;
          else if (!strcmp(val, STRING_EN_GREEN))
            s->dropout_color[SIDE_BACK] = COLOR_EN_GREEN;
          else if (!strcmp(val, STRING_EN_BLUE))
            s->dropout_color[SIDE_BACK] = COLOR_EN_BLUE;
          return SANE_STATUS_GOOD;

        case OPT_BUFFERMODE:
          s->buffermode = val_c;
          return SANE_STATUS_GOOD;

        case OPT_HW_CROP:
          s->hwcrop = val_c;
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_SPECSTRING:
          if (strlen(val) < IMPRINT_SPECSTRING_LEN){
            strncpy(s->pre_imprint.specstring, val, IMPRINT_SPECSTRING_LEN);
            return SANE_STATUS_GOOD;
          }
          DBG (5, "sane_control_option: pre-imprint spec string '%s' exceed the limit of %d characters\n", (SANE_String)val, IMPRINT_SPECSTRING_LEN);
          return SANE_STATUS_INVAL;

        case OPT_PRE_IMPRINT_H_OFFSET:
          s->pre_imprint.h_offset = val_c;
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_V_OFFSET:
          s->pre_imprint.v_offset = val_c;
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_FONT_SIZE:
          if (!strcmp (val, STRING_IMPRINTER_12x12_FONT)) {
            s->pre_imprint.font_size = IMPRINTER_12x12_FONT;
          }
          if (!strcmp (val, STRING_IMPRINTER_8x12_FONT)) {
            s->pre_imprint.font_size = IMPRINTER_8x12_FONT;
          }
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_FONT_ROT:
          switch (val_c){
            case 0:
              s->pre_imprint.font_rot = IMPRINTER_0_FONT_ROT;
              break;

            case 90:
              s->pre_imprint.font_rot = IMPRINTER_90_FONT_ROT;
              break;

            case 180:
              s->pre_imprint.font_rot = IMPRINTER_180_FONT_ROT;
              break;

            case 270:
              s->pre_imprint.font_rot = IMPRINTER_270_FONT_ROT;
              break;
          }
          return SANE_STATUS_GOOD;

        case OPT_PRE_IMPRINT_SPACING:
          s->pre_imprint.spacing = val_c;
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_SPECSTRING:
          if (strlen(val) < IMPRINT_SPECSTRING_LEN){
            strncpy(s->post_imprint.specstring, val, IMPRINT_SPECSTRING_LEN);
            return SANE_STATUS_GOOD;
          }
          DBG (5, "sane_control_option: post-imprint spec string '%s' exceed the limit of %d characters\n", (SANE_String)val, IMPRINT_SPECSTRING_LEN);
          return SANE_STATUS_INVAL;

        case OPT_POST_IMPRINT_H_OFFSET:
          s->post_imprint.h_offset = val_c;
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_V_OFFSET:
          s->post_imprint.v_offset = val_c;
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_FONT_SIZE:
          if (!strcmp (val, STRING_IMPRINTER_12x12_FONT)) {
            s->post_imprint.font_size = IMPRINTER_12x12_FONT;
          }
          if (!strcmp (val, STRING_IMPRINTER_8x12_FONT)) {
            s->post_imprint.font_size = IMPRINTER_8x12_FONT;
          }
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_FONT_ROT:
          switch (val_c){
            case 0:
              s->post_imprint.font_rot = IMPRINTER_0_FONT_ROT;
              break;

            case 90:
              s->post_imprint.font_rot = IMPRINTER_90_FONT_ROT;
              break;

            case 180:
              s->post_imprint.font_rot = IMPRINTER_180_FONT_ROT;
              break;

            case 270:
              s->post_imprint.font_rot = IMPRINTER_270_FONT_ROT;
              break;
          }
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_SPACING:
          s->post_imprint.spacing = val_c;
          return SANE_STATUS_GOOD;

        case OPT_POST_IMPRINT_ADDON_MODE:
          if (!strcmp (val, STRING_IMPRINTER_ADDON_BoW)) {
            s->post_imprint_addon_mode = ADDON_BoW;
          }
          if (!strcmp (val, STRING_IMPRINTER_ADDON_BoI)) {
            s->post_imprint_addon_mode = ADDON_BoI;
          }
          if (!strcmp (val, STRING_IMPRINTER_ADDON_WoB)) {
            s->post_imprint_addon_mode = ADDON_WoB;
          }
          if (!strcmp (val, STRING_NONE)) {
            s->post_imprint_addon_mode = ADDON_DISABLED;
          }
          return SANE_STATUS_GOOD;

      }
  }                           /* else */

  return SANE_STATUS_INVAL;
}

static SANE_Status
ssm_buffer (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "ssm_buffer: start\n");

  if(s->has_ssm){

    unsigned char cmd[SET_SCAN_MODE_len];
    size_t cmdLen = SET_SCAN_MODE_len;

    unsigned char out[SSM_PAY_len];
    size_t outLen = SSM_PAY_len;

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, SET_SCAN_MODE_code);
    set_SSM_pf(cmd, 1);
    set_SSM_pay_len(cmd, outLen);

    memset(out,0,outLen);
    if(s->has_ssm_pay_head_len){
      set_SSM_pay_head_len(out, SSM_PAY_HEAD_len);
    }
    set_SSM_page_code(out, SM_pc_buffer);
    set_SSM_page_len(out, SSM_PAGE_len);

    if(s->s.source == SOURCE_ADF_DUPLEX || s->s.source == SOURCE_CARD_DUPLEX){
      set_SSM_BUFF_duplex(out, 1);
    }
    if(s->s.source == SOURCE_FLATBED){
      set_SSM_BUFF_fb(out, 1);
    }
    else if(s->s.source >= SOURCE_CARD_FRONT){
      set_SSM_BUFF_card(out, 1);
    }
    if(s->buffermode){
      set_SSM_BUFF_async(out, 1);
    }
    if(0){
      set_SSM_BUFF_ald(out, 1);
    }
    if(0){
      set_SSM_BUFF_unk(out,1);
    }

    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, NULL
    );
  }

  else if(s->has_ssm2){

    unsigned char cmd[SET_SCAN_MODE2_len];
    size_t cmdLen = SET_SCAN_MODE2_len;

    unsigned char out[SSM2_PAY_len];
    size_t outLen = SSM2_PAY_len;

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, SET_SCAN_MODE2_code);
    set_SSM2_page_code(cmd, SM2_pc_buffer);
    set_SSM2_pay_len(cmd, outLen);

    memset(out,0,outLen);
    set_SSM2_BUFF_unk(out, !s->buffermode);
    set_SSM2_BUFF_unk2(out, 0x40);
    set_SSM2_BUFF_sync(out, !s->buffermode);

    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, NULL
    );
  }

  else{
    DBG (10, "ssm_buffer: unsupported\n");
  }

  DBG (10, "ssm_buffer: finish\n");

  return ret;
}

static SANE_Status
ssm_df (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "ssm_df: start\n");

  if(!s->has_df){
    DBG (10, "ssm_df: unsupported, finishing\n");
    return ret;
  }

  if(s->has_ssm){

    unsigned char cmd[SET_SCAN_MODE_len];
    size_t cmdLen = SET_SCAN_MODE_len;

    unsigned char out[SSM_PAY_len];
    size_t outLen = SSM_PAY_len;

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, SET_SCAN_MODE_code);
    set_SSM_pf(cmd, 1);
    set_SSM_pay_len(cmd, outLen);

    memset(out,0,outLen);
    if(s->has_ssm_pay_head_len){
      set_SSM_pay_head_len(out, SSM_PAY_HEAD_len);
    }
    set_SSM_page_code(out, SM_pc_df);
    set_SSM_page_len(out, SSM_PAGE_len);

    /* deskew by roller */
    if(s->rollerdeskew){
      set_SSM_DF_deskew_roll(out, 1);
    }

    /* staple detection */
    if(s->stapledetect){
      set_SSM_DF_staple(out, 1);
    }

    /* thickness */
    if(s->df_thickness){
      set_SSM_DF_thick(out, 1);
    }

    /* length */
    if(s->df_length){
      set_SSM_DF_len(out, 1);
    }

    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, NULL
    );

  }

  else if(s->has_ssm2){

    unsigned char cmd[SET_SCAN_MODE2_len];
    size_t cmdLen = SET_SCAN_MODE2_len;

    unsigned char out[SSM2_PAY_len];
    size_t outLen = SSM2_PAY_len;

    /* send ultrasonic offsets first */
    if(s->df_thickness && s->has_df_ultra){
      memset(cmd,0,cmdLen);
      set_SCSI_opcode(cmd, SET_SCAN_MODE2_code);
      set_SSM2_page_code(cmd, SM2_pc_ultra);
      set_SSM2_pay_len(cmd, outLen);

      memset(out,0,outLen);
      set_SSM2_ULTRA_top(out, 0);
      set_SSM2_ULTRA_bot(out, 0);

      ret = do_cmd (
          s, 1, 0,
          cmd, cmdLen,
          out, outLen,
          NULL, NULL
      );
    }

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, SET_SCAN_MODE2_code);
    set_SSM2_page_code(cmd, SM2_pc_df);
    set_SSM2_pay_len(cmd, outLen);

    memset(out,0,outLen);

    /* thickness */
    if(s->df_thickness){
      set_SSM2_DF_thick(out, 1);
    }

    /* length */
    if(s->df_length){
      set_SSM2_DF_len(out, 1);
    }

    /* staple detection */
    if(s->stapledetect){
      set_SSM2_DF_staple(out, 1);
    }

    int requires_postimprint = s->has_post_imprinter && (strlen(s->post_imprint.specstring) > 0);
    int requires_preimprint = s->has_pre_imprinter && (strlen(s->pre_imprint.specstring) > 0);
    if (s->has_post_imprinter)
      set_SSM2_DF_post_addon(out, requires_postimprint);
    if (requires_postimprint || requires_preimprint){
      set_SSM2_DF_imprint(out, 1);
    }

    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, NULL
    );

  }

  else{
    DBG (10, "ssm_df: unsupported\n");
  }

  DBG (10, "ssm_df: finish\n");

  return ret;
}

static SANE_Status
ssm2_hw_enhancement (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "ssm2_hw_enhancement: start\n");

  if(s->has_ssm2){
    unsigned char cmd[SET_SCAN_MODE2_len];
    size_t cmdLen = SET_SCAN_MODE2_len;

    unsigned char out[SSM2_PAY_len];
    size_t outLen = SSM2_PAY_len;

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, SET_SCAN_MODE2_code);
    set_SSM2_page_code(cmd, SM2_pc_hw_enhancement);
    set_SSM2_pay_len(cmd, outLen);

    memset(out,0,outLen);

    if(s->rollerdeskew){
      set_SSM2_roller_deskew(out, 1);
    }

    if(s->hwcrop){
      set_SSM2_hw_crop(out, 1);
    }

    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, NULL
    );

  }

  else{
    DBG (10, "ssm2_hw_enhancement: unsupported\n");
  }

  DBG (10, "ssm2_hw_enhancement: finish\n");

  return ret;
}

static SANE_Status
ssm_do (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

 DBG (10, "ssm_do: start\n");

 if(!s->can_color){
   DBG (10, "ssm_do: unsupported, finishing\n");
   return ret;
 }

 if(s->s.mode == MODE_COLOR){
   DBG (10, "ssm_do: unneeded, finishing\n");
   return ret;
 }

 if(s->has_ssm){

    unsigned char cmd[SET_SCAN_MODE_len];
    size_t cmdLen = SET_SCAN_MODE_len;

    unsigned char out[SSM_PAY_len];
    size_t outLen = SSM_PAY_len;

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, SET_SCAN_MODE_code);
    set_SSM_pf(cmd, 1);
    set_SSM_pay_len(cmd, outLen);

    memset(out,0,outLen);
    if(s->has_ssm_pay_head_len){
      set_SSM_pay_head_len(out, SSM_PAY_HEAD_len);
    }
    set_SSM_page_code(out, SM_pc_dropout);
    set_SSM_page_len(out, SSM_PAGE_len);

    set_SSM_DO_unk1(out, 0x03);

    switch(s->dropout_color[SIDE_FRONT]){
      case COLOR_RED:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_f_do(out,SSM_DO_red);
        break;
      case COLOR_GREEN:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_f_do(out,SSM_DO_green);
        break;
      case COLOR_BLUE:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_f_do(out,SSM_DO_blue);
        break;
      case COLOR_EN_RED:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_f_en(out,SSM_DO_red);
        break;
      case COLOR_EN_GREEN:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_f_en(out,SSM_DO_green);
        break;
      case COLOR_EN_BLUE:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_f_en(out,SSM_DO_blue);
        break;
    }

    switch(s->dropout_color[SIDE_BACK]){
      case COLOR_RED:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_b_do(out,SSM_DO_red);
        break;
      case COLOR_GREEN:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_b_do(out,SSM_DO_green);
        break;
      case COLOR_BLUE:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_b_do(out,SSM_DO_blue);
        break;
      case COLOR_EN_RED:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_b_en(out,SSM_DO_red);
        break;
      case COLOR_EN_GREEN:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_b_en(out,SSM_DO_green);
        break;
      case COLOR_EN_BLUE:
        set_SSM_DO_unk2(out, 0x05);
        set_SSM_DO_b_en(out,SSM_DO_blue);
        break;
    }

    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, NULL
    );

  }

  else if(s->has_ssm2){

    unsigned char cmd[SET_SCAN_MODE2_len];
    size_t cmdLen = SET_SCAN_MODE2_len;

    unsigned char out[SSM2_PAY_len];
    size_t outLen = SSM2_PAY_len;

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, SET_SCAN_MODE2_code);
    set_SSM2_page_code(cmd, SM2_pc_dropout);
    set_SSM2_pay_len(cmd, outLen);

    memset(out,0,outLen);

    switch(s->dropout_color[SIDE_FRONT]){
      case COLOR_RED:
        set_SSM2_DO_do(out,SSM_DO_red);
        break;
      case COLOR_GREEN:
        set_SSM2_DO_do(out,SSM_DO_green);
        break;
      case COLOR_BLUE:
        set_SSM2_DO_do(out,SSM_DO_blue);
        break;
      case COLOR_EN_RED:
        set_SSM2_DO_en(out,SSM_DO_red);
        break;
      case COLOR_EN_GREEN:
        set_SSM2_DO_en(out,SSM_DO_green);
        break;
      case COLOR_EN_BLUE:
        set_SSM2_DO_en(out,SSM_DO_blue);
        break;
    }

    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, NULL
    );

    if(ret == SANE_STATUS_GOOD &&
       (s->s.source == SOURCE_ADF_DUPLEX || s->s.source == SOURCE_CARD_DUPLEX)){

      memset(cmd,0,cmdLen);
      set_SCSI_opcode(cmd, SET_SCAN_MODE2_code);
      set_SSM2_page_code(cmd, SM2_pc_dropout);
      set_SSM2_DO_side(cmd, SIDE_BACK);
      set_SSM2_pay_len(cmd, outLen);

      memset(out,0,outLen);

      switch(s->dropout_color[SIDE_BACK]){
        case COLOR_RED:
          set_SSM2_DO_do(out,SSM_DO_red);
          break;
        case COLOR_GREEN:
          set_SSM2_DO_do(out,SSM_DO_green);
          break;
        case COLOR_BLUE:
          set_SSM2_DO_do(out,SSM_DO_blue);
          break;
        case COLOR_EN_RED:
          set_SSM2_DO_en(out,SSM_DO_red);
          break;
        case COLOR_EN_GREEN:
          set_SSM2_DO_en(out,SSM_DO_green);
          break;
        case COLOR_EN_BLUE:
          set_SSM2_DO_en(out,SSM_DO_blue);
          break;
      }

      ret = do_cmd (
          s, 1, 0,
          cmd, cmdLen,
          out, outLen,
          NULL, NULL
      );
    }
  }

  else{
    DBG (10, "ssm_do: unsupported\n");
  }

  DBG (10, "ssm_do: finish\n");

  return ret;
}

static SANE_Status
read_counters(struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char cmd[READ_len];
  size_t cmdLen = READ_len;

  unsigned char in[R_COUNTERS_len];
  size_t inLen = R_COUNTERS_len;

  if (!s->can_read_lifecycle_counters){
    DBG(10, "read_counters: unsupported\n");
    return ret;
  }

  DBG(10, "read_counters: start\n");

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, READ_code);
  set_R_datatype_code(cmd, SR_datatype_counters);
  set_R_xfer_length(cmd, inLen);

  ret = do_cmd(
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    in, &inLen
  );

  if (ret == SANE_STATUS_GOOD || ret == SANE_STATUS_EOF){

    s->total_counter = get_R_COUNTERS_total(in);
    s->roller_counter = s->total_counter - get_R_COUNTERS_last_srv(in);

    DBG(10, "read_counters: total counter: %d roller_counter %d \n",s->total_counter,s->roller_counter);
    ret = SANE_STATUS_GOOD;
  }else{
    DBG(10, "read_counters: ERROR: %d\n",ret);
  }

  return ret;
}

static SANE_Status
read_sensors(struct scanner *s,SANE_Int option)
{
  SANE_Status ret=SANE_STATUS_GOOD;

  unsigned char cmd[READ_len];
  size_t cmdLen = READ_len;

  unsigned char in[R_SENSORS_len];
  size_t inLen = R_SENSORS_len;

  DBG (10, "read_sensors: start %d\n", option);

  if(!s->can_read_sensors){
    DBG (10, "read_sensors: unsupported, finishing\n");
    return ret;
  }

  /* only run this if frontend has already read the last time we got it */
  /* or if we don't care for such bookkeeping (private use) */
  if (!option || !s->sensors_read[option-OPT_ADF_LOADED]) {

    DBG (15, "read_sensors: running\n");

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, READ_code);
    set_R_datatype_code (cmd, SR_datatype_sensors);
    set_R_xfer_length (cmd, inLen);

    ret = do_cmd (
      s, 1, 0,
      cmd, cmdLen,
      NULL, 0,
      in, &inLen
    );

    if (ret == SANE_STATUS_GOOD || ret == SANE_STATUS_EOF) {
      /*set flags indicating there is data to read*/
      memset(s->sensors_read,1,sizeof(s->sensors_read));

      s->sensor_adf_loaded = get_R_SENSORS_adf(in);
      s->sensor_card_loaded = get_R_SENSORS_card(in);

      ret = SANE_STATUS_GOOD;
    }
  }

  if(option)
    s->sensors_read[option-OPT_ADF_LOADED] = 0;

  DBG (10, "read_sensors: finish\n");

  return ret;
}

static SANE_Status
read_panel(struct scanner *s,SANE_Int option)
{
  SANE_Status ret=SANE_STATUS_GOOD;

  unsigned char cmd[READ_len];
  size_t cmdLen = READ_len;

  unsigned char in[R_PANEL_len];
  size_t inLen = R_PANEL_len;

  DBG (10, "read_panel: start %d\n", option);

  if(!s->can_read_panel){
    DBG (10, "read_panel: unsupported, finishing\n");
    return ret;
  }

  /* only run this if frontend has already read the last time we got it */
  /* or if we don't care for such bookkeeping (private use) */
  if (!option || !s->panel_read[option-OPT_START]) {

    DBG (15, "read_panel: running\n");

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, READ_code);
    set_R_datatype_code (cmd, SR_datatype_panel);
    set_R_xfer_length (cmd, inLen);

    ret = do_cmd (
      s, 1, 0,
      cmd, cmdLen,
      NULL, 0,
      in, &inLen
    );

    if (ret == SANE_STATUS_GOOD || ret == SANE_STATUS_EOF) {
      /*set flags indicating there is data to read*/
      memset(s->panel_read,1,sizeof(s->panel_read));

      s->panel_start = get_R_PANEL_start(in);
      s->panel_stop = get_R_PANEL_stop(in);
      s->panel_butt3 = get_R_PANEL_butt3(in);
      s->panel_new_file = get_R_PANEL_new_file(in);
      s->panel_count_only = get_R_PANEL_count_only(in);
      s->panel_bypass_mode = get_R_PANEL_bypass_mode(in);
      s->panel_enable_led = get_R_PANEL_enable_led(in);
      s->panel_counter = get_R_PANEL_counter(in);
      s->panel_function_number = get_R_PANEL_function_number(in);

      ret = SANE_STATUS_GOOD;
    }
  }

  if(option)
    s->panel_read[option-OPT_START] = 0;

  DBG (10, "read_panel: finish %d\n",s->panel_counter);

  return ret;
}

static SANE_Status
send_panel(struct scanner *s)
{
    SANE_Status ret=SANE_STATUS_GOOD;

    unsigned char cmd[SEND_len];
    size_t cmdLen = SEND_len;

    unsigned char out[S_PANEL_len];
    size_t outLen = S_PANEL_len;

    DBG (10, "send_panel: start\n");

    if(!s->can_write_panel){
      DBG (10, "send_panel: unsupported, finishing\n");
      return ret;
    }

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, SEND_code);
    set_S_xfer_datatype (cmd, SR_datatype_panel);
    set_S_xfer_length (cmd, outLen);

    memset(out,0,outLen);
    set_S_PANEL_enable_led(out,s->panel_enable_led);
    set_S_PANEL_counter(out,s->panel_counter);

    ret = do_cmd (
      s, 1, 0,
      cmd, cmdLen,
      out, outLen,
      NULL, NULL
    );

    if (ret == SANE_STATUS_EOF) {
        ret = SANE_STATUS_GOOD;
    }

    DBG (10, "send_panel: finish %d\n", ret);

    return ret;
}

/*
 * Request the size of the scanned image
 */
/* we should really be updating s->s and s->i instead */
static SANE_Status
get_pixelsize(struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char cmd[READ_len];
  size_t cmdLen = READ_len;

  unsigned char in[R_PSIZE_len];
  size_t inLen = R_PSIZE_len;

  int i = 0;
  const int MAX_TRIES = 5;

  DBG (10, "get_pixelsize: start\n");

  if(!s->hwcrop){
    DBG (10, "get_pixelsize: unneeded, finishing\n");
    return ret;
  }

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, READ_code);
  set_R_datatype_code(cmd, SR_datatype_pixelsize);
  set_R_xfer_lid(cmd, 0x02);
  set_R_xfer_length(cmd, inLen);

  /* May need to retry/block until the scanner is done */
  for(i=0;i<MAX_TRIES;i++){
    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        NULL, 0,
        in, &inLen
    );

    if(ret != SANE_STATUS_GOOD){
      DBG (10, "get_pixelsize: error reading, status = %d\n", ret);
      break;
    }

    if(get_R_PSIZE_width(in) > 0 && get_R_PSIZE_length(in) > 0){
      DBG (15, "get_pixelsize: w:%d h:%d\n",
           get_R_PSIZE_width(in) * s->u.dpi_x / 1200,
           get_R_PSIZE_length(in) * s->u.dpi_y / 1200);

      /*
       * Round up to byte boundary if needed.
       * For 1 bpp the resulting size may not fit in a byte boundary.
       */
      int remainder = (get_R_PSIZE_width(in) * s->u.dpi_x / 1200) % 8;

      if (s->u.mode < MODE_GRAYSCALE && remainder)
      {
        int rounded_up = (8 - remainder) + (get_R_PSIZE_width(in) * s->u.dpi_x / 1200);

        s->u.br_x = rounded_up * 1200 / s->u.dpi_x;
      }
      else{
        s->u.br_x = get_R_PSIZE_width(in);
      }

      s->u.tl_x = 0;
      s->u.br_y = get_R_PSIZE_length(in);
      s->u.tl_y = 0;

      s->u.page_x = s->u.br_x;
      s->u.page_y = s->u.br_y;

      update_params(s,0);
      clean_params(s);
      break;
    }

    else{
      DBG (10, "get_pixelsize: error reading, status = %d w:%d h:%d\n",
           ret, get_R_PSIZE_width(in), get_R_PSIZE_length(in));
      ret = SANE_STATUS_INVAL;
      usleep(1000000);
    }
  }
  DBG (10, "get_pixelsize: finish\n");

  return ret;
}

/*
 * @@ Section 4 - SANE scanning functions
 */
/*
 * Called by SANE to retrieve information about the type of data
 * that the current scan will return.
 *
 * From the SANE spec:
 * This function is used to obtain the current scan parameters. The
 * returned parameters are guaranteed to be accurate between the time
 * a scan has been started (sane_start() has been called) and the
 * completion of that request. Outside of that window, the returned
 * values are best-effort estimates of what the parameters will be
 * when sane_start() gets invoked.
 *
 * Calling this function before a scan has actually started allows,
 * for example, to get an estimate of how big the scanned image will
 * be. The parameters passed to this function are the handle h of the
 * device for which the parameters should be obtained and a pointer p
 * to a parameter structure.
 */
SANE_Status
sane_get_parameters (SANE_Handle handle, SANE_Parameters * params)
{
    SANE_Status ret = SANE_STATUS_GOOD;
    struct scanner *s = (struct scanner *) handle;

    DBG (10, "sane_get_parameters: start\n");

    if(!s->started){
      ret = update_params(s,0);
      if(ret){
        DBG (5, "sane_get_parameters: up error, returning %d\n", ret);
        return ret;
      }
    }

    /* this backend only sends single frame images */
    params->last_frame = 1;

    params->format = s->i.format;
    params->lines = s->i.height;
    params->depth = s->i.bpp;
    if(params->depth == 24) params->depth = 8;
    params->pixels_per_line = s->i.width;
    params->bytes_per_line = s->i.Bpl;

    DBG(15,"sane_get_parameters: x: max=%d, page=%d, gpw=%d, res=%d\n",
      s->valid_x, s->i.page_x, get_page_width(s), s->i.dpi_x);

    DBG(15,"sane_get_parameters: y: max=%d, page=%d, gph=%d, res=%d\n",
      s->max_y, s->i.page_y, get_page_height(s), s->i.dpi_y);

    DBG(15,"sane_get_parameters: area: tlx=%d, brx=%d, tly=%d, bry=%d\n",
      s->i.tl_x, s->i.br_x, s->i.tl_y, s->i.br_y);

    DBG (15, "sane_get_parameters: params: ppl=%d, Bpl=%d, lines=%d\n",
      params->pixels_per_line, params->bytes_per_line, params->lines);

    DBG (15, "sane_get_parameters: params: format=%d, depth=%d, last=%d\n",
      params->format, params->depth, params->last_frame);

    DBG (10, "sane_get_parameters: finish\n");

    return ret;
}

SANE_Status
update_params(struct scanner *s, int calib)
{
    SANE_Status ret = SANE_STATUS_GOOD;

    DBG (10, "update_params: start\n");

    s->u.width = (s->u.br_x - s->u.tl_x) * s->u.dpi_x / 1200;
    s->u.height = (s->u.br_y - s->u.tl_y) * s->u.dpi_y / 1200;

    if (s->u.mode == MODE_COLOR) {
      s->u.format = SANE_FRAME_RGB;
      s->u.bpp = 24;
    }
    else if (s->u.mode == MODE_GRAYSCALE) {
      s->u.format = SANE_FRAME_GRAY;
      s->u.bpp = 8;
    }
    else {
      s->u.format = SANE_FRAME_GRAY;
      s->u.bpp = 1;

      /* round down to byte boundary */
      s->u.width -= s->u.width % 8;
    }

    /* round down to pixel boundary for some scanners */
    s->u.width -= s->u.width % s->ppl_mod;

    /* jpeg requires 8x8 squares */
    if(s->compress == COMP_JPEG && s->u.mode >= MODE_GRAYSCALE){
      s->u.format = SANE_FRAME_JPEG;
      s->u.width -= s->u.width % 8;
      s->u.height -= s->u.height % 8;
    }

    s->u.Bpl = s->u.width * s->u.bpp / 8;
    s->u.valid_Bpl = s->u.Bpl;
    s->u.valid_width = s->u.width;

    DBG (15, "update_params: user params: w:%d h:%d m:%d f:%d b:%d\n",
      s->u.width, s->u.height, s->u.mode, s->u.format, s->u.bpp);
    DBG (15, "update_params: user params: B:%d vB:%d vw:%d\n",
      s->u.Bpl, s->u.valid_Bpl, s->u.valid_width);
    DBG (15, "update_params: user params: x b:%d t:%d d:%d y b:%d t:%d d:%d\n",
      s->u.br_x, s->u.tl_x, s->u.dpi_x, s->u.br_y, s->u.tl_y, s->u.dpi_y);

    /* some scanners are limited in their valid scan params
     * make a second version of the params struct, but
     * override the user's values with what the scanner can actually do */

    memcpy(&s->s,&s->u,sizeof(struct img_params));

    /*********** missing modes (move up to valid one) **************/
    if(s->s.mode == MODE_LINEART && !s->can_monochrome){
      s->s.mode = MODE_GRAYSCALE;
      s->s.format = SANE_FRAME_GRAY;
      s->s.bpp = 8;
    }
    if(s->s.mode == MODE_GRAYSCALE && !s->can_grayscale){
      s->s.mode = MODE_COLOR;
      s->s.format = SANE_FRAME_RGB;
      s->s.bpp = 24;
    }
    if(s->s.mode == MODE_COLOR && !s->can_color){
      DBG (5, "update_params: no valid mode\n");
      return SANE_STATUS_INVAL;
    }

    /********** missing resolutions (move up to valid one) *********/
    if(!s->step_x_res){
      int i;
      for(i=0;i<DPI_1200;i++){

        /* this res is smaller or invalid, skip it */
        if(s->s.dpi_x > dpi_list[i] || !s->std_res_x[i])
          continue;

        /* same & valid res, done */
        if(s->s.dpi_x == dpi_list[i])
          break;

        /* different & valid res, switch */
        s->s.dpi_x = dpi_list[i];
        break;
      }

      if(i > DPI_1200){
        DBG (5, "update_params: no dpi\n");
        return SANE_STATUS_INVAL;
      }
    }

    /*********** weird scan area (increase to valid one) *********/
    if(s->fixed_width){
      s->s.tl_x = 0;
      s->s.br_x = s->max_x;
      s->s.page_x = s->max_x;
    }

    /*recalculate new params*/
    s->s.width = (s->s.br_x - s->s.tl_x) * s->s.dpi_x / 1200;

    /* round down to byte boundary */
    if(s->s.mode < MODE_GRAYSCALE){
      s->s.width -= s->s.width % 8;
    }

    /* round down to pixel boundary for some scanners */
    s->s.width -= s->s.width % s->ppl_mod;

    s->s.valid_width = s->s.width;
    s->s.valid_Bpl = s->s.valid_width * s->s.bpp / 8;

    /* some machines (DR-2050) require even bytes per scanline */
    /* increase width and Bpl, but not valid_width and valid_Bpl */
    if(s->even_Bpl && (s->s.width % 2)){
      s->s.width++;
    }

    s->s.Bpl = s->s.width * s->s.bpp / 8;

    /* figure out how many valid bytes per line (2510 is padded) */
    if(s->color_interlace[SIDE_FRONT] == COLOR_INTERLACE_2510){
      s->s.valid_Bpl = s->s.Bpl*11/12;
      s->s.valid_width = s->s.width*11/12;
    }

    /* some scanners need longer scans because front/back is offset */
    if((s->u.source == SOURCE_ADF_DUPLEX || s->u.source == SOURCE_CARD_DUPLEX)
      && s->duplex_offset && !calib)
      s->s.height = (s->u.br_y-s->u.tl_y+s->duplex_offset) * s->u.dpi_y / 1200;

    /* round lines up to even number */
    s->s.height += s->s.height % 2;

    DBG (15, "update_params: scan params: w:%d h:%d m:%d f:%d b:%d\n",
      s->s.width, s->s.height, s->s.mode, s->s.format, s->s.bpp);
    DBG (15, "update_params: scan params: B:%d vB:%d vw:%d\n",
      s->s.Bpl, s->s.valid_Bpl, s->s.valid_width);
    DBG (15, "update_params: scan params: x b:%d t:%d d:%d y b:%d t:%d d:%d\n",
      s->s.br_x, s->s.tl_x, s->s.dpi_x, s->s.br_y, s->s.tl_y, s->s.dpi_y);

    /* make a third (intermediate) version of the params struct,
     * currently identical to the user's params. this is what
     * we actually will send back to the user (though buffer_xxx
     * functions might change these values after this runs) */

    /* calibration code needs the data just as it comes from the scanner */
    if(calib)
      memcpy(&s->i,&s->s,sizeof(struct img_params));
    /* normal scans need the data cleaned for presentation to the user */
    else{
      memcpy(&s->i,&s->u,sizeof(struct img_params));
      /*dumb scanners pad the top of front page in duplex*/
      if(s->i.source == SOURCE_ADF_DUPLEX || s->i.source == SOURCE_CARD_DUPLEX)
        s->i.skip_lines[s->duplex_offset_side] = s->duplex_offset * s->i.dpi_y / 1200;
    }

    DBG (15, "update_params: i params: w:%d h:%d m:%d f:%d b:%d\n",
      s->i.width, s->i.height, s->i.mode, s->i.format, s->i.bpp);
    DBG (15, "update_params: i params: B:%d vB:%d vw:%d\n",
      s->i.Bpl, s->i.valid_Bpl, s->i.valid_width);
    DBG (15, "update_params: i params: x b:%d t:%d d:%d y b:%d t:%d d:%d\n",
      s->i.br_x, s->i.tl_x, s->i.dpi_x, s->i.br_y, s->i.tl_y, s->i.dpi_y);

    DBG (10, "update_params: finish\n");
    return ret;
}

/* simplify handling cmd SANE_STATUS_EOF as SANE_STATUS_GOOD */
SANE_Status
send_cmd(struct scanner *s, unsigned char* cmd, size_t cmdLen,
                            unsigned char* out, size_t outLen,
                            unsigned char * inBuff, size_t * inLen)
{
  SANE_Status ret=SANE_STATUS_GOOD;

    ret = do_cmd (
      s, 1, 0,
      cmd, cmdLen,
      out, outLen,
      inBuff, inLen
    );

    if (ret == SANE_STATUS_EOF) {
        ret = SANE_STATUS_GOOD;
    }

    return ret;
}

SANE_Status
send_imprint_positioning(struct scanner* s, int is_postimprint, int enabled)
{
  unsigned char cmd[SET_SCAN_MODE2_len];
  size_t cmdLen=SET_SCAN_MODE2_len;

  unsigned char out[SSM2_PAY_len];
  size_t outLen=SSM2_PAY_len;

  unsigned char out_prefix[5]={ 0x01, 0x00, 0x60, 0x00, 0x60 };
  size_t outPrefixLen=5;

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd,SET_SCAN_MODE2_code);
  set_SSM2_page_code(cmd,SM2_pc_imprinter_settings);
  if (is_postimprint)
    set_SSM2_postimprint_cmd(cmd);
  set_SSM2_pay_len(cmd,outLen);

  memset(out,0,outLen);
  memcpy(out,out_prefix,outPrefixLen);

  int h_offset;
  int v_offset;
  if (is_postimprint){
    if (s->post_imprint_addon_mode != ADDON_DISABLED)
      set_SSM2_postimprint_addon(out);
    h_offset = s->post_imprint.h_offset;
    v_offset = s->post_imprint.v_offset;

    if (enabled)
      DBG (10, "send_imprint_positioning: post-imprinter: h_offset: %d v_offset: %d\n",h_offset,v_offset);
  }else{
    h_offset = s->pre_imprint.h_offset;
    v_offset = s->pre_imprint.v_offset;
    if (enabled)
      DBG (10, "send_imprint_positioning: pre-imprinter: h_offset: %d v_offset: %d\n",h_offset,v_offset);
  }
  if(!enabled)
    h_offset = v_offset = 0;
  set_SSM2_imprint_hoffset(out,h_offset);
  set_SSM2_imprint_voffset(out,v_offset);

  return send_cmd(s, cmd, cmdLen, out, outLen, NULL, NULL);
}

SANE_Status
send_imprint_specstring(struct scanner* s, int is_postimprint)
{
  unsigned char cmd[SET_SCAN_MODE2_len];
  size_t cmdLen = SET_SCAN_MODE2_len;

  unsigned char out[SSM2_IMPRINTER_STRING_PAY_len];
  size_t outLen = SSM2_IMPRINTER_STRING_PAY_len;

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, SET_SCAN_MODE2_code);
  set_SSM2_page_code(cmd, SM2_pc_imprinter_specstring);
  if (is_postimprint)
    set_SSM2_postimprint_cmd(cmd);
  set_SSM2_pay_len(cmd, outLen);

  memset(out,0,outLen);
  /* most of these bytes have yet to be identified to specific functionalities,
     as they never seem to change under different imprinting mode */
  unsigned char out_prefix[32] = {
      0x01, 0x00,
      0x60, 0x00,
      0x60, 0x00,
      0x00, 0x00,
      0x00, 0x00,
      0x00, 0x00,
      0x03, 0x00,
      0x00, 0x00,
      0x01, 0x00,
      0x00, 0x00,
      0x00, 0x00,
      0x00, 0x00,
      0x00, 0x00,
      0x00, 0x01,
      0x04, 0x00,
      0x00, 0x00
    };
  memcpy(out, out_prefix, 32);
  if (is_postimprint){
    set_SSM2_imprint_fontsize(out, s->post_imprint.font_size);
    set_SSM2_imprint_fontrot(out, s->post_imprint.font_rot);
    set_SSM2_imprint_spacing(out, s->post_imprint.spacing);
    if (s->post_imprint_addon_mode != ADDON_DISABLED)
      set_SSM2_imprint_addonmode(out, s->post_imprint_addon_mode);
    strcpy((SANE_Char*)(out + 45), (SANE_String_Const) s->post_imprint.specstring);
    DBG (10, "send_imprint_specstring: post-imprinter: font size: %d rotation: %d spacing: %d text: '%s' imprint-addon-mode: %d\n",s->post_imprint.font_size,s->post_imprint.font_rot,s->post_imprint.spacing,s->post_imprint.specstring,s->post_imprint_addon_mode);
  }else{
    set_SSM2_imprint_fontsize(out, s->pre_imprint.font_size);
    set_SSM2_imprint_fontrot(out, s->pre_imprint.font_rot);
    set_SSM2_imprint_spacing(out, s->pre_imprint.spacing);
    strcpy((SANE_Char*)(out + 45), (SANE_String_Const) s->pre_imprint.specstring);
    DBG (10, "send_imprint_specstring: pre-imprinter: font size: %d rotation: %d spacing: %d text: '%s'\n",s->pre_imprint.font_size,s->pre_imprint.font_rot,s->pre_imprint.spacing,s->pre_imprint.specstring);
  }

  return send_cmd(s, cmd, cmdLen, out, outLen, NULL, NULL);
}

SANE_Status
send_imprint_date_and_time(struct scanner* s)
{
  unsigned char cmd[SET_SCAN_MODE2_len];
  size_t cmdLen = SET_SCAN_MODE2_len;

  unsigned char out[SSM2_PAY_len];
  size_t outLen = SSM2_PAY_len;

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, SET_SCAN_MODE2_code);
  set_SSM2_page_code(cmd, SM2_pc_date_time);
  set_SSM2_pay_len(cmd, outLen);

  memset(out,0,outLen);

  time_t t = time(NULL);
  struct tm tM = *localtime(&t);

  set_SSM2_imprint_year(out, tM.tm_year + 1900);
  set_SSM2_imprint_month(out, tM.tm_mon + 1);
  set_SSM2_imprint_day(out, tM.tm_mday);
  set_SSM2_imprint_hour(out, tM.tm_hour);
  set_SSM2_imprint_min(out, tM.tm_min);
  set_SSM2_imprint_sec(out, tM.tm_sec);

  return send_cmd(s, cmd, cmdLen, out, outLen, NULL, NULL);
}

SANE_Status
load_imprinting_settings(struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  int requires_preimprint = (strlen(s->pre_imprint.specstring) > 0);
  int requires_postimprint = (strlen(s->post_imprint.specstring) > 0);
  int send_date_time = (s->has_pre_imprinter && requires_preimprint) || (s->has_post_imprinter && requires_postimprint);

  if (s->has_pre_imprinter){
    ret = send_imprint_positioning(s, 0, requires_preimprint);
    DBG(10, "load_imprinting_settings: send_pre_imprint_positioning = %d \n", ret);
    if (ret != SANE_STATUS_GOOD)
      return ret;
    if (requires_preimprint){
      ret = send_imprint_specstring(s, 0);
      DBG(10, "load_imprinting_settings: send_pre_imprint_specstring = %d \n", ret);
      if (ret != SANE_STATUS_GOOD)
        return ret;
    }
  }

  if (s->has_post_imprinter){
    ret = send_imprint_positioning(s, 1, requires_postimprint);
    DBG(10, "load_imprinting_settings: send_post_imprint_positioning = %d \n", ret);
    if (ret != SANE_STATUS_GOOD)
      return ret;
    if (requires_postimprint){
      ret = send_imprint_specstring(s, 1);
      DBG(10, "load_imprinting_settings: send_post_imprint_specstring = %d \n", ret);
      if (ret != SANE_STATUS_GOOD)
        return ret;
    }
  }

  if (send_date_time){
    ret = send_imprint_date_and_time(s);
    DBG(10, "load_imprinting_settings: send_imprint_date_and_time = %d \n", ret);
  }
  return ret;
}

/* look for a particular imprinter
 * SANE_STATUS_GOOD = found
 * SANE_STATUS_UNSUPPORTED = not found
 * SANE_STATUS_INVAL = all other errors
 */
static SANE_Status
detect_imprinter(struct scanner *s, SANE_Int imp_side)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char cmd[READ_len];
  size_t cmdLen = READ_len;

  unsigned char in[R_IMPRINTER_len];
  size_t inLen = R_IMPRINTER_len;

  DBG (10, "detect_imprinter: start %d\n", imp_side);

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, READ_code);
  set_R_datatype_code(cmd, SR_datatype_imprinters);
  set_R_xfer_uid(cmd, imp_side);
  set_R_xfer_length(cmd, inLen);

  ret = do_cmd(
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    in, &inLen
  );

  /* some scanners return eof for success, so we change it */
  if (ret == SANE_STATUS_EOF) {
    ret = SANE_STATUS_GOOD;
  }

  /* failed commands are 'inval' */
  if(ret){
    DBG (15, "detect_imprinter: error, converting %d to invalid\n", ret);
    ret = SANE_STATUS_INVAL;
  }

  /* negative responses are 'unsupported' */
  else if(!get_R_IMPRINTER_found(in)){
    DBG (15, "detect_imprinter: not found, converting to unsupported\n");
    ret = SANE_STATUS_UNSUPPORTED;
  }

  DBG (10, "detect_imprinter: finish %d\n", ret);
  return ret;
}

/* reset image size parameters after buffer_xxx functions changed them */
SANE_Status
update_i_params(struct scanner *s)
{
    SANE_Status ret = SANE_STATUS_GOOD;

    DBG (10, "update_i_params: start\n");

    s->i.width = s->u.width;
    s->i.Bpl = s->u.Bpl;

    DBG (10, "update_i_params: finish\n");
    return ret;
}

/*
 * Called by SANE when a page acquisition operation is to be started.
 * commands: set window, object pos, and scan
 *
 * this will be called between sides of a duplex scan,
 * and at the start of each page of an adf batch.
 * hence, we spend a lot of time playing with s->started, etc.
 */
SANE_Status
sane_start (SANE_Handle handle)
{
  struct scanner *s = handle;
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "sane_start: start\n");
  DBG (15, "started=%d, side=%d, source=%d\n",
    s->started, s->side, s->u.source);

  /* undo any prior sane_cancel calls */
  s->cancelled=0;

  /* protect this block from sane_cancel */
  s->reading=1;

  /* not finished with current side, error */
  if (s->started && !s->u.eof[s->side]) {
    DBG(5,"sane_start: previous transfer not finished?");
    return SANE_STATUS_INVAL;
  }

  /* batch start? inititalize struct and scanner */
  if(!s->started){

    /* load side marker */
    if(s->u.source == SOURCE_ADF_BACK || s->u.source == SOURCE_CARD_BACK){
      s->side = SIDE_BACK;
    }
    else{
      s->side = SIDE_FRONT;
    }

    /* eject paper leftover*/
    if(object_position (s, SANE_FALSE)){
      DBG (5, "sane_start: ERROR: cannot eject page\n");
    }

    /* wait for scanner to finish eject */
    ret = wait_scanner (s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot wait scanner\n");
      goto errors;
    }

    /* load the brightness/contrast lut with linear slope for calibration */
    ret = load_lut (s->lut, 8, 8, 0, 255, 0, 0);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot load lut\n");
      goto errors;
    }

    /* AFE cal */
    ret = calibrate_AFE(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot cal afe\n");
      goto errors;
    }

    /* fine cal */
    ret = calibrate_fine(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot cal fine\n");
      goto errors;
    }

    if (s->has_pre_imprinter || s->has_post_imprinter){
      ret = load_imprinting_settings(s);
      if (ret != SANE_STATUS_GOOD) {
        DBG (5, "sane_start: ERROR: invalid imprinting settings\n");
        goto errors;
      }
    }

    /* reset the page counter after calibration */
    s->panel_counter = 0;
    s->prev_page = 0;
    if(send_panel(s)){
      DBG (5, "sane_start: ERROR: cannot send panel\n");
    }

    /* we should really be updating s->s and s->i instead */
    if(s->hwcrop){
      s->u.br_x = s->max_x;
      s->u.tl_x = 0;
      s->u.br_y = s->max_y;
      s->u.tl_y = 0;
      s->u.page_x = s->max_x;
      s->u.page_y = s->max_y;
    }

    /* load our own private copy of scan params */
    ret = update_params(s,0);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot update_params\n");
      goto errors;
    }

    /* set window command */
    ret = set_window(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot set window\n");
      goto errors;
    }

    /* buffer/duplex/ald/fb/card command */
    ret = ssm_buffer(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot ssm buffer\n");
      goto errors;
    }

    /* dropout color command */
    ret = ssm_do(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot ssm do\n");
      goto errors;
    }

    /* double feed detection command */
    ret = ssm_df(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot ssm df\n");
      goto errors;
    }

    ret = ssm2_hw_enhancement(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot ssm2 hw enhancement\n");
      goto errors;
    }

    /* clean scan params for new scan */
    ret = clean_params(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot clean_params\n");
      goto errors;
    }

    /* make large buffers to hold the images */
    ret = image_buffers(s,1);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot load buffers\n");
      goto errors;
    }

    /* load the brightness/contrast lut with user choices */
    ret = load_lut (s->lut, 8, 8, 0, 255, s->contrast, s->brightness);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot load lut\n");
      goto errors;
    }

    /* card reader dislikes op? */
    if(s->s.source < SOURCE_CARD_FRONT){
      /* grab next page */
      ret = object_position (s, SANE_TRUE);
      if (ret != SANE_STATUS_GOOD) {
        DBG (5, "sane_start: ERROR: cannot load page\n");
        goto errors;
      }

      /* wait for scanner to finish load */
      ret = wait_scanner (s);
      if (ret != SANE_STATUS_GOOD) {
        DBG (5, "sane_start: ERROR: cannot wait scanner\n");
        goto errors;
      }
    }

    /* start scanning */
    ret = start_scan (s,0);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot start_scan\n");
      goto errors;
    }

    ret = get_pixelsize(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot get pixel size\n");
      goto errors;
    }

    s->started = 1;
  }

  /* stuff done for subsequent images */
  else{

    /* duplex needs to switch sides */
    if(s->s.source == SOURCE_ADF_DUPLEX || s->s.source == SOURCE_CARD_DUPLEX){
      s->side = !s->side;
    }

    /* reset the intermediate params */
    ret = update_i_params(s);
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot update_i_params\n");
      goto errors;
    }

    /* set clean defaults with new sheet of paper */
    /* don't reset the transfer vars on backside of duplex page */
    /* otherwise buffered back page will be lost */
    /* ingest paper with adf (no-op for fb) */
    /* don't call object pos or scan on back side of duplex scan */
    if(s->side == SIDE_FRONT || s->s.source == SOURCE_ADF_BACK || s->s.source == SOURCE_CARD_BACK){

      /* clean scan params for new scan */
      ret = clean_params(s);
      if (ret != SANE_STATUS_GOOD) {
        DBG (5, "sane_start: ERROR: cannot clean_params\n");
        goto errors;
      }

      /* big scanners and small ones in non-buff mode: OP to detect paper */
      if(s->always_op || !s->buffermode){
        ret = object_position (s, SANE_TRUE);
        if (ret != SANE_STATUS_GOOD) {
          DBG (5, "sane_start: ERROR: cannot load page\n");
          goto errors;
        }

        /* user wants unbuffered scans */
        /* send scan command */
        if(!s->buffermode){
          ret = start_scan (s,0);
          if (ret != SANE_STATUS_GOOD) {
            DBG (5, "sane_start: ERROR: cannot start_scan\n");
            goto errors;
          }
        }
      }

      /* small, buffering scanners check for more pages by reading counter */
      else{
        ret = read_panel (s, OPT_COUNTER);
        if (ret != SANE_STATUS_GOOD) {
          DBG (5, "sane_start: ERROR: cannot load page\n");
          goto errors;
        }
        if(s->prev_page == s->panel_counter){
          DBG (5, "sane_start: same counter (%d) no paper?\n",s->prev_page);
          ret = SANE_STATUS_NO_DOCS;
          goto errors;
        }
        DBG (5, "sane_start: diff counter (%d/%d)\n",
          s->prev_page,s->panel_counter);
      }

      ret = get_pixelsize(s);
      if (ret != SANE_STATUS_GOOD) {
        DBG (5, "sane_start: ERROR: cannot get pixel size\n");
        goto errors;
      }
    }
  }

  /* reset jpeg params on each page */
  s->jpeg_stage=JPEG_STAGE_NONE;
  s->jpeg_ff_offset=0;

  DBG (15, "started=%d, side=%d, source=%d\n",
    s->started, s->side, s->u.source);

  /* certain options require the entire image to
   * be collected from the scanner before we can
   * tell the user the size of the image. the sane
   * API has no way to inform the frontend of this,
   * so we block and buffer. yuck */
  if(must_fully_buffer(s)){

    /* get image */
    while(!s->s.eof[s->side] && !ret){
      SANE_Int len = 0;
      ret = sane_read((SANE_Handle)s, NULL, 0, &len);
    }

    /* check for errors */
    if (ret != SANE_STATUS_GOOD) {
      DBG (5, "sane_start: ERROR: cannot buffer image\n");
      goto errors;
    }

    DBG (5, "sane_start: OK: done buffering\n");

    /* finished buffering, adjust image as required */
    if(s->swdeskew){
      buffer_deskew(s,s->side);
    }
    if(s->swcrop){
      buffer_crop(s,s->side);
    }
    if(s->swdespeck){
      buffer_despeck(s,s->side);
    }
    if(s->swskip){
      /* Skipping means throwing out this image.
       * Pretend the user read the whole thing
       * and call sane_start again.
       * This assumes we are running in batch mode. */
      if(buffer_isblank(s,s->side)){
        s->u.eof[s->side] = 1;
        return sane_start(handle);
      }
    }
  }

  ret = check_for_cancel(s);
  s->reading = 0;

  DBG (10, "sane_start: finish %d\n", ret);
  return ret;

  errors:
    DBG (10, "sane_start: error %d\n", ret);
    s->started = 0;
    s->cancelled = 0;
    s->reading = 0;
    return ret;
}

/*
 * cleans params for new scan
 */
static SANE_Status
clean_params (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "clean_params: start\n");

  s->u.eof[0]=0;
  s->u.eof[1]=0;
  s->u.bytes_sent[0]=0;
  s->u.bytes_sent[1]=0;
  s->u.bytes_tot[0]=0;
  s->u.bytes_tot[1]=0;

  s->i.eof[0]=0;
  s->i.eof[1]=0;
  s->i.bytes_sent[0]=0;
  s->i.bytes_sent[1]=0;
  s->i.bytes_tot[0]=0;
  s->i.bytes_tot[1]=0;

  s->s.eof[0]=0;
  s->s.eof[1]=0;
  s->s.bytes_sent[0]=0;
  s->s.bytes_sent[1]=0;
  s->s.bytes_tot[0]=0;
  s->s.bytes_tot[1]=0;

  /* store the number of front bytes */
  if ( s->u.source != SOURCE_ADF_BACK && s->u.source != SOURCE_CARD_BACK )
    s->u.bytes_tot[SIDE_FRONT] = s->u.Bpl * s->u.height;

  if ( s->i.source != SOURCE_ADF_BACK && s->i.source != SOURCE_CARD_BACK )
    s->i.bytes_tot[SIDE_FRONT] = s->i.Bpl * s->i.height;

  if ( s->s.source != SOURCE_ADF_BACK && s->s.source != SOURCE_CARD_BACK )
    s->s.bytes_tot[SIDE_FRONT] = s->s.Bpl * s->s.height;

  /* store the number of back bytes */
  if ( s->u.source == SOURCE_ADF_DUPLEX || s->u.source == SOURCE_ADF_BACK
    || s->u.source == SOURCE_CARD_DUPLEX || s->u.source == SOURCE_CARD_BACK )
    s->u.bytes_tot[SIDE_BACK] = s->u.Bpl * s->u.height;

  if ( s->i.source == SOURCE_ADF_DUPLEX || s->i.source == SOURCE_ADF_BACK
    || s->i.source == SOURCE_CARD_DUPLEX || s->i.source == SOURCE_CARD_BACK )
    s->i.bytes_tot[SIDE_BACK] = s->i.Bpl * s->i.height;

  if ( s->s.source == SOURCE_ADF_DUPLEX || s->s.source == SOURCE_ADF_BACK
    || s->s.source == SOURCE_CARD_DUPLEX || s->s.source == SOURCE_CARD_BACK )
    s->s.bytes_tot[SIDE_BACK] = s->s.Bpl * s->s.height;

  DBG (10, "clean_params: finish\n");

  return ret;
}

/*
 * frees/callocs buffers to hold the scan data
 */
static SANE_Status
image_buffers (struct scanner *s, int setup)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int side;

  DBG (10, "image_buffers: start\n");

  for(side=0;side<2;side++){

    /* free current buffer */
    if (s->buffers[side]) {
      DBG (15, "image_buffers: free buffer %d.\n",side);
      free(s->buffers[side]);
      s->buffers[side] = NULL;
    }

    /* build new buffer if asked */
    if(s->i.bytes_tot[side] && setup){
      s->buffers[side] = calloc (1,s->i.bytes_tot[side]);
      if (!s->buffers[side]) {
        DBG (5, "image_buffers: Error, no buffer %d.\n",side);
        return SANE_STATUS_NO_MEM;
      }
    }
  }

  DBG (10, "image_buffers: finish\n");

  return ret;
}

/*
 * This routine issues a SCSI SET WINDOW command to the scanner, using the
 * values currently in the s->s param structure.
 */
static SANE_Status
set_window (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  /* The command specifies the number of bytes in the data phase
   * the data phase has a header, followed by 1 window desc block
   * the header specifies the number of bytes in 1 window desc block
   */

  unsigned char cmd[SET_WINDOW_len];
  size_t cmdLen = SET_WINDOW_len;

  unsigned char out[SW_header_len + SW_desc_len];
  size_t outLen = SW_header_len + SW_desc_len;

  unsigned char * header = out;                       /*header*/
  unsigned char * desc1 = out + SW_header_len;        /*descriptor*/

  DBG (10, "set_window: start\n");

  /*build the payload*/
  memset(out,0,outLen);

  /* set window desc size in header */
  set_WPDB_wdblen(header, SW_desc_len);

  /* init the window block */
  if (s->s.source == SOURCE_ADF_BACK || s->s.source == SOURCE_CARD_BACK) {
    set_WD_wid (desc1, WD_wid_back);
  }
  else{
    set_WD_wid (desc1, WD_wid_front);
  }

  set_WD_Xres (desc1, s->s.dpi_x);
  set_WD_Yres (desc1, s->s.dpi_y);

  /* some machines need max width */
  if(s->fixed_width){
    set_WD_ULX (desc1, 0);
    set_WD_width (desc1, s->max_x);
  }

  /* or they align left */
  else if(s->u.source == SOURCE_FLATBED){
    set_WD_ULX (desc1, s->s.tl_x);
    set_WD_width (desc1, s->s.width * 1200/s->s.dpi_x);
  }

  /* or we have to center the window ourselves */
  else{
    set_WD_ULX (desc1, (s->max_x - s->s.page_x) / 2 + s->s.tl_x);
    set_WD_width (desc1, s->s.width * 1200/s->s.dpi_x);
  }

  /* some models require that the tly value be inverted? */
  if(s->invert_tly)
    set_WD_ULY (desc1, ~s->s.tl_y);
  else
    set_WD_ULY (desc1, s->s.tl_y);

  set_WD_length (desc1, s->s.height * 1200/s->s.dpi_y);

  if(s->has_btc){
    /*convert our common -127 to +127 range into HW's range
     *FIXME: this code assumes hardware range of 0-255 */
    set_WD_brightness (desc1, s->brightness+128);

    set_WD_threshold (desc1, s->threshold);

    /*convert our common -127 to +127 range into HW's range
     *FIXME: this code assumes hardware range of 0-255 */
    set_WD_contrast (desc1, s->contrast+128);
  }

  set_WD_composition (desc1, s->s.mode);

  if(s->s.bpp == 24)
    set_WD_bitsperpixel (desc1, 8);
  else
    set_WD_bitsperpixel (desc1, s->s.bpp);

  if(s->s.mode == MODE_HALFTONE){
    /*set_WD_ht_type(desc1, s->ht_type);
    set_WD_ht_pattern(desc1, s->ht_pattern);*/
  }

  set_WD_rif (desc1, s->rif);
  set_WD_rgb(desc1, s->rgb_format);
  set_WD_padding(desc1, s->padding);

  /*FIXME: what is this? */
  set_WD_reserved2(desc1, s->unknown_byte2);

  set_WD_compress_type(desc1, COMP_NONE);
  set_WD_compress_arg(desc1, 0);

  /* some scanners support jpeg image compression, for color/gs only */
  if(s->s.format == SANE_FRAME_JPEG){
    set_WD_compress_type(desc1, COMP_JPEG);
    set_WD_compress_arg(desc1, s->compress_arg);
  }

  /*build the command*/
  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, SET_WINDOW_code);
  set_SW_xferlen(cmd, outLen);

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    out, outLen,
    NULL, NULL
  );

  if (!ret && (s->s.source == SOURCE_ADF_DUPLEX || s->s.source == SOURCE_CARD_DUPLEX)) {
      set_WD_wid (desc1, WD_wid_back);
      ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, NULL
      );
  }

  DBG (10, "set_window: finish\n");

  return ret;
}

/*
 * Issues the SCSI OBJECT POSITION command if an ADF is in use.
 */
static SANE_Status
object_position (struct scanner *s, int i_load)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char cmd[OBJECT_POSITION_len];
  size_t cmdLen = OBJECT_POSITION_len;

  DBG (10, "object_position: start\n");

  if (s->u.source == SOURCE_FLATBED) {
    DBG (10, "object_position: flatbed no-op\n");
    return SANE_STATUS_GOOD;
  }

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, OBJECT_POSITION_code);

  if (i_load) {
    DBG (15, "object_position: load\n");
    set_OP_autofeed (cmd, OP_Feed);
  }
  else {
    DBG (15, "object_position: eject\n");
    set_OP_autofeed (cmd, OP_Discharge);
  }

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    NULL, NULL
  );
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (10, "object_position: finish\n");

  return ret;
}

/*
 * Issues SCAN command.
 *
 * (This doesn't actually read anything, it just tells the scanner
 * to start scanning.)
 */
static SANE_Status
start_scan (struct scanner *s, int type)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char cmd[SCAN_len];
  size_t cmdLen = SCAN_len;

  unsigned char out[] = {WD_wid_front, WD_wid_back};
  size_t outLen = 2;

  DBG (10, "start_scan: start\n");

  /* calibration scans use 0xff or 0xfe */
  if(type){
    out[0] = type;
    out[1] = type;
  }

  if (s->s.source != SOURCE_ADF_DUPLEX && s->s.source != SOURCE_CARD_DUPLEX) {
    outLen--;
    if(s->s.source == SOURCE_ADF_BACK || s->s.source == SOURCE_CARD_BACK) {
      out[0] = WD_wid_back;
    }
  }

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, SCAN_code);
  set_SC_xfer_length (cmd, outLen);

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    out, outLen,
    NULL, NULL
  );

  DBG (10, "start_scan: finish\n");

  return ret;
}

/*
 * Called by SANE to read data.
 *
 * From the SANE spec:
 * This function is used to read image data from the device
 * represented by handle h.  Argument buf is a pointer to a memory
 * area that is at least maxlen bytes long.  The number of bytes
 * returned is stored in *len. A backend must set this to zero when
 * the call fails (i.e., when a status other than SANE_STATUS_GOOD is
 * returned).
 *
 * When the call succeeds, the number of bytes returned can be
 * anywhere in the range from 0 to maxlen bytes.
 */
SANE_Status
sane_read (SANE_Handle handle, SANE_Byte * buf, SANE_Int max_len, SANE_Int * len)
{
  struct scanner *s = (struct scanner *) handle;
  SANE_Status ret=SANE_STATUS_GOOD;

  DBG (10, "sane_read: start\n");

  *len=0;

  /* maybe cancelled? */
  if(!s->started){
    DBG (5, "sane_read: not started, call sane_start\n");
    return SANE_STATUS_CANCELLED;
  }

  /* sane_start required between sides */
  if(s->u.bytes_sent[s->side] == s->i.bytes_tot[s->side]){
    s->u.eof[s->side] = 1;
    DBG (15, "sane_read: returning eof\n");
    return SANE_STATUS_EOF;
  }

  s->reading = 1;

  /* double width pnm interlacing */
  if((s->s.source == SOURCE_ADF_DUPLEX || s->s.source == SOURCE_CARD_DUPLEX)
    && s->s.format <= SANE_FRAME_RGB
    && s->duplex_interlace != DUPLEX_INTERLACE_NONE
  ){

    /* buffer both sides */
    if(!s->s.eof[SIDE_FRONT] || !s->s.eof[SIDE_BACK]){
      ret = read_from_scanner_duplex(s, 0);
      if(ret){
        DBG(5,"sane_read: front returning %d\n",ret);
        goto errors;
      }
      /*read last block, update counter*/
      if(s->s.eof[SIDE_FRONT] && s->s.eof[SIDE_BACK]){
        s->prev_page++;
        DBG(15,"sane_read: duplex counter %d\n",s->prev_page);
      }
    }
  }

  /* simplex or non-alternating duplex */
  else{
    if(!s->s.eof[s->side]){
      ret = read_from_scanner(s, s->side, 0);
      if(ret){
        DBG(5,"sane_read: side %d returning %d\n",s->side,ret);
        goto errors;
      }
      /*read last block, update counter*/
      if(s->s.eof[s->side]){
        s->prev_page++;
        DBG(15,"sane_read: side %d counter %d\n",s->side,s->prev_page);
      }
    }
  }

  /* copy a block from buffer to frontend */
  ret = read_from_buffer(s,buf,max_len,len,s->side);
  if(ret)
    goto errors;

  ret = check_for_cancel(s);
  s->reading = 0;

  DBG (10, "sane_read: finish %d\n", ret);
  return ret;

  errors:
    DBG (10, "sane_read: error %d\n", ret);
    s->reading = 0;
    s->cancelled = 0;
    s->started = 0;
    return ret;
}

static SANE_Status
read_from_scanner(struct scanner *s, int side, int exact)
{
  SANE_Status ret=SANE_STATUS_GOOD;

  unsigned char cmd[READ_len];
  size_t cmdLen = READ_len;

  unsigned char * in;
  size_t inLen = 0;

  size_t bytes = s->buffer_size;
  size_t remain = s->s.bytes_tot[side] - s->s.bytes_sent[side];

  DBG (10, "read_from_scanner: start\n");

  /* all requests must end on line boundary */
  bytes -= (bytes % s->s.Bpl);

  /* some larger scanners require even bytes per block */
  if(bytes % 2){
    bytes -= s->s.Bpl;
  }

  /* usually (image) we want to read too much data, and get RS */
  /* sometimes (calib) we want to do an exact read */
  if(exact && bytes > remain){
    bytes = remain;
  }

  DBG(15, "read_from_scanner: si:%d to:%d rx:%d re:%lu bu:%d pa:%lu ex:%d\n",
      side, s->s.bytes_tot[side], s->s.bytes_sent[side],
      (unsigned long)remain, s->buffer_size, (unsigned long)bytes, exact);

  inLen = bytes;
  in = malloc(inLen);
  if(!in){
    DBG(5, "read_from_scanner: not enough mem for buffer: %d\n",(int)inLen);
    return SANE_STATUS_NO_MEM;
  }

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, READ_code);
  set_R_datatype_code (cmd, SR_datatype_image);

  set_R_xfer_length (cmd, inLen);

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    in, &inLen
  );

  if (ret == SANE_STATUS_GOOD) {
    DBG(15, "read_from_scanner: got GOOD, returning GOOD %lu\n", (unsigned long)inLen);
  }
  else if (ret == SANE_STATUS_EOF) {
    DBG(15, "read_from_scanner: got EOF, finishing %lu\n", (unsigned long)inLen);
  }
  else if (ret == SANE_STATUS_DEVICE_BUSY) {
    DBG(5, "read_from_scanner: got BUSY, returning GOOD\n");
    inLen = 0;
    ret = SANE_STATUS_GOOD;
  }
  else {
    DBG(5, "read_from_scanner: error reading data block status = %d\n",ret);
    inLen = 0;
  }

  /* this is jpeg data, we need to fix the missing image size */
  if(s->s.format == SANE_FRAME_JPEG){

    /* look for the SOF header near the beginning */
    if(s->jpeg_stage == JPEG_STAGE_NONE || s->jpeg_ff_offset < 0x0d){

      size_t i;

      for(i=0;i<inLen;i++){

        /* about to change stage */
        if(s->jpeg_stage == JPEG_STAGE_NONE && in[i] == 0xff){
          s->jpeg_ff_offset=0;
          continue;
        }

        s->jpeg_ff_offset++;

        /* last byte was an ff, this byte is SOF */
        if(s->jpeg_ff_offset == 1 && in[i] == 0xc0){
          s->jpeg_stage = JPEG_STAGE_SOF;
          continue;
        }

        if(s->jpeg_stage == JPEG_STAGE_SOF){

          /* lines in start of frame, overwrite it */
          if(s->jpeg_ff_offset == 5){
            in[i] = (s->s.height >> 8) & 0xff;
            continue;
          }
          if(s->jpeg_ff_offset == 6){
            in[i] = s->s.height & 0xff;
            continue;
          }

          /* width in start of frame, overwrite it */
          if(s->jpeg_ff_offset == 7){
            in[i] = (s->s.width >> 8) & 0xff;
            continue;
          }
          if(s->jpeg_ff_offset == 8){
            in[i] = s->s.width & 0xff;
            continue;
          }
        }
      }
    }
  }

  /*scanner may have sent more data than we asked for, chop it*/
  if(inLen > remain){
    inLen = remain;
  }

  /* we've got some data, descramble and store it */
  if(inLen){
    copy_simplex(s,in,inLen,side);
  }

  free(in);

  /* we've read all data, but not eof. clear and pretend */
  if(exact && inLen == remain){
    DBG (10, "read_from_scanner: exact read, clearing\n");
    ret = object_position (s,SANE_FALSE);
    if(ret){
      return ret;
    }
    ret = SANE_STATUS_EOF;
  }

  if(ret == SANE_STATUS_EOF){

    /* this is jpeg data, we need to change the total size */
    if(s->s.format == SANE_FRAME_JPEG){
      s->s.bytes_tot[side] = s->s.bytes_sent[side];
      s->i.bytes_tot[side] = s->i.bytes_sent[side];
      s->u.bytes_tot[side] = s->i.bytes_sent[side];
    }

    /* this is non-jpeg data, fill remainder, change rx'd size */
    else{
      fill_image(s,side);
    }

    s->i.eof[side] = 1;
    s->s.eof[side] = 1;
    ret = SANE_STATUS_GOOD;
  }

  DBG(15, "read_from_scanner: sto:%d srx:%d sef:%d uto:%d urx:%d uef:%d\n",
    s->s.bytes_tot[side], s->s.bytes_sent[side], s->s.eof[side],
    s->u.bytes_tot[side], s->u.bytes_sent[side], s->u.eof[side]);

  DBG (10, "read_from_scanner: finish\n");

  return ret;
}

/* cheaper scanners interlace duplex scans on a byte basis
 * this code requests double width lines from scanner */
static SANE_Status
read_from_scanner_duplex(struct scanner *s,int exact)
{
  SANE_Status ret=SANE_STATUS_GOOD;

  unsigned char cmd[READ_len];
  size_t cmdLen = READ_len;

  unsigned char * in;
  size_t inLen = 0;

  size_t bytes = s->buffer_size;
  size_t remain = s->s.bytes_tot[SIDE_FRONT] + s->s.bytes_tot[SIDE_BACK]
    - s->s.bytes_sent[SIDE_FRONT] - s->s.bytes_sent[SIDE_BACK];

  DBG (10, "read_from_scanner_duplex: start\n");

  /* all requests must end on WIDE line boundary */
  bytes -= (bytes % (s->s.Bpl*2));

  /* usually (image) we want to read too much data, and get RS */
  /* sometimes (calib) we want to do an exact read */
  if(exact && bytes > remain){
    bytes = remain;
  }

  DBG(15, "read_from_scanner_duplex: re:%lu bu:%d pa:%lu ex:%d\n",
      (unsigned long)remain, s->buffer_size, (unsigned long)bytes, exact);

  inLen = bytes;
  in = malloc(inLen);
  if(!in){
    DBG(5, "read_from_scanner_duplex: not enough mem for buffer: %d\n",
      (int)inLen);
    return SANE_STATUS_NO_MEM;
  }

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, READ_code);
  set_R_datatype_code (cmd, SR_datatype_image);

  set_R_xfer_length (cmd, inLen);

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    in, &inLen
  );

  if (ret == SANE_STATUS_GOOD) {
    DBG(15, "read_from_scanner_duplex: got GOOD, returning GOOD %lu\n", (unsigned long)inLen);
  }
  else if (ret == SANE_STATUS_EOF) {
    DBG(15, "read_from_scanner_duplex: got EOF, finishing %lu\n", (unsigned long)inLen);
  }
  else if (ret == SANE_STATUS_DEVICE_BUSY) {
    DBG(5, "read_from_scanner_duplex: got BUSY, returning GOOD\n");
    inLen = 0;
    ret = SANE_STATUS_GOOD;
  }
  else {
    DBG(5, "read_from_scanner_duplex: error reading data block status = %d\n",
      ret);
    inLen = 0;
  }

  /*scanner may have sent more data than we asked for, chop it*/
  if(inLen > remain){
    inLen = remain;
  }

  /* we've got some data, descramble and store it */
  if(inLen){
    copy_duplex(s,in,inLen);
  }

  free(in);

  /* we've read all data, but not eof. clear and pretend */
  if(exact && inLen == remain){
    DBG (10, "read_from_scanner_duplex: exact read, clearing\n");
    ret = object_position (s,SANE_FALSE);
    if(ret){
      return ret;
    }
    ret = SANE_STATUS_EOF;
  }

  if(ret == SANE_STATUS_EOF){

    /* this is jpeg data, we need to change the total size */
    if(s->s.format == SANE_FRAME_JPEG){
      s->s.bytes_tot[SIDE_FRONT] = s->s.bytes_sent[SIDE_FRONT];
      s->s.bytes_tot[SIDE_BACK] = s->s.bytes_sent[SIDE_BACK];
      s->i.bytes_tot[SIDE_FRONT] = s->i.bytes_sent[SIDE_FRONT];
      s->i.bytes_tot[SIDE_BACK] = s->i.bytes_sent[SIDE_BACK];
      s->u.bytes_tot[SIDE_FRONT] = s->i.bytes_sent[SIDE_FRONT];
      s->u.bytes_tot[SIDE_BACK] = s->i.bytes_sent[SIDE_BACK];
    }

    /* this is non-jpeg data, fill remainder, change rx'd size */
    else{
      fill_image(s,SIDE_FRONT);
      fill_image(s,SIDE_BACK);
    }

    s->i.eof[SIDE_FRONT] = 1;
    s->i.eof[SIDE_BACK] = 1;
    s->s.eof[SIDE_FRONT] = 1;
    s->s.eof[SIDE_BACK] = 1;
    ret = SANE_STATUS_GOOD;
  }

  DBG (10, "read_from_scanner_duplex: finish\n");

  return ret;
}

/* these functions copy image data from input buffer to scanner struct
 * descrambling it, and putting it in the right side buffer */
/* NOTE: they assume buffer is scanline aligned */
static SANE_Status
copy_simplex(struct scanner *s, unsigned char * buf, int len, int side)
{
  SANE_Status ret=SANE_STATUS_GOOD;
  int i, j;
  int bwidth = s->s.Bpl;
  int pwidth = s->s.width;
  int t = bwidth/3;
  int f = bwidth/4;
  int tw = bwidth/12;

  unsigned char * line = NULL;
  int line_next = 0;
  int inter = get_color_inter(s,side,s->s.dpi_x);

  /* jpeg data should not pass thru this function, so copy and bail out */
  if(s->s.format > SANE_FRAME_RGB){
    DBG (15, "copy_simplex: jpeg bulk copy\n");
    memcpy(s->buffers[side]+s->i.bytes_sent[side], buf, len);
    s->i.bytes_sent[side] += len;
    s->s.bytes_sent[side] += len;
    return ret;
  }

  DBG (15, "copy_simplex: per-line copy\n");

  line = malloc(bwidth);
  if(!line) return SANE_STATUS_NO_MEM;

  /* ingest each line */
  for(i=0; i<len; i+=bwidth){

    int lineNum = s->s.bytes_sent[side] / bwidth;

    /*increment number of bytes rx'd from scanner*/
    s->s.bytes_sent[side] += bwidth;

    /*have some padding from scanner to drop*/
    if ( lineNum < s->i.skip_lines[side]
      || lineNum - s->i.skip_lines[side] >= s->i.height
    ){
      continue;
    }

    line_next = 0;

    if(s->s.format == SANE_FRAME_GRAY){

      switch (s->gray_interlace[side]) {

        /* one line has the following format: ggg...GGG
         * where the 'capital' letters are the beginning of the line */
        case GRAY_INTERLACE_gG:
          DBG (17, "copy_simplex: gray, gG\n");
          for (j=bwidth-1; j>=0; j--){
            line[line_next++] = buf[i+j];
          }
          break;

        case GRAY_INTERLACE_2510:
          DBG (17, "copy_simplex: gray, 2510\n");

          /* first read head (third byte of every three) */
          for(j=bwidth-1;j>=0;j-=3){
            line[line_next++] = buf[i+j];
          }
          /* second read head (first byte of every three) */
          for(j=bwidth*3/4-3;j>=0;j-=3){
            line[line_next++] = buf[i+j];
          }
          /* third read head (second byte of every three) */
          for(j=bwidth-2;j>=0;j-=3){
            line[line_next++] = buf[i+j];
          }
          /* padding */
          for(j=0;j<tw;j++){
            line[line_next++] = 0;
          }
          break;

        case GRAY_INTERLACE_C120:
          DBG (17, "copy_simplex: gray, C120\n");

          /* first read head (third byte of every three) */
          for(j=bwidth-1;j>=0;j-=3){
            line[line_next++] = buf[i+j];
          }
          /* second read head (first byte of every three) */
          for(j=bwidth-3;j>=0;j-=3){
            line[line_next++] = buf[i+j];
          }
          /* third read head (second byte of every three) */
          for(j=bwidth-2;j>=0;j-=3){
            line[line_next++] = buf[i+j];
          }
          break;
      }
    }

    else if (s->s.format == SANE_FRAME_RGB){

      switch (inter) {

        /* scanner returns color data as bgrbgr... */
        case COLOR_INTERLACE_BGR:
          DBG (17, "copy_simplex: color, BGR\n");
          for (j=0; j<pwidth; j++){
            line[line_next++] = buf[i+j*3+2];
            line[line_next++] = buf[i+j*3+1];
            line[line_next++] = buf[i+j*3];
          }
          break;

        /* scanner returns color data as gbrgbr... */
        case COLOR_INTERLACE_GBR:
          DBG (17, "copy_simplex: color, GBR\n");
          for (j=0; j<pwidth; j++){
            line[line_next++] = buf[i+j*3+2];
            line[line_next++] = buf[i+j*3];
            line[line_next++] = buf[i+j*3+1];
          }
          break;

        /* scanner returns color data as brgbrg... */
        case COLOR_INTERLACE_BRG:
          DBG (17, "copy_simplex: color, BRG\n");
          for (j=0; j<pwidth; j++){
            line[line_next++] = buf[i+j*3+1];
            line[line_next++] = buf[i+j*3+2];
            line[line_next++] = buf[i+j*3];
          }
          break;

        /* one line has the following format: RRR...rrrGGG...gggBBB...bbb */
        case COLOR_INTERLACE_RRGGBB:
          DBG (17, "copy_simplex: color, RRGGBB\n");
          for (j=0; j<pwidth; j++){
            line[line_next++] = buf[i+j];
            line[line_next++] = buf[i+pwidth+j];
            line[line_next++] = buf[i+2*pwidth+j];
          }
          break;

        /* one line has the following format: rrr...RRRggg...GGGbbb...BBB
         * where the 'capital' letters are the beginning of the line */
        case COLOR_INTERLACE_rRgGbB:
          DBG (17, "copy_simplex: color, rRgGbB\n");
          for (j=pwidth-1; j>=0; j--){
            line[line_next++] = buf[i+j];
            line[line_next++] = buf[i+pwidth+j];
            line[line_next++] = buf[i+2*pwidth+j];
          }
          break;

        case COLOR_INTERLACE_2510:
          DBG (17, "copy_simplex: color, 2510\n");

          /* first read head (third byte of every three) */
          for(j=t-1;j>=0;j-=3){
            line[line_next++] = buf[i+j];
            line[line_next++] = buf[i+t+j];
            line[line_next++] = buf[i+2*t+j];
          }
          /* second read head (first byte of every three) */
          for(j=f-3;j>=0;j-=3){
            line[line_next++] = buf[i+j];
            line[line_next++] = buf[i+t+j];
            line[line_next++] = buf[i+2*t+j];
          }
          /* third read head (second byte of every three) */
          for(j=t-2;j>=0;j-=3){
            line[line_next++] = buf[i+j];
            line[line_next++] = buf[i+t+j];
            line[line_next++] = buf[i+2*t+j];
          }
          /* padding */
          for(j=0;j<tw;j++){
            line[line_next++] = 0;
          }
          break;

        case COLOR_INTERLACE_C120:
          DBG (17, "copy_simplex: color, C120\n");

          /* first read head (third byte of every three) */
          for(j=t-1;j>=0;j-=3){
            line[line_next++] = buf[i+j];
            line[line_next++] = buf[i+t+j];
            line[line_next++] = buf[i+2*t+j];
          }
          /* second read head (first byte of every three) */
          for(j=t-3;j>=0;j-=3){
            line[line_next++] = buf[i+j];
            line[line_next++] = buf[i+t+j];
            line[line_next++] = buf[i+2*t+j];
          }
          /* third read head (second byte of every three) */
          for(j=t-2;j>=0;j-=3){
            line[line_next++] = buf[i+j];
            line[line_next++] = buf[i+t+j];
            line[line_next++] = buf[i+2*t+j];
          }
          break;
      }
    }

    /* nothing sent above? just copy one line of the block */
    /* used by uninterlaced gray/color */
    if(!line_next){
      DBG (17, "copy_simplex: default\n");
      memcpy(line+line_next,buf+i,bwidth);
      line_next = bwidth;
    }

    /* invert image if scanner needs it for this mode */
    if(s->reverse_by_mode[s->s.mode]){
      for(j=0; j<line_next; j++){
        line[j] ^= 0xff;
      }
    }

    /* apply calibration if we have it */
    if(s->f_offset[side]){
      DBG (17, "copy_simplex: apply offset\n");
      for(j=0; j<s->s.valid_Bpl; j++){
        int curr = line[j] - s->f_offset[side][j];
        if(curr < 0) curr = 0;
        line[j] = curr;
      }
    }

    if(s->f_gain[side]){
      DBG (17, "copy_simplex: apply gain\n");
      for(j=0; j<s->s.valid_Bpl; j++){
        int curr = line[j] * 240/s->f_gain[side][j];
        if(curr > 255) curr = 255;
        line[j] = curr;
      }
    }

    /* apply brightness and contrast if hardware cannot do it */
    if(s->sw_lut && (s->s.mode == MODE_COLOR || s->s.mode == MODE_GRAYSCALE)){
      DBG (17, "copy_simplex: apply brightness/contrast\n");
      for(j=0; j<s->s.valid_Bpl; j++){
        line[j] = s->lut[line[j]];
      }
    }

    /*copy the line into the buffer*/
    ret = copy_line(s,line,side);
    if(ret){
      break;
    }
  }

  free(line);

  DBG (10, "copy_simplex: finished\n");

  return ret;
}

/* split the data between two buffers, hand them to copy_simplex()
 * assumes that the buffer aligns to a double-wide line boundary */
static SANE_Status
copy_duplex(struct scanner *s, unsigned char * buf, int len)
{
  SANE_Status ret=SANE_STATUS_GOOD;
  int i,j;
  int pwidth = s->s.width;
  int bwidth = s->s.Bpl;
  int dbwidth = 2*bwidth;
  unsigned char * front;
  unsigned char * back;
  int flen=0, blen=0;

  DBG (10, "copy_duplex: start\n");

  /*split the input into two simplex output buffers*/
  front = calloc(1,len/2);
  if(!front){
    DBG (5, "copy_duplex: no front mem\n");
    return SANE_STATUS_NO_MEM;
  }
  back = calloc(1,len/2);
  if(!back){
    DBG (5, "copy_duplex: no back mem\n");
    free(front);
    return SANE_STATUS_NO_MEM;
  }

  if(s->duplex_interlace == DUPLEX_INTERLACE_2510){

    DBG (10, "copy_duplex: 2510\n");

    for(i=0; i<len; i+=dbwidth){

      for(j=0;j<dbwidth;j+=6){

        /* we are actually only partially descrambling,
         * copy_simplex() does the rest */

        /* front */
        /* 2nd head: 2nd byte -> 1st byte */
        /* 3rd head: 4th byte -> 2nd byte */
        /* 1st head: 5th byte -> 3rd byte */
        front[flen++] = buf[i+j+2];
        front[flen++] = buf[i+j+4];
        front[flen++] = buf[i+j+5];

        /* back */
        /* 2nd head: 3rd byte -> 1st byte */
        /* 3rd head: 0th byte -> 2nd byte */
        /* 1st head: 1st byte -> 3rd byte */
        back[blen++] = buf[i+j+3];
        back[blen++] = buf[i+j];
        back[blen++] = buf[i+j+1];
      }
    }
  }

  /* line is in 6 sections, front red, back red, front green, etc. */
  else if(s->duplex_interlace == DUPLEX_INTERLACE_PER_CHANNEL){

    DBG (10, "copy_duplex: per channel\n");

    for(i=0; i<len; i+=dbwidth){
      for(j=0;j<3;j++){
        memcpy(front+flen,buf+i+j*pwidth*2,pwidth);
        flen+=pwidth;
        memcpy(back+blen,buf+i+j*pwidth*2+pwidth,pwidth);
        blen+=pwidth;
      }
    }
  }

  /* full line of front, then full line of back */
  else if(s->duplex_interlace == DUPLEX_INTERLACE_FfBb || s->duplex_interlace == DUPLEX_INTERLACE_fFBb){
    for(i=0; i<len; i+=dbwidth){
      if(s->duplex_interlace == DUPLEX_INTERLACE_FfBb){
        memcpy(front+flen,buf+i,bwidth);
      }else{
        rmemcpy(front+flen,buf+i,bwidth,3); // only 24bit color is supported
      }
      flen+=bwidth;
      memcpy(back+blen,buf+i+bwidth,bwidth);
      blen+=bwidth;
    }
  }

  /*just alternating bytes, FBfb*/
  else {
    for(i=0; i<len; i+=2){
      front[flen++] = buf[i];
      back[blen++] = buf[i+1];
    }
  }

  copy_simplex(s,front,flen,SIDE_FRONT);
  copy_simplex(s,back,blen,SIDE_BACK);

  free(front);
  free(back);

  DBG (10, "copy_duplex: finished\n");

  return ret;
}

/*
 * Reverse memcpy designed to mirror a line of data.
 * Use stride size to account for the number of bytes per pixel
 */
static void rmemcpy(void* dest, const void* src, size_t count, size_t stride) {
  char* dstptr = (char*)dest;
  char* srcptr = (char*)src;
  srcptr += count;
  while (count) {
    srcptr -= stride;
    memcpy(dstptr, srcptr, stride);
    dstptr += stride;
    count -= stride;
  }
}

/* downsample a single line from scanner's size to user's size */
/* and copy into final buffer */
static SANE_Status
copy_line(struct scanner *s, unsigned char * buff, int side)
{
  SANE_Status ret=SANE_STATUS_GOOD;
  int spwidth = s->s.width;
  int sbwidth = s->s.Bpl;
  int ibwidth = s->i.Bpl;
  unsigned char * line;
  int offset = 0;
  int i, j;

  DBG (20, "copy_line: start\n");

  /* the 'standard' case: non-stupid scan */
  if(s->s.width == s->i.width
    && s->s.dpi_x == s->i.dpi_x
    && s->s.mode == s->i.mode
  ){

    memcpy(s->buffers[side]+s->i.bytes_sent[side], buff, sbwidth);
    s->i.bytes_sent[side] += sbwidth;

    DBG (20, "copy_line: finished smart\n");
    return ret;
  }

  /* the 'corner' case: stupid scan */

  /*setup 24 bit color single line buffer*/
  line = malloc(spwidth*3);
  if(!line) return SANE_STATUS_NO_MEM;

  /*load single line color buffer*/
  switch (s->s.mode) {

    case MODE_COLOR:
      if(must_downsample(s) && s->dropout_color[side]){
        switch(s->dropout_color[side]){
          case COLOR_RED:
            for(i=0;i<spwidth;i++)
              line[i*3] = line[i*3+1] = line[i*3+2] = buff[i*3];
            break;
          case COLOR_GREEN:
            for(i=0;i<spwidth;i++)
              line[i*3] = line[i*3+1] = line[i*3+2] = buff[i*3+1];
            break;
          case COLOR_BLUE:
            for(i=0;i<spwidth;i++)
              line[i*3] = line[i*3+1] = line[i*3+2] = buff[i*3+2];
            break;
          case COLOR_EN_RED:
            for(i=0;i<spwidth;i++){
              line[i*3] = (buff[i*3+1] + buff[i*3+2])/2;
              line[i*3+1] = buff[i*3+1];
              line[i*3+2] = buff[i*3+2];
            }
            break;
          case COLOR_EN_GREEN:
            for(i=0;i<spwidth;i++){
              line[i*3] = buff[i*3];
              line[i*3+1] = (buff[i*3] + buff[i*3+2])/2;
              line[i*3+2] = buff[i*3+2];
            }
            break;
          case COLOR_EN_BLUE:
            for(i=0;i<spwidth;i++){
              line[i*3] = buff[i*3];
              line[i*3+1] = buff[i*3+1];
              line[i*3+2] = (buff[i*3] + buff[i*3+1])/2;
            }
            break;
        }
      }
      else{
        memcpy(line, buff, sbwidth);
      }
      break;

    case MODE_GRAYSCALE:
      for(i=0;i<spwidth;i++){
        line[i*3] = line[i*3+1] = line[i*3+2] = buff[i];
      }
      break;

    default:
      for(i=0;i<sbwidth;i++){
        unsigned char curr = buff[i];

        line[i*24+0] = line[i*24+1] = line[i*24+2] = ((curr >> 7) & 1) ?0:255;
        line[i*24+3] = line[i*24+4] = line[i*24+5] = ((curr >> 6) & 1) ?0:255;
        line[i*24+6] = line[i*24+7] = line[i*24+8] = ((curr >> 5) & 1) ?0:255;
        line[i*24+9] = line[i*24+10] = line[i*24+11] = ((curr >> 4) & 1) ?0:255;
        line[i*24+12] = line[i*24+13] = line[i*24+14] =((curr >> 3) & 1) ?0:255;
        line[i*24+15] = line[i*24+16] = line[i*24+17] =((curr >> 2) & 1) ?0:255;
        line[i*24+18] = line[i*24+19] = line[i*24+20] =((curr >> 1) & 1) ?0:255;
        line[i*24+21] = line[i*24+22] = line[i*24+23] =((curr >> 0) & 1) ?0:255;
      }
      break;
  }

  /* scan is higher res than user wanted, scale it */
  /*FIXME: interpolate instead */
  if(s->i.dpi_x != s->s.dpi_x){
    for(i=0;i<spwidth;i++){
      int source = i * s->s.dpi_x/s->i.dpi_x * 3;

      if(source+2 >= spwidth*3)
        break;

      line[i*3] = line[source];
      line[i*3+1] = line[source+1];
      line[i*3+2] = line[source+2];
    }
  }

  /* scan is wider than user wanted, skip some pixels on left side */
  if(s->i.width != s->s.width){
    offset = ((s->valid_x-s->i.page_x) / 2 + s->i.tl_x) * s->i.dpi_x/1200;
  }

  /* change mode, store line in buffer */
  switch (s->i.mode) {

    case MODE_COLOR:
      memcpy(s->buffers[side]+s->i.bytes_sent[side], line+(offset*3), ibwidth);
      s->i.bytes_sent[side] += ibwidth;
      break;

    case MODE_GRAYSCALE:
      for(i=0;i<ibwidth;i++){
        int source = (offset+i)*3;
        s->buffers[side][s->i.bytes_sent[side]++]
          = ((int)line[source] + line[source+1] + line[source+2])/3;
      }
      break;

    default:
      /*loop over output bytes*/
      for(i=0;i<ibwidth;i++){

        unsigned char curr = 0;
        int thresh = s->threshold*3;

        /*loop over output bits*/
        for(j=0;j<8;j++){
          int source = offset*3 + i*24 + j*3;
          if( (line[source] + line[source+1] + line[source+2]) < thresh ){
            curr |= 1 << (7-j);
          }
        }

        s->buffers[side][s->i.bytes_sent[side]++] = curr;
      }
      break;
  }

  free(line);

  DBG (20, "copy_line: finish stupid\n");

  return ret;
}

static SANE_Status
read_from_buffer(struct scanner *s, SANE_Byte * buf, SANE_Int max_len,
  SANE_Int * len, int side)
{
  SANE_Status ret=SANE_STATUS_GOOD;
  int bytes = max_len;
  int remain = s->i.bytes_sent[side] - s->u.bytes_sent[side];

  DBG (10, "read_from_buffer: start\n");

  /* figure out the max amount to transfer */
  if(bytes > remain)
    bytes = remain;

  *len = bytes;

  /*FIXME this needs to timeout eventually */
  if(!bytes){
    DBG(5,"read_from_buffer: nothing to do\n");
    return SANE_STATUS_GOOD;
  }

  DBG(15, "read_from_buffer: si:%d to:%d tx:%d bu:%d pa:%d\n", side,
    s->i.bytes_tot[side], s->u.bytes_sent[side], max_len, bytes);

  /* copy to caller */
  memcpy(buf,s->buffers[side]+s->u.bytes_sent[side],bytes);
  s->u.bytes_sent[side] += bytes;

  DBG (10, "read_from_buffer: finished\n");

  return ret;
}

/* fill remainder of buffer with background if scanner stops early */
static SANE_Status
fill_image(struct scanner *s,int side)
{
  SANE_Status ret=SANE_STATUS_GOOD;

  unsigned char bg_color = calc_bg_color(s);
  int fill_bytes = s->i.bytes_tot[side]-s->i.bytes_sent[side];

  if(!fill_bytes){
    return ret;
  }

  DBG (15, "fill_image: side:%d bytes:%d bg_color:%02x\n", side, fill_bytes, bg_color);

  /* fill the rest with bg_color */
  memset(s->buffers[side]+s->i.bytes_sent[side],bg_color,fill_bytes);

  /* pretend we got all the data from scanner */
  s->i.bytes_sent[side] = s->i.bytes_tot[side];
  s->s.bytes_sent[side] = s->s.bytes_tot[side];

  return ret;
}

/* return the bg color based on scanner settings */
static unsigned char
calc_bg_color(struct scanner *s)
{
  unsigned char bg_color = s->lut[s->bg_color];

  if(s->u.mode <= MODE_HALFTONE)
    bg_color = (bg_color<s->threshold)?0xff:0x00;

  return bg_color;
}

/*
 * @@ Section 5 - calibration functions
 */

#if 0
static SANE_Status
foo_AFE(struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char cmd[] = {
    0x3b, 0x00, 0x10, 0x08, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00
  };
  size_t cmdLen = 12;

  unsigned char in[4];
  size_t inLen = 4;

  DBG (10, "foo_AFE: start\n");

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    in, &inLen
  );
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (10, "foo_AFE: finish\n");

  return ret;
}
#endif

/*
 * makes several scans, adjusts coarse calibration
 */
static SANE_Status
calibrate_AFE (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int i, j, k;
  int min, max;
  int lines = 8;

  /*buffer these for later*/
  int old_tl_y = s->u.tl_y;
  int old_br_y = s->u.br_y;
  int old_mode = s->u.mode;
  int old_source = s->u.source;

  DBG (10, "calibrate_AFE: start\n");

  if(!s->need_ccal){
    DBG (10, "calibrate_AFE: not required\n");
    return ret;
  }

  /* always cal with a short scan in duplex color */
  s->u.tl_y = 0;
  s->u.br_y = lines * 1200 / s->u.dpi_y;
  s->u.mode = MODE_COLOR;
  s->u.source = SOURCE_ADF_DUPLEX;

  /* load our own private copy of scan params */
  ret = update_params(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot update_params\n");
    goto cleanup;
  }

  if(s->c_res == s->s.dpi_x && s->c_mode == s->s.mode){
    DBG (10, "calibrate_AFE: already done\n");
    goto cleanup;
  }

  /* clean scan params for new scan */
  ret = clean_params(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot clean_params\n");
    goto cleanup;
  }

  /* make buffers to hold the images */
  ret = image_buffers(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot load buffers\n");
    goto cleanup;
  }

  /*blast the existing fine cal data so reading code won't apply it*/
  ret = offset_buffers(s,0);
  ret = gain_buffers(s,0);

  /* need to tell it we want duplex */
  ret = ssm_buffer(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot ssm buffer\n");
    goto cleanup;
  }

  /* set window command */
  ret = set_window(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot set window\n");
    goto cleanup;
  }

  /* first pass (black offset), lamp off, no offset/gain/exposure */
  DBG (15, "calibrate_AFE: offset\n");

  /* blast all the existing coarse cal data */
  for(i=0;i<2;i++){
    s->c_gain[i]   = 1;
    s->c_offset[i] = 1;
    for(j=0;j<3;j++){
      s->c_exposure[i][j] = 0;
    }
  }

  ret = write_AFE(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot write afe\n");
    goto cleanup;
  }

  ret = calibration_scan(s,0xff);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot make offset cal scan\n");
    goto cleanup;
  }

  for(i=0;i<2;i++){
    min = 255;
    for(j=0; j<s->s.valid_Bpl; j++){
      if(s->buffers[i][j] < min)
        min = s->buffers[i][j];
    }
    s->c_offset[i] = min*3-2;
    DBG (15, "calibrate_AFE: offset %d %d %02x\n", i, min, s->c_offset[i]);
  }

  /*handle second pass (per channel exposure), lamp on, overexposed*/
  DBG (15, "calibrate_AFE: exposure\n");
  for(i=0;i<2;i++){
    for(j=0; j<3; j++){
      s->c_exposure[i][j] = 0x320;
    }
  }

  ret = write_AFE(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot write afe\n");
    goto cleanup;
  }

  ret = calibration_scan(s,0xfe);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot make exposure cal scan\n");
    goto cleanup;
  }

  for(i=0;i<2;i++){ /*sides*/
    for(j=0;j<3;j++){ /*channels*/
      max = 0;
      for(k=j; k<s->s.valid_Bpl; k+=3){ /*bytes*/
        if(s->buffers[i][k] > max)
          max = s->buffers[i][k];
      }

      /*generally we reduce the exposure (smaller number) */
      if(old_mode == MODE_COLOR)
        s->c_exposure[i][j] = s->c_exposure[i][j] * 102/max;
      else
        s->c_exposure[i][j] = s->c_exposure[i][j] * 64/max;

      DBG (15, "calibrate_AFE: exp %d %d %d %02x\n", i, j, max,
        s->c_exposure[i][j]);
    }
  }

  /*handle third pass (gain), lamp on with current offset/exposure */
  DBG (15, "calibrate_AFE: gain\n");

  ret = write_AFE(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot write afe\n");
    goto cleanup;
  }

  ret = calibration_scan(s,0xfe);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot make gain cal scan\n");
    goto cleanup;
  }

  for(i=0;i<2;i++){
    max = 0;
    for(j=0; j<s->s.valid_Bpl; j++){
      if(s->buffers[i][j] > max)
        max = s->buffers[i][j];
    }

    if(old_mode == MODE_COLOR)
      s->c_gain[i] = (250-max)*4/5;
    else
      s->c_gain[i] = (125-max)*4/5;

    if(s->c_gain[i] < 1)
      s->c_gain[i] = 1;

    DBG (15, "calibrate_AFE: gain %d %d %02x\n", i, max, s->c_gain[i]);
  }

  /*handle fourth pass (offset again), lamp off*/
#if 0
  DBG (15, "calibrate_AFE: offset2\n");

  ret = write_AFE(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot write afe\n");
    goto cleanup;
  }

  ret = calibration_scan(s,0xff);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot make offset2 cal scan\n");
    goto cleanup;
  }

  for(i=0;i<2;i++){
    min = 255;
    for(j=0; j<s->s.valid_Bpl; j++){
      if(s->buffers[i][j] < min)
        min = s->buffers[i][j];
    }
    /*s->c_offset[i] += min*3-2;*/
    DBG (15, "calibrate_AFE: offset2 %d %d %02x\n", i, min, s->c_offset[i]);
  }
#endif

  /*send final afe params to scanner*/
  ret = write_AFE(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_AFE: ERROR: cannot write afe\n");
    goto cleanup;
  }

  /* log current cal type */
  s->c_res = s->s.dpi_x;
  s->c_mode = s->s.mode;

  cleanup:

  /* recover user settings */
  s->u.tl_y = old_tl_y;
  s->u.br_y = old_br_y;
  s->u.mode = old_mode;
  s->u.source = old_source;

  DBG (10, "calibrate_AFE: finish %d\n",ret);

  return ret;
}

/*
 * fine calibration produces a per-cell offset and gain value,
 * which is then used to adjust the output from the scanner.
 * There is quite a bit of variation here, with different models
 * needing different types/amounts of help from the software.
 *
 * This function is a common entry point for all variations.
 */
static SANE_Status
calibrate_fine (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "calibrate_fine: start\n");

  if(s->fcal_src == FCAL_SRC_NONE || s->fcal_dest == FCAL_DEST_NONE){
    DBG (10, "calibrate_fine: not required\n");
    goto cleanup;
  }

  /* don't recalibrate if we've already done it with these params */
  if(s->f_res == s->s.dpi_x && s->f_mode == s->s.mode){
    DBG (10, "calibrate_fine: already done\n");
    goto cleanup;
  }

  /* get calibration data from scanner memory */
  if(s->fcal_src == FCAL_SRC_HW){
    ret = calibrate_fine_src_hw(s);
    if (ret != SANE_STATUS_GOOD)
      goto cleanup;
  }

  /* get calibration data by making scans */
  if(s->fcal_src == FCAL_SRC_SCAN){
    ret = calibrate_fine_src_scan(s);
    if (ret != SANE_STATUS_GOOD)
      goto cleanup;
  }

  /* send calibration data to scanner */
  if(s->fcal_dest == FCAL_DEST_HW){
    ret = calibrate_fine_dest_hw(s);
    if (ret != SANE_STATUS_GOOD)
      goto cleanup;
  }

  /* log current cal settings so we won't recalibrate on next scan with same params */
  s->f_res = s->s.dpi_x;
  s->f_mode = s->s.mode;

  cleanup:

  DBG (10, "calibrate_fine: finish %d\n",ret);

  return ret;
}


/* extracts fine calibration data from scanner memory */
static SANE_Status
calibrate_fine_src_hw (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int i, j, k;

  unsigned char cmd[READ_len];
  size_t cmdLen = READ_len;

  unsigned char * in = NULL;
  size_t inLen = 0, reqLen = 0;

  /*buffer these for later*/
  int old_tl_y = s->u.tl_y;
  int old_br_y = s->u.br_y;
  int old_source = s->u.source;

  DBG (10, "calibrate_fine_src_hw: start\n");

  /* pretend we are doing a 1 line scan in duplex */
  s->u.tl_y = 0;
  s->u.br_y = 1200 / s->u.dpi_y;
  s->u.source = SOURCE_ADF_DUPLEX;

  /* load our own private copy of scan params */
  ret = update_params(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_hw: ERROR: cannot update_params\n");
    goto cleanup;
  }

  /* clean scan params for new scan */
  ret = clean_params(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_hw: ERROR: cannot clean_params\n");
    goto cleanup;
  }

  /*calibration buffers in scanner are single color channel, but duplex*/
  reqLen = s->s.width*2;

  in = malloc(reqLen);
  if (!in) {
    DBG (5, "calibrate_fine_src_hw: ERROR: cannot malloc in\n");
    ret = SANE_STATUS_NO_MEM;
    goto cleanup;
  }

  /*fine offset*/
  ret = offset_buffers(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_hw: ERROR: cannot load offset buffers\n");
    goto cleanup;
  }

  DBG (10, "calibrate_fine_src_hw: %d %x\n", s->s.dpi_x/10, s->s.dpi_x/10);

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, READ_code);
  set_R_datatype_code (cmd, SR_datatype_fineoffset);
  set_R_xfer_lid (cmd, s->s.dpi_x/10);
  set_R_xfer_length (cmd, reqLen);

  inLen = reqLen;

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    NULL, 0,
    in, &inLen
  );
  if (ret != SANE_STATUS_GOOD)
    goto cleanup;

  for(i=0;i<2;i++){

    /*color mode, expand offset across all three channels? */
    if(s->s.format == SANE_FRAME_RGB){
      for(j=0; j<s->s.valid_width; j++){

        /*red*/
        s->f_offset[i][j*3] = in[j*2+i];
        if(s->f_offset[i][j*3] < 1)
          s->f_offset[i][j*3] = 1;

        /*green and blue, same as red*/
        s->f_offset[i][j*3+1] = s->f_offset[i][j*3+2] = s->f_offset[i][j*3];
      }
    }

    /*gray mode, copy*/
    else{
      for(j=0; j<s->s.valid_width; j++){

        s->f_offset[i][j] = in[j*2+i];
        if(s->f_offset[i][j] < 1)
          s->f_offset[i][j] = 1;
      }
    }
  }

  /*fine gain*/
  ret = gain_buffers(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_hw: ERROR: cannot load gain buffers\n");
    goto cleanup;
  }

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd, READ_code);
  set_R_datatype_code (cmd, SR_datatype_finegain);
  set_R_xfer_lid (cmd, s->s.dpi_x/10);
  set_R_xfer_length (cmd, reqLen);

  /*color gain split into three buffers, grab them and merge*/
  if(s->s.format == SANE_FRAME_RGB){

    int codes[] = {R_FINE_uid_red,R_FINE_uid_green,R_FINE_uid_blue};

    for(k=0;k<3;k++){

      set_R_xfer_uid (cmd, codes[k]);
      inLen = reqLen;

      ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        NULL, 0,
        in, &inLen
      );
      if (ret != SANE_STATUS_GOOD)
        goto cleanup;

      for(i=0;i<2;i++){
        for(j=0; j<s->s.valid_width; j++){

          s->f_gain[i][j*3+k] = in[j*2+i]*3/4;

          if(s->f_gain[i][j*3+k] < 1)
            s->f_gain[i][j*3+k] = 1;
        }
      }
    }
  }

  /*gray gain, copy*/
  else{

    set_R_xfer_uid (cmd, R_FINE_uid_gray);
    inLen = reqLen;

    ret = do_cmd (
      s, 1, 0,
      cmd, cmdLen,
      NULL, 0,
      in, &inLen
    );
    if (ret != SANE_STATUS_GOOD)
      goto cleanup;

    for(i=0;i<2;i++){
      for(j=0; j<s->s.valid_width; j++){

        s->f_gain[i][j] = in[j*2+i]*3/4;

        if(s->f_gain[i][j] < 1)
          s->f_gain[i][j] = 1;
      }
    }
  }

  cleanup:

  if(in){
    free(in);
  }

  /* recover user settings */
  s->u.tl_y = old_tl_y;
  s->u.br_y = old_br_y;
  s->u.source = old_source;

  DBG (10, "calibrate_fine_src_hw: finish %d\n",ret);

  return ret;
}

/*
 * makes several scans, generates fine calibration data
 */
static SANE_Status
calibrate_fine_src_scan (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int i, j, k;
  int min, max;
  int lines = 8;

  /*buffer these for later*/
  int old_tl_y = s->u.tl_y;
  int old_br_y = s->u.br_y;
  int old_source = s->u.source;

  DBG (10, "calibrate_fine_src_scan: start\n");

  /* always cal with a short scan in duplex */
  s->u.tl_y = 0;
  s->u.br_y = lines * 1200 / s->u.dpi_y;
  s->u.source = SOURCE_ADF_DUPLEX;

  /* load our own private copy of scan params */
  ret = update_params(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_scan: ERROR: cannot update_params\n");
    goto cleanup;
  }

  /* clean scan params for new scan */
  ret = clean_params(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibration_fine: ERROR: cannot clean_params\n");
    goto cleanup;
  }

  /* make buffers to hold the images */
  ret = image_buffers(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_scan: ERROR: cannot load buffers\n");
    goto cleanup;
  }

  /*blast the existing fine cal data so reading code won't apply it*/
  ret = offset_buffers(s,0);
  ret = gain_buffers(s,0);

  /* need to tell it we want duplex */
  ret = ssm_buffer(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_scan: ERROR: cannot ssm buffer\n");
    goto cleanup;
  }

  /* set window command */
  ret = set_window(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_scan: ERROR: cannot set window\n");
    goto cleanup;
  }

  /* first pass (fine offset), lamp off */
  DBG (15, "calibrate_fine_src_scan: offset\n");
  ret = calibration_scan(s,0xff);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_scan: ERROR: cannot make offset cal scan\n");
    goto cleanup;
  }

  ret = offset_buffers(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_scan: ERROR: cannot load offset buffers\n");
    goto cleanup;
  }

  for(i=0;i<2;i++){
    for(j=0; j<s->s.valid_Bpl; j++){
      min = 0;
      for(k=j;k<lines*s->s.Bpl;k+=s->s.Bpl){
        min += s->buffers[i][k];
      }
      s->f_offset[i][j] = min/lines;
    }
    hexdump(15, "off:", s->f_offset[i], s->s.valid_Bpl);
  }

  /* second pass (fine gain), lamp on */
  DBG (15, "calibrate_fine_src_scan: gain\n");
  ret = calibration_scan(s,0xfe);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_scan: ERROR: cannot make gain cal scan\n");
    goto cleanup;
  }

  ret = gain_buffers(s,1);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibrate_fine_src_scan: ERROR: cannot load gain buffers\n");
    goto cleanup;
  }

  for(i=0;i<2;i++){
    for(j=0; j<s->s.valid_Bpl; j++){
      max = 0;
      for(k=j;k<lines*s->s.Bpl;k+=s->s.Bpl){
        max += s->buffers[i][k];
      }
      s->f_gain[i][j] = max/lines;

      if(s->f_gain[i][j] < 1)
        s->f_gain[i][j] = 1;
    }
    hexdump(15, "gain:", s->f_gain[i], s->s.valid_Bpl);
  }

  cleanup:

  /* recover user settings */
  s->u.tl_y = old_tl_y;
  s->u.br_y = old_br_y;
  s->u.source = old_source;

  DBG (10, "calibrate_fine_src_scan: finish %d\n",ret);

  return ret;
}

/* write calibration data to scanner memory and delete from struct */
static SANE_Status
calibrate_fine_dest_hw (struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int i, j, k;

  unsigned char cmd[SEND_len];
  size_t cmdLen = SEND_len;

  unsigned char * out = NULL;
  size_t outLen = 0;

  DBG (10, "calibrate_fine_dest_hw: start\n");

  /* calibration buffers in scanner are single color channel, but 16 bit, plus 4 byte header */
  outLen = s->s.width*2 + 4;

  out = calloc(outLen,1);
  if (!out) {
    DBG (5, "calibrate_fine_dest_hw: ERROR: cannot calloc out\n");
    ret = SANE_STATUS_NO_MEM;
    goto cleanup;
  }

  // sides
  for(i=0;i<2;i++){

    // colors
    for(j=0;j<3;j++){

      int codes[] = {
        S_FCAL_id_f_red, S_FCAL_id_f_green, S_FCAL_id_f_blue,
        S_FCAL_id_b_red, S_FCAL_id_b_green, S_FCAL_id_b_blue};

      // offset
      memset(cmd,0,cmdLen);
      set_SCSI_opcode(cmd, SEND_code);
      set_S_xfer_datatype (cmd, SR_datatype_fineoffset);
      set_S_xfer_length (cmd, outLen);

      set_S_FCAL_datatype (out, codes[i*3+j]);

      for(k=0; k<s->s.valid_width; k++){
        out[4+k*2] = 0;

        // TODO: calculate this instead of hardcode
        out[4+k*2+1] = 140;
      }

      ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, 0
      );
      if (ret != SANE_STATUS_GOOD)
        goto cleanup;

      // gain
      set_S_FCAL_datatype (out, codes[i*3+j] | 0x40);

      for(k=0; k<s->s.valid_width; k++){
        out[4+k*2] = 0;

        // TODO: calculate this instead of hardcode
        out[4+k*2+1] = 40;
      }

      ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        out, outLen,
        NULL, 0
      );
      if (ret != SANE_STATUS_GOOD)
        goto cleanup;

    }
  }

  cleanup:

  /*blast the fine cal data we generated above, so reading code wont apply it*/
  offset_buffers(s,0);
  gain_buffers(s,0);

  if(out){
    free(out);
  }

  DBG (10, "calibrate_fine_dest_hw: finish %d\n",ret);

  return ret;
}

/*
 * does a simple scan, ingests entire duplex image into buffers
 */
static SANE_Status
calibration_scan (struct scanner *s, int scan)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "calibration_scan: start\n");

  /* clean scan params for new scan */
  ret = clean_params(s);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibration_scan: ERROR: cannot clean_params\n");
    return ret;
  }

  /* start scanning */
  ret = start_scan (s,scan);
  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "calibration_scan: ERROR: cannot start_scan\n");
    return ret;
  }

  while(!s->s.eof[SIDE_FRONT] && !s->s.eof[SIDE_BACK]){
    ret = read_from_scanner_duplex(s,1);
  }

  DBG (10, "calibration_scan: finished\n");

  return ret;
}

/*
 * sends AFE and exposure params
 */
static SANE_Status
write_AFE(struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char cmd[COR_CAL_len];
  size_t cmdLen = COR_CAL_len;

  /*use the longest payload for buffer*/
  unsigned char pay[CC3_pay_len];
  size_t payLen = CC3_pay_len;

  DBG (10, "write_AFE: start\n");

  /* newer scanners use a longer cc payload */
  if(s->ccal_version == 3){

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, COR_CAL_code);
    set_CC_version(cmd,CC3_pay_ver);
    set_CC_xferlen(cmd,payLen);

    memset(pay,0,payLen);

    set_CC3_gain_f_r(pay,s->c_gain[SIDE_FRONT]);
    set_CC3_gain_f_g(pay,s->c_gain[SIDE_FRONT]);
    set_CC3_gain_f_b(pay,s->c_gain[SIDE_FRONT]);

    set_CC3_off_f_r(pay,s->c_offset[SIDE_FRONT]);
    set_CC3_off_f_g(pay,s->c_offset[SIDE_FRONT]);
    set_CC3_off_f_b(pay,s->c_offset[SIDE_FRONT]);

    set_CC3_exp_f_r(pay,s->c_exposure[SIDE_FRONT][CHAN_RED]);
    set_CC3_exp_f_g(pay,s->c_exposure[SIDE_FRONT][CHAN_GREEN]);
    set_CC3_exp_f_b(pay,s->c_exposure[SIDE_FRONT][CHAN_BLUE]);

    set_CC3_gain_b_r(pay,s->c_gain[SIDE_BACK]);
    set_CC3_gain_b_g(pay,s->c_gain[SIDE_BACK]);
    set_CC3_gain_b_b(pay,s->c_gain[SIDE_BACK]);

    set_CC3_off_b_r(pay,s->c_offset[SIDE_BACK]);
    set_CC3_off_b_g(pay,s->c_offset[SIDE_BACK]);
    set_CC3_off_b_b(pay,s->c_offset[SIDE_BACK]);

    set_CC3_exp_b_r(pay,s->c_exposure[SIDE_BACK][CHAN_RED]);
    set_CC3_exp_b_g(pay,s->c_exposure[SIDE_BACK][CHAN_GREEN]);
    set_CC3_exp_b_b(pay,s->c_exposure[SIDE_BACK][CHAN_BLUE]);
  }

  else{
    payLen = CC_pay_len;

    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, COR_CAL_code);
    set_CC_version(cmd,CC_pay_ver);
    set_CC_xferlen(cmd,payLen);

    memset(pay,0,payLen);
    set_CC_f_gain(pay,s->c_gain[SIDE_FRONT]);
    set_CC_unk1(pay,1);
    set_CC_f_offset(pay,s->c_offset[SIDE_FRONT]);
    set_CC_unk2(pay,1);
    set_CC_exp_f_r1(pay,s->c_exposure[SIDE_FRONT][CHAN_RED]);
    set_CC_exp_f_g1(pay,s->c_exposure[SIDE_FRONT][CHAN_GREEN]);
    set_CC_exp_f_b1(pay,s->c_exposure[SIDE_FRONT][CHAN_BLUE]);
    set_CC_exp_f_r2(pay,s->c_exposure[SIDE_FRONT][CHAN_RED]);
    set_CC_exp_f_g2(pay,s->c_exposure[SIDE_FRONT][CHAN_GREEN]);
    set_CC_exp_f_b2(pay,s->c_exposure[SIDE_FRONT][CHAN_BLUE]);

    set_CC_b_gain(pay,s->c_gain[SIDE_BACK]);
    set_CC_b_offset(pay,s->c_offset[SIDE_BACK]);
    set_CC_exp_b_r1(pay,s->c_exposure[SIDE_BACK][CHAN_RED]);
    set_CC_exp_b_g1(pay,s->c_exposure[SIDE_BACK][CHAN_GREEN]);
    set_CC_exp_b_b1(pay,s->c_exposure[SIDE_BACK][CHAN_BLUE]);
    set_CC_exp_b_r2(pay,s->c_exposure[SIDE_BACK][CHAN_RED]);
    set_CC_exp_b_g2(pay,s->c_exposure[SIDE_BACK][CHAN_GREEN]);
    set_CC_exp_b_b2(pay,s->c_exposure[SIDE_BACK][CHAN_BLUE]);
  }

  ret = do_cmd (
    s, 1, 0,
    cmd, cmdLen,
    pay, payLen,
    NULL, NULL
  );
  if (ret != SANE_STATUS_GOOD)
    return ret;

  DBG (10, "write_AFE: finish\n");

  return ret;
}

/*
 * frees/callocs buffers to hold the fine cal offset data
 */
static SANE_Status
offset_buffers (struct scanner *s, int setup)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int side;

  DBG (10, "offset_buffers: start\n");

  for(side=0;side<2;side++){

    if (s->f_offset[side]) {
      DBG (15, "offset_buffers: free f_offset %d.\n",side);
      free(s->f_offset[side]);
      s->f_offset[side] = NULL;
    }

    if(setup){
      s->f_offset[side] = calloc (1,s->s.Bpl);
      if (!s->f_offset[side]) {
        DBG (5, "offset_buffers: error, no f_offset %d.\n",side);
        return SANE_STATUS_NO_MEM;
      }
    }
  }

  DBG (10, "offset_buffers: finish\n");

  return ret;
}

/*
 * frees/callocs buffers to hold the fine cal gain data
 */
static SANE_Status
gain_buffers (struct scanner *s, int setup)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int side;

  DBG (10, "gain_buffers: start\n");

  for(side=0;side<2;side++){

    if (s->f_gain[side]) {
      DBG (15, "gain_buffers: free f_gain %d.\n",side);
      free(s->f_gain[side]);
      s->f_gain[side] = NULL;
    }

    if(setup){
      s->f_gain[side] = calloc (1,s->s.Bpl);
      if (!s->f_gain[side]) {
        DBG (5, "gain_buffers: error, no f_gain %d.\n",side);
        return SANE_STATUS_NO_MEM;
      }
    }
  }

  DBG (10, "gain_buffers: finish\n");

  return ret;
}

/*
 * @@ Section 6 - SANE cleanup functions
 */
/*
 * Cancels a scan.
 *
 * It has been said on the mailing list that sane_cancel is a bit of a
 * misnomer because it is routinely called to signal the end of a
 * batch - quoting David Mosberger-Tang:
 *
 * > In other words, the idea is to have sane_start() be called, and
 * > collect as many images as the frontend wants (which could in turn
 * > consist of multiple frames each as indicated by frame-type) and
 * > when the frontend is done, it should call sane_cancel().
 * > Sometimes it's better to think of sane_cancel() as "sane_stop()"
 * > but that name would have had some misleading connotations as
 * > well, that's why we stuck with "cancel".
 *
 * The current consensus regarding duplex and ADF scans seems to be
 * the following call sequence: sane_start; sane_read (repeat until
 * EOF); sane_start; sane_read...  and then call sane_cancel if the
 * batch is at an end. I.e. do not call sane_cancel during the run but
 * as soon as you get a SANE_STATUS_NO_DOCS.
 *
 * From the SANE spec:
 * This function is used to immediately or as quickly as possible
 * cancel the currently pending operation of the device represented by
 * handle h.  This function can be called at any time (as long as
 * handle h is a valid handle) but usually affects long-running
 * operations only (such as image acquisition). It is safe to call
 * this function asynchronously (e.g., from within a signal handler).
 * It is important to note that completion of this operation does not
 * imply that the currently pending operation has been cancelled. It
 * only guarantees that cancellation has been initiated. Cancellation
 * completes only when the cancelled call returns (typically with a
 * status value of SANE_STATUS_CANCELLED).  Since the SANE API does
 * not require any other operations to be re-entrant, this implies
 * that a frontend must not call any other operation until the
 * cancelled operation has returned.
 */
void
sane_cancel (SANE_Handle handle)
{
  struct scanner * s = (struct scanner *) handle;

  DBG (10, "sane_cancel: start\n");
  s->cancelled = 1;

  /* if there is no other running function to check, we do it */
  if(!s->reading)
    check_for_cancel(s);

  DBG (10, "sane_cancel: finish\n");
}

/* checks started and cancelled flags in scanner struct,
 * sends cancel command to scanner if required. don't call
 * this function asynchronously, wait for pending operation */
static SANE_Status
check_for_cancel(struct scanner *s)
{
  SANE_Status ret=SANE_STATUS_GOOD;

  DBG (10, "check_for_cancel: start\n");

  if(s->started && s->cancelled){
    unsigned char cmd[CANCEL_len];
    size_t cmdLen = CANCEL_len;

    DBG (15, "check_for_cancel: cancelling\n");

    /* cancel scan */
    memset(cmd,0,cmdLen);
    set_SCSI_opcode(cmd, CANCEL_code);

    ret = do_cmd (
        s, 1, 0,
        cmd, cmdLen,
        NULL, 0,
        NULL, NULL
    );
    if(ret){
      DBG (5, "check_for_cancel: ignoring bad cancel: %d\n",ret);
    }

    ret = object_position(s,SANE_FALSE);
    if(ret){
      DBG (5, "check_for_cancel: ignoring bad eject: %d\n",ret);
    }

    s->started = 0;
    s->cancelled = 0;
    ret = SANE_STATUS_CANCELLED;
  }
  else if(s->cancelled){
    DBG (15, "check_for_cancel: already cancelled\n");
    s->cancelled = 0;
    ret = SANE_STATUS_CANCELLED;
  }

  DBG (10, "check_for_cancel: finish %d\n",ret);
  return ret;
}

/*
 * Ends use of the scanner.
 *
 * From the SANE spec:
 * This function terminates the association between the device handle
 * passed in argument h and the device it represents. If the device is
 * presently active, a call to sane_cancel() is performed first. After
 * this function returns, handle h must not be used anymore.
 */
void
sane_close (SANE_Handle handle)
{
  struct scanner * s = (struct scanner *) handle;

  DBG (10, "sane_close: start\n");
  disconnect_fd(s);
  image_buffers(s,0);
  offset_buffers(s,0);
  gain_buffers(s,0);
  DBG (10, "sane_close: finish\n");
}

static SANE_Status
disconnect_fd (struct scanner *s)
{
  DBG (10, "disconnect_fd: start\n");

  if(s->fd > -1){
    if (s->connection == CONNECTION_USB) {
      DBG (15, "disconnecting usb device\n");
      sanei_usb_close (s->fd);
    }
    else if (s->connection == CONNECTION_SCSI) {
      DBG (15, "disconnecting scsi device\n");
      sanei_scsi_close (s->fd);
    }
    s->fd = -1;
  }

  DBG (10, "disconnect_fd: finish\n");

  return SANE_STATUS_GOOD;
}

/*
 * Terminates the backend.
 *
 * From the SANE spec:
 * This function must be called to terminate use of a backend. The
 * function will first close all device handles that still might be
 * open (it is recommended to close device handles explicitly through
 * a call to sane_close(), but backends are required to release all
 * resources upon a call to this function). After this function
 * returns, no function other than sane_init() may be called
 * (regardless of the status value returned by sane_exit(). Neglecting
 * to call this function may result in some resources not being
 * released properly.
 */
void
sane_exit (void)
{
  struct scanner *dev, *next;

  DBG (10, "sane_exit: start\n");

  for (dev = scanner_devList; dev; dev = next) {
      disconnect_fd(dev);
      next = dev->next;
      free (dev);
  }

  if (sane_devArray)
    free (sane_devArray);

  scanner_devList = NULL;
  sane_devArray = NULL;

  DBG (10, "sane_exit: finish\n");
}


/*
 * @@ Section 7 - misc helper functions
 */
static void
default_globals(void)
{
  global_buffer_size = global_buffer_size_default;
  global_padded_read = global_padded_read_default;
  global_extra_status = global_extra_status_default;
  global_duplex_offset = global_duplex_offset_default;
  global_inquiry_length = INQUIRY_std_typ_len;
  global_vpd_length = INQUIRY_vpd_typ_len;
  global_tur_timeout = global_tur_timeout_default;
  global_vendor_name[0] = 0;
  global_model_name[0] = 0;
  global_version_name[0] = 0;
}

/*
 * Called by the SANE SCSI core and our usb code on device errors
 * parses the request sense return data buffer,
 * decides the best SANE_Status for the problem, produces debug msgs,
 * and copies the sense buffer into the scanner struct
 */
static SANE_Status
sense_handler (int fd, unsigned char * sensed_data, void *arg)
{
  struct scanner *s = arg;
  unsigned int sense = get_RS_sense_key (sensed_data);
  unsigned int asc = get_RS_ASC (sensed_data);
  unsigned int ascq = get_RS_ASCQ (sensed_data);
  unsigned int eom = get_RS_EOM (sensed_data);
  unsigned int ili = get_RS_ILI (sensed_data);
  unsigned int info = get_RS_information (sensed_data);

  DBG (5, "sense_handler: start\n");

  /* kill compiler warning */
  (void) fd;

  /* copy the rs return data into the scanner struct
     so that the caller can use it if he wants
  memcpy(&s->rs_buffer,sensed_data,RS_return_size);
  */

  DBG (5, "Sense=%#02x, ASC=%#02x, ASCQ=%#02x, EOM=%d, ILI=%d, info=%#08x\n", sense, asc, ascq, eom, ili, info);

  switch (sense) {
    case 0:
      if (ili == 1) {
        s->rs_info = info;
        DBG  (5, "No sense: EOM remainder:%d\n",info);
        return SANE_STATUS_EOF;
      }
      DBG  (5, "No sense: unknown asc/ascq\n");
      return SANE_STATUS_GOOD;

    case 1:
      if (asc == 0x37 && ascq == 0x00) {
        DBG  (5, "Recovered error: parameter rounded\n");
        return SANE_STATUS_GOOD;
      }
      DBG  (5, "Recovered error: unknown asc/ascq\n");
      return SANE_STATUS_GOOD;

    case 2:
      if (asc == 0x04 && ascq == 0x01) {
        DBG  (5, "Not ready: previous command unfinished\n");
        return SANE_STATUS_DEVICE_BUSY;
      }
      DBG  (5, "Not ready: unknown asc/ascq\n");
      return SANE_STATUS_DEVICE_BUSY;

    case 3:
      if (asc == 0x36 && ascq == 0x00) {
        DBG  (5, "Medium error: no cartridge\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x3a && ascq == 0x00) {
        DBG  (5, "Medium error: hopper empty\n");
        return SANE_STATUS_NO_DOCS;
      }
      if (asc == 0x80 && ascq == 0x00) {
        DBG  (5, "Medium error: paper jam\n");
        return SANE_STATUS_JAMMED;
      }
      if (asc == 0x80 && ascq == 0x01) {
        DBG  (5, "Medium error: cover open\n");
        return SANE_STATUS_COVER_OPEN;
      }
      if (asc == 0x81 && ascq == 0x01) {
        DBG  (5, "Medium error: double feed\n");
        return SANE_STATUS_JAMMED;
      }
      if (asc == 0x81 && ascq == 0x02) {
        DBG  (5, "Medium error: skew detected\n");
        return SANE_STATUS_JAMMED;
      }
      if (asc == 0x81 && ascq == 0x04) {
        DBG  (5, "Medium error: staple detected\n");
        return SANE_STATUS_JAMMED;
      }
      DBG  (5, "Medium error: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    case 4:
      if (asc == 0x60 && ascq == 0x00) {
        DBG  (5, "Hardware error: lamp error\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x80 && ascq == 0x01) {
        DBG  (5, "Hardware error: CPU check error\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x80 && ascq == 0x02) {
        DBG  (5, "Hardware error: RAM check error\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x80 && ascq == 0x03) {
        DBG  (5, "Hardware error: ROM check error\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x80 && ascq == 0x04) {
        DBG  (5, "Hardware error: hardware check error\n");
        return SANE_STATUS_IO_ERROR;
      }
      DBG  (5, "Hardware error: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    case 5:
      if (asc == 0x1a && ascq == 0x00) {
        DBG  (5, "Illegal request: Parameter list error\n");
        return SANE_STATUS_INVAL;
      }
      if (asc == 0x20 && ascq == 0x00) {
        DBG  (5, "Illegal request: invalid command\n");
        return SANE_STATUS_INVAL;
      }
      if (asc == 0x24 && ascq == 0x00) {
        DBG  (5, "Illegal request: invalid CDB field\n");
        return SANE_STATUS_INVAL;
      }
      if (asc == 0x25 && ascq == 0x00) {
        DBG  (5, "Illegal request: unsupported logical unit\n");
        return SANE_STATUS_UNSUPPORTED;
      }
      if (asc == 0x26 && ascq == 0x00) {
        DBG  (5, "Illegal request: invalid field in parm list\n");
        return SANE_STATUS_INVAL;
      }
      if (asc == 0x2c && ascq == 0x00) {
        DBG  (5, "Illegal request: command sequence error\n");
        return SANE_STATUS_INVAL;
      }
      if (asc == 0x2c && ascq == 0x01) {
        DBG  (5, "Illegal request: too many windows\n");
        return SANE_STATUS_INVAL;
      }
      if (asc == 0x3a && ascq == 0x00) {
        DBG  (5, "Illegal request: no paper\n");
        return SANE_STATUS_NO_DOCS;
      }
      if (asc == 0x3d && ascq == 0x00) {
        DBG  (5, "Illegal request: invalid IDENTIFY\n");
        return SANE_STATUS_INVAL;
      }
      if (asc == 0x55 && ascq == 0x00) {
        DBG  (5, "Illegal request: scanner out of memory\n");
        return SANE_STATUS_NO_MEM;
      }
      DBG  (5, "Illegal request: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;
      break;

    case 6:
      if (asc == 0x29 && ascq == 0x00) {
        DBG  (5, "Unit attention: device reset\n");
        return SANE_STATUS_GOOD;
      }
      if (asc == 0x2a && ascq == 0x00) {
        DBG  (5, "Unit attention: param changed by 2nd initiator\n");
        return SANE_STATUS_GOOD;
      }
      DBG  (5, "Unit attention: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;
      break;

    case 7:
      DBG  (5, "Data protect: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    case 8:
      DBG  (5, "Blank check: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    case 9:
      DBG  (5, "Vendor defined: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    case 0xa:
      DBG  (5, "Copy aborted: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    case 0xb:
      if (asc == 0x00 && ascq == 0x00) {
        DBG  (5, "Aborted command: no sense/cancelled\n");
        return SANE_STATUS_CANCELLED;
      }
      if (asc == 0x45 && ascq == 0x00) {
        DBG  (5, "Aborted command: reselect failure\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x47 && ascq == 0x00) {
        DBG  (5, "Aborted command: SCSI parity error\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x48 && ascq == 0x00) {
        DBG  (5, "Aborted command: initiator error message\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x49 && ascq == 0x00) {
        DBG  (5, "Aborted command: invalid message\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x80 && ascq == 0x00) {
        DBG  (5, "Aborted command: timeout\n");
        return SANE_STATUS_IO_ERROR;
      }
      DBG  (5, "Aborted command: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;
      break;

    case 0xc:
      DBG  (5, "Equal: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    case 0xd:
      DBG  (5, "Volume overflow: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    case 0xe:
      if (asc == 0x3b && ascq == 0x0d) {
        DBG  (5, "Miscompare: too many docs\n");
        return SANE_STATUS_IO_ERROR;
      }
      if (asc == 0x3b && ascq == 0x0e) {
        DBG  (5, "Miscompare: too few docs\n");
        return SANE_STATUS_IO_ERROR;
      }
      DBG  (5, "Miscompare: unknown asc/ascq\n");
      return SANE_STATUS_IO_ERROR;

    default:
      DBG (5, "Unknown Sense Code\n");
      return SANE_STATUS_IO_ERROR;
  }

  DBG (5, "sense_handler: should never happen!\n");

  return SANE_STATUS_IO_ERROR;
}

/*
 * take a bunch of pointers, send commands to scanner
 */
static SANE_Status
do_cmd(struct scanner *s, int runRS, int timeout,
 unsigned char * cmdBuff, size_t cmdLen,
 unsigned char * outBuff, size_t outLen,
 unsigned char * inBuff, size_t * inLen
)
{
    if (s->connection == CONNECTION_SCSI) {
        return do_scsi_cmd(s, runRS, timeout,
                 cmdBuff, cmdLen,
                 outBuff, outLen,
                 inBuff, inLen
        );
    }
    if (s->connection == CONNECTION_USB) {
        return do_usb_cmd(s, runRS, timeout,
                 cmdBuff, cmdLen,
                 outBuff, outLen,
                 inBuff, inLen
        );
    }
    return SANE_STATUS_INVAL;
}

static SANE_Status
do_scsi_cmd(struct scanner *s, int runRS, int timeout,
 unsigned char * cmdBuff, size_t cmdLen,
 unsigned char * outBuff, size_t outLen,
 unsigned char * inBuff, size_t * inLen
)
{
  int ret;

  /*shut up compiler*/
  (void) runRS;
  (void) timeout;

  DBG(10, "do_scsi_cmd: start\n");

  DBG(25, "cmd: writing %d bytes\n", (int)cmdLen);
  hexdump(30, "cmd: >>", cmdBuff, cmdLen);

  if(outBuff && outLen){
    DBG(25, "out: writing %d bytes\n", (int)outLen);
    hexdump(30, "out: >>", outBuff, outLen);
  }
  if (inBuff && inLen){
    DBG(25, "in: reading %d bytes\n", (int)*inLen);
    memset(inBuff,0,*inLen);
  }

  ret = sanei_scsi_cmd2(s->fd, cmdBuff, cmdLen, outBuff, outLen, inBuff, inLen);

  if(ret != SANE_STATUS_GOOD && ret != SANE_STATUS_EOF){
    DBG(5,"do_scsi_cmd: return '%s'\n",sane_strstatus(ret));
    return ret;
  }

  if (inBuff && inLen){
    if(ret == SANE_STATUS_EOF){
      DBG(25, "in: short read, remainder %lu bytes\n", (u_long)s->rs_info);
      *inLen -= s->rs_info;
    }
    hexdump(31, "in: <<", inBuff, *inLen);
    DBG(25, "in: read %d bytes\n", (int)*inLen);
  }

  DBG(10, "do_scsi_cmd: finish\n");

  return ret;
}

static SANE_Status
do_usb_cmd(struct scanner *s, int runRS, int timeout,
 unsigned char * cmdBuff, size_t cmdLen,
 unsigned char * outBuff, size_t outLen,
 unsigned char * inBuff, size_t * inLen
)
{
    size_t cmdOffset = 0;
    size_t cmdLength = 0;
    size_t cmdActual = 0;
    unsigned char * cmdBuffer = NULL;

    size_t outOffset = 0;
    size_t outLength = 0;
    size_t outActual = 0;
    unsigned char * outBuffer = NULL;

    size_t inOffset = 0;
    size_t inLength = 0;
    size_t inActual = 0;
    unsigned char * inBuffer = NULL;

    size_t extraLength = 0;
    int actTimeout = timeout ? timeout : USB_PACKET_TIMEOUT;

    int ret = 0;
    int ret2 = 0;

    struct timeval timer;
    gettimeofday(&timer,NULL);

    DBG (10, "do_usb_cmd: start %lu %lu\n", (long unsigned int)timer.tv_sec, (long unsigned int)timer.tv_usec);

    /* change timeout */
    sanei_usb_set_timeout(actTimeout);

    /****************************************************************/
    /* the command stage */
    {
      cmdOffset = USB_HEADER_LEN;
      cmdLength = cmdOffset+USB_COMMAND_LEN;
      cmdActual = cmdLength;

      /* build buffer */
      cmdBuffer = calloc(cmdLength,1);
      if(!cmdBuffer){
        DBG(5,"cmd: no mem\n");
        return SANE_STATUS_NO_MEM;
      }

      /* build a USB packet around the SCSI command */
      set_USB_CMD_xfer_length(cmdBuffer,cmdLength-4);
      cmdBuffer[5] = 1;
      cmdBuffer[6] = 0x90;
      memcpy(cmdBuffer+cmdOffset,cmdBuff,cmdLen);

      /* write the command out */
      DBG(25, "cmd: writing %d bytes, timeout %d\n", (int)cmdLength, actTimeout);
      hexdump(30, "cmd: >>", cmdBuffer, cmdLength);
      ret = sanei_usb_write_bulk(s->fd, cmdBuffer, &cmdActual);
      DBG(25, "cmd: wrote %d bytes, retVal %d\n", (int)cmdActual, ret);

      if(cmdLength != cmdActual){
        DBG(5,"cmd: wrong size %d/%d\n", (int)cmdLength, (int)cmdActual);
        free(cmdBuffer);
        return SANE_STATUS_IO_ERROR;
      }
      if(ret != SANE_STATUS_GOOD){
        DBG(5,"cmd: write error '%s'\n",sane_strstatus(ret));
        free(cmdBuffer);
        return ret;
      }
      free(cmdBuffer);
    }

    /****************************************************************/
    /* the extra status stage, used by few scanners                 */
    /* this is like the regular status block, with an additional    */
    /* length component at the end */
    if(s->extra_status){
      ret2 = do_usb_status(s,runRS,timeout,&extraLength);

      /* bail out on bad RS status */
      if(ret2){
        DBG(5,"extra: bad RS status, %d\n", ret2);
        return ret2;
      }
    }

    /****************************************************************/
    /* the output stage */
    if(outBuff && outLen){

      outOffset = USB_HEADER_LEN;
      outLength = outOffset+outLen;
      outActual = outLength;

      /* build outBuffer */
      outBuffer = calloc(outLength,1);
      if(!outBuffer){
        DBG(5,"out: no mem\n");
        return SANE_STATUS_NO_MEM;
      }

      /* build a USB packet around the SCSI command */
      set_USB_OUT_xfer_length(outBuffer,outLength-4);
      outBuffer[5] = 2;
      outBuffer[6] = 0xb0;
      memcpy(outBuffer+outOffset,outBuff,outLen);

      /* write the command out */
      DBG(25, "out: writing %d bytes, timeout %d\n", (int)outLength, actTimeout);
      hexdump(30, "out: >>", outBuffer, outLength);
      ret = sanei_usb_write_bulk(s->fd, outBuffer, &outActual);
      DBG(25, "out: wrote %d bytes, retVal %d\n", (int)outActual, ret);

      if(outLength != outActual){
        DBG(5,"out: wrong size %d/%d\n", (int)outLength, (int)outActual);
        free(outBuffer);
        return SANE_STATUS_IO_ERROR;
      }
      if(ret != SANE_STATUS_GOOD){
        DBG(5,"out: write error '%s'\n",sane_strstatus(ret));
        free(outBuffer);
        return ret;
      }
      free(outBuffer);
    }

    /****************************************************************/
    /* the input stage */
    if(inBuff && inLen){

      inOffset = 0;
      if(s->padded_read)
        inOffset = USB_HEADER_LEN;

      inLength = inOffset+*inLen;
      inActual = inLength;

      /* use the extra length to alter the amount of in we request */
      if(s->extra_status && extraLength && *inLen > extraLength){
        DBG(5,"in: adjust extra, %d %d\n", (int)*inLen, (int)extraLength);
        inActual = inOffset+extraLength;
      }

      /*blast caller's copy in case we error out*/
      *inLen = 0;

      /* build inBuffer */
      inBuffer = calloc(inActual,1);
      if(!inBuffer){
        DBG(5,"in: no mem\n");
        return SANE_STATUS_NO_MEM;
      }

      DBG(25, "in: reading %d bytes, timeout %d\n", (int)inActual, actTimeout);
      ret = sanei_usb_read_bulk(s->fd, inBuffer, &inActual);
      DBG(25, "in: read %d bytes, retval %d\n", (int)inActual, ret);
      hexdump(31, "in: <<", inBuffer, inActual);

      if(!inActual){
        DBG(5,"in: got no data, clearing\n");
        free(inBuffer);
	return do_usb_clear(s,1,runRS);
      }
      if(inActual < inOffset){
        DBG(5,"in: read shorter than inOffset\n");
        free(inBuffer);
        return SANE_STATUS_IO_ERROR;
      }
      if(ret != SANE_STATUS_GOOD){
        DBG(5,"in: return error '%s'\n",sane_strstatus(ret));
        free(inBuffer);
        return ret;
      }

      /* note that inBuffer is not copied and freed here...*/
    }

    /****************************************************************/
    /* the normal status stage */
    ret2 = do_usb_status(s,runRS,timeout,&extraLength);

    /* if status said EOF, adjust input with remainder count */
    if(ret2 == SANE_STATUS_EOF && inBuffer){

      /* EOF is ok */
      ret2 = SANE_STATUS_GOOD;

      if(inActual < inLength - s->rs_info){
        DBG(5,"in: we read < RS, ignoring RS: %d < %d (%d-%d)\n",
          (int)inActual,(int)(inLength-s->rs_info),(int)inLength,(int)s->rs_info);
      }
      else if(inActual > inLength - s->rs_info){
        DBG(5,"in: we read > RS, using RS: %d to %d (%d-%d)\n",
          (int)inActual,(int)(inLength-s->rs_info),(int)inLength,(int)s->rs_info);
        inActual = inLength - s->rs_info;
      }
    }

    /* bail out on bad RS status */
    if(ret2){
      if(inBuffer) free(inBuffer);
      DBG(5,"stat: bad RS status, %d\n", ret2);
      return ret2;
    }

    /* now that we have read status, deal with input buffer */
    if(inBuffer){
      if(inLength != inActual){
        ret = SANE_STATUS_EOF;
        DBG(5,"in: short read, %d/%d\n", (int)inLength,(int)inActual);
      }

      /* ignore the USB packet around the SCSI command */
      *inLen = inActual - inOffset;
      memcpy(inBuff,inBuffer+inOffset,*inLen);

      free(inBuffer);
    }

    gettimeofday(&timer,NULL);

    DBG (10, "do_usb_cmd: finish %lu %lu\n", (long unsigned int)timer.tv_sec, (long unsigned int)timer.tv_usec);

    return ret;
}

static SANE_Status
do_usb_status(struct scanner *s, int runRS, int timeout, size_t * extraLength)
{

#define EXTRA_READ_len 4

    size_t statPadding = 0;
    size_t statOffset = 0;
    size_t statLength = 0;
    size_t statActual = 0;
    unsigned char * statBuffer = NULL;

    int actTimeout = timeout ? timeout : USB_PACKET_TIMEOUT;

    int ret = 0;

    if(s->padded_read)
      statPadding = USB_HEADER_LEN;

    statLength = statPadding+USB_STATUS_LEN;
    statOffset = statLength-1;

    if(s->extra_status)
      statLength += EXTRA_READ_len;

    statActual = statLength;

    /* change timeout */
    sanei_usb_set_timeout(timeout ? timeout : USB_PACKET_TIMEOUT);

    /* build statBuffer */
    statBuffer = calloc(statLength,1);
    if(!statBuffer){
      DBG(5,"stat: no mem\n");
      return SANE_STATUS_NO_MEM;
    }

    DBG(25, "stat: reading %d bytes, timeout %d\n", (int)statLength, actTimeout);
    ret = sanei_usb_read_bulk(s->fd, statBuffer, &statActual);
    DBG(25, "stat: read %d bytes, retval %d\n", (int)statActual, ret);
    hexdump(30, "stat: <<", statBuffer, statActual);

    /*weird status*/
    if(ret != SANE_STATUS_GOOD){
      DBG(5,"stat: clearing error '%s'\n",sane_strstatus(ret));
      ret = do_usb_clear(s,1,runRS);
    }
    /*short read*/
    else if(statLength != statActual){
      DBG(5,"stat: clearing short %d/%d\n",(int)statLength,(int)statActual);
      ret = do_usb_clear(s,1,runRS);
    }
    /*inspect the status byte of the response*/
    else if(statBuffer[statOffset]){
      DBG(5,"stat: status %d\n",statBuffer[statOffset]);
      ret = do_usb_clear(s,0,runRS);
    }

    /*extract the extra length byte of the response*/
    if(s->extra_status){
      *extraLength = get_ES_length(statBuffer);
      DBG(15,"stat: extra %d\n",(int)*extraLength);
    }

    free(statBuffer);

    return ret;
}

static SANE_Status
do_usb_clear(struct scanner *s, int clear, int runRS)
{
    SANE_Status ret, ret2;

    DBG (10, "do_usb_clear: start\n");

    usleep(100000);

    if(clear){
      DBG (15, "do_usb_clear: clear halt\n");
      ret = sanei_usb_clear_halt(s->fd);
      if(ret != SANE_STATUS_GOOD){
        DBG(5,"do_usb_clear: can't clear halt, returning %d\n", ret);
        return ret;
      }
    }

    /* caller is interested in having RS run on errors */
    if(runRS){

        unsigned char rs_cmd[REQUEST_SENSE_len];
        size_t rs_cmdLen = REQUEST_SENSE_len;

        unsigned char rs_in[RS_return_size];
        size_t rs_inLen = RS_return_size;

        memset(rs_cmd,0,rs_cmdLen);
        set_SCSI_opcode(rs_cmd, REQUEST_SENSE_code);
	set_RS_return_size(rs_cmd, rs_inLen);

        DBG(25,"rs sub call >>\n");
        ret2 = do_cmd(
          s, 0, 0,
          rs_cmd, rs_cmdLen,
          NULL,0,
          rs_in, &rs_inLen
        );
        DBG(25,"rs sub call <<\n");

        if(ret2 == SANE_STATUS_EOF){
          DBG(5,"rs: got EOF, returning IO_ERROR\n");
          return SANE_STATUS_IO_ERROR;
        }
        if(ret2 != SANE_STATUS_GOOD){
          DBG(5,"rs: return error '%s'\n",sane_strstatus(ret2));
          return ret2;
        }

        /* parse the rs data */
        ret2 = sense_handler( 0, rs_in, (void *)s );

        DBG (10, "do_usb_clear: finish after RS\n");
        return ret2;
    }

    DBG (10, "do_usb_clear: finish with io error\n");

    return SANE_STATUS_IO_ERROR;
}

static SANE_Status
wait_scanner(struct scanner *s)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char cmd[TEST_UNIT_READY_len];
  size_t cmdLen = TEST_UNIT_READY_len;

  DBG (10, "wait_scanner: start\n");

  memset(cmd,0,cmdLen);
  set_SCSI_opcode(cmd,TEST_UNIT_READY_code);

  ret = do_cmd (
    s, 0, s->tur_timeout,
    cmd, cmdLen,
    NULL, 0,
    NULL, NULL
  );

  if (ret != SANE_STATUS_GOOD) {
    DBG(5,"WARNING: Brain-dead scanner. Hitting with stick.\n");
    ret = do_cmd (
      s, 0, s->tur_timeout,
      cmd, cmdLen,
      NULL, 0,
      NULL, NULL
    );
  }
  if (ret != SANE_STATUS_GOOD) {
    DBG(5,"WARNING: Brain-dead scanner. Hitting with stick again.\n");
    ret = do_cmd (
      s, 0, s->tur_timeout,
      cmd, cmdLen,
      NULL, 0,
      NULL, NULL
    );
  }
  // some scanners (such as DR-F120) are OK but will not respond to commands
  // when in sleep mode. By checking the sense it wakes them up.
  if (ret != SANE_STATUS_GOOD) {
    DBG(5,"WARNING: Brain-dead scanner. Hitting with stick and request sense.\n");
    ret = do_cmd (
      s, 1, s->tur_timeout,
      cmd, cmdLen,
      NULL, 0,
      NULL, NULL
    );
  }
  if (ret != SANE_STATUS_GOOD) {
    DBG(5,"WARNING: Brain-dead scanner. Hitting with stick a fourth time.\n");
    ret = do_cmd (
      s, 0, s->tur_timeout,
      cmd, cmdLen,
      NULL, 0,
      NULL, NULL
    );
  }

  if (ret != SANE_STATUS_GOOD) {
    DBG (5, "wait_scanner: error '%s'\n", sane_strstatus (ret));
  }

  DBG (10, "wait_scanner: finish (status=%d)\n", ret);

  return ret;
}

/* Some scanners have per-resolution
 * color interlacing values, but most
 * don't. This helper can tell the
 * difference.
 */
static int
get_color_inter(struct scanner *s, int side, int res)
{
  int i;
  for(i=0;i<DPI_1200;i++){
    if(res == dpi_list[i])
      break;
  }

  if(s->color_inter_by_res[i])
    return s->color_inter_by_res[i];

  return s->color_interlace[side];
}

/* s->u.page_x stores the user setting
 * for the paper width in adf. sometimes,
 * we need a value that differs from this
 * due to using FB or overscan.
 */
static int
get_page_width(struct scanner *s)
{
  int width = s->u.page_x;

  /* scanner max for fb */
  if(s->u.source == SOURCE_FLATBED){
      return s->max_x_fb;
  }

  /* can't overscan larger than scanner max */
  if(width > s->valid_x){
      return s->valid_x;
  }

  /* overscan adds a margin to both sides */
  return width;
}

/* s->u.page_y stores the user setting
 * for the paper height in adf. sometimes,
 * we need a value that differs from this
 * due to using FB or overscan.
 */
static int
get_page_height(struct scanner *s)
{
  int height = s->u.page_y;

  /* scanner max for fb */
  if(s->u.source == SOURCE_FLATBED){
      return s->max_y_fb;
  }

  /* can't overscan larger than scanner max */
  if(height > s->max_y){
      return s->max_y;
  }

  /* overscan adds a margin to both sides */
  return height;
}


/**
 * Convenience method to determine longest string size in a list.
 */
static size_t
maxStringSize (const SANE_String_Const strings[])
{
  size_t size, max_size = 0;
  int i;

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

  return max_size;
}

/*
 * Prints a hex dump of the given buffer onto the debug output stream.
 */
static void
hexdump (int level, char *comment, unsigned char *p, int l)
{
  int i;
  char line[70]; /* 'xxx: xx xx ... xx xx abc */
  char *hex = line+4;
  char *bin = line+53;

  if(DBG_LEVEL < level)
    return;

  line[0] = 0;

  DBG (level, "%s\n", comment);

  for (i = 0; i < l; i++, p++) {

    /* at start of line */
    if ((i % 16) == 0) {

      /* not at start of first line, print current, reset */
      if (i) {
        DBG (level, "%s\n", line);
      }

      memset(line,0x20,69);
      line[69] = 0;
      hex = line + 4;
      bin = line + 53;

      sprintf (line, "%3.3x:", i);
    }

    /* the hex section */
    sprintf (hex, " %2.2x", *p);
    hex += 3;
    *hex = ' ';

    /* the char section */
    if(*p >= 0x20 && *p <= 0x7e){
      *bin=*p;
    }
    else{
      *bin='.';
    }
    bin++;
  }

  /* print last (partial) line */
  if (i)
    DBG (level, "%s\n", line);
}

/**
 * An advanced method we don't support but have to define.
 */
SANE_Status
sane_set_io_mode (SANE_Handle h, SANE_Bool non_blocking)
{
  DBG (10, "sane_set_io_mode\n");
  DBG (15, "%d %p\n", non_blocking, h);
  return SANE_STATUS_UNSUPPORTED;
}

/**
 * An advanced method we don't support but have to define.
 */
SANE_Status
sane_get_select_fd (SANE_Handle h, SANE_Int *fdp)
{
  DBG (10, "sane_get_select_fd\n");
  DBG (15, "%p %d\n", h, *fdp);
  return SANE_STATUS_UNSUPPORTED;
}

/*
 * @@ Section 8 - Image processing functions
 */

/* Look in image for likely upper and left paper edges, then rotate
 * image so that upper left corner of paper is upper left of image.
 * FIXME: should we do this before we binarize instead of after? */
static SANE_Status
buffer_deskew(struct scanner *s, int side)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  unsigned char bg_color = calc_bg_color(s);

  DBG (10, "buffer_deskew: start\n");

  ret = sane_get_parameters((SANE_Handle) s, &s->s_params);

  /*only find skew on first image from a page, or if first image had error */
  if(s->side == SIDE_FRONT || s->u.source == SOURCE_ADF_BACK || s->deskew_stat){

    s->deskew_stat = sanei_magic_findSkew(
      &s->s_params,s->buffers[side],s->u.dpi_x,s->u.dpi_y,
      &s->deskew_vals[0],&s->deskew_vals[1],&s->deskew_slope);

    if(s->deskew_stat){
      DBG (5, "buffer_deskew: bad findSkew, bailing\n");
      goto cleanup;
    }
  }
  /* backside images can use a 'flipped' version of frontside data */
  else{
    s->deskew_slope *= -1;
    s->deskew_vals[0] = s->s_params.pixels_per_line - s->deskew_vals[0];
  }

  ret = sanei_magic_rotate(&s->s_params,s->buffers[side],
    s->deskew_vals[0],s->deskew_vals[1],s->deskew_slope,bg_color);

  if(ret){
    DBG(5,"buffer_deskew: rotate error: %d",ret);
    ret = SANE_STATUS_GOOD;
    goto cleanup;
  }

  cleanup:
  DBG (10, "buffer_deskew: finish\n");
  return ret;
}

/* Look in image for likely left/right/bottom paper edges, then crop
 * image to match. Does not attempt to rotate the image.
 * FIXME: should we do this before we binarize instead of after? */
static SANE_Status
buffer_crop(struct scanner *s, int side)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "buffer_crop: start\n");

  ret = sane_get_parameters((SANE_Handle) s, &s->s_params);

  ret = sanei_magic_findEdges(
    &s->s_params,s->buffers[side],s->u.dpi_x,s->u.dpi_y,
    &s->crop_vals[0],&s->crop_vals[1],&s->crop_vals[2],&s->crop_vals[3]);

  if(ret){
    DBG (5, "buffer_crop: bad edges, bailing\n");
    ret = SANE_STATUS_GOOD;
    goto cleanup;
  }

  DBG (15, "buffer_crop: t:%d b:%d l:%d r:%d\n",
    s->crop_vals[0],s->crop_vals[1],s->crop_vals[2],s->crop_vals[3]);

  /* if we will later binarize this image, make sure the width
   * is a multiple of 8 pixels, by adjusting the right side */
  if ( must_downsample(s) && s->u.mode < MODE_GRAYSCALE ){
    s->crop_vals[3] -= (s->crop_vals[3]-s->crop_vals[2]) % 8;
  }

  /* now crop the image */
  ret = sanei_magic_crop(&s->s_params,s->buffers[side],
      s->crop_vals[0],s->crop_vals[1],s->crop_vals[2],s->crop_vals[3]);

  if(ret){
    DBG (5, "buffer_crop: bad crop, bailing\n");
    ret = SANE_STATUS_GOOD;
    goto cleanup;
  }

  /* need to update user with new size */
  s->i.width = s->s_params.pixels_per_line;
  s->i.height = s->s_params.lines;
  s->i.Bpl = s->s_params.bytes_per_line;

  /* update image size counter to new, smaller size */
  s->i.bytes_tot[side] = s->s_params.lines * s->s_params.bytes_per_line;
  s->i.bytes_sent[side] = s->i.bytes_tot[side];
  s->u.bytes_sent[side] = 0;

  cleanup:
  DBG (10, "buffer_crop: finish\n");
  return ret;
}

/* Look in image for disconnected 'spots' of the requested size.
 * Replace the spots with the average color of the surrounding pixels.
 * FIXME: should we do this before we binarize instead of after? */
static SANE_Status
buffer_despeck(struct scanner *s, int side)
{
  SANE_Status ret = SANE_STATUS_GOOD;

  DBG (10, "buffer_despeck: start\n");

  ret = sane_get_parameters((SANE_Handle) s, &s->s_params);

  ret = sanei_magic_despeck(&s->s_params,s->buffers[side],s->swdespeck);
  if(ret){
    DBG (5, "buffer_despeck: bad despeck, bailing\n");
    ret = SANE_STATUS_GOOD;
    goto cleanup;
  }

  cleanup:
  DBG (10, "buffer_despeck: finish\n");
  return ret;
}

/* Look if image has too few dark pixels.*/
static int
buffer_isblank(struct scanner *s, int side)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int status = 0;

  DBG (10, "buffer_isblank: start\n");

  ret = sane_get_parameters((SANE_Handle) s, &s->s_params);

  ret = sanei_magic_isBlank2(&s->s_params, s->buffers[side],
    s->u.dpi_x, s->u.dpi_y, s->swskip);

  if(ret == SANE_STATUS_NO_DOCS){
    DBG (5, "buffer_isblank: blank!\n");
    status = 1;
  }
  else if(ret){
    DBG (5, "buffer_isblank: error %d\n",ret);
  }

  DBG (10, "buffer_isblank: finished\n");
  return status;
}

/* certain options require the entire image to
 * be collected from the scanner before we can
 * tell the user the size of the image. */
static int
must_fully_buffer(struct scanner *s)
{

  if(
    (s->swdeskew || s->swdespeck || s->swcrop)
    && s->s.format != SANE_FRAME_JPEG
  ){
    return 1;
  }

  return 0;
}

/* certain scanners require the mode of the
 * image to be changed in software. */
static int
must_downsample(struct scanner *s)
{
  if(s->s.mode != s->i.mode
    && s->compress != COMP_JPEG
  ){
    return 1;
  }

  return 0;
}

/* Function to build a lookup table (LUT), often
   used by scanners to implement brightness/contrast/gamma
   or by backends to speed binarization/thresholding

   offset and slope inputs are -127 to +127

   slope rotates line around central input/output val,
   0 makes horizontal line

       pos           zero          neg
       .       x     .             .  x
       .      x      .             .   x
   out .     x       .xxxxxxxxxxx  .    x
       .    x        .             .     x
       ....x.......  ............  .......x....
            in            in            in

   offset moves line vertically, and clamps to output range
   0 keeps the line crossing the center of the table

       pos            zero          neg
       .   xxxxxxxx   .        xx   .
       . x            .      x      .
   out x              .    x        .          x
       .              .  x          .        x
       ............   xx..........  xxxxxxxx....
            in             in

   out_min/max provide bounds on output values,
   useful when building thresholding lut.
   0 and 255 are good defaults otherwise.
  */
static SANE_Status
load_lut (unsigned char * lut,
  int in_bits, int out_bits,
  int out_min, int out_max,
  int slope, int offset)
{
  SANE_Status ret = SANE_STATUS_GOOD;
  int i, j;
  double shift, rise;
  int max_in_val = (1 << in_bits) - 1;
  int max_out_val = (1 << out_bits) - 1;
  unsigned char * lut_p = lut;

  DBG (10, "load_lut: start %d %d\n", slope, offset);

  /* slope is converted to rise per unit run:
   * first [-127,127] to [-.999,.999]
   * then to [-PI/4,PI/4] then [0,PI/2]
   * then take the tangent (T.O.A)
   * then multiply by the normal linear slope
   * because the table may not be square, i.e. 1024x256*/
  rise = tan((double)slope/128 * M_PI_4 + M_PI_4) * max_out_val / max_in_val;

  /* line must stay vertically centered, so figure
   * out vertical offset at central input value */
  shift = (double)max_out_val/2 - (rise*max_in_val/2);

  /* convert the user offset setting to scale of output
   * first [-127,127] to [-1,1]
   * then to [-max_out_val/2,max_out_val/2]*/
  shift += (double)offset / 127 * max_out_val / 2;

  for(i=0;i<=max_in_val;i++){
    j = rise*i + shift;

    if(j<out_min){
      j=out_min;
    }
    else if(j>out_max){
      j=out_max;
    }

    *lut_p=j;
    lut_p++;
  }

  hexdump(5, "load_lut: ", lut, max_in_val+1);

  DBG (10, "load_lut: finish\n");
  return ret;
}
