/*
vim: ts=4 sw=4 noexpandtab
 */

/* st400.c - SANE module for Siemens ST400 flatbed scanner

   Copyright (C) 1999-2000 Ingo Wilken (Ingo.Wilken@informatik.uni-oldenburg.de)

   This file is part of the SANE package.

   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation; either version 2 of the
   License, or (at your option) any later version.

   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston,
   MA 02111-1307, USA.

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

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

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

   *************************************************************************

   This file implements a SANE backend for the Siemens ST400 flatbed scanner.

   Unfortunately, I have no documentation for this scanner.  All I have
   is an old PC Pascal source and an Amiga C source (derived from the
   Pascal source).  Both are quite primitive, and so is this backend...

   Version numbers of this backend follow SANE version scheme:  The first
   number is SANE's major version (i.e. the version of the SANE API that
   this backend conforms to), the second is the version of this backend.
   Thus, version 1.2 is the second release of this backend for SANE v1.

   1.0 (08 Mar 1999): First public release, for SANE v1.0.0
   1.1 (12 Mar 1999): Fixed some stupid bugs (caused crash if accessed via net).
   1.2 (23 Apr 1999): Oops, got threshold backwards.
                      Tested with SANE 1.0.1.
   1.3 (27 Apr 1999): Seems the separate MODE SELECT to switch the light on
                      is not necessary.  Removed debugging output via syslog,
                      it was only used to track down a bug in saned.  Some
					  minor cleanups.  Removed illegal version check (only
					  frontends should do this).  Made "maxread" and "delay"
					  config options instead of compile-time #define's.
					  Added model check via INQUIRY, and related changes.
   1.4 (29 Jun 1999): New config options to configure scanner models.
					  See st400.conf for details.  These options are only
					  for testing, and will be removed someday.
   1.5 (26 Mar 2000): Check for optnum >= 0.  Fixed some hardcoded paths in
   					  manpage.  ST400 locks up with reads >64KB - added
					  maxread entry to model struct.  Tested with SANE 1.0.2.
   1.6 (08 Apr 2000): Minor cleanups.
*/

#include "sane/config.h"

#include <sys/types.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <ctype.h>
#include "sane/sane.h"
#include "sane/sanei.h"
#include "sane/sanei_config.h"
#include "sane/saneopts.h"
#include "sane/sanei_scsi.h"
#include "sane/sanei_debug.h"
#ifndef PATH_MAX
#	define PATH_MAX	1024
#endif

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

#include "st400.h"

/* supported scanners */
static ST400_Model st400_models[] = {
{ 8, "SIEMENS", 16, "ST 400", 6, 0x200000UL, 65536UL, NULL, "Siemens", "ST400", "flatbed scanner" },
{ 8, "SIEMENS", 16, "ST 800", 6, 0x200000UL, 65536UL, NULL, "Siemens", "ST800", "flatbed scanner" },	/* to be tested */
{ 0, "", 0, "", 6, 0x200000UL, 65536UL, NULL, "Unknown", "untested", "flatbed scanner" },	/* matches anything */

	/* must be last */
	{ 0, NULL }
};

#define MM_PER_INCH		25.4

static ST400_Device *st400_devices = NULL;
static unsigned int st400_num_devices = 0;
static const SANE_Device **st400_device_array = NULL;
/* The array pointer must stay valid between calls to sane_get_devices().
 * So we cannot modify or deallocate the array when a new device is attached
 * - until the next call to sane_get_devices().  The array_valid bit indicates
 * whether the array is still in sync with the device list or not (if new
 * devices have been attached in the meantime).
 */
static struct {
	unsigned array_valid: 1;
} st400_status = { 0 };
static size_t st400_maxread = 0;
static size_t st400_light_delay = 0;
static int st400_dump_data = 0;

/* SCSI commands */
#define CMD_TEST_UNIT_READY		0x00
#define CMD_INQUIRY				0x12
#define CMD_MODE_SELECT			0x15
#define CMD_RESERVE				0x16
#define CMD_RELEASE				0x17
#define CMD_START_STOP			0x1b
#define CMD_SET_WINDOW			0x24
#define CMD_READ_CAPACITY		0x25	/* get window settings - unused */
#define CMD_READ10				0x28

/* prototypes */
static SANE_Status st400_inquiry( int fd, ST400_Model **modelP );
static SANE_Status st400_sense_handler( int fd, SANE_Byte *result, void *arg );
static SANE_Status st400_attach( const char *devname, ST400_Device **devP );
static SANE_Status st400_attach_one( const char *device );
static void st400_init_options( ST400_Device *dev );
static SANE_Status st400_set_window( ST400_Device *dev );
static SANE_Status st400_wait_ready( int fd );
static SANE_Status st400_cmd6( int fd, SANE_Byte cmd, SANE_Byte ctrl );
static SANE_Status st400_read10( int fd, SANE_Byte *buf, size_t *lenP );
static void st400_reset_options( ST400_Device *dev );

#undef min
#define min(a, b)		((a) < (b) ? (a) : (b))
#define maxval(bpp)		((1<<(bpp))-1)

/* Debugging levels */
#define DERR	0		/* errors */
#define DINFO	1		/* misc information */
#define DSENSE	2		/* SCSI sense */
#define DSCSI	3		/* SCSI commands */
#define DOPT	4		/* option control */
#define DVAR	5		/* important variables */
#define DCODE	6		/* code flow */


/*********************************************************************
 * low-level SCSI functions
 *********************************************************************/

#define set24(m, x)	do {										\
	*((SANE_Byte *)(m)+0) = (SANE_Byte)(((x) >> 16) & 0xff);	\
	*((SANE_Byte *)(m)+1) = (SANE_Byte)(((x) >> 8) & 0xff);		\
	*((SANE_Byte *)(m)+2) = (SANE_Byte)(((x) >> 0) & 0xff);		\
} while(0)
#define set16(m, x)	do {										\
	*((SANE_Byte *)(m)+0) = (SANE_Byte)(((x) >> 8) & 0xff);		\
	*((SANE_Byte *)(m)+1) = (SANE_Byte)(((x) >> 0) & 0xff);		\
} while(0)


