/* SANE - Scanner Access Now Easy. Copyright (C) 2006-2007 Wittawat Yamwong 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 #include #include #include #include /* pow(C90) */ #include /* gettimeofday(4.3BSD) */ #include /* 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; char line[100]; /* actually only 1+8+1+8*3+1+8*3+1 = 61 bytes needed */ if (level > debug_level) return; ofs = 0; while (ofs < len) { char *p; line[0] = ' '; u32tohex (ofs, line + 1); line[9] = ':'; p = line + 10; for (c = 0; c != 16 && (ofs + c) < len; 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; } } 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 "EPAPAR_JAMMED"; case PIXMA_ECOVER_OPEN: return "ECOVER_OPEN"; case PIXMA_ENO_PAPER: return "ENO_PAPER"; } 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; *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); #ifndef NDEBUG 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"); } } #endif /* !NDEBUG */ 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); } unsigned 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 (void) { return pixma_collect_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); }