selphy_print/backend_mitsu9550.c

1463 lines
37 KiB
C
Raw Normal View History

/*
* Mitsubishi CP-9xxx Photo Printer Family CUPS backend
*
* (c) 2014-2016 Solomon Peachy <pizza@shaftnet.org>
*
* The latest version of this program can be found at:
*
* http://git.shaftnet.org/cgit/selphy_print.git
*
* 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 3 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.
*
* [http://www.gnu.org/licenses/gpl-3.0.html]
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <signal.h>
#define BACKEND mitsu9550_backend
#include "backend_common.h"
#define USB_VID_MITSU 0x06D3
#define USB_PID_MITSU_9500D 0x0393
#define USB_PID_MITSU_9000D 0x0394
#define USB_PID_MITSU_9000AM 0x0395
#define USB_PID_MITSU_9550D 0x03A1
#define USB_PID_MITSU_9550DS 0x03A5 // or DZ/DZS/DZU
#define USB_PID_MITSU_9600D 0x03A9
//#define USB_PID_MITSU_9600DS XXXXXX
#define USB_PID_MITSU_9800D 0x03AD
#define USB_PID_MITSU_9800DS 0x03AE
#define USB_PID_MITSU_98__D 0x3B21
//#define USB_PID_MITSU_9810D XXXXXX
//#define USB_PID_MITSU_9820DS XXXXXX
/* Spool file structures */
/* Print parameters1 */
struct mitsu9550_hdr1 {
uint8_t cmd[4]; /* 1b 57 20 2e */
uint8_t unk[10]; /* 00 0a 10 00 [...] */
uint16_t cols; /* BE */
uint16_t rows; /* BE */
2016-10-12 20:29:02 -04:00
uint8_t matte; /* CP9810 only. 01 for matte, 00 glossy */
uint8_t null[31];
} __attribute__((packed));
/* Print parameters2 */
struct mitsu9550_hdr2 {
uint8_t cmd[4]; /* 1b 57 21 2e */
2016-09-28 14:15:04 -04:00
uint8_t unk[24]; /* 00 80 00 22 08 03 00 [...] */
uint16_t copies; /* BE, 1-680 */
uint8_t null[2];
uint8_t cut; /* 00 == normal, 83 == 2x6*2 */
uint8_t unkb[5];
uint8_t mode; /* 00 == fine, 80 == superfine */
2016-09-28 14:15:04 -04:00
uint8_t unkc[11]; /* 00 [...] 00 01 */
} __attribute__((packed));
/* Fine Deep selection (9550 only) */
struct mitsu9550_hdr3 {
uint8_t cmd[4]; /* 1b 57 22 2e */
2016-09-28 14:15:04 -04:00
uint8_t unk[7]; /* 00 40 00 [...] */
uint8_t mode2; /* 00 == normal, 01 == finedeep */
2016-09-28 14:15:04 -04:00
uint8_t null[38];
} __attribute__((packed));
/* Error policy? */
struct mitsu9550_hdr4 {
uint8_t cmd[4]; /* 1b 57 26 2e */
2016-09-28 14:15:04 -04:00
uint8_t unk[46]; /* 00 70 00 00 00 00 00 00 01 01 00 [...] */
} __attribute__((packed));
/* Data plane header */
struct mitsu9550_plane {
uint8_t cmd[4]; /* 1b 5a 54 XX */ /* XX == 0x10 if 16bpp, 0x00 for 8bpp */
uint16_t row_offset; /* BE, normally 0, where we start dumping data */
uint16_t null; /* ??? */
uint16_t cols; /* BE */
uint16_t rows; /* BE */
} __attribute__((packed));
2014-12-14 19:09:32 -05:00
struct mitsu9550_cmd {
uint8_t cmd[4];
} __attribute__((packed));
/* Private data stucture */
struct mitsu9550_ctx {
struct libusb_device_handle *dev;
uint8_t endp_up;
uint8_t endp_down;
int type;
int is_s;
uint8_t *databuf;
uint32_t datalen;
uint16_t rows;
uint16_t cols;
uint32_t plane_len;
uint16_t last_donor;
uint16_t last_remain;
int marker_reported;
/* Parse headers separately */
struct mitsu9550_hdr1 hdr1;
int hdr1_present;
struct mitsu9550_hdr2 hdr2;
int hdr2_present;
struct mitsu9550_hdr3 hdr3;
int hdr3_present;
struct mitsu9550_hdr4 hdr4;
int hdr4_present;
};
/* Printer data structures */
struct mitsu9550_media {
uint8_t hdr[2]; /* 24 2e */
uint8_t unk[12];
uint8_t type;
uint8_t unka[13];
uint16_t max; /* BE, prints per media */
uint8_t unkb[2];
uint16_t remain; /* BE, prints remaining */
uint8_t unkc[14];
} __attribute__((packed));
struct mitsu9550_status {
uint8_t hdr[2]; /* 30 2e */
uint8_t null[4];
uint8_t sts1; // MM
2014-12-20 12:43:22 -05:00
uint8_t nullb[1];
2016-09-28 14:15:04 -04:00
uint16_t copies; // BE, NN
uint8_t sts2; // ZZ (9600 only?)
uint8_t nullc[5];
uint8_t sts3; // QQ
uint8_t sts4; // RR
uint8_t sts5; // SS
uint8_t nulld[25];
uint8_t sts6; // TT
uint8_t sts7; // UU
uint8_t nulle[2];
} __attribute__((packed));
struct mitsu9550_status2 {
uint8_t hdr[2]; /* 21 2e */
uint8_t unk[39];
uint16_t remain; /* BE, media remaining */
uint8_t unkb[4]; /* 0a 00 00 01 */
} __attribute__((packed));
#define CMDBUF_LEN 64
#define READBACK_LEN 128
#define QUERY_STATUS() \
do {\
struct mitsu9550_status *sts = (struct mitsu9550_status*) rdbuf;\
/* struct mitsu9550_status2 *sts2 = (struct mitsu9550_status2*) rdbuf; */ \
struct mitsu9550_media *media = (struct mitsu9550_media *) rdbuf; \
uint16_t donor, remain; \
/* media */ \
ret = mitsu9550_get_status(ctx, rdbuf, 0, 0, 1); \
if (ret < 0) \
return CUPS_BACKEND_FAILED; \
\
/* Tell CUPS about the consumables we report */ \
if (!ctx->marker_reported) { \
ctx->marker_reported = 1; \
ATTR("marker-colors=#00FFFF#FF00FF#FFFF00\n"); \
ATTR("marker-high-levels=100\n"); \
ATTR("marker-low-levels=10\n"); \
ATTR("marker-names='%s'\n", mitsu9550_media_types(media->type, ctx->is_s)); \
ATTR("marker-types=ribbonWax\n"); \
} \
\
/* Sanity-check media response */ \
if (media->remain == 0 || media->max == 0) { \
ERROR("Printer out of media!\n"); \
ATTR("marker-levels=%d\n", 0); \
return CUPS_BACKEND_HOLD; \
} \
remain = be16_to_cpu(media->remain); \
donor = be16_to_cpu(media->max); \
donor = remain/donor; \
if (donor != ctx->last_donor) { \
ctx->last_donor = donor; \
ATTR("marker-levels=%u\n", donor); \
} \
if (remain != ctx->last_remain) { \
ctx->last_remain = remain; \
ATTR("marker-message=\"%u prints remaining on '%s' ribbon\"\n", remain, mitsu9550_media_types(media->type, ctx->is_s)); \
} \
if (validate_media(ctx->type, media->type, ctx->cols, ctx->rows)) { \
ERROR("Incorrect media (%u) type for printjob (%ux%u)!\n", media->type, ctx->cols, ctx->rows); \
return CUPS_BACKEND_HOLD; \
} \
/* status2 */ \
ret = mitsu9550_get_status(ctx, rdbuf, 0, 1, 0); \
if (ret < 0) \
return CUPS_BACKEND_FAILED; \
/* status */ \
ret = mitsu9550_get_status(ctx, rdbuf, 1, 0, 0); \
if (ret < 0) \
return CUPS_BACKEND_FAILED; \
\
/* Make sure we're idle */ \
if (sts->sts5 != 0) { /* Printer ready for another job */ \
sleep(1); \
goto top; \
} \
/* Check for known errors */ \
if (sts->sts2 != 0) { \
ERROR("Printer cover open!\n"); \
return CUPS_BACKEND_STOP; \
} \
} while (0);
static void *mitsu9550_init(void)
{
struct mitsu9550_ctx *ctx = malloc(sizeof(struct mitsu9550_ctx));
if (!ctx) {
ERROR("Memory Allocation Failure!\n");
return NULL;
}
memset(ctx, 0, sizeof(struct mitsu9550_ctx));
return ctx;
}
static void mitsu9550_attach(void *vctx, struct libusb_device_handle *dev,
uint8_t endp_up, uint8_t endp_down, uint8_t jobid)
{
struct mitsu9550_ctx *ctx = vctx;
struct libusb_device *device;
struct libusb_device_descriptor desc;
UNUSED(jobid);
ctx->dev = dev;
ctx->endp_up = endp_up;
ctx->endp_down = endp_down;
device = libusb_get_device(dev);
libusb_get_device_descriptor(device, &desc);
ctx->type = lookup_printer_type(&mitsu9550_backend,
desc.idVendor, desc.idProduct);
if (ctx->type == P_MITSU_9550S ||
ctx->type == P_MITSU_9800S)
ctx->is_s = 1;
ctx->last_donor = ctx->last_remain = 65535;
}
static void mitsu9550_teardown(void *vctx) {
struct mitsu9550_ctx *ctx = vctx;
if (!ctx)
return;
if (ctx->databuf)
free(ctx->databuf);
free(ctx);
}
static int mitsu9550_read_parse(void *vctx, int data_fd) {
struct mitsu9550_ctx *ctx = vctx;
uint8_t buf[sizeof(struct mitsu9550_hdr1)];
int remain, i;
uint32_t planelen = 0;
if (!ctx)
return CUPS_BACKEND_FAILED;
if (ctx->databuf) {
free(ctx->databuf);
ctx->databuf = NULL;
}
ctx->hdr1_present = 0;
ctx->hdr2_present = 0;
ctx->hdr3_present = 0;
ctx->hdr4_present = 0;
top:
/* Read in initial header */
remain = sizeof(buf);
while (remain > 0) {
i = read(data_fd, buf + sizeof(buf) - remain, remain);
if (i == 0)
return CUPS_BACKEND_CANCEL;
if (i < 0)
return CUPS_BACKEND_CANCEL;
remain -= i;
}
/* Sanity check */
if (buf[0] != 0x1b || buf[1] != 0x57 || buf[3] != 0x2e) {
if (!ctx->hdr1_present || !ctx->hdr2_present) {
ERROR("Unrecognized data format!\n");
return CUPS_BACKEND_CANCEL;
} else if (buf[0] == 0x1b && buf[1] == 0x5a &&
buf[2] == 0x54) {
/* We're in the data portion now */
if (buf[3] == 0x10)
planelen *= 2;
goto hdr_done;
} else {
2016-10-23 11:59:29 -04:00
ERROR("Unrecognized data block!\n");
return CUPS_BACKEND_CANCEL;
}
}
switch(buf[2]) {
case 0x20: /* header 1 */
memcpy(&ctx->hdr1, buf, sizeof(ctx->hdr1));
ctx->hdr1_present = 1;
/* Work out printjob size */
ctx->rows = be16_to_cpu(ctx->hdr1.rows);
ctx->cols = be16_to_cpu(ctx->hdr1.cols);
planelen = ctx->rows * ctx->cols;
break;
case 0x21: /* header 2 */
memcpy(&ctx->hdr2, buf, sizeof(ctx->hdr2));
ctx->hdr2_present = 1;
break;
case 0x22: /* header 3 */
memcpy(&ctx->hdr3, buf, sizeof(ctx->hdr3));
ctx->hdr3_present = 1;
break;
case 0x26: /* header 4 */
memcpy(&ctx->hdr4, buf, sizeof(ctx->hdr4));
ctx->hdr4_present = 1;
break;
default:
ERROR("Unrecognized header format (%02x)!\n", buf[2]);
return CUPS_BACKEND_CANCEL;
}
/* Read in the next chunk */
goto top;
hdr_done:
/* We have three planes and the final terminator to read */
remain = 3 * (planelen + sizeof(struct mitsu9550_plane)) + sizeof(struct mitsu9550_cmd);
/* Mitsu9600 windows spool uses more, smaller blocks, but plane data is the same */
if (ctx->type == P_MITSU_9600) {
remain += 128 * sizeof(struct mitsu9550_plane); /* 39 extra seen on 4x6" */
}
/* Don't forget the matte plane! */
if (ctx->hdr1.matte) {
remain += planelen + sizeof(struct mitsu9550_plane) + sizeof(struct mitsu9550_cmd);
}
/* 9550S/9800S doesn't typically sent over hdr4! */
if (ctx->type == P_MITSU_9550S ||
ctx->type == P_MITSU_9800S) {
/* XXX Has to do with error policy, but not sure what.
Mitsu9550-S/9800-S will set this based on a command,
but it's not part of the standard job spool */
ctx->hdr4_present = 0;
}
/* Allocate buffer for the payload */
ctx->datalen = 0;
ctx->databuf = malloc(remain);
if (!ctx->databuf) {
ERROR("Memory allocation failure!\n");
return CUPS_BACKEND_FAILED;
}
/* Load up the data blocks.*/
while(1) {
2016-10-23 11:59:29 -04:00
/* Note that 'buf' needs to be already filled here! */
struct mitsu9550_plane *plane = (struct mitsu9550_plane *)buf;
/* Sanity check header... */
if (plane->cmd[0] != 0x1b ||
plane->cmd[1] != 0x5a ||
plane->cmd[2] != 0x54) {
ERROR("Unexpected data read, aborting job\n");
return CUPS_BACKEND_CANCEL;
}
/* Work out the length of this block */
planelen = be16_to_cpu(plane->rows) * be16_to_cpu(plane->cols);
if (plane->cmd[3] == 0x10)
planelen *= 2;
/* Copy plane header into buffer */
2016-10-23 13:56:59 -04:00
memcpy(ctx->databuf + ctx->datalen, buf, sizeof(buf));
ctx->datalen += sizeof(buf);
2016-10-23 11:59:29 -04:00
planelen -= sizeof(buf) - sizeof(struct mitsu9550_plane);
/* Read in the spool data */
while(planelen > 0) {
i = read(data_fd, ctx->databuf + ctx->datalen, planelen);
if (i == 0)
return CUPS_BACKEND_CANCEL;
if (i < 0)
return CUPS_BACKEND_CANCEL;
ctx->datalen += i;
planelen -= i;
}
/* Try to read in the next chunk. It will be one of:
- Additional block header (12B)
- Job footer (4B)
*/
i = read(data_fd, buf, 4);
if (i == 0)
return CUPS_BACKEND_CANCEL;
if (i < 0)
return CUPS_BACKEND_CANCEL;
/* Is this a "job end" marker? */
if (plane->cmd[0] != 0x1b ||
plane->cmd[1] != 0x5a ||
plane->cmd[2] != 0x54) {
/* store it in the buffer */
memcpy(ctx->databuf + ctx->datalen, buf, 4);
ctx->datalen += 4;
/* Unless we have a matte plane following, we're done */
if (!ctx->hdr1.matte)
break;
planelen = sizeof(buf);
} else {
/* It's part of a block header, mark what we've read */
planelen = sizeof(buf) - 4;
}
/* Read in the rest of the header */
while (planelen > 0) {
i = read(data_fd, buf + sizeof(buf) - planelen, planelen);
if (i == 0)
return CUPS_BACKEND_CANCEL;
if (i < 0)
return CUPS_BACKEND_CANCEL;
planelen -= i;
}
}
/* Disable matte if the printer doesn't support it */
2016-10-23 11:59:29 -04:00
if (ctx->hdr1.matte && ctx->type != P_MITSU_9810) {
WARNING("Matte not supported on this printer, disabling\n");
ctx->hdr1.matte = 0;
}
return CUPS_BACKEND_OK;
}
static int mitsu9550_get_status(struct mitsu9550_ctx *ctx, uint8_t *resp, int status, int status2, int media)
{
struct mitsu9550_cmd cmd;
int num, ret;
/* Send Printer Query */
cmd.cmd[0] = 0x1b;
cmd.cmd[1] = 0x56;
if (status)
cmd.cmd[2] = 0x30;
else if (status2)
cmd.cmd[2] = 0x21;
else if (media)
cmd.cmd[2] = 0x24;
cmd.cmd[3] = 0x00;
if ((ret = send_data(ctx->dev, ctx->endp_down,
(uint8_t*) &cmd, sizeof(cmd))))
return ret;
ret = read_data(ctx->dev, ctx->endp_up,
resp, sizeof(struct mitsu9550_status), &num);
if (ret < 0)
return ret;
if (num != sizeof(struct mitsu9550_status)) {
ERROR("Short Read! (%d/%d)\n", num, (int)sizeof(struct mitsu9550_status));
return 4;
}
return 0;
}
static char *mitsu9550_media_types(uint8_t type, uint8_t is_s)
{
if (is_s) {
switch (type & 0xf) { /* values can be 0x0? or 0x4? */
case 0x02:
return "CK9015 (4x6)";
case 0x04:
return "CK9318 (5x7)";
case 0x05:
return "CK9523 (6x9)";
default:
return "Unknown";
}
return NULL;
}
2016-09-28 14:15:04 -04:00
switch (type & 0xf) { /* values can be 0x0? or 0x4? */
case 0x01:
2016-09-28 14:15:04 -04:00
return "CK9035 (3.5x5)";
case 0x02:
2016-09-28 14:15:04 -04:00
return "CK9046 (4x6)";
case 0x03:
2016-09-28 14:15:04 -04:00
return "CK9046PST (4x6)";
case 0x04:
2016-09-28 14:15:04 -04:00
return "CK9057 (5x7)";
case 0x05:
2016-09-28 14:15:04 -04:00
return "CK9069 (6x9)";
case 0x06:
2016-09-28 14:15:04 -04:00
return "CK9068 (6x8)";
default:
return "Unknown";
}
return NULL;
}
static int validate_media(int type, int media, int cols, int rows)
{
switch(type) {
case P_MITSU_9550:
switch(media & 0xf) {
case 0x01: /* 3.5x5 */
if (cols != 1812 && rows != 1240)
return 1;
break;
case 0x02: /* 4x6 */
case 0x03: /* 4x6 postcard */
if (cols != 2152)
return 1;
if (rows != 1416 && rows != 1184 && rows != 1240)
return 1;
break;
case 0x04: /* 5x7 */
if (cols != 1812)
return 1;
if (rows != 1240 && rows != 2452)
return 1;
break;
case 0x05: /* 6x9 */
if (cols != 2152)
return 1;
if (rows != 1416 && rows != 2792 &&
rows != 2956 && rows != 3146)
return 1;
break;
case 0x06: /* V (6x8??) */
if (cols != 2152)
return 1;
if (rows != 1416 && rows != 2792)
return 1;
break;
default: /* Unknown */
WARNING("Unknown media type %02x\n", media);
break;
}
break;
case P_MITSU_9550S:
switch(media & 0xf) {
case 0x02: /* 4x6 */
case 0x03: /* 4x6 postcard */
if (cols != 2152)
return 1;
if (rows != 1416 && rows != 1184 && rows != 1240)
return 1;
break;
case 0x04: /* 5x7 */
if (cols != 1812 && rows != 2452)
return 1;
break;
case 0x05: /* 6x9 */
if (cols != 2152)
return 1;
if (rows != 1416 && rows != 2792 &&
rows != 2956 && rows != 3146)
return 1;
break;
case 0x06: /* V (6x8??) */
if (cols != 2152)
return 1;
if (rows != 1416 && rows != 2792)
return 1;
break;
default: /* Unknown */
WARNING("Unknown media type %02x\n", media);
break;
}
break;
case P_MITSU_9600: // XXX 9600S doesn't support 5" media at all!
switch(media & 0xf) {
case 0x01: /* 3.5x5 */
if (cols == 1572) {
if (rows == 1076)
break;
} else if (cols == 3144) {
if (rows == 2152)
break;
}
return 1;
case 0x02: /* 4x6 */
case 0x03: /* 4x6 postcard */
if (cols == 1868) {
if (rows == 1228)
break;
} else if (cols == 3736) {
if (rows == 2458)
break;
}
return 1;
case 0x04: /* 5x7 */
if (cols == 1572) {
if (rows == 1076 || rows == 2128)
break;
} else if (cols == 3144) {
if (rows == 2152 || rows == 4256)
break;
}
return 1;
case 0x05: /* 6x9 */
if (cols == 1868) {
if (rows == 1228 || rows == 2442 || rows == 2564 || rows == 2730)
break;
} else if (cols == 3736) {
if (rows == 2458 || rows == 4846 || rows == 5130 || rows == 5462)
break;
}
return 1;
case 0x06: /* V (6x8??) */
if (cols == 1868) {
if (rows == 1228 || rows == 2442)
break;
} else if (cols == 3736) {
if (rows == 2458 || rows == 4846)
break;
}
return 1;
default: /* Unknown */
WARNING("Unknown media type %02x\n", media);
break;
}
break;
case P_MITSU_9800:
case P_MITSU_9810: // XXX and don't forget the 9820S
switch(media & 0xf) {
case 0x01: /* 3.5x5 */
if (cols != 1572 && rows != 1076)
return 1;
break;
case 0x02: /* 4x6 */
case 0x03: /* 4x6 postcard */
if (cols != 1868 && rows != 1228)
return 1;
break;
case 0x04: /* 5x7 */
if (cols != 1572 && rows != 2128)
return 1;
break;
case 0x05: /* 6x9 */
if (cols != 1868)
return 1;
if (rows != 1228 && rows != 2442 &&
rows != 2564 && rows != 2730)
return 1;
break;
case 0x06: /* V (6x8??) */
if (cols != 1868)
return 1;
if (rows != 1228 && rows != 2442)
return 1;
break;
default: /* Unknown */
WARNING("Unknown media type %02x\n", media);
break;
}
break;
case P_MITSU_9800S:
switch(media & 0xf) {
case 0x02: /* 4x6 */
case 0x03: /* 4x6 postcard */
if (cols != 1868 && rows != 1228)
return 1;
break;
case 0x04: /* 5x7 */
if (cols != 1572 && rows != 2128)
return 1;
break;
case 0x05: /* 6x9 */
if (cols != 1868)
return 1;
if (rows != 1228 && rows != 2442 &&
rows != 2564 && rows != 2730)
return 1;
break;
case 0x06: /* V (6x8??) */
if (cols != 1868)
return 1;
if (rows != 1228 && rows != 2442)
return 1;
break;
default: /* Unknown */
WARNING("Unknown media type %02x\n", media);
break;
}
break;
default:
WARNING("Unknown printer type %d\n", type);
break;
}
return 0;
}
static int mitsu9550_main_loop(void *vctx, int copies) {
struct mitsu9550_ctx *ctx = vctx;
struct mitsu9550_cmd cmd;
uint8_t rdbuf[READBACK_LEN];
uint8_t *ptr;
int ret;
if (!ctx)
return CUPS_BACKEND_FAILED;
/* Update printjob header to reflect number of requested copies */
ctx->hdr2.copies = cpu_to_be16(copies);
/* Okay, let's do this thing */
ptr = ctx->databuf;
top:
if (ctx->is_s) {
int num;
/* Send "unknown 1" command */
cmd.cmd[0] = 0x1b;
cmd.cmd[1] = 0x53;
cmd.cmd[2] = 0xc5;
cmd.cmd[3] = 0x9d;
if ((ret = send_data(ctx->dev, ctx->endp_down,
(uint8_t*) &cmd, sizeof(cmd))))
return CUPS_BACKEND_FAILED;
/* Send "unknown 2" command */
cmd.cmd[0] = 0x1b;
cmd.cmd[1] = 0x4b;
cmd.cmd[2] = 0x7f;
cmd.cmd[3] = 0x00;
if ((ret = send_data(ctx->dev, ctx->endp_down,
(uint8_t*) &cmd, sizeof(cmd))))
return CUPS_BACKEND_FAILED;
ret = read_data(ctx->dev, ctx->endp_up,
rdbuf, READBACK_LEN, &num);
if (ret < 0)
return CUPS_BACKEND_FAILED;
// seen so far: eb 4b 7f 00 02 00 5e
}