static int str_at_offset(char *str, size_t offset, char *data)
{
	size_t len;

	len = strlen(str);
	return !strncmp(&data[offset], str, len);
}


static SANE_Status
st400_inquiry( int fd, ST400_Model **modelP )
{
	struct { SANE_Byte cmd, lun, reserved[2], tr_len, ctrl; } scsi_cmd;
/*
	struct {
		SANE_Byte devtype, devqual, version;
		SANE_Byte reserved1, additionallength;
		SANE_Byte reserved2[2], flags;
		SANE_Byte vendor[8], product[16], release[4];
		SANE_Byte reserved3[60];
	} inqdata;
*/
	struct { SANE_Byte bytes[96]; } inqdata;

	size_t inqlen;
	SANE_Status status;
	ST400_Model *model;

	inqlen = sizeof(inqdata);
	memset(&scsi_cmd, 0, sizeof(scsi_cmd));
	scsi_cmd.cmd = CMD_INQUIRY;
	scsi_cmd.tr_len = inqlen;

	DBG(DSCSI, "SCSI: sending INQUIRY (%lu bytes)\n", (u_long)inqlen);
	status = sanei_scsi_cmd(fd, &scsi_cmd, sizeof(scsi_cmd), &inqdata, &inqlen);
	DBG(DSCSI, "SCSI: result=%s (%lu bytes)\n", sane_strstatus(status), (u_long)inqlen);
	if( status != SANE_STATUS_GOOD )
		return status;

	if( st400_dump_data ) {
		FILE *fp;

		fp = fopen("/tmp/st400.dump", "ab");
		if( fp != NULL ) {
			fwrite(inqdata.bytes, 1, inqlen, fp);
			fclose(fp);
		}
	}

	if( inqlen != sizeof(inqdata) )
		return SANE_STATUS_IO_ERROR;

	for( model = st400_models; model->inq_vendor; model++ ) {
		if( str_at_offset(model->inq_vendor, model->inq_voffset, inqdata.bytes) && str_at_offset(model->inq_model, model->inq_moffset, inqdata.bytes) ) {
			*modelP = model;
			DBG(DINFO, "found matching scanner model \"%s %s\" in list\n", model->sane_vendor, model->sane_model);
			return SANE_STATUS_GOOD;
		}
	}

	return SANE_STATUS_UNSUPPORTED;
}

static SANE_Status
st400_cmd6( int fd, SANE_Byte cmd, SANE_Byte ctrl )
{
	struct { SANE_Byte cmd, lun, reserved[2], tr_len, ctrl; } scsi_cmd;
	SANE_Status status;

	memset(&scsi_cmd, 0, sizeof(scsi_cmd));
	scsi_cmd.cmd = cmd;
	scsi_cmd.ctrl = ctrl;

	DBG(DSCSI, "SCSI: sending cmd6 0x%02x (ctrl=%d)\n", (int)cmd, (int)ctrl);
	status = sanei_scsi_cmd(fd, &scsi_cmd, sizeof(scsi_cmd), 0, 0);
	DBG(DSCSI, "SCSI: result=%s\n", sane_strstatus(status));

	return status;
}

#define st400_test_ready(fd)	st400_cmd6(fd, CMD_TEST_UNIT_READY, 0)
#define st400_reserve(fd)		st400_cmd6(fd, CMD_RESERVE, 0)
#define st400_release(fd)		st400_cmd6(fd, CMD_RELEASE, 0)
#define st400_start_scan(fd)	st400_cmd6(fd, CMD_START_STOP, 0)
#define st400_light_on(fd)		st400_cmd6(fd, CMD_MODE_SELECT, 0x80)
#define st400_light_off(fd)		st400_cmd6(fd, CMD_MODE_SELECT, 0)


static SANE_Status
st400_wait_ready( int fd )
{
#define SLEEPTIME	100000L		/* 100ms */
	long max_sleep = 60000000L;	/* 60 seconds */
	SANE_Status status;

	DBG(DCODE, "st400_wait_ready(%d)\n", fd);

	while(1) {
		status = st400_test_ready(fd);
		switch( status ) {
			case SANE_STATUS_DEVICE_BUSY:
				if( max_sleep > 0 ) {
					usleep(SLEEPTIME);	/* retry after 100ms */
					max_sleep -= SLEEPTIME;
					break;
				}
				/* else fall through */
			default:
				DBG(DERR, "st400_wait_ready: failed, error=%s\n", sane_strstatus(status));
				/* fall through */
			case SANE_STATUS_GOOD:
				return status;
		}
	}
	/*NOTREACHED*/
}


static SANE_Status
st400_set_window( ST400_Device *dev )
{
	unsigned short xoff, yoff;
	SANE_Byte th;

	struct {
		/* 10byte command */
		SANE_Byte cmd, lun, reserved1[4], tr_len[3], ctrl;

		/* 40byte window struct */
		SANE_Byte reserved2[6], wd_len[2], winnr, reserved3;
		SANE_Byte x_res[2], y_res[2];			/* resolution: 200, 300, 400 */
		SANE_Byte x_ul[2], y_ul[2];				/* upper left corner */
		SANE_Byte width[2], height[2];
		SANE_Byte reserved4, threshold;
		SANE_Byte reserved5, halftone;			/* ht: 0 or 2 */
		SANE_Byte bitsperpixel, reserved6[13];	/* bpp: 1 or 8 */
	} scsi_cmd;
	/* The PC/Amiga source uses reserved5 to indicate A4/A5 paper size
	 * (values 4 and 5), but a comment implies that this is only for the
	 * scanning program and the value is ignored by the scanner.
	 */
	SANE_Status status;

	memset(&scsi_cmd, 0, sizeof(scsi_cmd));
	scsi_cmd.cmd = CMD_SET_WINDOW;
	set24(scsi_cmd.tr_len, 40);
	set16(scsi_cmd.wd_len, 32);

	/* These offsets seem to be required to avoid damaging the scanner:
	 * If a scan with 0/0 as the top left corner is started, the scanner
	 * seems to try to move the carriage over the bottom end (not a
	 * pretty sound).
	 */
	xoff = (11L * dev->val[OPT_RESOLUTION]) / 100;
	yoff = 6;
	th = (double)maxval(dev->model->bits) * SANE_UNFIX(dev->val[OPT_THRESHOLD]) / 100.0;

	scsi_cmd.winnr = 1;
	set16(scsi_cmd.x_res, (unsigned short)dev->val[OPT_RESOLUTION]);
	set16(scsi_cmd.y_res, (unsigned short)dev->val[OPT_RESOLUTION]);
	set16(scsi_cmd.x_ul, dev->x + xoff);
	set16(scsi_cmd.y_ul, dev->wy + yoff);
	set16(scsi_cmd.width, dev->w);
	set16(scsi_cmd.height, dev->wh);
	scsi_cmd.threshold = th;
	scsi_cmd.halftone = (dev->val[OPT_DEPTH] == 1) ? 0 : 2;
	scsi_cmd.bitsperpixel = dev->val[OPT_DEPTH];

	DBG(DSCSI, "SCSI: sending SET_WINDOW (x=%hu y=%hu w=%hu h=%hu wy=%hu wh=%hu th=%d\n", dev->x, dev->y, dev->w, dev->h, dev->wy, dev->wh, (int)th);
	status = sanei_scsi_cmd(dev->fd, &scsi_cmd, sizeof(scsi_cmd), 0, 0);
	DBG(DSCSI, "SCSI: result=%s\n", sane_strstatus(status));

	return status;
}

