aboutsummaryrefslogtreecommitdiff
path: root/backend/command.c
diff options
context:
space:
mode:
Diffstat (limited to 'backend/command.c')
-rw-r--r--backend/command.c814
1 files changed, 814 insertions, 0 deletions
diff --git a/backend/command.c b/backend/command.c
new file mode 100644
index 0000000..7c35aa6
--- /dev/null
+++ b/backend/command.c
@@ -0,0 +1,814 @@
+/* command.c -- assorted ESC/I protocol commands
+ * Copyright (C) 2008--2014 SEIKO EPSON CORPORATION
+ *
+ * License: GPLv2+|iscan
+ * Authors: AVASYS CORPORATION
+ *
+ * This file is part of the SANE backend distributed with Image Scan!
+ *
+ * Image Scan!'s SANE backend 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 ought to have received a copy of the GNU General Public License
+ * along with this package. If not, see <http://www.gnu.org/licenses/>.
+ *
+ *
+ * Linking Image Scan!'s SANE backend statically or dynamically with
+ * other modules is making a combined work based on this SANE backend.
+ * Thus, the terms and conditions of the GNU General Public License
+ * cover the whole combination.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * SANE frontends that communicate with Image Scan!'s SANE backend
+ * solely through the SANE Application Programming Interface,
+ * regardless of the license terms of these SANE frontends, and to
+ * copy and distribute the resulting combined work under terms of your
+ * choice, provided that every copy of the combined work is
+ * accompanied by a complete copy of the source code of Image Scan!'s
+ * SANE backend (the version of Image Scan!'s SANE backend used to
+ * produce the combined work), being distributed under the terms of
+ * the GNU General Public License plus this exception. An independent
+ * module is a module which is not derived from or based on Image
+ * Scan!'s SANE backend.
+ *
+ * As a special exception, the copyright holders of Image Scan!'s SANE
+ * backend give you permission to link Image Scan!'s SANE backend with
+ * independent modules that communicate with Image Scan!'s SANE
+ * backend solely through the "Interpreter" interface, regardless of
+ * the license terms of these independent modules, and to copy and
+ * distribute the resulting combined work under terms of your choice,
+ * provided that every copy of the combined work is accompanied by a
+ * complete copy of the source code of Image Scan!'s SANE backend (the
+ * version of Image Scan!'s SANE backend used to produce the combined
+ * work), being distributed under the terms of the GNU General Public
+ * License plus this exception. An independent module is a module
+ * which is not derived from or based on Image Scan!'s SANE backend.
+ *
+ * Note that people who make modified versions of Image Scan!'s SANE
+ * backend are not obligated to grant special exceptions for their
+ * modified versions; it is their choice whether to do so. The GNU
+ * General Public License gives permission to release a modified
+ * version without this exception; this exception also makes it
+ * possible to release a modified version which carries forward this
+ * exception.
+ */
+
+
+/*! \file
+ \brief Implements 'bare' ESC/I commands.
+
+ This file contains functions that implement of a number of ESC/I
+ commands. These functions only handle the sending and receiving
+ of data, data interpretation and I/O or memory related errors.
+
+ Interpretation of received data is limited to taking bytes apart
+ into bit flags and combining bytes into larger entities, such as
+ 2- or 4-byte integers and strings. The hardware device object's
+ state is updated to correspond with the interpreted data.
+
+ Under \e no circumstances shall the functions implemented in this
+ file draw any conclusions whatsoever as to the device's resulting
+ state. This is the reponsibility of the hardware device object.
+ Also, it is the hardware device object's responsibility to check
+ the timing/validity of calling any of the functions implemented
+ here.
+
+ All functions shall return a status that is one of:
+
+ - \c SANE_STATUS_GOOD
+ - \c SANE_STATUS_NO_MEM
+ - \c SANE_STATUS_IO_ERROR
+ - \c SANE_STATUS_INVAL
+
+ Function names follow the description in the ESC/I specification
+ and shall start with a \c cmd_ prefix.
+
+ \note I want to replace the device::cmd crap with function
+ pointers and use a no-op for commands that are not supported by
+ the device. Functions may return SANE_STATUS_UNSUPPORTED in the
+ interim.
+
+ \note I also want to introduce a function pointer based callback
+ mechanism to fix up broken firmware replies. That way, we do not
+ have to pass the firmware name everytime. We can set appropriate
+ callbacks for a device once and be done with it.
+ */
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include "command.h"
+
+#include "utils.h"
+
+#include <string.h>
+
+#define SANE_INT_MAX 2147483647
+
+
+static void
+fixme_request_identity (const char *fw_name, byte *buf, size_t size)
+{
+ if (!fw_name) return;
+ if (!buf) return;
+
+ if (0 == strcmp_c ("NX100", fw_name) && 16 < size)
+ {
+ buf[12] = 'A';
+ buf[13] = 0xEC;
+ buf[14] = 0x13;
+ buf[15] = 0x6C;
+ buf[16] = 0x1B;
+ }
+}
+
+/*! \brief Establishes basic device capabilities.
+
+ \note This command is assumed to be supported by \e all ESC/I
+ devices.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_request_identity (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, 'I' };
+ byte info[4];
+
+ byte *data = NULL;
+ size_t size;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, info, num_of (info), &status);
+
+ hw->status = info[1];
+ size = info[3] << 8 | info[2];
+
+ if (0 < size)
+ {
+ data = t_calloc (size, byte);
+ if (!data)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ channel_recv (hw->channel, data, size, &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ byte *p = data + 2;
+
+ fixme_request_identity (hw->fw_name, data, size);
+
+ hw->cmd_lvl[0] = data[0];
+ hw->cmd_lvl[1] = data[1];
+
+ free_resolution_info (&hw->res);
+ init_resolution_info (&hw->res, p);
+ init_resolution_info (&hw->resolution, NULL);
+ copy_resolution_info (&hw->resolution, &hw->res, SANE_TRUE);
+
+ hw->max_x = data[size-3] << 8 | data[size-4];
+ hw->max_y = data[size-1] << 8 | data[size-2];
+ }
+ delete (data);
+ }
+
+ return status;
+}
+
+static void
+fixme_request_hardware_property (const char *fw_name, byte *buf, size_t size)
+{
+ if (!fw_name) return;
+ if (!buf) return;
+
+ if (0 == strcmp_c ("NX100", fw_name) && 33 < size)
+ {
+ buf[32] = 0xB0;
+ buf[33] = 0x04;
+ }
+}
+
+/*! \brief Query additional device capabilities.
+
+ \note This command is not supported for B level devices. It is
+ supported for D level devices.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_request_hardware_property (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, 'i' };
+ byte info[4];
+
+ byte *data = NULL;
+ size_t size;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, info, num_of (info), &status);
+
+ hw->status = info[1];
+ size = info[3] << 8 | info[2];
+
+ if (0 < size)
+ {
+ data = t_calloc (size, byte);
+ if (!data)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ channel_recv (hw->channel, data, size, &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ byte *p = data + 14;
+
+ fixme_request_hardware_property (hw->fw_name, data, size);
+
+ hw->optical_res = data[1] << 8 | data[0];
+ hw->sensor_info = data[2];
+ hw->scan_order = data[3];
+ hw->line_dist_x = data[4];
+ hw->line_dist_y = data[5];
+
+ free_resolution_info (&hw->res_x);
+ init_resolution_info (&hw->res_x, p);
+
+ while (resolution_info_ESC_i_cond (p))
+ p += 2;
+
+ p += 2; /* start of sub resolution info */
+ free_resolution_info (&hw->res_y);
+ init_resolution_info (&hw->res_y, p);
+ }
+ delete (data);
+ }
+
+ return status;
+}
+
+SANE_Status
+cmd_set_scanning_parameter (device* hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte FS_W[] = {FS, 'W'};
+
+ byte ack_buf;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, FS_W, 2, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &ack_buf, 1, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ if (ACK != ack_buf) return SANE_STATUS_UNSUPPORTED;
+
+ channel_send (hw->channel, hw->param_buf, 64, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &ack_buf, 1, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ if (ACK != ack_buf) return SANE_STATUS_INVAL;
+
+ return status;
+}
+
+SANE_Status
+cmd_request_scanning_parameter (device* hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+ const byte FS_S[] = {FS, 'S'};
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, FS_S, 2, &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, hw->param_buf, 64, &status);
+
+ return status;
+}
+
+SANE_Status
+cmd_request_scanner_status (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { FS, 'F' };
+ byte buf[16];
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, buf, num_of (buf), &status);
+
+ hw->fsf_status = buf[0];
+ { /* transfer corresponding flags */
+ byte mask = FSF_STATUS_FER | FSF_STATUS_WU;
+ hw->ext_status &= ~mask;
+ hw->ext_status |= (mask & hw->fsf_status);
+ }
+
+ if ((ADF_STATUS_IST & buf[1]) && !hw->adf)
+ {
+ hw->adf = t_calloc (1, adf_extension);
+ if (!hw->adf) return SANE_STATUS_NO_MEM;
+ }
+ if ((TPU_STATUS_IST & buf[2]) && !hw->tpu)
+ {
+ hw->tpu = t_calloc (1, tpu_extension);
+ if (!hw->tpu) return SANE_STATUS_NO_MEM;
+ }
+
+ if (hw->fbf)
+ {
+ hw->fbf->status = buf[3];
+ update_doc_size (hw->fbf, buf[8] << 8 | buf[7]);
+ }
+ if (hw->adf)
+ {
+ hw->adf->status = buf[1];
+ hw->adf->ext_status = buf[10];
+ update_doc_size (hw->adf, buf[6] << 8 | buf[5]);
+ }
+ if (hw->tpu)
+ {
+ hw->tpu->status = buf[2];
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static SANE_Int
+buf_to_sane_int (const byte *p, const char *var)
+{
+ SANE_Int result = 0;
+
+ require (p);
+ result = buf_to_uint32 (p);
+
+ if (SANE_INT_MAX < result)
+ {
+ err_major ("overflow: %s", var);
+ result = SANE_INT_MAX;
+ }
+ return result;
+}
+
+SANE_Status
+cmd_request_extended_identity (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { FS, 'I' };
+ byte buf[80];
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, buf, num_of (buf), &status);
+
+ hw->cmd_lvl[0] = buf[0];
+ hw->cmd_lvl[1] = buf[1];
+
+ hw->version[0] = buf[62];
+ hw->version[1] = buf[63];
+ hw->version[2] = buf[64];
+ hw->version[3] = buf[65];
+
+ hw->fsi_cap_1 = buf[44];
+ hw->fsi_cap_2 = buf[45];
+ hw->fsi_cap_3 = buf[76];
+ { /* transfer corresponding flags */
+ byte mask = (EXT_STATUS_NO_FBF | EXT_STATUS_ADFT | EXT_STATUS_ADFS
+ | EXT_STATUS_ADFO | EXT_STATUS_LID | EXT_STATUS_PB);
+ hw->ext_status &= ~mask;
+ hw->ext_status |= (mask & hw->fsi_cap_1);
+ }
+
+ if (!(EXT_STATUS_NO_FBF & hw->fsi_cap_1) && !hw->fbf)
+ {
+ hw->fbf = t_calloc (1, fbf_extension);
+ if (!hw->fbf) return SANE_STATUS_NO_MEM;
+ }
+
+ hw->cmd->request_push_button_status
+ = ((EXT_STATUS_PB & hw->fsi_cap_1) ? '!' : 0);
+
+ hw->base_res = buf_to_sane_int (buf + 4, "base resolution");
+
+ hw->dpi_range.min = buf_to_sane_int (buf + 8, "minimum resolution");
+ hw->dpi_range.max = buf_to_sane_int (buf + 12, "maixmum resolution");
+ hw->dpi_range.quant = 1;
+
+ hw->scan_width_limit = buf_to_sane_int (buf + 16, "scan width");
+
+ if (hw->fbf)
+ {
+ hw->fbf->max_x = buf_to_sane_int (buf + 20, "fbf max_x");
+ hw->fbf->max_y = buf_to_sane_int (buf + 24, "fbf max_y");
+ update_ranges (hw, hw->fbf);
+ }
+ if (hw->adf)
+ {
+ hw->adf->max_x = buf_to_sane_int (buf + 28, "adf max_x");
+ hw->adf->max_y = buf_to_sane_int (buf + 32, "adf max_y");
+ update_ranges (hw, hw->adf);
+ }
+ if (hw->tpu)
+ {
+ hw->tpu->max_x = buf_to_sane_int (buf + 36, "tpu max_x");
+ hw->tpu->max_y = buf_to_sane_int (buf + 40, "tpu max_y");
+ update_ranges (hw, hw->tpu);
+ }
+
+ return SANE_STATUS_GOOD;
+}
+
+static void
+fixme_request_extended_status (const char *fw_name, byte *buf, size_t size)
+{
+ if (!fw_name) return;
+ if (!buf) return;
+
+ if (0 == strcmp_c ("GT-8200", fw_name) && 15 < size)
+ {
+ uint16_t max_x;
+ uint16_t max_y;
+
+ max_x = buf[13] << 8 | buf[12];
+ max_y = buf[15] << 8 | buf[14];
+ if (max_y < max_x)
+ {
+ err_minor ("Fixing up buggy FBF max scan dimensions.");
+ max_y *= 2;
+ buf[14] = 0xFF & max_y;
+ buf[15] = 0xFF & (max_y >> 8);
+ }
+
+ max_x = buf[ 8] << 8 | buf[7];
+ max_y = buf[10] << 8 | buf[9];
+ if (max_y < max_x)
+ {
+ err_minor ("Fixing up buggy TPU max scan dimensions.");
+ max_y *= 2;
+ buf[ 9] = 0xFF & max_y;
+ buf[10] = 0xFF & (max_y >> 8);
+ }
+ }
+
+ if ((0 == strcmp_c ("ES-9000H", fw_name) ||
+ 0 == strcmp_c ("GT-30000", fw_name))
+ && 5 < size)
+ {
+ err_minor ("Fixing up buggy ADF max scan dimensions.");
+ buf[2] = 0xB0;
+ buf[3] = 0x6D;
+ buf[4] = 0x60;
+ buf[5] = 0x9F;
+ }
+}
+
+/*! Updates the extended status of a hardware device object.
+ */
+SANE_Status
+cmd_request_extended_status (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, 'f' };
+ byte info[4];
+
+ byte *data = NULL;
+ size_t size;
+
+ const size_t DEVNAME_OFFSET = 26;
+
+ const byte DEVT_MASK = 0xC0;
+ const byte DEVTYPE_3 = 0xC0;
+
+ log_call ();
+ require (hw);
+
+ if (!hw->cmd->request_extended_status)
+ return SANE_STATUS_UNSUPPORTED;
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, info, num_of (info), &status);
+
+ hw->status = info[1];
+ size = info[3] << 8 | info[2];
+
+ require (DEVNAME_OFFSET + DEVNAME_LENGTH <= size);
+
+ if (0 < size)
+ {
+ data = t_calloc (size, byte);
+ if (!data)
+ {
+ return SANE_STATUS_NO_MEM;
+ }
+
+ channel_recv (hw->channel, data, size, &status);
+ if (SANE_STATUS_GOOD == status)
+ {
+ fixme_request_extended_status (hw->fw_name, data, size);
+
+ hw->ext_status = data[0];
+
+ hw->cmd->request_push_button_status
+ = ((EXT_STATUS_PB & data[0]) ? '!' : 0);
+
+ if (!(EXT_STATUS_NO_FBF & data[0]) && !hw->fbf)
+ {
+ hw->fbf = t_calloc (1, fbf_extension);
+ if (!hw->fbf) status = SANE_STATUS_NO_MEM;
+ }
+ if ((ADF_STATUS_IST & data[1]) && !hw->adf)
+ {
+ hw->adf = t_calloc (1, adf_extension);
+ if (!hw->adf) status = SANE_STATUS_NO_MEM;
+ }
+ if ((TPU_STATUS_IST & data[6]) && !hw->tpu)
+ {
+ hw->tpu = t_calloc (1, tpu_extension);
+ if (!hw->tpu) status = SANE_STATUS_NO_MEM;
+ }
+
+ if (hw->fbf)
+ {
+ hw->fbf->status = 0x00;
+ if (DEVTYPE_3 == (DEVT_MASK & data[11]))
+ {
+ hw->fbf->status = data[11];
+ hw->fbf->max_x = data[13] << 8 | data[12];
+ hw->fbf->max_y = data[15] << 8 | data[14];
+ }
+ else
+ {
+ hw->fbf->max_x = hw->max_x;
+ hw->fbf->max_y = hw->max_y;
+ }
+ update_ranges (hw, hw->fbf);
+ update_doc_size (hw->fbf, data[19] << 8 | data[18]);
+ }
+ if (hw->adf)
+ {
+ hw->adf->status = data[1];
+ hw->adf->max_x = data[3] << 8 | data[2];
+ hw->adf->max_y = data[5] << 8 | data[4];
+ update_ranges (hw, hw->adf);
+ update_doc_size (hw->adf, data[17] << 8 | data[16]);
+ }
+ if (hw->tpu)
+ {
+ hw->tpu->status = data[6];
+ hw->tpu->max_x = data[ 8] << 8 | data[7];
+ hw->tpu->max_y = data[10] << 8 | data[9];
+ update_ranges (hw, hw->tpu);
+ }
+ }
+ delete (data);
+ }
+
+ return status;
+}
+
+
+/*! Sets the option unit to use as well as the unit's behaviour.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_control_option_unit (device *hw, byte value)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, 'e' };
+ byte reply = NUL;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+ channel_send (hw->channel, &value, 1, &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ return status;
+}
+
+
+/*! Resets the device to a well known state.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_initialize (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, '@' };
+ byte reply = NUL;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ return status;
+}
+
+/*! Loads a sheet on a page type ADF extension.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_load_paper (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { PF };
+ byte reply = NUL;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ return status;
+}
+
+/*! Ejects sheets from the ADF extension.
+
+ \todo Implement error checking.
+ */
+SANE_Status
+cmd_eject_paper (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { FF };
+ byte reply = NUL;
+
+ log_call ();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ return status;
+}
+
+SANE_Status
+cmd_lock (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, '(' };
+ byte reply = NUL;
+
+ log_call();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ if (SANE_STATUS_GOOD == status)
+ {
+ if (0x80 == reply)
+ {
+ hw->is_locked = true;
+ }
+ else if (0x40 == reply)
+ {
+ err_minor ("failed to acquire lock");
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+ else if (NAK == reply)
+ {
+ err_minor ("locking not supported by device, disabling");
+ hw->uses_locking = false;
+ status = SANE_STATUS_GOOD;
+ }
+ else
+ {
+ err_major ("unexpected reply to lock command (%02x)", reply);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ }
+ return status;
+}
+
+SANE_Status
+cmd_unlock (device *hw)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, ')' };
+ byte reply = NUL;
+
+ log_call();
+ require (hw);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &reply, 1, &status);
+
+ if (SANE_STATUS_GOOD == status)
+ {
+ if (0x80 == reply)
+ {
+ hw->is_locked = false;
+ }
+ else if (NAK == reply)
+ {
+ err_minor ("locking not supported by device, disabling");
+ hw->uses_locking = false;
+ status = SANE_STATUS_GOOD;
+ }
+ else
+ {
+ err_major ("unexpected reply to unlock command (%02x)", reply);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ }
+
+ return status;
+}
+
+SANE_Status
+cmd_request_scanner_maintenance (device *hw, uint16_t mode)
+{
+ SANE_Status status = SANE_STATUS_GOOD;
+
+ const byte cmd[] = { ESC, '1' };
+ byte param[8];
+ byte reply = NUL;
+
+ log_call ("(%04x)", mode);
+ require (hw);
+
+ memset (param, 0, sizeof (param));
+ uint16_to_buf (mode, param);
+
+ channel_send (hw->channel, cmd, num_of (cmd), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &reply, sizeof (reply), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ if (ACK != reply)
+ {
+ err_major ("unexpected reply to maintenance command (%02x)", reply);
+ return SANE_STATUS_IO_ERROR;
+ }
+
+ channel_send (hw->channel, (const byte *)param, num_of (param), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+ channel_recv (hw->channel, &reply, sizeof (reply), &status);
+ if (SANE_STATUS_GOOD != status) return status;
+
+ if (BUSY == reply)
+ {
+ status = SANE_STATUS_DEVICE_BUSY;
+ }
+ else if (NAK == reply)
+ {
+ err_minor ("invalid maintenance command (%04x)", mode);
+ status = SANE_STATUS_INVAL;
+ }
+ else if (ACK != reply)
+ {
+ err_major ("unexpected reply to maintenance command (mode=%04x, %02x)",
+ mode, reply);
+ status = SANE_STATUS_IO_ERROR;
+ }
+ return status;
+}