sane-project-backends/backend/pixma_common.c

895 wiersze
21 KiB
C

/* SANE - Scanner Access Now Easy.
Copyright (C) 2007-2008 Nicolas Martin, <nicols-guest at alioth dot debian dot org>
Copyright (C) 2006-2007 Wittawat Yamwong <wittawat@web.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.
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.
*/
#include "../include/sane/config.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <math.h> /* pow(C90) */
#include <sys/time.h> /* gettimeofday(4.3BSD) */
#include <unistd.h> /* usleep */
#include "pixma_rename.h"
#include "pixma_common.h"
#include "pixma_io.h"
#ifdef __GNUC__
# define UNUSED(v) (void) v
#else
# define UNUSED(v)
#endif
extern const pixma_config_t pixma_mp150_devices[];
extern const pixma_config_t pixma_mp750_devices[];
extern const pixma_config_t pixma_mp730_devices[];
extern const pixma_config_t pixma_iclass_devices[];
static const pixma_config_t *const pixma_devices[] = {
pixma_mp150_devices,
pixma_mp750_devices,
pixma_mp730_devices,
pixma_iclass_devices,
NULL
};
static pixma_t *first_pixma = NULL;
static time_t tstart_sec = 0;
static uint32_t tstart_usec = 0;
static int debug_level = 1;
#ifndef NDEBUG
static void
u8tohex (uint8_t x, char *str)
{
static const char hdigit[16] =
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd',
'e', 'f'
};
str[0] = hdigit[(x >> 4) & 0xf];
str[1] = hdigit[x & 0xf];
str[2] = '\0';
}
static void
u32tohex (uint32_t x, char *str)
{
u8tohex (x >> 24, str);
u8tohex (x >> 16, str + 2);
u8tohex (x >> 8, str + 4);
u8tohex (x, str + 6);
}
void
pixma_hexdump (int level, const void *d_, unsigned len)
{
const uint8_t *d = (const uint8_t *) (d_);
unsigned ofs, c, plen;
char line[100]; /* actually only 1+8+1+8*3+1+8*3+1 = 61 bytes needed */
if (level > debug_level)
return;
if (level == debug_level)
/* if debuglevel == exact match and buffer contains more than 3 lines, print 2 lines + .... */
plen = (len > 64) ? 32: len;
else
plen = len;
ofs = 0;
while (ofs < plen)
{
char *p;
line[0] = ' ';
u32tohex (ofs, line + 1);
line[9] = ':';
p = line + 10;
for (c = 0; c != 16 && (ofs + c) < plen; c++)
{
u8tohex (d[ofs + c], p);
p[2] = ' ';
p += 3;
if (c == 7)
{
p[0] = ' ';
p++;
}
}
p[0] = '\0';
pixma_dbg (level, "%s\n", line);
ofs += c;
}
if (len > plen)
pixma_dbg(level, "......\n");
}
static void
time2str (char *buf, unsigned size)
{
time_t sec;
uint32_t usec;
pixma_get_time (&sec, &usec);
sec -= tstart_sec;
if (usec >= tstart_usec)
{
usec -= tstart_usec;
}
else
{
usec = 1000000 + usec - tstart_usec;
sec--;
}
snprintf (buf, size, "%lu.%03u", (unsigned long) sec,
(unsigned) (usec / 1000));
}
void
pixma_dump (int level, const char *type, const void *data, int len,
int size, int max)
{
int actual_len, print_len;
char buf[20];
if (level > debug_level)
return;
if (debug_level >= 20)
max = -1; /* dump every bytes */
time2str (buf, sizeof (buf));
pixma_dbg (level, "%s T=%s len=%d\n", type, buf, len);
actual_len = (size >= 0) ? size : len;
print_len = (max >= 0 && max < actual_len) ? max : actual_len;
if (print_len >= 0)
{
pixma_hexdump (level, data, print_len);
if (print_len < actual_len)
pixma_dbg (level, " ...\n");
}
if (len < 0)
pixma_dbg (level, " ERROR: %s\n", pixma_strerror (len));
pixma_dbg (level, "\n");
}
#endif /* NDEBUG */
/* NOTE: non-reentrant */
const char *
pixma_strerror (int error)
{
static char buf[50];
/* TODO: more human friendly messages */
switch (error)
{
case PIXMA_EIO:
return "EIO";
case PIXMA_ENODEV:
return "ENODEV";
case PIXMA_EACCES:
return "EACCES";
case PIXMA_ENOMEM:
return "ENOMEM";
case PIXMA_EINVAL:
return "EINVAL";
case PIXMA_EBUSY:
return "EBUSY";
case PIXMA_ECANCELED:
return "ECANCELED";
case PIXMA_ENOTSUP:
return "ENOTSUP";
case PIXMA_ETIMEDOUT:
return "ETIMEDOUT";
case PIXMA_EPROTO:
return "EPROTO";
case PIXMA_EPAPER_JAMMED:
return "EPAPER_JAMMED";
case PIXMA_ECOVER_OPEN:
return "ECOVER_OPEN";
case PIXMA_ENO_PAPER:
return "ENO_PAPER";
case PIXMA_EOF:
return "EEOF";
}
snprintf (buf, sizeof (buf), "EUNKNOWN:%d", error);
return buf;
}
void
pixma_set_debug_level (int level)
{
debug_level = level;
}
void
pixma_set_be16 (uint16_t x, uint8_t * buf)
{
buf[0] = x >> 8;
buf[1] = x;
}
void
pixma_set_be32 (uint32_t x, uint8_t * buf)
{
buf[0] = x >> 24;
buf[1] = x >> 16;
buf[2] = x >> 8;
buf[3] = x;
}
uint16_t
pixma_get_be16 (const uint8_t * buf)
{
return ((uint16_t) buf[0] << 8) | buf[1];
}
uint32_t
pixma_get_be32 (const uint8_t * buf)
{
return ((uint32_t) buf[0] << 24) + ((uint32_t) buf[1] << 16) +
((uint32_t) buf[2] << 8) + buf[3];
}
uint8_t
pixma_sum_bytes (const void *data, unsigned len)
{
const uint8_t *d = (const uint8_t *) data;
unsigned i, sum = 0;
for (i = 0; i != len; i++)
sum += d[i];
return sum;
}
void
pixma_sleep (unsigned long usec)
{
usleep (usec);
}
void
pixma_get_time (time_t * sec, uint32_t * usec)
{
struct timeval tv;
gettimeofday (&tv, NULL);
if (sec)
*sec = tv.tv_sec;
if (usec)
*usec = tv.tv_usec;
}
int
pixma_map_status_errno (unsigned status)
{
switch (status)
{
case PIXMA_STATUS_OK:
return 0;
case PIXMA_STATUS_FAILED:
return PIXMA_ECANCELED;
case PIXMA_STATUS_BUSY:
return PIXMA_EBUSY;
default:
return PIXMA_EPROTO;
}
}
int
pixma_check_result (pixma_cmdbuf_t * cb)
{
const uint8_t *r = cb->buf;
unsigned header_len = cb->res_header_len;
unsigned expected_reslen = cb->expected_reslen;
int error;
unsigned len;
if (cb->reslen < 0)
return cb->reslen;
len = (unsigned) cb->reslen;
if (len >= header_len)
{
error = pixma_map_status_errno (pixma_get_be16 (r));
if (expected_reslen != 0)
{
if (len == expected_reslen)
{
if (pixma_sum_bytes (r + header_len, len - header_len) != 0)
error = PIXMA_EPROTO;
}
else
{
/* This case will happen when a command cannot be completely
executed, e.g. because you press the cancel button. The
device will return only a header with PIXMA_STATUS_FAILED. */
if (len != header_len)
error = PIXMA_EPROTO;
}
}
}
else
error = PIXMA_EPROTO;
#ifndef NDEBUG
if (error == PIXMA_EPROTO)
{
pixma_dbg (1, "WARNING: result len=%d expected %d\n",
len, cb->expected_reslen);
pixma_hexdump (1, r, MIN (len, 64));
}
#endif
return error;
}
int
pixma_cmd_transaction (pixma_t * s, const void *cmd, unsigned cmdlen,
void *data, unsigned expected_len)
{
int error, tmo;
error = pixma_write (s->io, cmd, cmdlen);
if (error != (int) cmdlen)
{
if (error >= 0)
{
/* Write timeout is too low? */
PDBG (pixma_dbg
(1, "ERROR:incomplete write, %u out of %u written\n",
(unsigned) error, cmdlen));
error = PIXMA_ETIMEDOUT;
}
return error;
}
/* When you send the start_session command while the scanner optic is
going back to the home position after the last scan session has been
cancelled, you won't get the response before it arrives home. This takes
about 5 seconds. If the last session was succeeded, the scanner will
immediatly answer with PIXMA_STATUS_BUSY.
Is 8 seconds timeout enough? This affects ALL commands that use
pixma_cmd_transaction(). */
tmo = 8;
do
{
error = pixma_read (s->io, data, expected_len);
if (error == PIXMA_ETIMEDOUT)
PDBG (pixma_dbg (2, "No response yet. Timed out in %d sec.\n", tmo));
}
while (error == PIXMA_ETIMEDOUT && --tmo != 0);
if (error < 0)
{
PDBG (pixma_dbg (1, "WARNING:Error in response phase. cmd:%02x%02x\n",
((const uint8_t *) cmd)[0],
((const uint8_t *) cmd)[1]));
PDBG (pixma_dbg (1," If the scanner hangs, reset it and/or unplug the "
"USB cable.\n"));
}
return error; /* length of the result packet or error */
}
uint8_t *
pixma_newcmd (pixma_cmdbuf_t * cb, unsigned cmd,
unsigned dataout, unsigned datain)
{
unsigned cmdlen = cb->cmd_header_len + dataout;
unsigned reslen = cb->res_header_len + datain;
if (cmdlen > cb->size || reslen > cb->size)
return NULL;
memset (cb->buf, 0, cmdlen);
cb->cmdlen = cmdlen;
cb->expected_reslen = reslen;
pixma_set_be16 (cmd, cb->buf);
pixma_set_be16 (dataout + datain, cb->buf + cb->cmd_len_field_ofs);
if (dataout != 0)
return cb->buf + cb->cmd_header_len;
else
return cb->buf + cb->res_header_len;
}
int
pixma_exec (pixma_t * s, pixma_cmdbuf_t * cb)
{
if (cb->cmdlen > cb->cmd_header_len)
pixma_fill_checksum (cb->buf + cb->cmd_header_len,
cb->buf + cb->cmdlen - 1);
cb->reslen =
pixma_cmd_transaction (s, cb->buf, cb->cmdlen, cb->buf,
cb->expected_reslen);
return pixma_check_result (cb);
}
int
pixma_exec_short_cmd (pixma_t * s, pixma_cmdbuf_t * cb, unsigned cmd)
{
pixma_newcmd (cb, cmd, 0, 0);
return pixma_exec (s, cb);
}
int
pixma_check_dpi (unsigned dpi, unsigned max)
{
/* valid dpi = 75 * 2^n */
unsigned temp = dpi / 75;
if (dpi > max || dpi < 75 || 75 * temp != dpi || (temp & (temp - 1)) != 0)
return PIXMA_EINVAL;
return 0;
}
int
pixma_init (void)
{
PDBG (pixma_dbg (2, "pixma version %d.%d.%d\n", PIXMA_VERSION_MAJOR,
PIXMA_VERSION_MINOR, PIXMA_VERSION_BUILD));
PASSERT (first_pixma == NULL);
if (tstart_sec == 0)
pixma_get_time (&tstart_sec, &tstart_usec);
return pixma_io_init ();
}
void
pixma_cleanup (void)
{
while (first_pixma)
pixma_close (first_pixma);
pixma_io_cleanup ();
}
int
pixma_open (unsigned devnr, pixma_t ** handle)
{
int error;
pixma_t *s;
const pixma_config_t *cfg;
*handle = NULL;
cfg = pixma_get_device_config (devnr);
if (!cfg)
return PIXMA_EINVAL; /* invalid devnr */
PDBG (pixma_dbg (2, "pixma_open(): %s\n", cfg->name));
s = (pixma_t *) calloc (1, sizeof (s[0]));
if (!s)
return PIXMA_ENOMEM;
s->next = first_pixma;
first_pixma = s;
s->cfg = cfg;
error = pixma_connect (devnr, &s->io);
if (error < 0)
{
PDBG (pixma_dbg
(2, "pixma_connect() failed %s\n", pixma_strerror (error)));
goto rollback;
}
strncpy (s->id, pixma_get_device_id (devnr), sizeof (s->id) - 1);
s->ops = s->cfg->ops;
s->scanning = 0;
error = s->ops->open (s);
if (error < 0)
goto rollback;
error = pixma_deactivate (s->io);
if (error < 0)
goto rollback;
*handle = s;
return 0;
rollback:
PDBG (pixma_dbg (2, "pixma_open() failed %s\n", pixma_strerror (error)));
pixma_close (s);
return error;
}
void
pixma_close (pixma_t * s)
{
pixma_t **p;
if (!s)
return;
for (p = &first_pixma; *p && *p != s; p = &((*p)->next))
{
}
PASSERT (*p);
if (!(*p))
return;
PDBG (pixma_dbg (2, "pixma_close(): %s\n", s->cfg->name));
if (s->io)
{
if (s->scanning)
{
PDBG (pixma_dbg (3, "pixma_close():scanning in progress, call"
" finish_scan()\n"));
s->ops->finish_scan (s);
}
s->ops->close (s);
pixma_disconnect (s->io);
}
*p = s->next;
free (s);
}
int
pixma_scan (pixma_t * s, pixma_scan_param_t * sp)
{
int error;
error = pixma_check_scan_param (s, sp);
if (error < 0)
return error;
#ifndef NDEBUG
pixma_dbg (3, "\n");
pixma_dbg (3, "pixma_scan(): start\n");
pixma_dbg (3, " line_size=%u image_size=%u channels=%u depth=%u\n",
sp->line_size, sp->image_size, sp->channels, sp->depth);
pixma_dbg (3, " dpi=%ux%u offset=(%u,%u) dimension=%ux%u\n",
sp->xdpi, sp->ydpi, sp->x, sp->y, sp->w, sp->h);
pixma_dbg (3, " gamma_table=%p source=%d\n", sp->gamma_table, sp->source);
#endif
s->param = sp;
s->cancel = 0;
s->cur_image_size = 0;
s->imagebuf.wptr = NULL;
s->imagebuf.wend = NULL;
s->imagebuf.rptr = NULL;
s->imagebuf.rend = NULL;
s->underrun = 0;
error = s->ops->scan (s);
if (error >= 0)
{
s->scanning = 1;
}
else
{
PDBG (pixma_dbg
(3, "pixma_scan() failed %s\n", pixma_strerror (error)));
}
return error;
}
static uint8_t *
fill_pixels (pixma_t * s, uint8_t * ptr, uint8_t * end, uint8_t value)
{
if (s->cur_image_size < s->param->image_size)
{
int n = s->param->image_size - s->cur_image_size;
if (n > (end - ptr))
n = end - ptr;
memset (ptr, value, n);
s->cur_image_size += n;
ptr += n;
}
return ptr;
}
int
pixma_read_image (pixma_t * s, void *buf, unsigned len)
{
int result;
pixma_imagebuf_t ib;
if (!s->scanning)
return 0;
if (s->cancel)
{
result = PIXMA_ECANCELED;
goto cancel;
}
ib = s->imagebuf; /* get rptr and rend */
ib.wptr = (uint8_t *) buf;
ib.wend = ib.wptr + len;
if (s->underrun)
{
if (s->cur_image_size < s->param->image_size)
{
ib.wptr = fill_pixels (s, ib.wptr, ib.wend, 0xff);
}
else
{
PDBG (pixma_dbg
(3, "pixma_read_image():completed (underrun detected)\n"));
s->scanning = 0;
}
return ib.wptr - (uint8_t *) buf;
}
while (ib.wptr != ib.wend)
{
if (ib.rptr == ib.rend)
{
ib.rptr = ib.rend = NULL;
result = s->ops->fill_buffer (s, &ib);
if (result < 0)
goto cancel;
if (result == 0)
{ /* end of image? */
s->ops->finish_scan (s);
if (s->cur_image_size != s->param->image_size)
{
pixma_dbg (1, "WARNING:image size mismatches\n");
pixma_dbg (1,
" %u expected (%d lines) but %u received (%d lines)\n",
s->param->image_size, s->param->h,
s->cur_image_size,
s->cur_image_size / s->param->line_size);
if ((s->cur_image_size % s->param->line_size) != 0)
{
pixma_dbg (1,
"BUG:received data not multiple of line_size\n");
}
}
if (s->cur_image_size < s->param->image_size)
{
s->underrun = 1;
ib.wptr = fill_pixels (s, ib.wptr, ib.wend, 0xff);
}
else
{
PDBG (pixma_dbg (3, "pixma_read_image():completed\n"));
s->scanning = 0;
}
break;
}
s->cur_image_size += result;
PASSERT (s->cur_image_size <= s->param->image_size);
}
if (ib.rptr)
{
unsigned count = MIN (ib.rend - ib.rptr, ib.wend - ib.wptr);
memcpy (ib.wptr, ib.rptr, count);
ib.rptr += count;
ib.wptr += count;
}
}
s->imagebuf = ib; /* store rptr and rend */
return ib.wptr - (uint8_t *) buf;
cancel:
s->ops->finish_scan (s);
s->scanning = 0;
if (result == PIXMA_ECANCELED)
{
PDBG (pixma_dbg (3, "pixma_read_image():cancelled by %sware\n",
(s->cancel) ? "soft" : "hard"));
}
else
{
PDBG (pixma_dbg (3, "pixma_read_image() failed %s\n",
pixma_strerror (result)));
}
return result;
}
void
pixma_cancel (pixma_t * s)
{
s->cancel = 1;
}
int
pixma_enable_background (pixma_t * s, int enabled)
{
return pixma_set_interrupt_mode (s->io, enabled);
}
int
pixma_activate_connection(pixma_t * s)
{
return pixma_activate (s->io);
}
int
pixma_deactivate_connection(pixma_t * s)
{
return pixma_deactivate (s->io);
}
uint32_t
pixma_wait_event (pixma_t * s, int timeout /*ms */ )
{
unsigned events;
if (s->events == PIXMA_EV_NONE && s->ops->wait_event)
s->ops->wait_event (s, timeout);
events = s->events;
s->events = PIXMA_EV_NONE;
return events;
}
#define CLAMP2(x,w,min,max,dpi) do { \
unsigned m = (max) * (dpi) / 75; \
x = MIN(x, m - min); \
w = MIN(w, m - x); \
if (w < min) w = min; \
} while(0)
int
pixma_check_scan_param (pixma_t * s, pixma_scan_param_t * sp)
{
if (!(sp->channels == 3 ||
(sp->channels == 1 && (s->cfg->cap & PIXMA_CAP_GRAY) != 0)))
return PIXMA_EINVAL;
if (pixma_check_dpi (sp->xdpi, s->cfg->xdpi) < 0 ||
pixma_check_dpi (sp->ydpi, s->cfg->ydpi) < 0)
return PIXMA_EINVAL;
/* xdpi must be equal to ydpi except that
xdpi = max_xdpi and ydpi = max_ydpi. */
if (!(sp->xdpi == sp->ydpi ||
(sp->xdpi == s->cfg->xdpi && sp->ydpi == s->cfg->ydpi)))
return PIXMA_EINVAL;
if (s->ops->check_param (s, sp) < 0)
return PIXMA_EINVAL;
/* FIXME: I assume the same minimum width and height for every model. */
CLAMP2 (sp->x, sp->w, 13, s->cfg->width, sp->xdpi);
CLAMP2 (sp->y, sp->h, 8, s->cfg->height, sp->ydpi);
switch (sp->source)
{
case PIXMA_SOURCE_FLATBED:
break;
case PIXMA_SOURCE_TPU:
if ((s->cfg->cap & PIXMA_CAP_TPU) != PIXMA_CAP_TPU)
{
sp->source = PIXMA_SOURCE_FLATBED;
PDBG (pixma_dbg
(1, "WARNING: TPU unsupported, fallback to flatbed.\n"));
}
break;
case PIXMA_SOURCE_ADF:
if ((s->cfg->cap & PIXMA_CAP_ADF) != PIXMA_CAP_ADF)
{
sp->source = PIXMA_SOURCE_FLATBED;
PDBG (pixma_dbg
(1, "WARNING: ADF unsupported, fallback to flatbed.\n"));
}
break;
case PIXMA_SOURCE_ADFDUP:
if ((s->cfg->cap & PIXMA_CAP_ADFDUP) != PIXMA_CAP_ADFDUP)
{
if (s->cfg->cap & PIXMA_CAP_ADF)
{
sp->source = PIXMA_SOURCE_ADF;
}
else
{
sp->source = PIXMA_SOURCE_FLATBED;
}
PDBG (pixma_dbg
(1, "WARNING: ADF duplex unsupported, fallback to %d.\n",
sp->source));
}
break;
}
if (sp->depth == 0)
sp->depth = 8;
if ((sp->depth % 8) != 0)
return PIXMA_EINVAL;
sp->line_size = 0;
if (s->ops->check_param (s, sp) < 0)
return PIXMA_EINVAL;
if (sp->line_size == 0)
sp->line_size = sp->depth / 8 * sp->channels * sp->w;
sp->image_size = sp->line_size * sp->h;
return 0;
}
const char *
pixma_get_string (pixma_t * s, pixma_string_index_t i)
{
switch (i)
{
case PIXMA_STRING_MODEL:
return s->cfg->name;
case PIXMA_STRING_ID:
return s->id;
case PIXMA_STRING_LAST:
return NULL;
}
return NULL;
}
const pixma_config_t *
pixma_get_config (pixma_t * s)
{
return s->cfg;
}
void
pixma_fill_gamma_table (double gamma, uint8_t * table, unsigned n)
{
int i;
double r_gamma = 1.0 / gamma;
double out_scale = 255.0;
double in_scale = 1.0 / (n - 1);
for (i = 0; (unsigned) i != n; i++)
{
table[i] = (int) (out_scale * pow (i * in_scale, r_gamma) + 0.5);
}
}
int
pixma_find_scanners (const char **conf_devices)
{
return pixma_collect_devices (conf_devices, pixma_devices);
}
const char *
pixma_get_device_model (unsigned devnr)
{
const pixma_config_t *cfg = pixma_get_device_config (devnr);
return (cfg) ? cfg->name : NULL;
}
int
pixma_get_device_status (pixma_t * s, pixma_device_status_t * status)
{
if (!status)
return PIXMA_EINVAL;
memset (status, 0, sizeof (*status));
return s->ops->get_status (s, status);
}