static SANE_Status
st400_read10( int fd, SANE_Byte *buf, size_t *lenP )
{
	struct { SANE_Byte cmd, lun, res[4], tr_len[3], ctrl; } scsi_cmd;
	SANE_Status status;

	memset(&scsi_cmd, 0, sizeof(scsi_cmd));
	scsi_cmd.cmd = CMD_READ10;
	set24(scsi_cmd.tr_len, *lenP);

	DBG(DSCSI, "SCSI: sending READ10 (%lu bytes)\n", (u_long)(*lenP));
	status = sanei_scsi_cmd(fd, &scsi_cmd, sizeof(scsi_cmd), buf, lenP);
	DBG(DSCSI, "SCSI: result=%s (%lu bytes)\n", sane_strstatus(status), (u_long)(*lenP));

	return status;
}

static SANE_Status
st400_fill_scanner_buffer( ST400_Device *dev )
{
	SANE_Status status;

	DBG(DCODE, "st400_fill_scanner_buffer(%p)\n", dev);

	if( dev->lines_to_read == 0 )
		dev->status.eof = 1;
	if( dev->status.eof )
		return SANE_STATUS_EOF;

	dev->wh = dev->model->bufsize / dev->params.bytes_per_line;
	if( dev->wh > dev->lines_to_read )
		dev->wh = dev->lines_to_read;
	DBG(DVAR, "dev->wh = %hu\n", dev->wh);

	status = st400_set_window(dev);
	if( status != SANE_STATUS_GOOD )
		return status;

	status = st400_start_scan(dev->fd);
	if( status != SANE_STATUS_GOOD )
		return status;

	dev->wy += dev->wh;
	dev->lines_to_read -= dev->wh;
	dev->bytes_in_scanner = dev->wh * dev->params.bytes_per_line;

	return SANE_STATUS_GOOD;
}

static SANE_Status
st400_fill_backend_buffer( ST400_Device *dev )
{
	size_t r;
	SANE_Status status;

	DBG(DCODE, "st400_fill_backend_buffer(%p)\n", dev);

	if( dev->bytes_in_scanner == 0 ) {
		status = st400_fill_scanner_buffer(dev);
		if( status != SANE_STATUS_GOOD )
			return status;
	}

	r = min(dev->bufsize, dev->bytes_in_scanner);
	status = st400_read10(dev->fd, dev->buffer, &r);
	if( status == SANE_STATUS_GOOD ) {
		dev->bufp = dev->buffer;
		dev->bytes_in_buffer = r;
		dev->bytes_in_scanner -= r;

		if( r == 0 )
			dev->status.eof = 1;
	}

	return status;
}

static SANE_Status
st400_sense_handler( int fd, SANE_Byte *result, void *arg )
{
	/* ST400_Device *dev = arg; */
	SANE_Status status;

	switch( result[0] & 0x0f ) {
		case 0x0:
			status = SANE_STATUS_GOOD;
			break;
		case 0x1:
			DBG(DSENSE, "SCSI: sense RECOVERED_ERROR\n");
			status = SANE_STATUS_GOOD;	/* ?? */
			break;
		case 0x2:
			DBG(DSENSE, "SCSI: sense NOT_READY\n");
			status = SANE_STATUS_DEVICE_BUSY;
			break;
		case 0x4:
			DBG(DSENSE, "SCSI: sense HARDWARE_ERROR\n");
			status = SANE_STATUS_IO_ERROR;
			break;
		case 0x5:
			DBG(DSENSE, "SCSI: sense ILLEGAL_REQUEST\n");
			status = SANE_STATUS_IO_ERROR;
			break;
		case 0x6:
			DBG(DSENSE, "SCSI: sense UNIT_ATTENTION\n");
			status = SANE_STATUS_DEVICE_BUSY;
			break;
		case 0xb:
			DBG(DSENSE, "SCSI: sense ABORTED_COMMAND\n");
			status = SANE_STATUS_CANCELLED;	/* ?? */
			break;
		default:
			DBG(DSENSE, "SCSI: sense unknown (%d)\n", result[0] & 0x0f);
			status = SANE_STATUS_IO_ERROR;
			break;
	}
	return status;
}


/*********************************************************************
 * Sane initializing stuff
 *********************************************************************/

