/* * epsonds-cmd.c - Epson ESC/I-2 routines. * * Copyright (C) 2015 Tower Technologies * Author: Alessandro Zummo * * 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, version 2. */ #define DEBUG_DECLARE_ONLY #include "sane/config.h" #include #include /* sleep */ #include "epsonds.h" #include "epsonds-io.h" #include "epsonds-cmd.h" #include "epsonds-ops.h" #include "epsonds-net.h" static SANE_Status esci2_parse_block(char *buf, int len, void *userdata, SANE_Status (*cb)(void *userdata, char *token, int len)) { SANE_Status status = SANE_STATUS_GOOD; SANE_Status delayed_status = SANE_STATUS_GOOD; char *start = buf; char *end = (buf + len) - 1; /* 0 : # * 1-3: param * 4- : data */ while (1) { char param[4]; while (*start != '#' && start < end) start++; if (*start != '#') break; param[0] = *++start; param[1] = *++start; param[2] = *++start; param[3] = '\0'; if (strncmp("---", param, 3) == 0) break; /* ugly hack to skip over GMT in RESA */ if (strncmp("GMT", param, 3) == 0 && *(start + 5) == 'h') { start = start + 4 + 0x100; continue; } /* find the end of the token */ { int tlen; char *next = start; while (*next != '#' && *next != 0x00 && next < end) next++; tlen = next - start - 1; if (cb) { status = cb(userdata, start - 2, tlen); if (status != SANE_STATUS_GOOD) { delayed_status = status; } } start = next; } } if (delayed_status != SANE_STATUS_GOOD) return delayed_status; return status; } static SANE_Bool esci2_check_header(const char *cmd, const char *buf, unsigned int *more) { int err; *more = 0; if (strncmp(cmd, buf, 4) != 0) { if (strncmp("UNKN", buf, 4) == 0) { DBG(1, "UNKN reply code received\n"); } else if (strncmp("INVD", buf, 4) == 0) { DBG(1, "INVD reply code received\n"); } else { DBG(1, "%c%c%c%c, unexpected reply code\n", buf[0], buf[1], buf[2], buf[3]); } return 0; } /* INFOx0000100#.... */ /* read the answer len */ if (buf[4] != 'x') { DBG(1, "unknown type in header: %c\n", buf[4]); return 0; } err = sscanf(&buf[5], "%x#", more); if (err != 1) { DBG(1, "cannot decode length from header\n"); return 0; } return 1; } static SANE_Status esci2_cmd(epsonds_scanner* s, char *cmd, size_t len, char *payload, size_t plen, void *userdata, SANE_Status (*cb)(void *userdata, char *token, int len)) { SANE_Status status; unsigned int more; char header[13], rbuf[64]; /* add one more byte for header buffer to correct buffer overflow issue,*/ DBG(8, "%s: %4s len %lu, payload len: %lu\n", __func__, cmd, len, plen); memset(header, 0x00, sizeof(header)); memset(rbuf, 0x00, sizeof(rbuf)); // extra safety check, will not happen if (len != 12) { DBG(1, "%s: command has wrong size (%lu != 12)\n", __func__, len); return SANE_STATUS_INVAL; } // merge ParameterBlock size sprintf(header, "%4.4sx%07x", cmd, (unsigned int)plen); // send RequestBlock, request immediate response if there's no payload status = eds_txrx(s, header, len, rbuf, (plen > 0) ? 0 : 64); if (status != SANE_STATUS_GOOD) { return status; } /* send ParameterBlock, request response */ if (plen) { DBG(8, " %12.12s (%lu)\n", header, plen); status = eds_txrx(s, payload, plen, rbuf, 64); if (status != SANE_STATUS_GOOD) { return status; } } /* rxbuf holds the DataHeaderBlock, which should be * parsed to know if we need to read more data */ if (!esci2_check_header(cmd, rbuf, &more)) { return SANE_STATUS_IO_ERROR; } /* parse the received header block */ if (cb) { status = esci2_parse_block(rbuf + 12, 64 - 12, userdata, cb); if (status != SANE_STATUS_GOOD && status != SANE_STATUS_DEVICE_BUSY) { DBG(1, "%s: %4s error while parsing received header\n", __func__, cmd); } } /* header valid, get the data block if present */ if (more) { char *pbuf = malloc(more); if (pbuf) { if (s->hw->connection == SANE_EPSONDS_NET) { epsonds_net_request_read(s, more); } ssize_t read = eds_recv(s, pbuf, more, &status); if (read != more) { } /* parse the received data block */ if (cb) { status = esci2_parse_block(pbuf, more, userdata, cb); if (status != SANE_STATUS_GOOD) { DBG(1, "%s: %4s error while parsing received data block\n", __func__, cmd); } } free(pbuf); } else { return SANE_STATUS_NO_MEM; } } return status; } static SANE_Status esci2_cmd_simple(epsonds_scanner* s, char *cmd, SANE_Status (*cb)(void *userdata, char *token, int len)) { return esci2_cmd(s, cmd, 12, NULL, 0, s, cb); } SANE_Status esci2_fin(epsonds_scanner *s) { SANE_Status status; DBG(5, "%s\n", __func__); status = esci2_cmd_simple(s, "FIN x0000000", NULL); s->locked = 0; return status; } SANE_Status esci2_can(epsonds_scanner *s) { return esci2_cmd_simple(s, "CAN x0000000", NULL); } static int decode_value(char *buf, int len) { char tmp[10]; memcpy(tmp, buf, len); tmp[len] = '\0'; if (buf[0] == 'd' && len == 4) { return strtol(buf + 1, NULL, 10); } else if (buf[0] == 'i' && len == 8) { return strtol(buf + 1, NULL, 10); } else if (buf[0] == 'x' && len == 8) { return strtol(buf + 1, NULL, 16); } else if (buf[0] == 'h' && len == 4) { return strtol(buf + 1, NULL, 16); } return -1; } /* h000 */ static char *decode_binary(char *buf) { char tmp[6]; int hl; memcpy(tmp, buf, 4); tmp[4] = '\0'; if (buf[0] != 'h') return NULL; hl = strtol(tmp + 1, NULL, 16); if (hl) { char *v = malloc(hl + 1); memcpy(v, buf + 4, hl); v[hl] = '\0'; return v; } return NULL; } static char *decode_string(char *buf) { char *p, *s = decode_binary(buf); if (s == NULL) return NULL; /* trim white space at the end */ p = s + strlen(s); while (*--p == ' ') *p = '\0'; return s; } static void debug_token(int level, const char *func, char *token, int len) { char *tdata = malloc(len + 1); memcpy(tdata, token + 3, len); tdata[len] = '\0'; DBG(level, "%s: %3.3s / %s / %d\n", func, token, tdata, len); free(tdata); } static SANE_Status info_cb(void *userdata, char *token, int len) { epsonds_scanner *s = (epsonds_scanner *)userdata; char *value; if (DBG_LEVEL >= 11) { debug_token(DBG_LEVEL, __func__, token, len); } /* pointer to the token's value */ value = token + 3; /* nrd / nrdBUSY */ if (strncmp("nrd", token, 3) == 0) { if (strncmp("BUSY", value, 4) == 0) { return SANE_STATUS_DEVICE_BUSY; } } if (strncmp("PRD", token, 3) == 0) { free(s->hw->model); s->hw->model = decode_string(value); s->hw->sane.model = s->hw->model; DBG(1, " product: %s\n", s->hw->model); /* we will free the string later */ } if (strncmp("VER", token, 3) == 0) { char *v = decode_string(value); DBG(1, " version: %s\n", v); free(v); } if (strncmp("S/N", token, 3) == 0) { char *v = decode_string(value); DBG(1, " serial: %s\n", v); free(v); } if (strncmp("ADF", token, 3) == 0) { s->hw->has_adf = 1; if (len == 8) { if (strncmp("TYPEPAGE", value, len) == 0) { DBG(1, " ADF: page type\n"); } if (strncmp("TYPEFEED", value, len) == 0) { DBG(1, " ADF: sheet feed type\n"); } if (strncmp("DPLX1SCN", value, len) == 0) { DBG(1, " ADF: duplex single pass\n"); s->hw->adf_singlepass = 1; } if (strncmp("DPLX2SCN", value, len) == 0) { DBG(1, " ADF: duplex double pass\n"); s->hw->adf_singlepass = 0; } if (strncmp("FORDPF1N", value, len) == 0) { DBG(1, " ADF: order is 1 to N\n"); } if (strncmp("FORDPFN1", value, len) == 0) { DBG(1, " ADF: order is N to 1\n"); } if (strncmp("ALGNLEFT", value, len) == 0) { DBG(1, " ADF: left aligned\n"); s->hw->adf_alignment = 0; } if (strncmp("ALGNCNTR", value, len) == 0) { DBG(1, " ADF: center aligned\n"); s->hw->adf_alignment = 1; } if (strncmp("ALGNRIGT", value, len) == 0) { DBG(1, " ADF: right aligned (not supported!)\n"); s->hw->adf_alignment = 2; } } if (len == 4) { if (strncmp("PREF", value, len) == 0) { DBG(1, " ADF: auto pre-feed\n"); } if (strncmp("ASCN", value, len) == 0) { DBG(1, " ADF: auto scan\n"); } if (strncmp("RCVR", value, len) == 0) { DBG(1, " ADF: auto recovery\n"); } } if (len == 20) { /* ADFAREAi0000850i0001400 */ if (strncmp("AREA", value, 4) == 0) { int min = decode_value(value + 4, 8); int max = decode_value(value + 4 + 8, 8); DBG(1, " ADF: area %dx%d @ 100dpi\n", min, max); } if (strncmp("AMIN", value, 4) == 0) { int min = decode_value(value + 4, 8); int max = decode_value(value + 4 + 8, 8); DBG(1, " ADF: min %dx%d @ 100dpi\n", min, max); } if (strncmp("AMAX", value, 4) == 0) { int min = decode_value(value + 4, 8); int max = decode_value(value + 4 + 8, 8); DBG(1, " ADF: max %dx%d @ 100dpi\n", min, max); eds_set_adf_area(s->hw, min, max, 100); } } if (len == 12) { /* RESOi0000600 */ if (strncmp("RESO", value, 4) == 0) { int res = decode_value(value + 4, 8); DBG(1, " ADF: basic resolution is %d dpi\n", res); } /* OVSNd025d035 */ if (strncmp("OVSN", value, 4) == 0) { int x = decode_value(value + 4, 4); int y = decode_value(value + 4 + 4, 4); DBG(1, " ADF: overscan %dx%d @ 100dpi\n", x, y); } } } if (strncmp("FB ", token, 3) == 0) { s->hw->has_fb = 1; if (len == 20) { /* AREAi0000850i0001400 */ if (strncmp("AREA", value, 4) == 0) { int min = decode_value(value + 4, 8); int max = decode_value(value + 4 + 8, 8); DBG(1, " FB: area %dx%d @ 100dpi\n", min, max); eds_set_fbf_area(s->hw, min, max, 100); } } if (len == 8) { if (strncmp("ALGNLEFT", value, len) == 0) { DBG(1, " FB: left aligned\n"); s->hw->fbf_alignment = 0; } if (strncmp("ALGNCNTR", value, len) == 0) { DBG(1, " FB: center aligned\n"); s->hw->fbf_alignment = 1; } if (strncmp("ALGNRIGT", value, len) == 0) { DBG(1, " FB: right aligned (not supported!)\n"); s->hw->fbf_alignment = 2; } } if (len == 12) { /* RESOi0000600 */ if (strncmp("RESO", value, 4) == 0) { int res = decode_value(value + 4, 8); DBG(1, " FB: basic resolution is %d dpi\n", res); } /* OVSNd025d035 */ if (strncmp("OVSN", value, 4) == 0) { int x = decode_value(value + 4, 4); int y = decode_value(value + 4 + 4, 4); DBG(1, " FB: overscan %dx%d @ 100dpi\n", x, y); } } if (len == 4) { if (strncmp("DETX", value, len) == 0) { DBG(1, " FB: paper width detection\n"); } if (strncmp("DETY", value, len) == 0) { DBG(1, " FB: paper height detection\n"); } } } return SANE_STATUS_GOOD; } SANE_Status esci2_info(epsonds_scanner *s) { SANE_Status status; int i = 4; DBG(1, "= gathering device information\n"); do { status = esci2_cmd_simple(s, "INFOx0000000", &info_cb); if (status == SANE_STATUS_DEVICE_BUSY) { sleep(2); } i--; } while (status == SANE_STATUS_DEVICE_BUSY && i); return status; } /* CAPA */ static SANE_Status capa_cb(void *userdata, char *token, int len) { epsonds_scanner *s = (epsonds_scanner *)userdata; char *value = token + 3; if (DBG_LEVEL >= 11) { debug_token(DBG_LEVEL, __func__, token, len); } if (len == 4) { if (strncmp("ADFDPLX", token, 3 + 4) == 0) { DBG(1, " ADF: duplex\n"); s->hw->adf_is_duplex = 1; } if (strncmp("ADFSKEW", token, 3 + 4) == 0) { DBG(1, " ADF: skew correction\n"); s->hw->adf_has_skew = 1; } if (strncmp("ADFOVSN", token, 3 + 4) == 0) { DBG(1, " ADF: overscan\n"); } if (strncmp("ADFPEDT", token, 3 + 4) == 0) { DBG(1, " ADF: paper end detection\n"); } if (strncmp("ADFLOAD", token, 3 + 4) == 0) { DBG(1, " ADF: paper load\n"); s->hw->adf_has_load = 1; } if (strncmp("ADFEJCT", token, 3 + 4) == 0) { DBG(1, " ADF: paper eject\n"); s->hw->adf_has_eject = 1; } if (strncmp("ADFCRP ", token, 3 + 4) == 0) { DBG(1, " ADF: image cropping\n"); } if (strncmp("ADFFAST", token, 3 + 4) == 0) { DBG(1, " ADF: fast mode available\n"); } if (strncmp("ADFDFL1", token, 3 + 4) == 0) { DBG(1, " ADF: double feed detection\n"); s->hw->adf_has_dfd = 1; } } if (len == 8 && strncmp("ADFDFL1DFL2", token, 3 + 4) == 0) { DBG(1, " ADF: double feed detection (high sensitivity)\n"); s->hw->adf_has_dfd = 2; } if (strncmp("FMT", token, 3) == 0) { /* a bit ugly... */ if (len >= 8) { if (strncmp("RAW ", value + 4, 4) == 0) { s->hw->has_raw = 1; } } if (len >= 12) { if (strncmp("RAW ", value + 8, 4) == 0) { s->hw->has_raw = 1; } } } /* RSMRANGi0000050i0000600 */ if (strncmp("RSMRANG", token, 3 + 4) == 0) { char *p = token + 3 + 4; if (p[0] == 'i') { int min = decode_value(p, 8); int max = decode_value(p + 8, 8); eds_set_resolution_range(s->hw, min, max); DBG(1, "resolution min/max %d/%d\n", min, max); } } /* RSMLISTi0000300i0000600 */ if (strncmp("RSMLIST", token, 3 + 4) == 0) { char *p = token + 3 + 4; if (p[0] == 'i') { int i; int count = (len - 4) / 8; for (i = 0; i < count; i++) { eds_add_resolution(s->hw, decode_value(p, 8)); p += 8; } } } return SANE_STATUS_GOOD; } SANE_Status esci2_capa(epsonds_scanner *s) { return esci2_cmd_simple(s, "CAPAx0000000", &capa_cb); } /* STAT */ static SANE_Status stat_cb(void *userdata, char *token, int len) { /* epsonds_scanner *s = (epsonds_scanner *)userdata; char *value = token + 3; */ userdata = userdata; if (DBG_LEVEL >= 11) { debug_token(DBG_LEVEL, __func__, token, len); } return SANE_STATUS_GOOD; } SANE_Status esci2_stat(epsonds_scanner *s) { return esci2_cmd_simple(s, "STATx0000000", &stat_cb); } /* RESA */ static SANE_Status resa_cb(void *userdata, char *token, int len) { /* epsonds_scanner *s = (epsonds_scanner *)userdata; */ userdata = userdata; if (DBG_LEVEL >= 11) { debug_token(DBG_LEVEL, __func__, token, len); } return SANE_STATUS_GOOD; } SANE_Status esci2_resa(epsonds_scanner *s) { return esci2_cmd_simple(s, "RESAx0000000", &resa_cb); } /* PARA */ static SANE_Status para_cb(void *userdata, char *token, int len) { if (DBG_LEVEL >= 11) { debug_token(DBG_LEVEL, __func__, token, len); } userdata = userdata; if (strncmp("par", token, 3) == 0) { if (strncmp("FAIL", token + 3, 4) == 0) { DBG(1, "%s: parameter setting failed\n", __func__); return SANE_STATUS_INVAL; } } return SANE_STATUS_GOOD; } SANE_Status esci2_para(epsonds_scanner *s, char *parameters) { DBG(8, "%s: %s\n", __func__, parameters); return esci2_cmd(s, "PARAx0000000", 12, parameters, strlen(parameters), NULL, ¶_cb); } SANE_Status esci2_mech(epsonds_scanner *s, char *parameters) { DBG(8, "%s: %s\n", __func__, parameters); return esci2_cmd(s, "MECHx0000000", 12, parameters, strlen(parameters), NULL, ¶_cb); } SANE_Status esci2_trdt(epsonds_scanner *s) { return esci2_cmd_simple(s, "TRDTx0000000", NULL); } static SANE_Status img_cb(void *userdata, char *token, int len) { struct epsonds_scanner *s = userdata; if (DBG_LEVEL >= 11) { debug_token(DBG_LEVEL, __func__, token, len); } /* psti0000256i0000000i0000945 / 24 */ /* integer comparison first so it's faster */ if (len == 24 && strncmp("pst", token, 3) == 0) { s->dummy = decode_value(token + 3 + 8, 8); DBG(10, "%s: pst width: %d, height: %d, dummy: %d\n", __func__, decode_value(token + 3, 8), decode_value(token + 3 + 8 + 8, 8), s->dummy); return SANE_STATUS_GOOD; } if (len == 16 && strncmp("pen", token, 3) == 0) { DBG(10, "%s: page end\n", __func__); s->eof = 1; return SANE_STATUS_EOF; } /* typIMGA or typIMGB */ if (len == 4 && strncmp("typ", token, 3) == 0) { if (token[6] == 'B') s->backside = 1; else s->backside = 0; return SANE_STATUS_GOOD; } if (strncmp("err", token, 3) == 0) { char *option = token + 3; /* ADF, TPU, FB */ char *cause = token + 3 + 4; /* OPN, PJ, PE, ERR, LTF, LOCK, DFED, DTCL, AUT, PERM */ s->scanning = 0; DBG(1, "%s: error on option %3.3s, cause %4.4s\n", __func__, option, cause); if (cause[0] == 'P' && cause[1] == 'J') return SANE_STATUS_JAMMED; if (cause[0] == 'P' && cause[1] == 'E') return SANE_STATUS_NO_DOCS; if (cause[0] == 'O' && cause[1] == 'P' && cause[2] == 'N') return SANE_STATUS_COVER_OPEN; return SANE_STATUS_IO_ERROR; } if (len == 4 && strncmp("atnCAN ", token, 3 + 4) == 0) { DBG(1, "%s: cancel request\n", __func__); s->canceling = 1; s->scanning = 0; return SANE_STATUS_CANCELLED; } if (len == 4 && strncmp("lftd000", token, 3 + 4) == 0) { s->scanning = 0; } return SANE_STATUS_GOOD; } SANE_Status esci2_img(struct epsonds_scanner *s, SANE_Int *length) { SANE_Status status = SANE_STATUS_GOOD; SANE_Status parse_status; unsigned int more; ssize_t read; *length = 0; if (s->canceling) return SANE_STATUS_CANCELLED; /* request image data */ eds_send(s, "IMG x0000000", 12, &status, 64); if (status != SANE_STATUS_GOOD) { return status; } /* receive DataHeaderBlock */ memset(s->buf, 0x00, 64); eds_recv(s, s->buf, 64, &status); if (status != SANE_STATUS_GOOD) { return status; } /* check if we need to read any image data */ more = 0; if (!esci2_check_header("IMG ", (char *)s->buf, &more)) { return SANE_STATUS_IO_ERROR; } /* this handles eof and errors */ parse_status = esci2_parse_block((char *)s->buf + 12, 64 - 12, s, &img_cb); /* no more data? return using the status of the esci2_parse_block * call, which might hold other error conditions. */ if (!more) { return parse_status; } /* ALWAYS read image data */ if (s->hw->connection == SANE_EPSONDS_NET) { epsonds_net_request_read(s, more); } read = eds_recv(s, s->buf, more, &status); if (status != SANE_STATUS_GOOD) { return status; } if (read != more) { return SANE_STATUS_IO_ERROR; } /* handle esci2_parse_block errors */ if (parse_status != SANE_STATUS_GOOD) { return parse_status; } DBG(15, "%s: read %lu bytes, status: %d\n", __func__, (unsigned long) read, status); *length = read; if (s->canceling) { return SANE_STATUS_CANCELLED; } return SANE_STATUS_GOOD; }