static SANE_Status
st400_attach( const char *devname, ST400_Device **devP )
{
	ST400_Device *dev;
	ST400_Model *model;
	SANE_Status status;
	int fd;

	DBG(DCODE, "st400_attach(%s, %p)\n", devname, devP);
	if( devP )
		*devP = NULL;

	for( dev = st400_devices; dev != NULL; dev = dev->next ) {
		if( strcmp(dev->sane.name, devname) == 0 ) {
			if( devP )
				*devP = dev;
			DBG(DCODE, "st400_attach: found device in list\n");
			return SANE_STATUS_GOOD;
		}
	}

	dev = calloc(1, sizeof(*dev));
	if( !dev )
		return SANE_STATUS_NO_MEM;
	DBG(DCODE, "st400_attach: new device struct at %p\n", dev);

	status = sanei_scsi_open(devname, &fd, st400_sense_handler, dev);
	if( status == SANE_STATUS_GOOD ) {
		status = st400_inquiry(fd, &model);
		if( status == SANE_STATUS_GOOD )
			status = st400_test_ready(fd);
		sanei_scsi_close(fd);
	}
	if( status != SANE_STATUS_GOOD ) {
		free(dev);
		return status;
	}

	/* initialize device structure */
	dev->sane.name = strdup(devname);
	if( !dev->sane.name ) {
		free(dev);
		return SANE_STATUS_NO_MEM;
	}
	dev->sane.vendor = model->sane_vendor;
	dev->sane.model = model->sane_model;
	dev->sane.type = model->sane_type;
	dev->status.open = 0;
	dev->status.scanning = 0;
	dev->status.eof = 0;
	dev->fd = -1;
	dev->buffer = NULL;
	dev->model = model;

	st400_init_options(dev);

	DBG(DCODE, "st400_attach: everything ok, adding device to list\n");

	dev->next = st400_devices;
	st400_devices = dev;
	++st400_num_devices;
	st400_status.array_valid = 0;

	if( devP )
		*devP = dev;
	return SANE_STATUS_GOOD;
}

static SANE_Status
st400_attach_one( const char *device )
{
	DBG(DCODE, "st400_attach_one(%s)\n", device);
	return st400_attach(device, NULL);
}

static SANE_Status
st400_config_get_arg(char **optP, unsigned long *argP, size_t linenum)
{
	int n;

	if( sscanf(*optP, "%lu%n", argP, &n) == 1 ) {
		*optP += n;
		*optP = (char *)sanei_config_skip_whitespace(*optP);
		return SANE_STATUS_GOOD;
	}
	return SANE_STATUS_INVAL;
}


static SANE_Status
st400_config_get_single_arg(char *opt, unsigned long *argP, size_t linenum)
{
	int n;

	if( sscanf(opt, "%lu%n", argP, &n) == 1 ) {
		opt += n;
		opt = (char *)sanei_config_skip_whitespace(opt);
		if( *opt == '\0' )
			return SANE_STATUS_GOOD;
		else {
			DBG(DERR, "extraneous arguments at line %lu: %s\n", (u_long)linenum, opt);
			return SANE_STATUS_INVAL;
		}
	}
	DBG(DERR, "invalid option argument at line %lu: %s\n", (u_long)linenum, opt);
	return SANE_STATUS_INVAL;
}


static SANE_Status
st400_config_do_option(char *opt, size_t linenum)
{
	unsigned long arg;
	SANE_Status status;
	int i;

	status = SANE_STATUS_GOOD;

	opt = (char *)sanei_config_skip_whitespace(opt);
	if( strncmp(opt, "maxread", 7) == 0 && isspace(opt[7]) ) {
		opt += 8;
		status = st400_config_get_single_arg(opt, &arg, linenum);
		if( status == SANE_STATUS_GOOD ) {
			if( arg == 0 )
				st400_maxread = sanei_scsi_max_request_size;
			else
				st400_maxread = (size_t)arg;
		}
	}
	else
	if( strncmp(opt, "delay", 5) == 0 && isspace(opt[5]) ) {
		opt += 6;
		status = st400_config_get_single_arg(opt, &arg, linenum);
		if( status == SANE_STATUS_GOOD )
			st400_light_delay = (size_t)arg;
	}
	else
	if( strncmp(opt, "scanner_bufsize", 15) == 0 && isspace(opt[15]) ) {
		opt += 16;
		status = st400_config_get_single_arg(opt, &arg, linenum);
		if( status == SANE_STATUS_GOOD )
			if( st400_devices )
				st400_devices->model->bufsize = arg;	/* FIXME: changes bufsize for all scanners of this model! */
	}
	else
	if( strncmp(opt, "scanner_bits", 12) == 0 && isspace(opt[12]) ) {
		opt += 13;
		status = st400_config_get_single_arg(opt, &arg, linenum);
		if( status == SANE_STATUS_GOOD )
			if( st400_devices )
				st400_devices->model->bits = arg;	/* FIXME */
	}
	else
	if( strncmp(opt, "scanner_maxread", 15) == 0 && isspace(opt[15]) ) {
		opt += 16;
		status = st400_config_get_single_arg(opt, &arg, linenum);
		if( status == SANE_STATUS_GOOD )
			if( st400_devices )
				st400_devices->model->maxread = arg;	/* FIXME */
	}
	else
	if( strncmp(opt, "scanner_resolutions", 19) == 0 && isspace(opt[19]) ) {
		opt += 20;
		st400_devices->model->dpi_list = malloc(16 * sizeof(SANE_Int));
		i = 0;
		do {
			status = st400_config_get_arg(&opt, &arg, linenum);
			if( status == SANE_STATUS_GOOD ) {
				++i;
				st400_devices->model->dpi_list[i] = (SANE_Int)arg;
			}
		} while( status == SANE_STATUS_GOOD && i < 15 );
		st400_devices->model->dpi_list[0] = i;
		DBG(DINFO, "%d entries for resolution\n", i);
		status = SANE_STATUS_GOOD;
	}
	else
	if( strncmp(opt, "dump_inquiry", 12) == 0 ) {
		st400_dump_data = 1;
	}
	if( st400_devices )
		st400_reset_options(st400_devices);
	return status;
}

SANE_Status
sane_init( SANE_Int *versionP, SANE_Auth_Callback authorize )
{
	FILE *fp;
	SANE_Status status;

	DBG_INIT();
	DBG(DCODE, "sane_init(%p, %p)\n", versionP, authorize);

	if( versionP != NULL )
		*versionP = SANE_VERSION_CODE(V_MAJOR, V_MINOR, 0);

	status = SANE_STATUS_GOOD;
	if( (fp = sanei_config_open(ST400_CONFIG_FILE)) != NULL ) {
		char line[PATH_MAX], *str;
		size_t len, linenum;

		linenum = 0;
		DBG(DCODE, "sane_init: reading config file\n");
		while( sanei_config_read(line, sizeof(line), fp) ) {
			++linenum;
			str = line;
			if( str[0] == '#' )
				continue;	/* ignore comments */
			str = (char *)sanei_config_skip_whitespace(str);
			len = strlen(str);
			if( !len )
				continue;	/* ignore empty lines */
			if( strncmp(str, "option", 6) == 0 && isspace(str[6]) ) {
				DBG(DCODE, "sane_init: config line <%s>\n", line);
				status = st400_config_do_option(str+7, linenum);
			}
			else {
				DBG(DCODE, "sane_init: attaching device <%s>\n", line);
				sanei_config_attach_matching_devices(line, st400_attach_one);
			}
			if( status != SANE_STATUS_GOOD )
				break;
		}
		DBG(DCODE, "sane_init: closing config file\n");
		fclose(fp);
	}

	if( status == SANE_STATUS_GOOD && st400_devices == NULL ) {
		DBG(DCODE, "sane_init: attaching default device <%s>\n", ST400_DEFAULT_DEVICE);
		sanei_config_attach_matching_devices(ST400_DEFAULT_DEVICE, st400_attach_one);
	}

	return status;
}

void
sane_exit( void )
{
	ST400_Device *dev;

	DBG(DCODE, "sane_exit()\n");

	while( (dev = st400_devices) != NULL ) {
		st400_devices = dev->next;

		sane_close(dev);
		free((char *)(dev->sane.name));
		free(dev);
	}
	st400_num_devices = 0;
	if( st400_device_array ) {
		DBG(DCODE, "sane_exit: freeing device array\n");
		free(st400_device_array);
		st400_device_array = NULL;
		st400_status.array_valid = 0;
	}
}

SANE_Status
sane_get_devices( const SANE_Device ***devarrayP, SANE_Bool local_only )
{
	ST400_Device *dev;
	unsigned int i;

	DBG(DCODE, "sane_get_devices(%p, %d)\n", devarrayP, (int)local_only);

	if( !st400_status.array_valid ) {
		if( st400_device_array ) {
			DBG(DCODE, "sane_get_devices: freeing old device array\n");
			free(st400_device_array);
		}
		st400_device_array = malloc((st400_num_devices + 1) * sizeof(*st400_device_array));
		if( !st400_device_array )
			return SANE_STATUS_NO_MEM;
		DBG(DCODE, "sane_get_devices: new device array at %p\n", st400_device_array);

		dev = st400_devices;
		for( i = 0; i < st400_num_devices; i++ ) {
			st400_device_array[i] = &dev->sane;
			dev = dev->next;
		}
		st400_device_array[st400_num_devices] = NULL;
		st400_status.array_valid = 1;
	}
	DBG(DCODE, "sane_get_devices: %u entries in device array\n", st400_num_devices);
	if( devarrayP )
		*devarrayP = st400_device_array;
	return SANE_STATUS_GOOD;
}


SANE_Status
sane_open( SANE_String_Const devicename, SANE_Handle *handleP )
{
	ST400_Device *dev;
	SANE_Status status;

	DBG(DCODE, "sane_open(%s, %p)\n", devicename, handleP);

	*handleP = NULL;
	if( devicename && devicename[0] ) {
		status = st400_attach(devicename, &dev);
		if( status != SANE_STATUS_GOOD )
			return status;
	}
	else
		dev = st400_devices;

	if( !dev )
		return SANE_STATUS_INVAL;

	if( dev->status.open )
		return SANE_STATUS_DEVICE_BUSY;

	dev->status.open = 1;
	st400_reset_options(dev);
	*handleP = (SANE_Handle)dev;

	return SANE_STATUS_GOOD;
}


void
sane_close( SANE_Handle handle )
{
	ST400_Device *dev = handle;

	DBG(DCODE, "sane_close(%p)\n", handle);

	if( dev->status.open ) {
		sane_cancel(dev);
		dev->status.open = 0;
	}
}


/*
 * options
 */
static void
st400_reset_options( ST400_Device *dev )
{
	DBG(DCODE, "st400_reset_options(%p)\n", dev);

	dev->val[OPT_NUM_OPTS]	= NUM_OPTIONS;
	dev->val[OPT_RESOLUTION]	= dev->opt[OPT_RESOLUTION].constraint.word_list[1];
	dev->val[OPT_DEPTH]		= dev->opt[OPT_DEPTH].constraint.word_list[1];
    dev->val[OPT_THRESHOLD]	= SANE_FIX(50.0);
	dev->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
    dev->val[OPT_TL_X]		= SANE_FIX(0.0);
	dev->val[OPT_TL_Y]		= SANE_FIX(0.0);
	dev->val[OPT_BR_X]		= SANE_FIX(0.0);
	dev->val[OPT_BR_Y]		= SANE_FIX(0.0);

	if( dev->model->dpi_list )
		dev->opt[OPT_RESOLUTION].constraint.word_list = dev->model->dpi_list;
}

static void
st400_init_options( ST400_Device *dev )
{
	static const SANE_Int depth_list[] = { 2, 1, 8 };
	static const SANE_Int dpi_list[] = { 3, 200, 300, 400 };
	static const SANE_Range thres_range = {
		SANE_FIX(0.0), SANE_FIX(100.0), SANE_FIX(0.0)
	};
	static const SANE_Range x_range = {
		SANE_FIX(0.0), SANE_FIX(ST400_MAX_X * MM_PER_INCH), SANE_FIX(0.0)
	};
	static const SANE_Range y_range = {
		SANE_FIX(0.0), SANE_FIX(ST400_MAX_Y * MM_PER_INCH), SANE_FIX(0.0)
	};

	DBG(DCODE, "st400_init_options(%p)\n", dev);

	dev->opt[OPT_NUM_OPTS].name	= SANE_NAME_NUM_OPTIONS;
	dev->opt[OPT_NUM_OPTS].title	= SANE_TITLE_NUM_OPTIONS;
	dev->opt[OPT_NUM_OPTS].desc		= SANE_DESC_NUM_OPTIONS;
	dev->opt[OPT_NUM_OPTS].type	= SANE_TYPE_INT;
	dev->opt[OPT_NUM_OPTS].unit	= SANE_UNIT_NONE;
	dev->opt[OPT_NUM_OPTS].size = sizeof(SANE_Word);
	dev->opt[OPT_NUM_OPTS].cap	= SANE_CAP_SOFT_DETECT;
	dev->opt[OPT_NUM_OPTS].constraint_type = SANE_CONSTRAINT_NONE;

	dev->opt[OPT_MODE_GROUP].title= "Scan Mode";
	dev->opt[OPT_MODE_GROUP].type = SANE_TYPE_GROUP;

	dev->opt[OPT_RESOLUTION].name	= SANE_NAME_SCAN_RESOLUTION;
	dev->opt[OPT_RESOLUTION].title= SANE_TITLE_SCAN_RESOLUTION;
	dev->opt[OPT_RESOLUTION].desc	= SANE_DESC_SCAN_RESOLUTION;
	dev->opt[OPT_RESOLUTION].type	= SANE_TYPE_INT;
	dev->opt[OPT_RESOLUTION].unit	= SANE_UNIT_DPI;
	dev->opt[OPT_RESOLUTION].size = sizeof(SANE_Word);
	dev->opt[OPT_RESOLUTION].cap	= SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT;
	dev->opt[OPT_RESOLUTION].constraint_type = SANE_CONSTRAINT_WORD_LIST;
	dev->opt[OPT_RESOLUTION].constraint.word_list = dpi_list;

	dev->opt[OPT_DEPTH].name	= SANE_NAME_BIT_DEPTH;
	dev->opt[OPT_DEPTH].title	= SANE_TITLE_BIT_DEPTH;
	dev->opt[OPT_DEPTH].desc	= SANE_DESC_BIT_DEPTH;
	dev->opt[OPT_DEPTH].type	= SANE_TYPE_INT;
	dev->opt[OPT_DEPTH].unit	= SANE_UNIT_BIT;
	dev->opt[OPT_DEPTH].size	= sizeof(SANE_Word);
	dev->opt[OPT_DEPTH].cap	= SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT;
	dev->opt[OPT_DEPTH].constraint_type = SANE_CONSTRAINT_WORD_LIST;
	dev->opt[OPT_DEPTH].constraint.word_list = depth_list;

	dev->opt[OPT_THRESHOLD].name	= SANE_NAME_THRESHOLD;
	dev->opt[OPT_THRESHOLD].title	= SANE_TITLE_THRESHOLD;
	dev->opt[OPT_THRESHOLD].desc	= SANE_DESC_THRESHOLD;
	dev->opt[OPT_THRESHOLD].type	= SANE_TYPE_FIXED;
	dev->opt[OPT_THRESHOLD].unit	= SANE_UNIT_PERCENT;
	dev->opt[OPT_THRESHOLD].size	= sizeof(SANE_Word);
	dev->opt[OPT_THRESHOLD].cap	= SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT;
	dev->opt[OPT_THRESHOLD].constraint_type = SANE_CONSTRAINT_RANGE;
	dev->opt[OPT_THRESHOLD].constraint.range = &thres_range;

	dev->opt[OPT_GEOMETRY_GROUP].title= "Geometry";
	dev->opt[OPT_GEOMETRY_GROUP].type = SANE_TYPE_GROUP;

	dev->opt[OPT_TL_X].name	= SANE_NAME_SCAN_TL_X;
	dev->opt[OPT_TL_X].title	= SANE_TITLE_SCAN_TL_X;
	dev->opt[OPT_TL_X].desc	= SANE_DESC_SCAN_TL_X;
	dev->opt[OPT_TL_X].type	= SANE_TYPE_FIXED;
	dev->opt[OPT_TL_X].unit	= SANE_UNIT_MM;
	dev->opt[OPT_TL_X].size	= sizeof(SANE_Word);
	dev->opt[OPT_TL_X].cap	= SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT;
	dev->opt[OPT_TL_X].constraint_type = SANE_CONSTRAINT_RANGE;
	dev->opt[OPT_TL_X].constraint.range = &x_range;

	dev->opt[OPT_TL_Y].name	= SANE_NAME_SCAN_TL_Y;
	dev->opt[OPT_TL_Y].title	= SANE_TITLE_SCAN_TL_Y;
	dev->opt[OPT_TL_Y].desc	= SANE_DESC_SCAN_TL_Y;
	dev->opt[OPT_TL_Y].type	= SANE_TYPE_FIXED;
	dev->opt[OPT_TL_Y].unit	= SANE_UNIT_MM;
	dev->opt[OPT_TL_Y].size	= sizeof(SANE_Word);
	dev->opt[OPT_TL_Y].cap	= SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT;
	dev->opt[OPT_TL_Y].constraint_type = SANE_CONSTRAINT_RANGE;
	dev->opt[OPT_TL_Y].constraint.range = &y_range;

	dev->opt[OPT_BR_X].name	= SANE_NAME_SCAN_BR_X;
	dev->opt[OPT_BR_X].title	= SANE_TITLE_SCAN_BR_X;
	dev->opt[OPT_BR_X].desc	= SANE_DESC_SCAN_BR_X;
	dev->opt[OPT_BR_X].type	= SANE_TYPE_FIXED;
	dev->opt[OPT_BR_X].unit	= SANE_UNIT_MM;
	dev->opt[OPT_BR_X].size	= sizeof(SANE_Word);
	dev->opt[OPT_BR_X].cap	= SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT;
	dev->opt[OPT_BR_X].constraint_type = SANE_CONSTRAINT_RANGE;
	dev->opt[OPT_BR_X].constraint.range = &x_range;

	dev->opt[OPT_BR_Y].name	= SANE_NAME_SCAN_BR_Y;
	dev->opt[OPT_BR_Y].title	= SANE_TITLE_SCAN_BR_Y;
	dev->opt[OPT_BR_Y].desc	= SANE_DESC_SCAN_BR_Y;
	dev->opt[OPT_BR_Y].type	= SANE_TYPE_FIXED;
	dev->opt[OPT_BR_Y].unit	= SANE_UNIT_MM;
	dev->opt[OPT_BR_Y].size	= sizeof(SANE_Word);
	dev->opt[OPT_BR_Y].cap	= SANE_CAP_SOFT_SELECT|SANE_CAP_SOFT_DETECT;
	dev->opt[OPT_BR_Y].constraint_type = SANE_CONSTRAINT_RANGE;
	dev->opt[OPT_BR_Y].constraint.range = &y_range;

	st400_reset_options(dev);
}

const SANE_Option_Descriptor *
sane_get_option_descriptor( SANE_Handle handle, SANE_Int optnum )
{
	ST400_Device *dev = handle;

	DBG(DOPT, "sane_get_option_descriptor(%p, %d)\n", handle, (int)optnum);

	if( dev->status.open && optnum >= 0 && optnum < NUM_OPTIONS )
		return &dev->opt[optnum];

	return NULL;
}

SANE_Status
sane_control_option( SANE_Handle handle, SANE_Int optnum,
	SANE_Action action, void *valP, SANE_Int *infoP)
{
	ST400_Device *dev = handle;
	SANE_Status status;

	DBG(DCODE, "sane_control_option(%p, %d, %d, %p, %p)\n", handle, (int)optnum, (int)action, valP, infoP);

	if( infoP )
		*infoP = 0;

	if( !dev->status.open )
		return SANE_STATUS_INVAL;
	if( dev->status.scanning )
		return SANE_STATUS_DEVICE_BUSY;

	if( optnum < 0 || optnum >= NUM_OPTIONS )
		return SANE_STATUS_INVAL;

	switch( action ) {
		case SANE_ACTION_GET_VALUE:

			DBG(DOPT, "getting option %d (value=%d)\n", (int)optnum, (int)dev->val[optnum]);

			switch( optnum ) {
				case OPT_NUM_OPTS:
				case OPT_RESOLUTION:
				case OPT_DEPTH:
				case OPT_THRESHOLD:
				case OPT_TL_X:
				case OPT_TL_Y:
				case OPT_BR_X:
				case OPT_BR_Y:
					*(SANE_Word *)valP = dev->val[optnum];
					break;
				default:
					return SANE_STATUS_INVAL;
			}
			break;

		case SANE_ACTION_SET_VALUE:
			if( !SANE_OPTION_IS_SETTABLE(dev->opt[optnum].cap) )
				return SANE_STATUS_INVAL;
			status = sanei_constrain_value(&dev->opt[optnum], valP, infoP);
			if( status != SANE_STATUS_GOOD )
				return status;

			DBG(DOPT, "setting option %d to %d\n", (int)optnum, (int)*(SANE_Word *)valP);

			switch( optnum ) {
				case OPT_DEPTH:
					if( *(SANE_Word *)valP != 1 )
						dev->opt[OPT_THRESHOLD].cap |= SANE_CAP_INACTIVE;
					else
						dev->opt[OPT_THRESHOLD].cap &= ~SANE_CAP_INACTIVE;
					if( infoP )
						*infoP |= SANE_INFO_RELOAD_OPTIONS;
					/* fall through */
				case OPT_RESOLUTION:
				case OPT_TL_X:
				case OPT_TL_Y:
				case OPT_BR_X:
				case OPT_BR_Y:
					if( infoP )
						*infoP |= SANE_INFO_RELOAD_PARAMS;
					/* fall through */
				case OPT_THRESHOLD:
					dev->val[optnum] = *(SANE_Word *)valP;
					break;
				default:
					return SANE_STATUS_INVAL;
			}
			break;

		case SANE_ACTION_SET_AUTO:

			DBG(DOPT, "automatic option setting\n");

			return SANE_STATUS_UNSUPPORTED;

		default:
			return SANE_STATUS_INVAL;
	}
	return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_parameters( SANE_Handle handle, SANE_Parameters *paramsP )
{
	ST400_Device *dev = handle;

	DBG(DCODE, "sane_get_parameters(%p, %p)\n", handle, paramsP);

	if( !dev->status.open )
		return SANE_STATUS_INVAL;

	if( !dev->status.scanning ) {
		double width, height, dpi;

		dev->params.format = SANE_FRAME_GRAY;
		dev->params.last_frame = SANE_TRUE;
		dev->params.lines = 0;
		dev->params.depth = dev->val[OPT_DEPTH];

		width  = SANE_UNFIX(dev->val[OPT_BR_X] - dev->val[OPT_TL_X]);
		height = SANE_UNFIX(dev->val[OPT_BR_Y] - dev->val[OPT_TL_Y]);
		dpi = dev->val[OPT_RESOLUTION];

		/* make best-effort guess at what parameters will look like once
		   scanning starts.  */
		if( dpi > 0.0  &&  width > 0.0  &&  height > 0.0 ) {
			double dots_per_mm = dpi / MM_PER_INCH;

			dev->params.pixels_per_line = width * dots_per_mm + 0.5;
			dev->params.lines = height * dots_per_mm + 0.5;

			if( dev->params.depth == 1 ) {
				/* Pad to an even multiple of 8.  This way we can simply
				 * copy the bytes from the scanner to the SANE buffer
				 * (only need to invert them).
				 */
				dev->params.pixels_per_line += 7;
				dev->params.pixels_per_line &= ~7;

				/*dev->params.bytes_per_line = (dev->params.pixels_per_line + 7)/8;*/
				dev->params.bytes_per_line = dev->params.pixels_per_line/8;
			}
			else
				dev->params.bytes_per_line = dev->params.pixels_per_line;

			dev->x = SANE_UNFIX(dev->val[OPT_TL_X]) * dots_per_mm + 0.5;
			dev->y = SANE_UNFIX(dev->val[OPT_TL_Y]) * dots_per_mm + 0.5;
			dev->w = dev->params.pixels_per_line;
			dev->h = dev->params.lines;

			DBG(DVAR, "parameters: bpl=%d, x=%hu, y=%hu, w=%hu, h=%hu\n", (int)dev->params.bytes_per_line, dev->x, dev->y, dev->w, dev->h);
		}
	}

	if( paramsP )
		*paramsP = dev->params;
	return SANE_STATUS_GOOD;
}


SANE_Status
sane_start( SANE_Handle handle )
{
	ST400_Device *dev = handle;
	SANE_Status status;

	DBG(DCODE, "sane_start(%p)\n", handle);

	if( !dev->status.open )
		return SANE_STATUS_INVAL;
	if( dev->status.scanning )
		return SANE_STATUS_DEVICE_BUSY;

	status = sane_get_parameters(dev, NULL);
	if( status != SANE_STATUS_GOOD )
		return status;

	if( !dev->buffer ) {
		if( st400_maxread > 0 )
			dev->bufsize = min(st400_maxread, sanei_scsi_max_request_size);
		else
		if( dev->model->maxread > 0 )
			dev->bufsize = min(dev->model->maxread, sanei_scsi_max_request_size);
		else
			dev->bufsize = sanei_scsi_max_request_size;
		DBG(DVAR, "allocating %lu bytes buffer\n", (u_long)dev->bufsize);
		dev->buffer = malloc(dev->bufsize);
		if( !dev->buffer )
			return SANE_STATUS_NO_MEM;
	}
	dev->bufp = dev->buffer;
	dev->bytes_in_buffer = 0;

	if( dev->fd < 0 ) {
		status = sanei_scsi_open(dev->sane.name, &dev->fd, st400_sense_handler, dev);
		if( status != SANE_STATUS_GOOD )
			goto return_error;
	}

	dev->status.eof = 0;

	status = st400_wait_ready(dev->fd);
	if( status != SANE_STATUS_GOOD )
		goto close_and_return;

	status = st400_reserve(dev->fd);
	if( status != SANE_STATUS_GOOD )
		goto close_and_return;

	if( st400_light_delay > 0 ) {
		status = st400_light_on(dev->fd);
		if( status != SANE_STATUS_GOOD )
			goto release_and_return;
		usleep(st400_light_delay * 100000);	/* 1/10 seconds */
	}

	dev->wy = dev->y;
	dev->lines_to_read = dev->h;
	dev->bytes_in_scanner = 0;

	status = st400_fill_scanner_buffer(dev);
	if( status != SANE_STATUS_GOOD )
		goto lightoff_and_return;

	/* everything ok */
	dev->status.scanning = 1;
	return SANE_STATUS_GOOD;

lightoff_and_return:
	if( st400_light_delay )
		st400_light_off(dev->fd);
release_and_return:
	st400_release(dev->fd);
close_and_return:
	sanei_scsi_close(dev->fd);
return_error:
	dev->fd = -1;
	return status;
}

void
sane_cancel( SANE_Handle handle )
{
	ST400_Device *dev = handle;

	DBG(DCODE, "sane_cancel(%p)\n", handle);

	if( dev->status.scanning ) {
#if 0
		st400_stop_scan(dev->fd);
#endif
		if( st400_light_delay ) 
			st400_light_off(dev->fd);
		st400_release(dev->fd);
		sanei_scsi_close(dev->fd);
		dev->status.scanning = 0;
		dev->fd = -1;
	}
	if( dev->buffer ) {
		free(dev->buffer);
		dev->buffer = NULL;
	}
}


SANE_Status
sane_read( SANE_Handle handle, SANE_Byte *buf, SANE_Int maxlen, SANE_Int *lenP )
{
	ST400_Device *dev = handle;
	SANE_Status status;
	size_t r, i;
	SANE_Byte val;

	DBG(DCODE, "sane_read(%p, %p, %d, %p)\n", handle, buf, (int)maxlen, lenP);

	*lenP = 0;
	if( !dev->status.scanning )
		return SANE_STATUS_INVAL;
	if( dev->status.eof )
		return SANE_STATUS_EOF;

	status = SANE_STATUS_GOOD;
	while( maxlen > 0 ) {
		if( dev->bytes_in_buffer == 0 ) {
			status = st400_fill_backend_buffer(dev);
			if( status == SANE_STATUS_EOF )
				return SANE_STATUS_GOOD;
			if( status != SANE_STATUS_GOOD ) {
				*lenP = 0;
				return status;
			}
		}

		r = min(dev->bytes_in_buffer, maxlen);

		if( dev->val[OPT_DEPTH] == 1 || dev->model->bits == 8 ) {
			/* This is simple.  We made sure the scanning are is aligned to
			 * 8 pixels (see sane_get_parameters()), so we can simply copy
			 * the stuff - only need to invert it.
			 */
			for( i = 0; i < r; i++ )
				*buf++ = ~(*dev->bufp++);
		}
		else {
			SANE_Byte mv;

			/* The scanner sends bytes with 6bit-values (0..63), where 0 means
			 * white.  To convert to 8bit, we invert the values (so 0 means
			 * black) and then shift them two bits to the left and replicate
			 * the most- significant bits in the lowest two bits of the
			 * 8bit-value:
			 *     bit-pattern x x 5 4 3 2 1 0  becomes  5 4 3 2 1 0 5 4
			 * This is more accurate than simply shifting the values two bits
			 * to the left (e.g. 6bit-white 00111111 gets converted to 8bit-
			 * white 11111111 instead of almost-white 11111100) and is still
			 * reasonably fast.
			 */
			mv = (SANE_Byte)maxval(dev->model->bits);

			/* Note: this works with any bit depth <= 8 */
			for( i = 0; i < r; i++ ) {
				val = mv - *dev->bufp++;
				val <<= (8 - dev->model->bits);
				val += (val >> dev->model->bits);
				*buf++ = val;
			}
		}
		maxlen -= r;
		dev->bytes_in_buffer -= r;
		*lenP += r;
	}
	return status;
}


/*********************************************************************
 * Advanced functions (not supported)
 *********************************************************************/

SANE_Status
sane_set_io_mode( SANE_Handle handle, SANE_Bool nonblock )
{
	DBG(DCODE, "sane_set_io_mode(%p, %d)\n", handle, (int)nonblock);

	if( nonblock == SANE_TRUE )
		return SANE_STATUS_UNSUPPORTED;
	return SANE_STATUS_GOOD;
}

SANE_Status
sane_get_select_fd( SANE_Handle handle, SANE_Int *fdP )
{
	DBG(DCODE, "sane_get_select_fd(%p, %p)\n", handle, fdP);

	return SANE_STATUS_UNSUPPORTED;
}
/* The End */
