1 /*
2  *   Citizen / DNP Photo Printer CUPS backend -- libusb-1.0 version
3  *
4  *   (c) 2013-2019 Solomon Peachy <pizza@shaftnet.org>
5  *
6  *   Development of this backend was sponsored by:
7  *
8  *     Marco Di Antonio and [ ilgruppodigitale.com ]
9  *     LiveLink Technology [ www.livelinktechnology.net ]
10  *     A generous benefactor who wishes to remain anonymous
11  *
12  *   The latest version of this program can be found at:
13  *
14  *     http://git.shaftnet.org/cgit/selphy_print.git
15  *
16  *   This program is free software; you can redistribute it and/or modify it
17  *   under the terms of the GNU General Public License as published by the Free
18  *   Software Foundation; either version 2 of the License, or (at your option)
19  *   any later version.
20  *
21  *   This program is distributed in the hope that it will be useful, but
22  *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
23  *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
24  *   for more details.
25  *
26  *   You should have received a copy of the GNU General Public License
27  *   along with this program.  If not, see <https://www.gnu.org/licenses/>.
28  *
29  *          [http://www.gnu.org/licenses/gpl-2.0.html]
30  *
31  *   SPDX-License-Identifier: GPL-2.0+
32  *
33  */
34 
35 //#define DNP_ONLY
36 //#define CITIZEN_ONLY
37 
38 /* Enables caching of last print type to speed up
39    job pipelining.  Without this we always have to
40    assume the worst */
41 //#define STATE_DIR "/tmp"
42 
43 #include <stdio.h>
44 #include <stdlib.h>
45 #include <string.h>
46 #include <unistd.h>
47 
48 #include <sys/types.h>
49 #include <sys/stat.h>
50 #include <fcntl.h>
51 #include <signal.h>
52 
53 #define BACKEND dnpds40_backend
54 
55 #include "backend_common.h"
56 
57 /* Private data structure */
58 struct dnpds40_printjob {
59 	uint8_t *databuf;
60 	int datalen;
61 
62 	int copies;
63 	uint32_t dpi;
64 	int matte;
65 	int cutter;
66 	uint32_t multicut;
67 	int fullcut;
68 	int printspeed;
69 	int can_rewind;
70 	int buf_needed;
71 	int cut_paper;
72 };
73 
74 struct dnpds40_ctx {
75 	struct libusb_device_handle *dev;
76 	uint8_t endp_up;
77 	uint8_t endp_down;
78 
79 	int type;
80 
81 	/* Version and whatnot */
82 	char *serno;
83 	char *version;
84 	int ver_major;
85 	int ver_minor;
86 
87 	/* State */
88 	uint32_t media;
89 	uint32_t duplex_media;
90 	int      duplex_media_status;
91 	uint16_t media_count_new;
92 
93 	uint32_t last_multicut;
94 	int last_matte;
95 
96 	int mediaoffset;
97 	int correct_count;
98 	int needs_mlot;
99 
100 	struct marker marker[2];
101 	int marker_count;
102 
103 	/* Printer capabilities */
104 	uint32_t native_width;
105 	uint32_t max_height;
106 	int supports_6x9;
107 	int supports_2x6;
108 	int supports_3x5x2;
109 	int supports_matte;
110 	int supports_finematte;
111 	int supports_luster;
112 	int supports_advmatte;
113 	int supports_fullcut;
114 	int supports_rewind;
115 	int supports_standby;
116 	int supports_6x4_5;
117 	int supports_mqty_default;
118 	int supports_iserial;
119 	int supports_6x6;
120 	int supports_5x5;
121 	int supports_counterp;
122 	int supports_adv_fullcut;
123 	int supports_mediaoffset;
124 	int supports_media_ext;
125 	int supports_printspeed;
126 	int supports_lowspeed;
127 	int supports_highdensity;
128 	int supports_gamma;
129 };
130 
131 struct dnpds40_cmd {
132 	uint8_t esc; /* Fixed at ascii ESC, aka 0x1B */
133 	uint8_t p;   /* Fixed at ascii 'P' aka 0x50 */
134 	uint8_t arg1[6];
135 	uint8_t arg2[16];
136 	uint8_t arg3[8]; /* Decimal value of arg4's length, or empty */
137 	uint8_t arg4[0]; /* Extra payload if arg3 is non-empty
138 			    Doesn't have to be sent in the same URB */
139 
140 	/* All unused elements are set to 0x20 (ie ascii space) */
141 };
142 
143 #define MULTICUT_5x3_5     1
144 #define MULTICUT_6x4       2
145 #define MULTICUT_5x7       3
146 #define MULTICUT_6x8       4
147 #define MULTICUT_6x9       5
148 #define MULTICUT_8x10      6
149 #define MULTICUT_8x12      7
150 #define MULTICUT_8x4       8
151 #define MULTICUT_8x5       9
152 #define MULTICUT_8x6      10
153 #define MULTICUT_8x8      11
154 #define MULTICUT_6x4X2    12
155 #define MULTICUT_8x4X2    13
156 #define MULTICUT_8x5X2    14
157 #define MULTICUT_8x6X2    15
158 #define MULTICUT_8x5_8x4  16
159 #define MULTICUT_8x6_8x4  17
160 #define MULTICUT_8x6_8x5  18
161 #define MULTICUT_8x8_8x4  19
162 #define MULTICUT_8x4X3    20
163 #define MULTICUT_8xA4LEN  21
164 #define MULTICUT_5x3_5X2  22
165 #define MULTICUT_6x6      27
166 #define MULTICUT_5x5      29
167 #define MULTICUT_6x4_5    30
168 #define MULTICUT_6x4_5X2  31
169 #define MULTICUT_8x7      32
170 #define MULTICUT_8x9      33
171 #define MULTICUT_A5       34
172 #define MULTICUT_A5X2     35
173 #define MULTICUT_A4x4     36
174 #define MULTICUT_A4x5     37
175 #define MULTICUT_A4x6     38
176 #define MULTICUT_A4x8     39
177 #define MULTICUT_A4x10    40
178 #define MULTICUT_A4       41
179 #define MULTICUT_A4x5X2   43
180 
181 #define MULTICUT_S_SIMPLEX  100
182 #define MULTICUT_S_FRONT    200
183 #define MULTICUT_S_BACK     300
184 
185 #define MULTICUT_S_8x10     6
186 #define MULTICUT_S_8x12     7
187 #define MULTICUT_S_8x4      8
188 #define MULTICUT_S_8x5      9
189 #define MULTICUT_S_8x6     10
190 #define MULTICUT_S_8x8     11
191 #define MULTICUT_S_8x4X2   13
192 #define MULTICUT_S_8x5X2   14
193 #define MULTICUT_S_8x6X2   15
194 #define MULTICUT_S_8x10_5  25
195 #define MULTICUT_S_8x10_75 26
196 #define MULTICUT_S_8x4X3   28  // different than roll type.
197 
198 #define min(__x, __y) ((__x) < (__y)) ? __x : __y
199 
200 /* Legacy spool file support */
201 static int legacy_cw01_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
202 static int legacy_dnp_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
203 static int legacy_dnp620_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
204 static int legacy_dnp820_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data);
205 
206 static void dnpds40_cleanup_job(const void *vjob);
207 static int dnpds40_query_markers(void *vctx, struct marker **markers, int *count);
208 
209 #define JOB_EQUIV(__x)  if (job1->__x != job2->__x) goto done
210 
combine_jobs(const struct dnpds40_printjob * job1,const struct dnpds40_printjob * job2)211 static struct dnpds40_printjob *combine_jobs(const struct dnpds40_printjob *job1,
212 					     const struct dnpds40_printjob *job2)
213 {
214 	struct dnpds40_printjob *newjob = NULL;
215 	uint32_t new_multicut;
216 	uint16_t new_w, new_h;
217 	uint16_t gap_bytes;
218 
219 	/* Sanity check */
220 	if (!job1 || !job2)
221 		goto done;
222 
223 	/* Make sure pertinent paremeters are the same */
224 	JOB_EQUIV(dpi);
225 	JOB_EQUIV(matte);
226 	JOB_EQUIV(cutter);
227 	JOB_EQUIV(fullcut);
228 	JOB_EQUIV(multicut);  // TODO:  Support fancier modes for 8" models (eg 8x4+8x6, etc)
229 	JOB_EQUIV(datalen); // <-- cheating a little?
230 	// JOV_EQUIV(printspeed); <-- does it matter?
231 
232 	/* Any cutter means we shouldn't bother */
233 	if (job1->fullcut || job1->cutter)
234 		goto done;
235 
236 #if 0
237 	// XXX TODO:  2x6*2 + 2x6*2 --> 8x6+cutter!
238 	// problem is that 8x6" size is 4 rows smaller than 2* 4x6" prints, posing a problem.
239 
240 	/* Only handle cutter if it's for 2x6" strips */
241 	if (job1->cutter != 0 && job1->cutter != 120)
242 		goto done;
243 #endif
244 
245 	/* Make sure we can combine these two prints */
246 	switch (job1->multicut) {
247 	case MULTICUT_5x3_5:
248 		new_multicut = MULTICUT_5x3_5X2;
249 		new_w = 1920;
250 		new_h = 2176;
251 		gap_bytes = 0;
252 		break;
253 	case MULTICUT_6x4:
254 #if 0
255 		if (job1->cutter != 120) {
256 			new_multicut = MULTICUT_6x8;
257 			new_h = 2436;
258 			gap_bytes = -4;
259 		} else {
260 #endif
261 			new_multicut = MULTICUT_6x4X2;
262 			new_h = 2498;
263 			gap_bytes = 18;
264 #if 0
265 		}
266 #endif
267 		new_w = 1920;
268 		break;
269 	case MULTICUT_6x4_5:
270 		new_multicut = MULTICUT_6x4_5X2;
271 		new_w = 1920;
272 		new_h = 2802;
273 		gap_bytes = 30;
274 		break;
275 	case MULTICUT_8x4:
276 		new_multicut = MULTICUT_8x4X2;
277 		new_w = 2560;
278 		new_h = 2502;
279 		gap_bytes = 30;
280 		break;
281 	case MULTICUT_8x5:
282 		new_multicut = MULTICUT_8x5X2;
283 		new_w = 2560;
284 		new_h = 3102;
285 		gap_bytes = 30;
286 		break;
287 	case MULTICUT_8x6:
288 		new_multicut = MULTICUT_8x6X2;
289 		new_w = 2560;
290 		new_h = 3702;
291 		gap_bytes = 30;
292 		break;
293 	default:
294 		// 2-up 8x6 prints too?
295 		/* Everything else is NOT handled */
296 		goto done;
297 	}
298 	gap_bytes *= new_w;
299 	if (job1->dpi == 600) {
300 		gap_bytes *= 2;
301 		new_h *= 2;
302 	}
303 
304 	DEBUG("Combining jobs to save media\n");
305 
306 	/* Okay, it's kosher to proceed */
307 
308 	newjob = malloc(sizeof(*newjob));
309 	if (!newjob) {
310 		ERROR("Memory allocation failure!\n");
311 		goto done;
312 	}
313 	memcpy(newjob, job1, sizeof(*newjob));
314 
315 	newjob->databuf = malloc(((new_w*new_h+1024+54+10))*3+1024);
316 	newjob->datalen = 0;
317 	newjob->multicut = new_multicut;
318 	if (!newjob->databuf) {
319 		dnpds40_cleanup_job(newjob);
320 		newjob = NULL;
321 		ERROR("Memory allocation failure!\n");
322 		goto done;
323 	}
324 
325 	/* Copy data blocks from job1 */
326 	uint8_t *ptr, *ptr2;
327 	char buf[9];
328 	ptr = job1->databuf;
329 	while(ptr && ptr < (job1->databuf + job1->datalen)) {
330 		int i;
331 		buf[8] = 0;
332 		memcpy(buf, ptr + 24, 8);
333 		i = atoi(buf) + 32;
334 		memcpy(newjob->databuf + newjob->datalen, ptr, i);
335 
336 		/* If we're on a plane data block... */
337 		if (!memcmp("PLANE", newjob->databuf + newjob->datalen + 9, 5)) {
338 			long planelen = (new_w * new_h) + 1088;
339 			uint32_t newlen;
340 
341 			/* Fix up length in command */
342 			snprintf(buf, sizeof(buf), "%08ld", planelen);
343 			memcpy(newjob->databuf + newjob->datalen + 24, buf, 8);
344 
345 			/* Alter BMP header */
346 			newlen = cpu_to_le32(planelen);
347 			memcpy(newjob->databuf + newjob->datalen + 32 + 2, &newlen, 4);
348 
349 			/* alter DIB header */
350 			newlen = cpu_to_le32(new_h);
351 			memcpy(newjob->databuf + newjob->datalen + 32 + 22, &newlen, 4);
352 
353 			/* Insert gap/padding after first image */
354 			memset(newjob->databuf + newjob->datalen + i, 0, gap_bytes);
355 			newjob->datalen += gap_bytes;
356 
357 			// locate job2's PLANE properly?  Assumption is it's in the same place.
358 			ptr2 = job2->databuf + (ptr - job1->databuf);
359 			/* Copy over job2's image data */
360 			memcpy(newjob->databuf + newjob->datalen + i,
361 			        ptr2 + 32 + 1088, i - 32 - 1088);
362 			newjob->datalen += i - 32 - 1088;  /* add in job2 length */
363 		}
364 
365 		newjob->datalen += i;
366 		ptr += i;
367 	}
368 
369 done:
370 	return newjob;
371 }
372 
373 #undef JOB_EQUIV
374 
dnpds40_build_cmd(struct dnpds40_cmd * cmd,char * arg1,char * arg2,uint32_t arg3_len)375 static void dnpds40_build_cmd(struct dnpds40_cmd *cmd, char *arg1, char *arg2, uint32_t arg3_len)
376 {
377 	memset(cmd, 0x20, sizeof(*cmd));
378 	cmd->esc = 0x1b;
379 	cmd->p = 0x50;
380 	memcpy(cmd->arg1, arg1, min(strlen(arg1), sizeof(cmd->arg1)));
381 	memcpy(cmd->arg2, arg2, min(strlen(arg2), sizeof(cmd->arg2)));
382 	if (arg3_len) {
383 		char buf[9];
384 		snprintf(buf, sizeof(buf), "%08u", arg3_len);
385 		memcpy(cmd->arg3, buf, 8);
386 	}
387 
388 }
389 
dnpds40_cleanup_string(char * start,int len)390 static void dnpds40_cleanup_string(char *start, int len)
391 {
392 	char *ptr = strchr(start, 0x0d);
393 
394 	if (ptr && (ptr - start < len)) {
395 		*ptr = 0x00; /* If there is a <CR>, terminate there */
396 		len = ptr - start;
397 	} else {
398 		start[--len] = 0x00;  /* force null-termination */
399 	}
400 
401 	/* Trim trailing spaces */
402 	while (len && start[len-1] == ' ') {
403 		start[--len] = 0;
404 	}
405 }
406 
dnpds40_printer_type(int type)407 static const char *dnpds40_printer_type(int type)
408 {
409 	switch(type) {
410 	case P_DNP_DS40: return "DS40";
411 	case P_DNP_DS80: return "DS80";
412 	case P_DNP_DS80D: return "DS80DX";
413 	case P_DNP_DSRX1: return "DSRX1";
414 	case P_DNP_DS620: return "DS620";
415 	case P_DNP_DS820: return "DS820";
416 	case P_CITIZEN_CW01: return "CW01";
417 	case P_CITIZEN_OP900II: return "OP900ii";
418 	default: break;
419 	}
420 	return "Unknown";
421 }
422 
dnpds40_media_types(int media)423 static const char *dnpds40_media_types(int media)
424 {
425 	switch (media) {
426 	case 100: return "UNKNOWN100"; // seen in driver dumps
427 	case 110: return "UNKNOWN110"; // seen in driver dumps
428 	case 200: return "5x3.5 (L)";
429 	case 210: return "5x7 (2L)";
430 	case 300: return "6x4 (PC)";
431 	case 310: return "6x8 (A5)";
432 	case 400: return "6x9 (A5W)";
433 	case 500: return "8x10";
434 	case 510: return "8x12";
435 	case 600: return "A4";
436 	default:
437 		break;
438 	}
439 
440 	return "Unknown type";
441 }
442 
dnpds620_media_extension_code(int media)443 static const char *dnpds620_media_extension_code(int media)
444 {
445 	switch (media) {
446 	case 00: return "Normal Paper";
447 	case 01: return "Sticky Paper";
448 	case 99: return "Unknown Paper";
449 	default:
450 		break;
451 	}
452 
453 	return "Unknown type";
454 }
455 
dnpds820_media_subtypes(int media)456 static const char *dnpds820_media_subtypes(int media)
457 {
458 	switch (media) {
459 	case 0001: return "SD";
460 	case 0003: return "PP";
461 	default:
462 		break;
463 	}
464 
465 	return "Unknown type";
466 }
467 
dnpds80_duplex_media_types(int media)468 static const char *dnpds80_duplex_media_types(int media)
469 {
470 	switch (media) {
471 	case 100: return "8x10.75";
472 	case 200: return "8x12";
473 	default:
474 		break;
475 	}
476 
477 	return "Unknown type";
478 }
479 
480 #define DUPLEX_UNIT_PAPER_NONE 0
481 #define DUPLEX_UNIT_PAPER_PROTECTIVE 1
482 #define DUPLEX_UNIT_PAPER_PRESENT 2
483 
dnpds80_duplex_paper_status(int media)484 static const char *dnpds80_duplex_paper_status(int media)
485 {
486 	switch (media) {
487 	case DUPLEX_UNIT_PAPER_NONE: return "No Paper";
488 	case DUPLEX_UNIT_PAPER_PROTECTIVE: return "Protective Sheet";
489 	case DUPLEX_UNIT_PAPER_PRESENT: return "Cut Paper Present";
490 	default:
491 		return "Unknown";
492 	}
493 }
494 
dnpds80_duplex_statuses(int status)495 static const char *dnpds80_duplex_statuses(int status)
496 {
497 	switch (status) {
498 	case 5000: return "No Error";
499 
500 	case 5500: return "Duplex Unit Not Connected";
501 
502 	case 5017: return "Paper Jam: Supply Sensor On";
503 	case 5018: return "Paper Jam: Supply Sensor Off";
504 	case 5019: return "Paper Jam: Slot Sensor On";
505 	case 5020: return "Paper Jam: Slot Sensor Off";
506 	case 5021: return "Paper Jam: Pass Sensor On";
507 	case 5022: return "Paper Jam: Pass Sensor Off";
508 	case 5023: return "Paper Jam: Shell Sensor 1 On";
509 	case 5024: return "Paper Jam: Shell Sensor 1 Off";
510 	case 5025: return "Paper Jam: Shell Sensor 2 On";
511 	case 5026: return "Paper Jam: Shell Sensor 2 Off";
512 	case 5027: return "Paper Jam: Eject Sensor On";
513 	case 5028: return "Paper Jam: Eject Sensor Off";
514 	case 5029: return "Paper Jam: Slot FG Sensor";
515 	case 5030: return "Paper Jam: Shell FG Sensor";
516 
517 	case 5033: return "Paper Supply Sensor Off";
518 	case 5034: return "Printer Feed Slot Sensor Off";
519 	case 5035: return "Pinch Pass Sensor Off";
520 	case 5036: return "Shell Pass Sensor 1 Off";
521 	case 5037: return "Shell Pass Sensor 2 Off";
522 	case 5038: return "Eject Sensor Off";
523 
524 	case 5049: return "Capstan Drive Control Error";
525 	case 5065: return "Shell Roller Error";
526 
527 	case 5081: return "Pinch Open Error";
528 	case 5082: return "Pinch Close Error";
529 	case 5083: return "Pinch Init Error";
530 	case 5084: return "Pinch Position Error";
531 
532 	case 5097: return "Pass Guide Supply Error";
533 	case 5098: return "Pass Guide Shell Error";
534 	case 5099: return "Pass Guide Eject Error";
535 	case 5100: return "Pass Guide Init Error";
536 	case 5101: return "Pass Guide Position Error";
537 
538 	case 5113: return "Side Guide Home Error";
539 	case 5114: return "Side Guide Position Error";
540 	case 5115: return "Side Guide Init Error";
541 
542 	case 5129: return "Act Guide Home Error";
543 
544 	case 5145: return "Shell Rotate Home Error";
545 	case 5146: return "Shell Rotate Rev Error";
546 
547 	case 5161: return "Paper Feed Lever Down Error";
548 	case 5162: return "Paper Feed Lever Lock Error";
549 	case 5163: return "Paper Feed Lever Up Error";
550 
551 	case 5177: return "Cutter Home Error";
552 	case 5178: return "Cutter Away Error";
553 	case 5179: return "Cutter Init Error";
554 	case 5180: return "Cutter Position Error";
555 
556 	case 5193: return "Paper Tray Removed";
557 	case 5209: return "Cover Opened";
558 	case 5241: return "System Error";
559 
560 	default:
561 		break;
562 	}
563 
564 	return "Unknown Duplexer Error";
565 }
566 
dnpds40_statuses(int status)567 static const char *dnpds40_statuses(int status)
568 {
569 	if (status >= 5000 && status <= 5999)
570 		return dnpds80_duplex_statuses(status);
571 
572 	switch (status) {
573 	case 0:	return "Idle";
574 	case 1:	return "Printing";
575 	case 500: return "Cooling Print Head";
576 	case 510: return "Cooling Paper Motor";
577 	case 900: return "Standby Mode";
578 	case 1000: return "Cover Open";
579 	case 1010: return "No Scrap Box";
580 	case 1100: return "Paper End";
581 	case 1200: return "Ribbon End";
582 	case 1300: return "Paper Jam";
583 	case 1400: return "Ribbon Error";
584 	case 1500: return "Paper Definition Error";
585 	case 1600: return "Data Error";
586 	case 2000: return "Head Voltage Error";
587 	case 2100: return "Head Position Error";
588 	case 2200: return "Power Supply Fan Error";
589 	case 2300: return "Cutter Error";
590 	case 2400: return "Pinch Roller Error";
591 	case 2500: return "Abnormal Head Temperature";
592 	case 2600: return "Abnormal Media Temperature";
593 	case 2610: return "Abnormal Paper Motor Temperature";
594 	case 2700: return "Ribbon Tension Error";
595 	case 2800: return "RF-ID Module Error";
596 	case 3000: return "System Error";
597 	default:
598 		break;
599 	}
600 
601 	return "Unknown Error";
602 }
603 
dnpds40_do_cmd(struct dnpds40_ctx * ctx,struct dnpds40_cmd * cmd,uint8_t * data,int len)604 static int dnpds40_do_cmd(struct dnpds40_ctx *ctx,
605 			  struct dnpds40_cmd *cmd,
606 			  uint8_t *data, int len)
607 {
608 	int ret;
609 
610 	if ((ret = send_data(ctx->dev, ctx->endp_down,
611 			     (uint8_t*)cmd, sizeof(*cmd))))
612 		return ret;
613 
614 	if (data && len)
615 		if ((ret = send_data(ctx->dev, ctx->endp_down,
616 				     data, len)))
617 			return ret;
618 
619 	return CUPS_BACKEND_OK;
620 }
621 
dnpds40_resp_cmd2(struct dnpds40_ctx * ctx,struct dnpds40_cmd * cmd,int * len,uint8_t * buf,uint32_t buf_len)622 static uint8_t *dnpds40_resp_cmd2(struct dnpds40_ctx *ctx,
623 				  struct dnpds40_cmd *cmd,
624 				  int *len,
625 				  uint8_t *buf, uint32_t buf_len)
626 {
627 	char tmp[9];
628 	uint8_t *respbuf;
629 
630 	int ret, i, num = 0;
631 
632 	memset(tmp, 0, sizeof(tmp));
633 
634 	if ((ret = dnpds40_do_cmd(ctx, cmd, buf, buf_len)))
635 		return NULL;
636 
637 	/* Read in the response header */
638 	ret = read_data(ctx->dev, ctx->endp_up,
639 			(uint8_t*)tmp, 8, &num);
640 	if (ret < 0)
641 		return NULL;
642 
643 	if (num != 8) {
644 		ERROR("Short read! (%d/%d)\n", num, 8);
645 		return NULL;
646 	}
647 
648 	i = atoi(tmp);  /* Length of payload in bytes, possibly padded */
649 	respbuf = malloc(i);
650 	if (!respbuf) {
651 		ERROR("Memory allocation failure (%d bytes)!\n", i);
652 		return NULL;
653 	}
654 
655 	/* Read in the actual response */
656 	ret = read_data(ctx->dev, ctx->endp_up,
657 			respbuf, i, &num);
658 	if (ret < 0) {
659 		free(respbuf);
660 		return NULL;
661 	}
662 
663 	if (num != i) {
664 		ERROR("Short read! (%d/%d)\n", num, i);
665 		free(respbuf);
666 		return NULL;
667 	}
668 
669 	*len = num;
670 	return respbuf;
671 }
672 
673 #define dnpds40_resp_cmd(__ctx, __cmd, __len) dnpds40_resp_cmd2(__ctx, __cmd, __len, NULL, 0)
674 
dnpds40_query_serno(struct libusb_device_handle * dev,uint8_t endp_up,uint8_t endp_down,char * buf,int buf_len)675 static int dnpds40_query_serno(struct libusb_device_handle *dev, uint8_t endp_up, uint8_t endp_down, char *buf, int buf_len)
676 {
677 	struct dnpds40_cmd cmd;
678 	uint8_t *resp;
679 	int len = 0;
680 
681 	struct dnpds40_ctx ctx = {
682 		.dev = dev,
683 		.endp_up = endp_up,
684 		.endp_down = endp_down,
685 	};
686 
687 	/* Get Serial Number */
688 	dnpds40_build_cmd(&cmd, "INFO", "SERIAL_NUMBER", 0);
689 
690 	resp = dnpds40_resp_cmd(&ctx, &cmd, &len);
691 	if (!resp)
692 		return CUPS_BACKEND_FAILED;
693 
694 	dnpds40_cleanup_string((char*)resp, len);
695 
696 	strncpy(buf, (char*)resp, buf_len);
697 	buf[buf_len-1] = 0;
698 
699 	free(resp);
700 
701 	return CUPS_BACKEND_OK;
702 }
703 
dnpds40_init(void)704 static void *dnpds40_init(void)
705 {
706 	struct dnpds40_ctx *ctx = malloc(sizeof(struct dnpds40_ctx));
707 	if (!ctx) {
708 		ERROR("Memory allocation failure (%d bytes)!\n", (int)sizeof(struct dnpds40_ctx));
709 		return NULL;
710 	}
711 	memset(ctx, 0, sizeof(struct dnpds40_ctx));
712 
713 	return ctx;
714 }
715 
716 #define FW_VER_CHECK(__major, __minor) \
717 	((ctx->ver_major > (__major)) || \
718 	 (ctx->ver_major == (__major) && ctx->ver_minor >= (__minor)))
719 
dnpds40_query_mqty(struct dnpds40_ctx * ctx)720 static int dnpds40_query_mqty(struct dnpds40_ctx *ctx)
721 {
722 	struct dnpds40_cmd cmd;
723 	uint8_t *resp;
724 	int len = 0, count;
725 
726 	/* Get Media remaining */
727 	dnpds40_build_cmd(&cmd, "INFO", "MQTY", 0);
728 
729 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
730 	if (!resp)
731 		return -1;
732 
733 	dnpds40_cleanup_string((char*)resp, len);
734 
735 	count = atoi((char*)resp+4);
736 	free(resp);
737 
738 	if (count) {
739 		/* Old-sk00l models report one less than they should */
740 		if (!ctx->correct_count)
741 			count++;
742 
743 		count -= ctx->mediaoffset;
744 	}
745 
746 	return count;
747 }
748 
dnpds80dx_query_paper(struct dnpds40_ctx * ctx)749 static int dnpds80dx_query_paper(struct dnpds40_ctx *ctx)
750 {
751 	struct dnpds40_cmd cmd;
752 	uint8_t *resp;
753 	int len = 0;
754 
755 	/* Query Duplex Media Info */
756 	dnpds40_build_cmd(&cmd, "INFO", "UNIT_CUT_PAPER", 0);
757 
758 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
759 	if (resp) {
760 		char tmp[5];
761 		char status;
762 
763 		dnpds40_cleanup_string((char*)resp, len);
764 
765 		memcpy(tmp, resp + 4, 4);
766 		status = tmp[3];
767 		tmp[3] = '0';
768 		tmp[4] = 0;
769 
770 		ctx->duplex_media = atoi(tmp);
771 
772 		tmp[0] = tmp[1] = tmp[2] = '0';
773 		tmp[3] = status;
774 		ctx->duplex_media_status = atoi(tmp);
775 
776 		free(resp);
777 	} else {
778 		return CUPS_BACKEND_FAILED;
779 	}
780 
781 	return CUPS_BACKEND_OK;
782 }
783 
dnpds40_attach(void * vctx,struct libusb_device_handle * dev,int type,uint8_t endp_up,uint8_t endp_down,uint8_t jobid)784 static int dnpds40_attach(void *vctx, struct libusb_device_handle *dev, int type,
785 			  uint8_t endp_up, uint8_t endp_down, uint8_t jobid)
786 {
787 	struct dnpds40_ctx *ctx = vctx;
788 
789 	UNUSED(jobid);
790 
791 	ctx->dev = dev;
792 	ctx->endp_up = endp_up;
793 	ctx->endp_down = endp_down;
794 	ctx->type = type;
795 
796 	if (test_mode < TEST_MODE_NOATTACH) {
797 		struct dnpds40_cmd cmd;
798 		uint8_t *resp;
799 		int len = 0;
800 
801 		/* Get Firmware Version */
802 		dnpds40_build_cmd(&cmd, "INFO", "FVER", 0);
803 
804 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
805 		if (resp) {
806 			char *ptr;
807 			dnpds40_cleanup_string((char*)resp, len);
808 			ctx->version = strdup((char*) resp);
809 
810 			/* Parse version */
811 			/* ptr = */ strtok((char*)resp, " .");
812 			ptr = strtok(NULL, ".");
813 			ctx->ver_major = atoi(ptr);
814 			ptr = strtok(NULL, ".");
815 			ctx->ver_minor = atoi(ptr);
816 			free(resp);
817 		} else {
818 			return CUPS_BACKEND_FAILED;
819 		}
820 
821 		/* Get Serial Number */
822 		dnpds40_build_cmd(&cmd, "INFO", "SERIAL_NUMBER", 0);
823 
824 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
825 		if (resp) {
826 			dnpds40_cleanup_string((char*)resp, len);
827 			ctx->serno = (char*) resp;
828 			/* Do NOT free resp! */
829 		} else {
830 			return CUPS_BACKEND_FAILED;
831 		}
832 
833 		/* Query Media Info */
834 		dnpds40_build_cmd(&cmd, "INFO", "MEDIA", 0);
835 
836 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
837 		if (resp) {
838 			char tmp[4];
839 
840 			dnpds40_cleanup_string((char*)resp, len);
841 
842 			memcpy(tmp, resp + 4, 3);
843 			tmp[3] = 0;
844 
845 			ctx->media = atoi(tmp);
846 
847 			/* Subtract out the "mark" type */
848 			if (ctx->media & 1)
849 				ctx->media--;
850 
851 			free(resp);
852 		} else {
853 			return CUPS_BACKEND_FAILED;
854 		}
855 
856 		if (ctx->type == P_DNP_DS80D) {
857 			if (dnpds80dx_query_paper(ctx))
858 				return CUPS_BACKEND_FAILED;
859 		}
860 
861 #if (defined(DNP_ONLY) || defined(CITIZEN_ONLY))
862 		{
863 			char buf[256];
864 			buf[0] = 0;
865 			libusb_get_string_descriptor_ascii(dev, desc->iManufacturer, (unsigned char*)buf, STR_LEN_MAX);
866 			sanitize_string(buf);
867 #ifdef DNP_ONLY  /* Only allow DNP printers to work. */
868 			if (strncmp(buf, "Dai", 3)) /* "Dai Nippon Printing" */
869 				return CUPS_BACKEND_FAILED;
870 #endif
871 #ifdef CITIZEN_ONLY   /* Only allow CITIZEN printers to work. */
872 			if (strncmp(buf, "CIT", 3)) /* "CITIZEN SYSTEMS" */
873 				return CUPS_BACKEND_FAILED;
874 #endif
875 		}
876 #endif
877 	} else {
878 		ctx->ver_major = 3;
879 		ctx->ver_minor = 0;
880 		ctx->version = strdup("UNKNOWN");
881 		switch(ctx->type) {
882 		case P_DNP_DS80D:
883 			ctx->duplex_media = 200;
884 			/* Intentional fallthrough */
885 		case P_DNP_DS80:
886 		case P_DNP_DS820:
887 			ctx->media = 510; /* 8x12 */
888 			break;
889 		case P_DNP_DSRX1:
890 			ctx->media = 310; /* 6x8 */
891 			break;
892 		default:
893 			ctx->media = 400; /* 6x9 */
894 			break;
895 		}
896 
897 		if (getenv("MEDIA_CODE"))
898 			ctx->media = atoi(getenv("MEDIA_CODE"));
899 	}
900 
901 	/* Per-printer options */
902 	switch (ctx->type) {
903 	case P_DNP_DS40:
904 		ctx->native_width = 1920;
905 		ctx->max_height = 5480;
906 		ctx->supports_6x9 = 1;
907 		if (FW_VER_CHECK(1,04))
908 			ctx->supports_counterp = 1;
909 		if (FW_VER_CHECK(1,30))
910 			ctx->supports_matte = 1;
911 		if (FW_VER_CHECK(1,40))
912 			ctx->supports_2x6 = 1;
913 		if (FW_VER_CHECK(1,50))
914 			ctx->supports_3x5x2 = 1;
915 		if (FW_VER_CHECK(1,60))
916 			ctx->supports_fullcut = ctx->supports_6x6 = 1; // No 5x5!
917 		break;
918 	case P_DNP_DS80:
919 	case P_DNP_DS80D:
920 		ctx->native_width = 2560;
921 		ctx->max_height = 7536;
922 		if (FW_VER_CHECK(1,02))
923 			ctx->supports_counterp = 1;
924 		if (FW_VER_CHECK(1,30))
925 			ctx->supports_matte = 1;
926 		break;
927 	case P_DNP_DSRX1:
928 		ctx->native_width = 1920;
929 		ctx->max_height = 5480;
930 		ctx->supports_counterp = 1;
931 		ctx->supports_matte = 1;
932 		if (FW_VER_CHECK(1,10))
933 			ctx->supports_2x6 = ctx->supports_mqty_default = 1;
934                 if (FW_VER_CHECK(1,20))
935 			ctx->supports_3x5x2 = 1;
936 		if (FW_VER_CHECK(2,00)) { /* AKA RX1HS */
937 			ctx->needs_mlot = 1;
938 			ctx->supports_mediaoffset = 1;
939 			ctx->supports_iserial = 1;
940 		}
941 		if (FW_VER_CHECK(2,06)) {
942 			ctx->supports_5x5 = ctx->supports_6x6 = 1;
943 		}
944 		break;
945 	case P_CITIZEN_OP900II:
946 		ctx->native_width = 1920;
947 		ctx->max_height = 5480;
948 		ctx->supports_counterp = 1;
949 		ctx->supports_matte = 1;
950 		ctx->supports_mqty_default = 1;
951 		ctx->supports_6x9 = 1;
952 		if (FW_VER_CHECK(1,11))
953 			ctx->supports_2x6 = 1;
954 		break;
955 	case P_CITIZEN_CW01:
956 		ctx->native_width = 2048;
957 		ctx->max_height = 5480;
958 		ctx->supports_6x9 = 1;
959 		break;
960 	case P_DNP_DS620:
961 		ctx->native_width = 1920;
962 		ctx->max_height = 5604;
963 		ctx->correct_count = 1;
964 		ctx->supports_counterp = 1;
965 		ctx->supports_matte = 1;
966 		ctx->supports_2x6 = 1;
967 		ctx->supports_fullcut = 1;
968 		ctx->supports_mqty_default = 1;
969 		if (strchr(ctx->version, 'A'))
970 			ctx->supports_rewind = 0;
971 		else
972 			ctx->supports_rewind = 1;
973 		ctx->supports_standby = 1;
974 		ctx->supports_iserial = 1;
975 		ctx->supports_6x6 = 1;
976 		ctx->supports_5x5 = 1;
977 		ctx->supports_lowspeed = 1;
978 		if (FW_VER_CHECK(0,30))
979 			ctx->supports_3x5x2 = 1;
980 		if (FW_VER_CHECK(1,10))
981 			ctx->supports_6x9 = ctx->supports_6x4_5 = 1;
982 		if (FW_VER_CHECK(1,20))
983 			ctx->supports_adv_fullcut = ctx->supports_advmatte = 1;
984 		if (FW_VER_CHECK(1,30))
985 			ctx->supports_luster = 1;
986 		if (FW_VER_CHECK(1,33))
987 			ctx->supports_media_ext = 1;
988 		if (FW_VER_CHECK(1,52))
989 			ctx->supports_finematte = 1;
990 		break;
991 	case P_DNP_DS820:
992 		ctx->native_width = 2560;
993 		ctx->max_height = 7536;
994 		ctx->correct_count = 1;
995 		ctx->supports_counterp = 1;
996 		ctx->supports_matte = 1;
997 		ctx->supports_fullcut = 1;
998 		ctx->supports_mqty_default = 1;
999 		if (strchr(ctx->version, 'A'))
1000 			ctx->supports_rewind = 0;
1001 		else
1002 			ctx->supports_rewind = 1;
1003 		ctx->supports_standby = 1;
1004 		ctx->supports_iserial = 1;
1005 		ctx->supports_adv_fullcut = 1;
1006 		ctx->supports_advmatte = 1;
1007 		ctx->supports_luster = 1;
1008 		ctx->supports_finematte = 1;
1009 		ctx->supports_printspeed = 1;
1010 		ctx->supports_lowspeed = 1;
1011 		ctx->supports_highdensity = 1;
1012 		if (FW_VER_CHECK(0,50))
1013 			ctx->supports_gamma = 1;
1014 		break;
1015 	default:
1016 		ERROR("Unknown printer type %d\n", ctx->type);
1017 		return CUPS_BACKEND_FAILED;
1018 	}
1019 
1020 	ctx->last_matte = -1;
1021 #ifdef STATE_DIR
1022 	/* Check our current job's lamination vs previous job. */
1023 	{
1024 		/* Load last matte status from file */
1025 		char buf[64];
1026 		FILE *f;
1027 		snprintf(buf, sizeof(buf), STATE_DIR "/%s-last", ctx->serno);
1028 		f = fopen(buf, "r");
1029 		if (f) {
1030 			fscanf(f, "%d", &ctx->last_matte);
1031 			fclose(f);
1032 		}
1033 	}
1034 #endif
1035 
1036 	if (test_mode < TEST_MODE_NOATTACH && ctx->supports_mediaoffset) {
1037 		/* Get Media Offset */
1038 		struct dnpds40_cmd cmd;
1039 		uint8_t *resp;
1040 		int len = 0;
1041 
1042 		dnpds40_build_cmd(&cmd, "INFO", "MEDIA_OFFSET", 0);
1043 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
1044 		if (resp) {
1045 			ctx->mediaoffset = atoi((char*)resp+4);
1046 			free(resp);
1047 		} else {
1048 			return CUPS_BACKEND_FAILED;
1049 		}
1050 	} else if (!ctx->correct_count) {
1051 		ctx->mediaoffset = 50;
1052 	}
1053 
1054 	if (test_mode < TEST_MODE_NOATTACH && ctx->supports_mqty_default) {
1055 		struct dnpds40_cmd cmd;
1056 		uint8_t *resp;
1057 		int len = 0;
1058 
1059 		dnpds40_build_cmd(&cmd, "INFO", "MQTY_DEFAULT", 0);
1060 
1061 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
1062 		if (resp) {
1063 			dnpds40_cleanup_string((char*)resp, len);
1064 			ctx->media_count_new = atoi((char*)resp+4);
1065 			free(resp);
1066 			ctx->media_count_new -= ctx->mediaoffset;
1067 		} else {
1068 			return CUPS_BACKEND_FAILED;
1069 		}
1070 	} else {
1071 		/* Look it up for legacy models & FW */
1072 		switch (ctx->type) {
1073 		case P_DNP_DS40:
1074 			switch (ctx->media) {
1075 			case 200: // L
1076 				ctx->media_count_new = 460;
1077 				break;
1078 			case 210: // 2L
1079 				ctx->media_count_new = 230;
1080 				break;
1081 			case 300: // PC
1082 				ctx->media_count_new = 400;
1083 				break;
1084 			case 310: // A5
1085 				ctx->media_count_new = 200;
1086 				break;
1087 			case 400: // A5W
1088 				ctx->media_count_new = 180;
1089 				break;
1090 			default:
1091 				ctx->media_count_new = 0;
1092 				break;
1093 			}
1094 			break;
1095 		case P_DNP_DSRX1:
1096 			switch (ctx->media) {
1097 			case 210: // 2L
1098 				ctx->media_count_new = 350;
1099 				break;
1100 			case 300: // PC
1101 				ctx->media_count_new = 700;
1102 				break;
1103 			case 310: // A5
1104 				ctx->media_count_new = 350;
1105 				break;
1106 			default:
1107 				ctx->media_count_new = 0;
1108 				break;
1109 			}
1110 			break;
1111 		case P_CITIZEN_OP900II:
1112 			switch (ctx->media) {
1113 			case 210: // 2L
1114 				ctx->media_count_new = 350;
1115 				break;
1116 			case 300: // PC
1117 				ctx->media_count_new = 600;
1118 				break;
1119 			case 310: // A5
1120 				ctx->media_count_new = 300;
1121 				break;
1122 			case 400: // A5W
1123 				ctx->media_count_new = 280;
1124 				break;
1125 			default:
1126 				ctx->media_count_new = 0;
1127 				break;
1128 			}
1129 			break;
1130 		case P_CITIZEN_CW01:
1131 			switch (ctx->media) {
1132 			case 300: // PC
1133 				ctx->media_count_new = 600;
1134 				break;
1135 			case 350: // 2L
1136 				ctx->media_count_new = 230;
1137 				break;
1138 			case 400: // A5W
1139 				ctx->media_count_new = 280;
1140 				break;
1141 			default:
1142 				ctx->media_count_new = 0;
1143 				break;
1144 			}
1145 			break;
1146 		case P_DNP_DS80:
1147 		case P_DNP_DS80D:
1148 			switch (ctx->media) {
1149 			case 500: // 8x10
1150 				ctx->media_count_new = 130;
1151 				break;
1152 			case 510: // 8x12
1153 				ctx->media_count_new = 110;
1154 				break;
1155 			default:
1156 				ctx->media_count_new = 0;
1157 				break;
1158 			}
1159 			break;
1160 		default:
1161 			ctx->media_count_new = 0;
1162 			break;
1163 		}
1164 	}
1165 
1166 	/* Fill out marker structure */
1167 	ctx->marker[0].color = "#00FFFF#FF00FF#FFFF00";
1168 	ctx->marker[0].name = dnpds40_media_types(ctx->media);
1169 	ctx->marker[0].levelmax = ctx->media_count_new;
1170 	ctx->marker[0].levelnow = -2;
1171 	ctx->marker_count = 1;
1172 
1173 	if (ctx->type == P_DNP_DS80D) {
1174 		ctx->marker[1].color = "#00FFFF#FF00FF#FFFF00";
1175 		ctx->marker[1].name = dnpds80_duplex_media_types(ctx->duplex_media);
1176 		ctx->marker[1].levelmax = ctx->marker[0].levelmax/2;
1177 		ctx->marker[1].levelnow = -2;
1178 		ctx->marker_count++;
1179 	}
1180 
1181 	return CUPS_BACKEND_OK;
1182 }
1183 
dnpds40_cleanup_job(const void * vjob)1184 static void dnpds40_cleanup_job(const void *vjob) {
1185 	const struct dnpds40_printjob *job = vjob;
1186 
1187 	if (job->databuf)
1188 		free(job->databuf);
1189 
1190 	free((void*)job);
1191 }
1192 
dnpds40_teardown(void * vctx)1193 static void dnpds40_teardown(void *vctx) {
1194 	struct dnpds40_ctx *ctx = vctx;
1195 
1196 	if (!ctx)
1197 		return;
1198 
1199 	if (test_mode < TEST_MODE_NOATTACH && ctx->type == P_DNP_DS80D) {
1200 		struct dnpds40_cmd cmd;
1201 
1202 		/* Check to see if last print was the front side
1203 		   of a duplex job, and if so, cancel things so we're done */
1204 		if (ctx->last_multicut >= 200 &&
1205 		    ctx->last_multicut < 300) {
1206 			dnpds40_build_cmd(&cmd, "CNTRL", "DUPLEX_CANCEL", 0);
1207 			if ((dnpds40_do_cmd(ctx, &cmd, NULL, 0)) != 0)
1208 				return;
1209 		}
1210 	}
1211 
1212 	if (ctx->serno)
1213 		free(ctx->serno);
1214 	if (ctx->version)
1215 		free(ctx->version);
1216 	free(ctx);
1217 }
1218 
1219 #define MAX_PRINTJOB_LEN (((ctx->native_width*ctx->max_height+1024+54+10))*3+1024) /* Worst-case, YMC */
1220 
dnpds40_read_parse(void * vctx,const void ** vjob,int data_fd,int copies)1221 static int dnpds40_read_parse(void *vctx, const void **vjob, int data_fd, int copies) {
1222 	struct dnpds40_ctx *ctx = vctx;
1223 	int run = 1;
1224 	char buf[9] = { 0 };
1225 
1226 	struct dnpds40_printjob *job = NULL;
1227 	struct dyesub_joblist *list;
1228 	int can_combine = 0;
1229 
1230 	if (!ctx)
1231 		return CUPS_BACKEND_FAILED;
1232 
1233 	job = malloc(sizeof(*job));
1234 	if (!job) {
1235 		ERROR("Memory allocation failure!\n");
1236 		return CUPS_BACKEND_RETRY_CURRENT;
1237 	}
1238 	memset(job, 0, sizeof(*job));
1239 	job->printspeed = -1;
1240 	job->copies = copies;
1241 
1242 	/* There's no way to figure out the total job length in advance, we
1243 	   have to parse the stream until we get to the image plane data,
1244 	   and even then the stream can contain arbitrary commands later.
1245 
1246 	   So instead, we allocate a buffer of the maximum possible length,
1247 	   then parse the incoming stream until we hit the START command at
1248 	   the end of the job.
1249 	*/
1250 
1251 	job->databuf = malloc(MAX_PRINTJOB_LEN);
1252 	if (!job->databuf) {
1253 		dnpds40_cleanup_job(job);
1254 		ERROR("Memory allocation failure!\n");
1255 		return CUPS_BACKEND_RETRY_CURRENT;
1256 	}
1257 
1258 	while (run) {
1259 		int remain, i, j;
1260 		/* Read in command header */
1261 		i = read(data_fd, job->databuf + job->datalen,
1262 			 sizeof(struct dnpds40_cmd));
1263 		if (i < 0) {
1264 			dnpds40_cleanup_job(job);
1265 			return i;
1266 		}
1267 		if (i == 0)
1268 			break;
1269 		if (i < (int) sizeof(struct dnpds40_cmd)) {
1270 			dnpds40_cleanup_job(job);
1271 			return CUPS_BACKEND_CANCEL;
1272 		}
1273 
1274 		/* Special case handling for beginning of job */
1275 		if (job->datalen == 0) {
1276 			/* See if job lacks the standard ESC-P start sequence */
1277 			if (job->databuf[job->datalen + 0] != 0x1b ||
1278 			    job->databuf[job->datalen + 1] != 0x50) {
1279 				switch(ctx->type) {
1280 				case P_CITIZEN_CW01:
1281 					i = legacy_cw01_read_parse(job, data_fd, i);
1282 					break;
1283 				case P_DNP_DS620:
1284 					i = legacy_dnp620_read_parse(job, data_fd, i);
1285 					break;
1286 				case P_DNP_DS820:
1287 					i = legacy_dnp820_read_parse(job, data_fd, i);
1288 					break;
1289 				case P_DNP_DSRX1:
1290 				case P_DNP_DS40:
1291 				case P_DNP_DS80:
1292 				case P_DNP_DS80D:
1293 				default:
1294 					i = legacy_dnp_read_parse(job, data_fd, i);
1295 					break;
1296 				}
1297 
1298 				if (i == CUPS_BACKEND_OK) {
1299 					goto parsed;
1300 				}
1301 				dnpds40_cleanup_job(job);
1302 				return i;
1303 			}
1304 		}
1305 
1306 		/* Parse out length of data chunk, if any */
1307 		memcpy(buf, job->databuf + job->datalen + 24, 8);
1308 		j = atoi(buf);
1309 
1310 		/* Read in data chunk as quickly as possible */
1311 		remain = j;
1312 		while (remain > 0) {
1313 			i = read(data_fd, job->databuf + job->datalen + sizeof(struct dnpds40_cmd),
1314 				 remain);
1315 			if (i < 0) {
1316 				ERROR("Data Read Error: %d (%d/%d @%d/%d)\n", i, remain, j, job->datalen,MAX_PRINTJOB_LEN);
1317 				dnpds40_cleanup_job(job);
1318 				return i;
1319 			}
1320 			if (i == 0) {
1321 				dnpds40_cleanup_job(job);
1322 				return 1;
1323 			}
1324 			job->datalen += i;
1325 			remain -= i;
1326 		}
1327 		job->datalen -= j; /* Back it off */
1328 
1329 		/* Check for some offsets */
1330 		if(!memcmp("CNTRL QTY", job->databuf + job->datalen+2, 9)) {
1331 			/* Ignore this.  We will insert our own later on */
1332 			continue;
1333 		}
1334 		if(!memcmp("CNTRL CUTTER", job->databuf + job->datalen+2, 12)) {
1335 			memcpy(buf, job->databuf + job->datalen + 32, 8);
1336 			job->cutter = atoi(buf);
1337 			/* We'll insert it ourselves later */
1338 			continue;
1339 		}
1340 		if(!memcmp("CNTRL BUFFCNTRL", job->databuf + job->datalen+2, 15)) {
1341 			/* Ignore this.  We will insert our own later on
1342 			   if the printer and job support it. */
1343 			continue;
1344 		}
1345 		if(!memcmp("CNTRL OVERCOAT", job->databuf + job->datalen+2, 14)) {
1346 			if (ctx->supports_matte) {
1347 				memcpy(buf, job->databuf + job->datalen + 32, 8);
1348 				job->matte = atoi(buf);
1349 			} else {
1350 				WARNING("Printer FW does not support matte prints, using glossy mode\n");
1351 			}
1352 			/* We'll insert our own later, if appropriate */
1353 			continue;
1354 		}
1355 		if(!memcmp("IMAGE MULTICUT", job->databuf + job->datalen+2, 14)) {
1356 			memcpy(buf, job->databuf + job->datalen + 32, 8);
1357 			job->multicut = atoi(buf);
1358 			/* Backend automatically handles rewind support, so
1359 			   ignore application requests to use it. */
1360 			if (job->multicut > 400)
1361 				job->multicut -= 400;
1362 
1363 			/* We'll insert this ourselves later. */
1364 			continue;
1365 		}
1366 		if(!memcmp("CNTRL FULL_CUTTER_SET", job->databuf + job->datalen+2, 21)) {
1367 			if (!ctx->supports_fullcut) {
1368 				WARNING("Printer FW does not support full cutter control!\n");
1369 				continue;
1370 			}
1371 
1372 			if (ctx->type == P_DNP_DS820) {
1373 				if (j != 24) {
1374 					WARNING("Full cutter argument length incorrect, ignoring!\n");
1375 					continue;
1376 				}
1377 			} else if (j != 16) {
1378 				WARNING("Full cutter argument length incorrect, ignoring!\n");
1379 				continue;
1380 			} else if (!ctx->supports_adv_fullcut) {
1381 				if (job->databuf[job->datalen + 32 + 12] != '0' ||
1382 				    job->databuf[job->datalen + 32 + 13] != '0' ||
1383 				    job->databuf[job->datalen + 32 + 14] != '0') {
1384 					WARNING("Full cutter scrap setting not supported on this firmware, ignoring!\n");
1385 					continue;
1386 				}
1387 			}
1388 			// XXX enforce cut counts/sizes?
1389 
1390 			job->fullcut = 1;
1391 		}
1392 		if(!memcmp("IMAGE YPLANE", job->databuf + job->datalen + 2, 12)) {
1393 			uint32_t y_ppm; /* Pixels Per Meter */
1394 
1395 			/* Validate vertical resolution */
1396 			memcpy(&y_ppm, job->databuf + job->datalen + 32 + 42, sizeof(y_ppm));
1397 			y_ppm = le32_to_cpu(y_ppm);
1398 
1399 			switch (y_ppm) {
1400 			case 11808:
1401 				job->dpi = 300;
1402 				break;
1403 			case 13146:
1404 				job->dpi = 334;
1405 				break;
1406 			case 23615:
1407 			case 23616:
1408 				job->dpi = 600;
1409 				break;
1410 			default:
1411 				ERROR("Unrecognized printjob resolution (%u ppm)\n", y_ppm);
1412 				dnpds40_cleanup_job(job);
1413 				return CUPS_BACKEND_CANCEL;
1414 			}
1415 
1416 			/* Validate horizontal size */
1417 			memcpy(&y_ppm, job->databuf + job->datalen + 32 + 18, sizeof(y_ppm));
1418 			y_ppm = le32_to_cpu(y_ppm);
1419 			if (y_ppm != ctx->native_width) {
1420 				ERROR("Incorrect horizontal resolution (%u), aborting!\n", y_ppm);
1421 				dnpds40_cleanup_job(job);
1422 				return CUPS_BACKEND_CANCEL;
1423 			}
1424 		}
1425 		if(!memcmp("CNTRL PRINTSPEED", job->databuf + job->datalen + 2, 16)) {
1426 			if (!ctx->supports_printspeed) {
1427 				WARNING("Printer does not support PRINTSPEED\n");
1428 				continue;
1429 			}
1430 			memcpy(buf, job->databuf + job->datalen + 32, 8);
1431 			job->printspeed = atoi(buf) / 10;
1432 
1433 			/* We'll insert this ourselves later. */
1434 			continue;
1435 		}
1436 
1437 		/* This is the last block.. */
1438 	        if(!memcmp("CNTRL START", job->databuf + job->datalen + 2, 11))
1439 			run = 0;
1440 
1441 		/* Add in the size of this chunk */
1442 		job->datalen += sizeof(struct dnpds40_cmd) + j;
1443 	}
1444 parsed:
1445 	/* If we have no data.. don't bother */
1446 	if (!job->datalen) {
1447 		dnpds40_cleanup_job(job);
1448 		return CUPS_BACKEND_CANCEL;
1449 	}
1450 
1451 	/* Sanity check matte mode */
1452 	if (job->matte == 21 && !ctx->supports_finematte) {
1453 		WARNING("Printer FW does not support Fine Matte mode, downgrading to normal matte\n");
1454 		job->matte = 1;
1455 	} else if (job->matte == 22 && !ctx->supports_luster) {
1456 		WARNING("Printer FW does not support Luster mode, downgrading to normal matte\n");
1457 		job->matte = 1;
1458 	} else if (job->matte > 1 && !ctx->supports_advmatte) {
1459 		WARNING("Printer FW does not support advanced matte modes, downgrading to normal matte\n");
1460 		job->matte = 1;
1461 	}
1462 
1463 	/* Pick a sane default value for printspeed if not specified */
1464 	if (job->printspeed == -1 || job->printspeed > 3)
1465 	{
1466 		if (job->dpi == 600)
1467 			job->printspeed = 1;
1468 		else
1469 			job->printspeed = 0;
1470 	}
1471 	/* And sanity-check whatever value is there */
1472 	if (job->printspeed == 0 && job->dpi == 600) {
1473 		job->printspeed = 1;
1474 	} else if (job->printspeed >= 1 && job->dpi == 300) {
1475 		job->printspeed = 0;
1476 	}
1477 
1478 	/* Make sure MULTICUT is sane, most validation needs this */
1479 	if (!job->multicut && ctx->type != P_CITIZEN_CW01) {
1480 		WARNING("Missing or illegal MULTICUT command!\n");
1481 		if (job->dpi == 300)
1482 			job->buf_needed = 1;
1483 		else
1484 			job->buf_needed = 2;
1485 
1486 		goto skip_checks;
1487 	}
1488 
1489 	/* Only DS80D supports Cut Paper types */
1490 	if (job->multicut > 100) {
1491 		if ( ctx->type == P_DNP_DS80D) {
1492 			job->cut_paper = 1;
1493 		} else {
1494 			ERROR("Only DS80D supports cut-paper sizes!\n");
1495 			dnpds40_cleanup_job(job);
1496 			return CUPS_BACKEND_CANCEL;
1497 		}
1498 	}
1499 
1500 	/* Figure out the number of buffers we need. */
1501 	job->buf_needed = 1;
1502 
1503 	if (job->dpi == 600) {
1504 		switch(ctx->type) {
1505 		case P_DNP_DS620:
1506 			if (job->multicut == MULTICUT_6x9 ||
1507 			    job->multicut == MULTICUT_6x4_5X2)
1508 				job->buf_needed = 2;
1509 			break;
1510 		case P_DNP_DS80:  /* DS80/CX-W */
1511 			if (job->matte && (job->multicut == MULTICUT_8xA4LEN ||
1512 					   job->multicut == MULTICUT_8x4X3 ||
1513 					   job->multicut == MULTICUT_8x8_8x4 ||
1514 					   job->multicut == MULTICUT_8x6X2 ||
1515 					   job->multicut == MULTICUT_8x12))
1516 				job->buf_needed = 2;
1517 			break;
1518 		case P_DNP_DS80D:
1519 			if (job->matte) {
1520 				int mcut = job->multicut;
1521 
1522 				if (mcut > MULTICUT_S_BACK)
1523 					mcut -= MULTICUT_S_BACK;
1524 				else if (mcut > MULTICUT_S_FRONT)
1525 					mcut -= MULTICUT_S_FRONT;
1526 
1527 				if (mcut == MULTICUT_8xA4LEN ||
1528 				    mcut == MULTICUT_8x4X3 ||
1529 				    mcut == MULTICUT_8x8_8x4 ||
1530 				    mcut == MULTICUT_8x6X2 ||
1531 				    mcut == MULTICUT_8x12)
1532 					job->buf_needed = 2;
1533 
1534 				if (mcut == MULTICUT_S_8x12 ||
1535 				    mcut == MULTICUT_S_8x6X2 ||
1536 				    mcut == MULTICUT_S_8x4X3)
1537 					job->buf_needed = 2;
1538 			}
1539 			break;
1540 		case P_DNP_DS820:
1541 			// Nothing; all sizes only need 1 buffer
1542 			break;
1543 		case P_CITIZEN_CW01:
1544 			job->buf_needed = 2;
1545 			break;
1546 		default: /* DS40/CX/RX1/CY/everything else */
1547 			if (job->matte) {
1548 				if (job->multicut == MULTICUT_6x8 ||
1549 				    job->multicut == MULTICUT_6x9 ||
1550 				    job->multicut == MULTICUT_6x4X2 ||
1551 				    job->multicut == MULTICUT_5x7 ||
1552 				    job->multicut == MULTICUT_5x3_5X2)
1553 					job->buf_needed = 2;
1554 
1555 			} else {
1556 				if (job->multicut == MULTICUT_6x8 ||
1557 				    job->multicut == MULTICUT_6x9 ||
1558 				    job->multicut == MULTICUT_6x4X2)
1559 					job->buf_needed = 1;
1560 			}
1561 			break;
1562 		}
1563 	}
1564 	if (job->dpi == 334 && ctx->type != P_CITIZEN_CW01)
1565 	{
1566 		ERROR("Illegal resolution (%u) for printer!\n", job->dpi);
1567 		dnpds40_cleanup_job(job);
1568 		return CUPS_BACKEND_CANCEL;
1569 	}
1570 
1571 	/* Sanity-check type vs loaded media */
1572 	if (job->multicut == 0)
1573 		goto skip_multicut;
1574 
1575 	if (job->multicut < 100) {
1576 		switch(ctx->media) {
1577 		case 200: //"5x3.5 (L)"
1578 			if (job->multicut != MULTICUT_5x3_5) {
1579 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
1580 				dnpds40_cleanup_job(job);
1581 				return CUPS_BACKEND_CANCEL;
1582 			}
1583 			break;
1584 		case 210: //"5x7 (2L)"
1585 			if (job->multicut != MULTICUT_5x3_5 && job->multicut != MULTICUT_5x7 &&
1586 			    job->multicut != MULTICUT_5x3_5X2 && job->multicut != MULTICUT_5x5) {
1587 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
1588 				dnpds40_cleanup_job(job);
1589 				return CUPS_BACKEND_CANCEL;
1590 			}
1591 			/* Only 3.5x5 on 7x5 media can be rewound */
1592 			if (job->multicut == MULTICUT_5x3_5)
1593 				job->can_rewind = 1;
1594 			break;
1595 		case 300: //"6x4 (PC)"
1596 			if (job->multicut != MULTICUT_6x4) {
1597 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
1598 				dnpds40_cleanup_job(job);
1599 				return CUPS_BACKEND_CANCEL;
1600 			}
1601 			break;
1602 		case 310: //"6x8 (A5)"
1603 			if (job->multicut != MULTICUT_6x4 && job->multicut != MULTICUT_6x8 &&
1604 			    job->multicut != MULTICUT_6x4X2 &&
1605 			    job->multicut != MULTICUT_6x6 && job->multicut != 30) {
1606 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
1607 				dnpds40_cleanup_job(job);
1608 				return CUPS_BACKEND_CANCEL;
1609 			}
1610 			/* Only 6x4 on 6x8 media can be rewound */
1611 			if (job->multicut == MULTICUT_6x4)
1612 				job->can_rewind = 1;
1613 			break;
1614 		case 400: //"6x9 (A5W)"
1615 			if (job->multicut != MULTICUT_6x4 && job->multicut != MULTICUT_6x8 &&
1616 			    job->multicut != MULTICUT_6x9 && job->multicut != MULTICUT_6x4X2 &&
1617 			    job->multicut != MULTICUT_6x6 &&
1618 			    job->multicut != MULTICUT_6x4_5 && job->multicut != MULTICUT_6x4_5X2) {
1619 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
1620 				dnpds40_cleanup_job(job);
1621 				return CUPS_BACKEND_CANCEL;
1622 			}
1623 			/* Only 6x4 or 6x4.5 on 6x9 media can be rewound */
1624 			if (job->multicut == MULTICUT_6x4 || job->multicut == MULTICUT_6x4_5)
1625 				job->can_rewind = 1;
1626 			break;
1627 		case 500: //"8x10"
1628 			if (ctx->type == P_DNP_DS820 &&
1629 			    (job->multicut == MULTICUT_8x7 || job->multicut == MULTICUT_8x9)) {
1630 				/* These are okay */
1631 			} else if (job->multicut < MULTICUT_8x10 || job->multicut == MULTICUT_8x12 ||
1632 			    job->multicut == MULTICUT_8x6X2 || job->multicut >= MULTICUT_8x6_8x5 ) {
1633 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
1634 				dnpds40_cleanup_job(job);
1635 				return CUPS_BACKEND_CANCEL;
1636 			}
1637 
1638 			/* 8x4, 8x5 can be rewound */
1639 			if (job->multicut == MULTICUT_8x4 ||
1640 			    job->multicut == MULTICUT_8x5)
1641 				job->can_rewind = 1;
1642 			break;
1643 		case 510: //"8x12"
1644 			if (job->multicut < MULTICUT_8x10 || (job->multicut > MULTICUT_8xA4LEN && !(job->multicut == MULTICUT_8x7 || job->multicut == MULTICUT_8x9))) {
1645 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
1646 				dnpds40_cleanup_job(job);
1647 				return CUPS_BACKEND_CANCEL;
1648 			}
1649 
1650 			/* 8x4, 8x5, 8x6 can be rewound */
1651 			if (job->multicut == MULTICUT_8x4 ||
1652 			    job->multicut == MULTICUT_8x5 ||
1653 			    job->multicut == MULTICUT_8x6)
1654 				job->can_rewind = 1;
1655 			break;
1656 		case 600: //"A4"
1657 			if (job->multicut < MULTICUT_A5 || job->multicut > MULTICUT_A4x5X2) {
1658 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->media, job->multicut);
1659 				dnpds40_cleanup_job(job);
1660 				return CUPS_BACKEND_CANCEL;
1661 			}
1662 			/* A4xn and A5 can be rewound */
1663 			if (job->multicut == MULTICUT_A4x4 ||
1664 			    job->multicut == MULTICUT_A4x5 ||
1665 			    job->multicut == MULTICUT_A4x6 ||
1666 			    job->multicut == MULTICUT_A5)
1667 				job->can_rewind = 1;
1668 			break;
1669 		default:
1670 			ERROR("Unknown media (%u vs %u)!\n", ctx->media, job->multicut);
1671 			dnpds40_cleanup_job(job);
1672 			return CUPS_BACKEND_CANCEL;
1673 		}
1674 	} else if (job->multicut < 400) {
1675 		int mcut = job->multicut;
1676 
1677 		switch(ctx->duplex_media) {
1678 		case 100: //"8x10.75"
1679 			if (mcut > MULTICUT_S_BACK)
1680 				mcut -= MULTICUT_S_BACK;
1681 			else if (mcut > MULTICUT_S_FRONT)
1682 				mcut -= MULTICUT_S_FRONT;
1683 
1684 			if (mcut == MULTICUT_S_8x12 ||
1685 			    mcut == MULTICUT_S_8x6X2 ||
1686 			    mcut == MULTICUT_S_8x4X3) {
1687 				ERROR("Incorrect media for job loaded (%u vs %u)\n", ctx->duplex_media, job->multicut);
1688 				dnpds40_cleanup_job(job);
1689 				return CUPS_BACKEND_CANCEL;
1690 			}
1691 			break;
1692 		case 200: //"8x12"
1693 			/* Everything is legal */
1694 			break;
1695 		default:
1696 			ERROR("Unknown duplexer media (%u vs %u)!\n", ctx->duplex_media, job->multicut);
1697 			dnpds40_cleanup_job(job);
1698 			return CUPS_BACKEND_CANCEL;
1699 		}
1700 	} else {
1701 		ERROR("Multicut value out of range! (%u)\n", job->multicut);
1702 		dnpds40_cleanup_job(job);
1703 		return CUPS_BACKEND_CANCEL;
1704 	}
1705 
1706 	/* Additional santity checks, make sure printer support exists */
1707 	if (!ctx->supports_6x6 && job->multicut == MULTICUT_6x6) {
1708 		ERROR("Printer does not support 6x6 prints, aborting!\n");
1709 		dnpds40_cleanup_job(job);
1710 		return CUPS_BACKEND_CANCEL;
1711 	}
1712 
1713 	if (!ctx->supports_5x5 && job->multicut == MULTICUT_5x5) {
1714 		ERROR("Printer does not support 5x5 prints, aborting!\n");
1715 		dnpds40_cleanup_job(job);
1716 		return CUPS_BACKEND_CANCEL;
1717 	}
1718 
1719 	if ((job->multicut == MULTICUT_6x4_5 || job->multicut == MULTICUT_6x4_5X2) &&
1720 	    !ctx->supports_6x4_5) {
1721 		ERROR("Printer does not support 6x4.5 prints, aborting!\n");
1722 		dnpds40_cleanup_job(job);
1723 		return CUPS_BACKEND_CANCEL;
1724 	}
1725 
1726 	if (job->multicut == MULTICUT_6x9 && !ctx->supports_6x9) {
1727 		ERROR("Printer does not support 6x9 prints, aborting!\n");
1728 		dnpds40_cleanup_job(job);
1729 		return CUPS_BACKEND_CANCEL;
1730 	}
1731 
1732 	if (job->multicut == MULTICUT_5x3_5X2 && !ctx->supports_3x5x2) {
1733 		ERROR("Printer does not support 3.5x5*2 prints, aborting!\n");
1734 		dnpds40_cleanup_job(job);
1735 		return CUPS_BACKEND_CANCEL;
1736 	}
1737 
1738 skip_multicut:
1739 
1740 	if (job->fullcut && !ctx->supports_adv_fullcut &&
1741 	    job->multicut != MULTICUT_6x8) {
1742 		ERROR("Printer does not support full control on sizes other than 6x8, aborting!\n");
1743 		dnpds40_cleanup_job(job);
1744 		return CUPS_BACKEND_CANCEL;
1745 	}
1746 
1747 	if (job->fullcut && job->cutter) {
1748 		WARNING("Cannot simultaneously use FULL_CUTTER and CUTTER, using the former\n");
1749 		job->cutter = 0;
1750 	}
1751 
1752 	if (job->cutter == 120) {
1753 		if (job->multicut == MULTICUT_6x4 || job->multicut == MULTICUT_6x8) {
1754 			if (!ctx->supports_2x6) {
1755 				ERROR("Printer does not support 2x6 prints, aborting!\n");
1756 				dnpds40_cleanup_job(job);
1757 				return CUPS_BACKEND_CANCEL;
1758 			}
1759 		} else {
1760 			ERROR("Printer only supports legacy 2-inch cuts on 4x6 or 8x6 jobs!");
1761 			dnpds40_cleanup_job(job);
1762 			return CUPS_BACKEND_CANCEL;
1763 		}
1764 	}
1765 
1766 skip_checks:
1767 	DEBUG("job->dpi %u matte %d mcut %u cutter %d/%d, bufs %d spd %d\n",
1768 	      job->dpi, job->matte, job->multicut, job->cutter, job->fullcut, job->buf_needed, job->printspeed);
1769 
1770 	list = dyesub_joblist_create(&dnpds40_backend, ctx);
1771 
1772 	can_combine = job->can_rewind; /* Any rewindable size can be stacked */
1773 
1774 	/* Try to combine prints */
1775 	if (copies > 1 && can_combine) {
1776 		struct dnpds40_printjob *combined;
1777 		combined = combine_jobs(job, job);
1778 		if (combined) {
1779 			combined->copies = job->copies / 2;
1780 			combined->can_rewind = 0;
1781 			dyesub_joblist_addjob(list, combined);
1782 
1783 			if (job->copies & 1) {
1784 				job->copies = 1;
1785 			} else {
1786 				dnpds40_cleanup_job(job);
1787 				job = NULL;
1788 			}
1789 		}
1790 	}
1791 	if (job) {
1792 		dyesub_joblist_addjob(list, job);
1793 	}
1794 
1795 	*vjob = list;
1796 
1797 	return CUPS_BACKEND_OK;
1798 }
1799 
dnpds40_main_loop(void * vctx,const void * vjob)1800 static int dnpds40_main_loop(void *vctx, const void *vjob) {
1801 	struct dnpds40_ctx *ctx = vctx;
1802 	int ret;
1803 	struct dnpds40_cmd cmd;
1804 	uint8_t *resp;
1805 	int len = 0;
1806 	uint8_t *ptr;
1807 	char buf[9];
1808 	int status;
1809 	int buf_needed;
1810 	int multicut;
1811 	int count = 0;
1812 	int manual_copies = 0;
1813 	int copies;
1814 
1815 	const struct dnpds40_printjob *job = vjob;
1816 
1817 	if (!ctx)
1818 		return CUPS_BACKEND_FAILED;
1819 	if (!job)
1820 		return CUPS_BACKEND_FAILED;
1821 
1822 	buf_needed = job->buf_needed;
1823 	multicut = job->multicut;
1824 	copies = job->copies;
1825 
1826 	/* If we switch major overcoat modes, we need both buffers */
1827 	if (!!job->matte != ctx->last_matte)
1828 		buf_needed = 2;
1829 
1830 	if (job->cutter == 120) {
1831 		/* Work around firmware bug on DS40 where if we run out
1832 		   of media, we can't resume the job without losing the
1833 		   cutter setting. */
1834 		// XXX add version test? what about other printers?
1835 		manual_copies = 1;
1836 	}
1837 
1838 	/* RX1HS requires HS media, but the only way to tell is that the
1839 	   HS media reports a lot code, while the non-HS media does not. */
1840 	if (ctx->needs_mlot) {
1841 		/* Get Media Lot */
1842 		dnpds40_build_cmd(&cmd, "INFO", "MLOT", 0);
1843 
1844 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
1845 		if (!resp)
1846 			return CUPS_BACKEND_FAILED;
1847 
1848 		dnpds40_cleanup_string((char*)resp, len);
1849 
1850 		len = strlen((char*)resp);
1851 		free(resp);
1852 		if (!len) {
1853 			ERROR("Media does not report a valid lot number (non-HS media in RX1HS?)\n");
1854 			return CUPS_BACKEND_STOP;
1855 		}
1856 	}
1857 
1858 top:
1859 
1860 	/* Query status */
1861 	dnpds40_build_cmd(&cmd, "STATUS", "", 0);
1862 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
1863 	if (!resp)
1864 		return CUPS_BACKEND_FAILED;
1865 	dnpds40_cleanup_string((char*)resp, len);
1866 	status = atoi((char*)resp);
1867 	free(resp);
1868 
1869 	/* Figure out what's going on */
1870 	switch(status) {
1871 	case 0:	/* Idle; we can continue! */
1872 	case 1: /* Printing */
1873 	{
1874 		int bufs;
1875 
1876 		/* Query buffer state */
1877 		dnpds40_build_cmd(&cmd, "INFO", "FREE_PBUFFER", 0);
1878 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
1879 
1880 		if (!resp)
1881 			return CUPS_BACKEND_FAILED;
1882 
1883 		dnpds40_cleanup_string((char*)resp, len);
1884 		/* Check to see if we have sufficient buffers */
1885 		bufs = atoi(((char*)resp)+3);
1886 		free(resp);
1887 		if (bufs < buf_needed) {
1888 			INFO("Insufficient printer buffers (%d vs %d), retrying...\n", bufs, buf_needed);
1889 			sleep(1);
1890 			goto top;
1891 		}
1892 		break;
1893 	}
1894 	case 500: /* Cooling print head */
1895 	case 510: /* Cooling paper motor */
1896 		INFO("Printer cooling down...\n");
1897 		sleep(1);
1898 		goto top;
1899 	case 900:
1900 		INFO("Waking printer up from standby...\n");
1901 		// XXX do someting here?
1902 		break;
1903 	case 1000: /* Cover open */
1904 	case 1010: /* No Scrap Box */
1905 	case 1100: /* Paper End */
1906 	case 1200: /* Ribbon End */
1907 	case 1300: /* Paper Jam */
1908 	case 1400: /* Ribbon Error */
1909 		WARNING("Printer not ready: %s, please correct...\n", dnpds40_statuses(status));
1910 		sleep(1);
1911 		goto top;
1912 	case 1500: /* Paper definition error */
1913 		ERROR("Paper definition error, aborting job\n");
1914 		return CUPS_BACKEND_CANCEL;
1915 	case 1600: /* Data error */
1916 		ERROR("Data error, aborting job\n");
1917 		return CUPS_BACKEND_CANCEL;
1918 	default:
1919 		ERROR("Fatal Printer Error: %d => %s, halting queue!\n", status, dnpds40_statuses(status));
1920 		return CUPS_BACKEND_HOLD;
1921 	}
1922 
1923 	{
1924 		/* Figure out remaining native prints */
1925 		if (dnpds40_query_markers(ctx, NULL, NULL))
1926 			return CUPS_BACKEND_FAILED;
1927 		if (ctx->marker[0].levelnow < 0)
1928 			return CUPS_BACKEND_FAILED;
1929 		dump_markers(ctx->marker, ctx->marker_count, 0);
1930 
1931 		// For logic below.
1932 		count = ctx->marker[0].levelnow;
1933 		if (job->cut_paper && count > ctx->marker[1].levelnow)
1934 			count = ctx->marker[1].levelnow;
1935 
1936 		/* See if we can rewind to save media */
1937 		if (job->can_rewind && ctx->supports_rewind) {
1938 			/* Tell printer to use rewind */
1939 			multicut += 400;
1940 
1941 			/* Get Media remaining */
1942 			dnpds40_build_cmd(&cmd, "INFO", "RQTY", 0);
1943 
1944 			resp = dnpds40_resp_cmd(ctx, &cmd, &len);
1945 			if (!resp)
1946 				return CUPS_BACKEND_FAILED;
1947 
1948 			dnpds40_cleanup_string((char*)resp, len);
1949 			count = atoi((char*)resp+4);
1950 			free(resp);
1951 		}
1952 
1953 		if (ctx->type == P_CITIZEN_CW01) {
1954 			/* Get Vertical resolution */
1955 			dnpds40_build_cmd(&cmd, "INFO", "RESOLUTION_V", 0);
1956 
1957 			resp = dnpds40_resp_cmd(ctx, &cmd, &len);
1958 			if (!resp)
1959 				return CUPS_BACKEND_FAILED;
1960 
1961 			dnpds40_cleanup_string((char*)resp, len);
1962 
1963 #if 0  // XXX Fix 600dpi support on CW01
1964 			// have to read the last DPI, and send the correct CWD over?
1965 			if (ctx->dpi == 600 && strcmp("RV0334", *char*)resp) {
1966 				ERROR("600DPI prints not yet supported, need 600DPI CWD load");
1967 				return CUPS_BACKEND_CANCEL;
1968 			}
1969 #endif
1970 			free(resp);
1971 		}
1972 
1973 		/* Verify we have sufficient media for prints */
1974 
1975 #if 0 // disabled this to allow error to be reported on the printer panel
1976 		if (count < 1) {
1977 			ERROR("Printer out of media, please correct!\n");
1978 			return CUPS_BACKEND_STOP;
1979 		}
1980 #endif
1981 		if (count < copies) {
1982 			WARNING("Printer does not have sufficient remaining media (%d) to complete job (%d)\n", copies, count);
1983 		}
1984 	}
1985 
1986 	/* Work around a bug in older gutenprint releases. */
1987 	if (ctx->last_multicut >= 200 && ctx->last_multicut < 300 &&
1988 	    multicut >= 200 && multicut < 300) {
1989 		WARNING("Bogus multicut value for duplex page, correcting\n");
1990 		multicut += 100;
1991 	}
1992 
1993 	/* Store our last multicut state */
1994 	ctx->last_multicut = multicut;
1995 
1996 	/* Tell printer how many copies to make */
1997 	snprintf(buf, sizeof(buf), "%07d\r", manual_copies ? 1 : copies);
1998 	dnpds40_build_cmd(&cmd, "CNTRL", "QTY", 8);
1999 	if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)buf, 8)))
2000 		return CUPS_BACKEND_FAILED;
2001 
2002 	if (!manual_copies)
2003 		copies = 1;
2004 
2005 	/* Enable job resumption on correctable errors */
2006 	if (ctx->supports_matte) {
2007 		snprintf(buf, sizeof(buf), "%08d", 1);
2008 		/* DS80D does not support BUFFCNTRL when using
2009 		   cut media; all others support this */
2010 		if (ctx->type != P_DNP_DS80D ||
2011 		    multicut < 100) {
2012 			dnpds40_build_cmd(&cmd, "CNTRL", "BUFFCNTRL", 8);
2013 			if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)buf, 8)))
2014 				return CUPS_BACKEND_FAILED;
2015 		}
2016 	}
2017 
2018 	/* Set overcoat parameters if appropriate */
2019 	if (ctx->supports_matte) {
2020 		snprintf(buf, sizeof(buf), "%08d", job->matte);
2021 		dnpds40_build_cmd(&cmd, "CNTRL", "OVERCOAT", 8);
2022 		if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)buf, 8)))
2023 			return CUPS_BACKEND_FAILED;
2024 	}
2025 
2026 	/* Program in the cutter setting */
2027 	if (job->cutter) {
2028 		snprintf(buf, sizeof(buf), "%08d", job->cutter);
2029 		dnpds40_build_cmd(&cmd, "CNTRL", "CUTTER", 8);
2030 		if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)buf, 8)))
2031 			return CUPS_BACKEND_FAILED;
2032 	}
2033 
2034 	/* Send over the printspeed if appropriate */
2035 	if (ctx->supports_printspeed) {
2036 		snprintf(buf, sizeof(buf), "%08d", job->printspeed * 10);
2037 		dnpds40_build_cmd(&cmd, "CNTRL", "PRINTSPEED", 8);
2038 		if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)buf, 8)))
2039 			return CUPS_BACKEND_FAILED;
2040 	}
2041 
2042 	/* Program in the multicut setting, if one exists */
2043 	if (multicut) {
2044 		snprintf(buf, sizeof(buf), "%08d", multicut);
2045 		dnpds40_build_cmd(&cmd, "IMAGE", "MULTICUT", 8);
2046 		if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)buf, 8)))
2047 			return CUPS_BACKEND_FAILED;
2048 	}
2049 
2050 	/* Finally, send the stream over as individual data chunks */
2051 	ptr = job->databuf;
2052 	while(ptr && ptr < (job->databuf + job->datalen)) {
2053 		int i;
2054 		buf[8] = 0;
2055 		memcpy(buf, ptr + 24, 8);
2056 		i = atoi(buf) + 32;
2057 
2058 		if ((ret = send_data(ctx->dev, ctx->endp_down,
2059 				     ptr, i)))
2060 			return CUPS_BACKEND_FAILED;
2061 
2062 		ptr += i;
2063 	}
2064 	sleep(1);  /* Give things a moment */
2065 
2066 	if (fast_return && !manual_copies) {
2067 		INFO("Fast return mode enabled.\n");
2068 	} else {
2069 		INFO("Waiting for job to complete...\n");
2070 		int started = 0;
2071 
2072 		while (1) {
2073 			/* Query status */
2074 			dnpds40_build_cmd(&cmd, "STATUS", "", 0);
2075 			resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2076 			if (!resp)
2077 				return CUPS_BACKEND_FAILED;
2078 			dnpds40_cleanup_string((char*)resp, len);
2079 			status = atoi((char*)resp);
2080 			free(resp);
2081 
2082 			/* If we're idle or there's an error..*/
2083 			if (status == 0 && started)
2084 				break;
2085 			if (status)
2086 				started = 1;
2087 			if (status >= 1000) {
2088 				ERROR("Printer encountered error: %s\n", dnpds40_statuses(status));
2089 				break;
2090 			}
2091 			sleep(1);
2092 		}
2093 
2094 		/* Figure out remaining native prints */
2095 		if (dnpds40_query_markers(ctx, NULL, NULL))
2096 			return CUPS_BACKEND_FAILED;
2097 		dump_markers(ctx->marker, ctx->marker_count, 0);
2098 	}
2099 
2100 	/* Clean up */
2101 	if (terminate)
2102 		copies = 1;
2103 
2104 	INFO("Print complete (%d copies remaining)\n", copies - 1);
2105 
2106 	if (copies && --copies) {
2107 		/* No need to wait on buffers due to matte switching */
2108 		buf_needed = job->buf_needed;
2109 		goto top;
2110 	}
2111 
2112 	/* Finally, account for overcoat mode of last print */
2113 	ctx->last_matte = !!job->matte;
2114 #ifdef STATE_DIR
2115 	{
2116 		/* Store last matte status into file */
2117 		char buf[64];
2118 		FILE *f;
2119 		snprintf(buf, sizeof(buf), STATE_DIR "/%s-last", ctx->serno);
2120 		f = fopen(buf, "w");
2121 		if (f) {
2122 			fprintf(f, "%08d", ctx->last_matte);
2123 			fclose(f);
2124 		}
2125 	}
2126 #endif
2127 
2128 	return CUPS_BACKEND_OK;
2129 }
2130 
dnpds40_get_sensors(struct dnpds40_ctx * ctx)2131 static int dnpds40_get_sensors(struct dnpds40_ctx *ctx)
2132 {
2133 	struct dnpds40_cmd cmd;
2134 	uint8_t *resp;
2135 	int len = 0;
2136 	char *tok;
2137 
2138 	/* Get Sensor Info */
2139 	dnpds40_build_cmd(&cmd, "INFO", "SENSOR", 0);
2140 
2141 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2142 	if (!resp)
2143 		return CUPS_BACKEND_FAILED;
2144 
2145 	dnpds40_cleanup_string((char*)resp, len);
2146 
2147 	tok = strtok((char*)resp, "; -");
2148 	do {
2149 		char *val = strtok(NULL, "; -");
2150 
2151 		if (!strcmp("HDT", tok)) {
2152 			INFO("Head Temperature   : %s\n", val);
2153 		} else if (!strcmp("MDT", tok)) {
2154 			INFO("Media Temperature  : %s\n", val);
2155 		} else if (!strcmp("PMK", tok)) {
2156 			INFO("Paper Mark         : %s\n", val);
2157 		} else if (!strcmp("RML", tok)) {
2158 			INFO("Ribbon Mark Left   : %s\n", val);
2159 		} else if (!strcmp("RMC", tok)) {
2160 			INFO("Ribbon Mark Right  : %s\n", val);
2161 		} else if (!strcmp("RMR", tok)) {
2162 			INFO("Ribbon Mark Center : %s\n", val);
2163 		} else if (!strcmp("PSZ", tok)) {
2164 			INFO("Paper Size         : %s\n", val);
2165 		} else if (!strcmp("PNT", tok)) {
2166 			INFO("Paper Notch        : %s\n", val);
2167 		} else if (!strcmp("PJM", tok)) {
2168 			INFO("Paper Jam          : %s\n", val);
2169 		} else if (!strcmp("PED", tok)) {
2170 			INFO("Paper End          : %s\n", val);
2171 		} else if (!strcmp("PET", tok)) {
2172 			INFO("Paper Empty        : %s\n", val);
2173 		} else if (!strcmp("HDV", tok)) {
2174 			INFO("Head Voltage       : %s\n", val);
2175 		} else if (!strcmp("HMD", tok)) {
2176 			INFO("Humidity           : %s\n", val);
2177 		} else if (!strcmp("RP1", tok)) {
2178 			INFO("Roll Paper End 1   : %s\n", val);
2179 		} else if (!strcmp("RP2", tok)) {
2180 			INFO("Roll Paper End 2   : %s\n", val);
2181 		} else if (!strcmp("CSR", tok)) {
2182 			INFO("Color Sensor Red   : %s\n", val);
2183 		} else if (!strcmp("CSG", tok)) {
2184 			INFO("Color Sensor Green : %s\n", val);
2185 		} else if (!strcmp("CSB", tok)) {
2186 			INFO("Color Sensor Blue  : %s\n", val);
2187 		} else {
2188 			INFO("Unknown Sensor: '%s' '%s'\n",
2189 			     tok, val);
2190 		}
2191 	} while ((tok = strtok(NULL, "; -")) != NULL);
2192 
2193 	free(resp);
2194 
2195 	return CUPS_BACKEND_OK;
2196 }
2197 
dnpds40_get_info(struct dnpds40_ctx * ctx)2198 static int dnpds40_get_info(struct dnpds40_ctx *ctx)
2199 {
2200 	struct dnpds40_cmd cmd;
2201 	uint8_t *resp;
2202 	int len = 0;
2203 
2204 	INFO("Model: %s\n", dnpds40_printer_type(ctx->type));
2205 
2206 	/* Serial number already queried */
2207 	INFO("Serial Number: %s\n", ctx->serno);
2208 
2209 	/* Firmware version already queried */
2210 	INFO("Firmware Version: %s\n", ctx->version);
2211 
2212 	/* Figure out Duplexer */
2213 	if (ctx->type == P_DNP_DS80D) {
2214 		dnpds40_build_cmd(&cmd, "INFO", "UNIT_FVER", 0);
2215 
2216 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2217 		if (!resp)
2218 			return CUPS_BACKEND_FAILED;
2219 
2220 		dnpds40_cleanup_string((char*)resp, len);
2221 
2222 		INFO("Duplexer Version: %s\n", resp);
2223 
2224 		free(resp);
2225 	}
2226 
2227 	if (ctx->type == P_CITIZEN_CW01) {
2228 		/* Get Horizonal resolution */
2229 		dnpds40_build_cmd(&cmd, "INFO", "RESOLUTION_H", 0);
2230 
2231 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2232 		if (!resp)
2233 			return CUPS_BACKEND_FAILED;
2234 
2235 		dnpds40_cleanup_string((char*)resp, len);
2236 
2237 		INFO("Horizontal Resolution: %s dpi\n", (char*)resp + 3);
2238 
2239 		free(resp);
2240 
2241 		/* Get Vertical resolution */
2242 		dnpds40_build_cmd(&cmd, "INFO", "RESOLUTION_V", 0);
2243 
2244 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2245 		if (!resp)
2246 			return CUPS_BACKEND_FAILED;
2247 
2248 		dnpds40_cleanup_string((char*)resp, len);
2249 
2250 		INFO("Vertical Resolution: %s dpi\n", (char*)resp + 3);
2251 
2252 		free(resp);
2253 
2254 		/* Get Color Control Data Version */
2255 		dnpds40_build_cmd(&cmd, "TBL_RD", "Version", 0);
2256 
2257 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2258 		if (!resp)
2259 			return CUPS_BACKEND_FAILED;
2260 
2261 		dnpds40_cleanup_string((char*)resp, len);
2262 
2263 		INFO("Color Data Version: %s ", (char*)resp);
2264 
2265 		free(resp);
2266 
2267 		/* Get Color Control Data Checksum */
2268 		dnpds40_build_cmd(&cmd, "MNT_RD", "CTRLD_CHKSUM", 0);
2269 
2270 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2271 		if (!resp)
2272 			return CUPS_BACKEND_FAILED;
2273 
2274 		dnpds40_cleanup_string((char*)resp, len);
2275 
2276 		DEBUG2("(%s)\n", (char*)resp);
2277 
2278 		free(resp);
2279 	}
2280 
2281 	/* Get Media Color offset */
2282 	dnpds40_build_cmd(&cmd, "INFO", "MCOLOR", 0);
2283 
2284 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2285 	if (!resp)
2286 		return CUPS_BACKEND_FAILED;
2287 
2288 	dnpds40_cleanup_string((char*)resp, len);
2289 
2290 	INFO("Media Color Offset: Y %u M %u C %u L %u\n", *(resp+2), *(resp+3),
2291 	     *(resp+4), *(resp+5));
2292 
2293 	free(resp);
2294 
2295 	/* Get Media Class */
2296 	dnpds40_build_cmd(&cmd, "INFO", "MEDIA_CLASS", 0);
2297 
2298 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2299 	if (!resp)
2300 		return CUPS_BACKEND_FAILED;
2301 
2302 	dnpds40_cleanup_string((char*)resp, len);
2303 
2304 	INFO("Media Class: %d\n", atoi((char*)resp + 4));
2305 
2306 	free(resp);
2307 
2308 	/* Get Media Lot */
2309 	dnpds40_build_cmd(&cmd, "INFO", "MLOT", 0);
2310 
2311 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2312 	if (!resp)
2313 		return CUPS_BACKEND_FAILED;
2314 
2315 	dnpds40_cleanup_string((char*)resp, len);
2316 
2317 	INFO("Media Lot Code: %s\n", (char*)resp+2);
2318 	free(resp);
2319 
2320 	/* Get Media ID Set (?) */
2321 	dnpds40_build_cmd(&cmd, "MNT_RD", "MEDIA_ID_SET", 0);
2322 
2323 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2324 	if (!resp)
2325 		return CUPS_BACKEND_FAILED;
2326 
2327 	dnpds40_cleanup_string((char*)resp, len);
2328 
2329 	INFO("Media ID: %d\n", atoi((char*)resp+4));
2330 
2331 	free(resp);
2332 
2333 	if (ctx->type == P_CITIZEN_CW01)
2334 		goto skip;
2335 
2336 	/* Get Ribbon ID code (?) */
2337 	dnpds40_build_cmd(&cmd, "MNT_RD", "RIBBON_ID_CODE", 0);
2338 
2339 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2340 	if (!resp)
2341 		return CUPS_BACKEND_FAILED;
2342 
2343 	dnpds40_cleanup_string((char*)resp, len);
2344 
2345 	INFO("Ribbon ID: %s\n", (char*)resp);
2346 
2347 	free(resp);
2348 
2349 	/* Figure out control data and checksums */
2350 
2351 	/* 300 DPI */
2352 	dnpds40_build_cmd(&cmd, "TBL_RD", "CWD300_Version", 0);
2353 
2354 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2355 	if (!resp)
2356 		return CUPS_BACKEND_FAILED;
2357 
2358 	dnpds40_cleanup_string((char*)resp, len);
2359 
2360 	INFO("300 DPI Color Data: %s ", (char*)resp);
2361 
2362 	free(resp);
2363 
2364 	dnpds40_build_cmd(&cmd, "TBL_RD", "CWD300_Checksum", 0);
2365 
2366 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2367 	if (!resp)
2368 		return CUPS_BACKEND_FAILED;
2369 
2370 	dnpds40_cleanup_string((char*)resp, len);
2371 
2372 	DEBUG2("(%s)\n", (char*)resp);
2373 
2374 	free(resp);
2375 
2376 	/* 600 DPI */
2377 	dnpds40_build_cmd(&cmd, "TBL_RD", "CWD600_Version", 0);
2378 
2379 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2380 	if (!resp)
2381 		return CUPS_BACKEND_FAILED;
2382 
2383 	dnpds40_cleanup_string((char*)resp, len);
2384 
2385 	INFO("600 DPI Color Data: %s ", (char*)resp);
2386 
2387 	free(resp);
2388 
2389 	dnpds40_build_cmd(&cmd, "TBL_RD", "CWD600_Checksum", 0);
2390 
2391 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2392 	if (!resp)
2393 		return CUPS_BACKEND_FAILED;
2394 
2395 	dnpds40_cleanup_string((char*)resp, len);
2396 
2397 	DEBUG2("(%s)\n", (char*)resp);
2398 
2399 	free(resp);
2400 
2401 	if (ctx->supports_lowspeed) {
2402 		/* "Low Speed" */
2403 		dnpds40_build_cmd(&cmd, "TBL_RD", "CWD610_Version", 0);
2404 
2405 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2406 		if (!resp)
2407 			return CUPS_BACKEND_FAILED;
2408 
2409 		dnpds40_cleanup_string((char*)resp, len);
2410 
2411 		INFO("Low Speed Color Data: %s ", (char*)resp);
2412 
2413 		free(resp);
2414 
2415 		dnpds40_build_cmd(&cmd, "TBL_RD", "CWD610_Checksum", 0);
2416 
2417 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2418 		if (!resp)
2419 			return CUPS_BACKEND_FAILED;
2420 
2421 		dnpds40_cleanup_string((char*)resp, len);
2422 
2423 		DEBUG2("(%s)\n", (char*)resp);
2424 
2425 		free(resp);
2426 	}
2427 	if (ctx->supports_highdensity) {
2428 		uint8_t buf[5];
2429 		int i = 0;
2430 
2431 		snprintf((char*)buf, sizeof(buf), "%04d", i);
2432 
2433 		/* "High Density" */
2434 		dnpds40_build_cmd(&cmd, "TBL_RD", "CWD620_Version", 4);
2435 
2436 		resp = dnpds40_resp_cmd2(ctx, &cmd, &len, buf, 4);
2437 		if (!resp)
2438 			return CUPS_BACKEND_FAILED;
2439 
2440 		dnpds40_cleanup_string((char*)resp, len);
2441 
2442 		INFO("High Density Color Data: %s ", (char*)resp);
2443 
2444 		free(resp);
2445 
2446 		dnpds40_build_cmd(&cmd, "TBL_RD", "CWD620_Checksum", 4);
2447 
2448 		resp = dnpds40_resp_cmd2(ctx, &cmd, &len, buf, 4);
2449 		if (!resp)
2450 			return CUPS_BACKEND_FAILED;
2451 
2452 		dnpds40_cleanup_string((char*)resp, len);
2453 
2454 		DEBUG2("(%s)\n", (char*)resp);
2455 
2456 		free(resp);
2457 	}
2458 	if (ctx->supports_gamma) {
2459 		/* "Low Speed" */
2460 		dnpds40_build_cmd(&cmd, "TBL_RD", "CTRLD_GAMMA16", 0);
2461 
2462 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2463 		if (!resp)
2464 			return CUPS_BACKEND_FAILED;
2465 
2466 		dnpds40_cleanup_string((char*)resp, len);
2467 
2468 		INFO("Gamma Correction Data Checksum: %s\n", (char*)resp);
2469 
2470 		free(resp);
2471 	}
2472 
2473 	if (ctx->supports_standby) {
2474 		/* Get Standby stuff */
2475 		int i;
2476 
2477 		dnpds40_build_cmd(&cmd, "MNT_RD", "STANDBY_TIME", 0);
2478 
2479 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2480 		if (!resp)
2481 			return CUPS_BACKEND_FAILED;
2482 
2483 		dnpds40_cleanup_string((char*)resp, len);
2484 		i = atoi((char*)resp);
2485 
2486 		INFO("Standby Transition time: %d minutes\n", i);
2487 
2488 		free(resp);
2489 
2490 		/* Get Media End Keep */
2491 		dnpds40_build_cmd(&cmd, "MNT_RD", "END_KEEP_MODE", 0);
2492 
2493 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2494 		if (!resp)
2495 			return CUPS_BACKEND_FAILED;
2496 
2497 		dnpds40_cleanup_string((char*)resp, len);
2498 		i = atoi((char*)resp);
2499 		INFO("Media End kept across power cycles: %s\n",
2500 		     i ? "Yes" : "No");
2501 
2502 		free(resp);
2503 	}
2504 
2505 	if (ctx->supports_iserial) {
2506 		int i;
2507 		/* Get USB serial descriptor status */
2508 		dnpds40_build_cmd(&cmd, "MNT_RD", "USB_ISERI_SET", 0);
2509 
2510 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2511 		if (!resp)
2512 			return CUPS_BACKEND_FAILED;
2513 
2514 		dnpds40_cleanup_string((char*)resp, len);
2515 		i = atoi((char*)resp);
2516 
2517 		INFO("Report Serial Number in USB descriptor: %s\n",
2518 		     i ? "Yes" : "No");
2519 
2520 		free(resp);
2521 	}
2522 
2523 skip:
2524 	return CUPS_BACKEND_OK;
2525 }
2526 
dnpds40_get_status(struct dnpds40_ctx * ctx)2527 static int dnpds40_get_status(struct dnpds40_ctx *ctx)
2528 {
2529 	struct dnpds40_cmd cmd;
2530 	uint8_t *resp;
2531 	int len = 0;
2532 	int count;
2533 
2534 	/* Generate command */
2535 	dnpds40_build_cmd(&cmd, "STATUS", "", 0);
2536 
2537 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2538 	if (!resp)
2539 		return CUPS_BACKEND_FAILED;
2540 
2541 	dnpds40_cleanup_string((char*)resp, len);
2542 	count = atoi((char*)resp);
2543 
2544 	INFO("Printer Status: %s (%d)\n", dnpds40_statuses(count), count);
2545 
2546 	free(resp);
2547 
2548 	/* Figure out Duplexer */
2549 	if (ctx->type == P_DNP_DS80D) {
2550 		dnpds40_build_cmd(&cmd, "INFO", "UNIT_STATUS", 0);
2551 
2552 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2553 		if (!resp)
2554 			return CUPS_BACKEND_FAILED;
2555 
2556 		dnpds40_cleanup_string((char*)resp, len);
2557 		count = atoi((char*)resp);
2558 
2559 		INFO("Duplexer Status: %s\n", dnpds80_duplex_statuses(count));
2560 		INFO("Duplexer Media Status: %s\n", dnpds80_duplex_paper_status(ctx->duplex_media_status));
2561 
2562 		free(resp);
2563 	}
2564 
2565 	/* Get remaining print quantity */
2566 	dnpds40_build_cmd(&cmd, "INFO", "PQTY", 0);
2567 
2568 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2569 	if (!resp)
2570 		return CUPS_BACKEND_FAILED;
2571 
2572 	dnpds40_cleanup_string((char*)resp, len);
2573 
2574 	INFO("Prints remaining in job: %d\n", atoi((char*)resp + 4));
2575 
2576 	free(resp);
2577 
2578 	/* Generate command */
2579 	dnpds40_build_cmd(&cmd, "INFO", "FREE_PBUFFER", 0);
2580 
2581 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2582 	if (!resp)
2583 		return CUPS_BACKEND_FAILED;
2584 
2585 	dnpds40_cleanup_string((char*)resp, len);
2586 
2587 	INFO("Free Buffers: %d\n", atoi((char*)resp + 3));
2588 	free(resp);
2589 
2590 	/* Report media */
2591 	INFO("Media Type: %s\n", dnpds40_media_types(ctx->media));
2592 
2593 	if (ctx->supports_media_ext) {
2594 		int type;
2595 		dnpds40_build_cmd(&cmd, "INFO", "MEDIA_EXT_CODE", 0);
2596 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2597 		if (!resp)
2598 			return CUPS_BACKEND_FAILED;
2599 
2600 		dnpds40_cleanup_string((char*)resp, len);
2601 		*(resp+2) = 0;  // Only the first two chars are used.
2602 		type = atoi((char*)resp);
2603 		INFO("Media Code: %s\n", dnpds620_media_extension_code(type));
2604 		free(resp);
2605 	}
2606 
2607 	/* Try to figure out media subtype */
2608 	if (ctx->type == P_DNP_DS820) {
2609 		int type;
2610 		dnpds40_build_cmd(&cmd, "INFO", "MEDIA_CLASS_RFID", 0);
2611 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2612 		if (!resp)
2613 			return CUPS_BACKEND_FAILED;
2614 
2615 		dnpds40_cleanup_string((char*)resp, len);
2616 		type = atoi((char*)resp);
2617 		INFO("Media Subtype: %s\n", dnpds820_media_subtypes(type));
2618 		free(resp);
2619 	}
2620 
2621 	/* Report Cut Media */
2622 	if (ctx->type == P_DNP_DS80D) {
2623 		INFO("Duplex Media Type: %s\n", dnpds80_duplex_media_types(ctx->duplex_media));
2624 		INFO("Duplexer Media Status: %s\n", dnpds80_duplex_paper_status(ctx->duplex_media_status));
2625 	}
2626 
2627 	if (ctx->media_count_new)
2628 		INFO("Native Prints Available on New Media: %u\n", ctx->media_count_new);
2629 
2630 	/* Get Media remaining */
2631 	count = dnpds40_query_mqty(ctx);
2632 	if (count < 0)
2633 		return CUPS_BACKEND_FAILED;
2634 
2635 	INFO("Native Prints Remaining on Media: %d\n", count);
2636 
2637 	if (ctx->supports_rewind) {
2638 		/* Get Media remaining */
2639 		dnpds40_build_cmd(&cmd, "INFO", "RQTY", 0);
2640 
2641 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2642 		if (!resp)
2643 			return CUPS_BACKEND_FAILED;
2644 
2645 		dnpds40_cleanup_string((char*)resp, len);
2646 
2647 		count = atoi((char*)resp+4);
2648 		free(resp);
2649 	} else {
2650 		// Do nothing, re-use native print count.
2651 	}
2652 	INFO("Half-Size Prints Remaining on Media: %d\n", count);
2653 
2654 	return 0;
2655 }
2656 
dnpds40_get_counters(struct dnpds40_ctx * ctx)2657 static int dnpds40_get_counters(struct dnpds40_ctx *ctx)
2658 {
2659 	struct dnpds40_cmd cmd;
2660 	uint8_t *resp;
2661 	int len = 0;
2662 
2663 	/* Generate command */
2664 	dnpds40_build_cmd(&cmd, "MNT_RD", "COUNTER_LIFE", 0);
2665 
2666 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2667 	if (!resp)
2668 		return CUPS_BACKEND_FAILED;
2669 
2670 	dnpds40_cleanup_string((char*)resp, len);
2671 
2672 	INFO("Lifetime Counter: %d\n", atoi((char*)resp+2));
2673 
2674 	free(resp);
2675 
2676 	if (ctx->type == P_DNP_DS620 ||
2677 	    ctx->type == P_DNP_DS820) {
2678 		/* Generate command */
2679 		dnpds40_build_cmd(&cmd, "MNT_RD", "COUNTER_HEAD", 0);
2680 
2681 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2682 		if (!resp)
2683 			return CUPS_BACKEND_FAILED;
2684 
2685 		dnpds40_cleanup_string((char*)resp, len);
2686 
2687 		INFO("Head Counter: %d\n", atoi((char*)resp+2));
2688 
2689 		free(resp);
2690 	}
2691 
2692 	/* Generate command */
2693 	dnpds40_build_cmd(&cmd, "MNT_RD", "COUNTER_A", 0);
2694 
2695 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2696 	if (!resp)
2697 		return CUPS_BACKEND_FAILED;
2698 
2699 	dnpds40_cleanup_string((char*)resp, len);
2700 
2701 	INFO("A Counter: %d\n", atoi((char*)resp+2));
2702 
2703 	free(resp);
2704 
2705 	/* Generate command */
2706 	dnpds40_build_cmd(&cmd, "MNT_RD", "COUNTER_B", 0);
2707 
2708 	resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2709 	if (!resp)
2710 		return CUPS_BACKEND_FAILED;
2711 
2712 	dnpds40_cleanup_string((char*)resp, len);
2713 
2714 	INFO("B Counter: %d\n", atoi((char*)resp+2));
2715 
2716 	free(resp);
2717 
2718 	if (ctx->supports_counterp) {
2719 		/* Generate command */
2720 		dnpds40_build_cmd(&cmd, "MNT_RD", "COUNTER_P", 0);
2721 
2722 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2723 		if (!resp)
2724 			return CUPS_BACKEND_FAILED;
2725 
2726 		dnpds40_cleanup_string((char*)resp, len);
2727 
2728 		INFO("P Counter: %d\n", atoi((char*)resp+2));
2729 
2730 		free(resp);
2731 	}
2732 
2733 	if (ctx->supports_matte) {
2734 		/* Generate command */
2735 		dnpds40_build_cmd(&cmd, "MNT_RD", "COUNTER_M", 0);
2736 
2737 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2738 		if (!resp)
2739 			return CUPS_BACKEND_FAILED;
2740 
2741 		dnpds40_cleanup_string((char*)resp, len);
2742 
2743 		INFO("M Counter: %d\n", atoi((char*)resp+2));
2744 
2745 		free(resp);
2746 
2747 		/* Generate command */
2748 		dnpds40_build_cmd(&cmd, "MNT_RD", "COUNTER_MATTE", 0);
2749 
2750 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2751 		if (!resp)
2752 			return CUPS_BACKEND_FAILED;
2753 
2754 		dnpds40_cleanup_string((char*)resp, len);
2755 
2756 		INFO("Matte Counter: %d\n", atoi((char*)resp+4));
2757 
2758 		free(resp);
2759 	}
2760 
2761 	if (ctx->type == P_DNP_DS80D) {
2762 		dnpds40_build_cmd(&cmd, "MNT_RD", "COUNTER_DUPLEX", 0);
2763 
2764 		resp = dnpds40_resp_cmd(ctx, &cmd, &len);
2765 		if (!resp)
2766 			return CUPS_BACKEND_FAILED;
2767 
2768 		dnpds40_cleanup_string((char*)resp, len);
2769 
2770 		INFO("Duplexer Counter: %d\n", atoi((char*)resp));
2771 
2772 		free(resp);
2773 	}
2774 
2775 	return CUPS_BACKEND_OK;
2776 }
2777 
dnpds40_clear_counter(struct dnpds40_ctx * ctx,char counter)2778 static int dnpds40_clear_counter(struct dnpds40_ctx *ctx, char counter)
2779 {
2780 	struct dnpds40_cmd cmd;
2781 	char msg[4];
2782 	int ret;
2783 
2784 	/* Generate command */
2785 	dnpds40_build_cmd(&cmd, "MNT_WT", "COUNTER_CLEAR", 4);
2786 	msg[0] = 'C';
2787 	msg[1] = counter;
2788 	msg[2] = 0x0d; /* ie carriage return, ASCII '\r' */
2789 	msg[3] = 0x00;
2790 
2791 	if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)msg, 4)))
2792 		return ret;
2793 
2794 	return 0;
2795 }
2796 
dnpds40_cancel_job(struct dnpds40_ctx * ctx)2797 static int dnpds40_cancel_job(struct dnpds40_ctx *ctx)
2798 {
2799 	struct dnpds40_cmd cmd;
2800 	int ret;
2801 
2802 	/* Generate command */
2803 	dnpds40_build_cmd(&cmd, "CNTRL", "CANCEL", 0);
2804 
2805 	if ((ret = dnpds40_do_cmd(ctx, &cmd, NULL, 0)))
2806 		return ret;
2807 
2808 	return 0;
2809 }
2810 
dnpds40_reset_printer(struct dnpds40_ctx * ctx)2811 static int dnpds40_reset_printer(struct dnpds40_ctx *ctx)
2812 {
2813 	struct dnpds40_cmd cmd;
2814 	int ret;
2815 
2816 	/* Generate command */
2817 	dnpds40_build_cmd(&cmd, "CNTRL", "PRINTER_RESET", 0);
2818 
2819 	if ((ret = dnpds40_do_cmd(ctx, &cmd, NULL, 0)))
2820 		return ret;
2821 
2822 	return 0;
2823 }
2824 
dnpds620_standby_mode(struct dnpds40_ctx * ctx,int delay)2825 static int dnpds620_standby_mode(struct dnpds40_ctx *ctx, int delay)
2826 {
2827 	struct dnpds40_cmd cmd;
2828 	char msg[9];
2829 	int ret;
2830 
2831 	/* Generate command */
2832 	dnpds40_build_cmd(&cmd, "MNT_WT", "STANDBY_TIME", 8);
2833 	snprintf(msg, sizeof(msg), "%08d", delay);
2834 
2835 	if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)msg, 8)))
2836 		return ret;
2837 
2838 	return 0;
2839 }
2840 
dnpds620_media_keep_mode(struct dnpds40_ctx * ctx,int delay)2841 static int dnpds620_media_keep_mode(struct dnpds40_ctx *ctx, int delay)
2842 {
2843 	struct dnpds40_cmd cmd;
2844 	char msg[9];
2845 	int ret;
2846 
2847 	/* Generate command */
2848 	dnpds40_build_cmd(&cmd, "MNT_WT", "END_KEEP_MODE", 4);
2849 	snprintf(msg, sizeof(msg), "%02d\r", delay);
2850 
2851 	if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)msg, 4)))
2852 		return ret;
2853 
2854 	return 0;
2855 }
2856 
dnpds620_iserial_mode(struct dnpds40_ctx * ctx,int enable)2857 static int dnpds620_iserial_mode(struct dnpds40_ctx *ctx, int enable)
2858 {
2859 	struct dnpds40_cmd cmd;
2860 	char msg[9];
2861 	int ret;
2862 
2863 	/* Generate command */
2864 	dnpds40_build_cmd(&cmd, "MNT_WT", "USB_ISERI_SET", 4);
2865 	snprintf(msg, sizeof(msg), "%02d\r", enable);
2866 
2867 	if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)msg, 8)))
2868 		return ret;
2869 
2870 	return 0;
2871 }
2872 
dnpds40_set_counter_p(struct dnpds40_ctx * ctx,char * arg)2873 static int dnpds40_set_counter_p(struct dnpds40_ctx *ctx, char *arg)
2874 {
2875 	struct dnpds40_cmd cmd;
2876 	char msg[9];
2877 	int i = atoi(arg);
2878 	int ret;
2879 
2880 	/* Generate command */
2881 	dnpds40_build_cmd(&cmd, "MNT_WT", "COUNTERP_SET", 8);
2882 	snprintf(msg, sizeof(msg), "%08d", i);
2883 
2884 	if ((ret = dnpds40_do_cmd(ctx, &cmd, (uint8_t*)msg, 8)))
2885 		return ret;
2886 
2887 	return 0;
2888 }
2889 
dnpds40_cmdline(void)2890 static void dnpds40_cmdline(void)
2891 {
2892 	DEBUG("\t\t[ -i ]           # Query printer info\n");
2893 	DEBUG("\t\t[ -I ]           # Query sensor  info\n");
2894 	DEBUG("\t\t[ -k num ]       # Set standby time (1-99 minutes, 0 disables)\n");
2895 	DEBUG("\t\t[ -K num ]       # Keep Media Status Across Power Cycles (1 on, 0 off)\n");
2896 	DEBUG("\t\t[ -n ]           # Query counters\n");
2897 	DEBUG("\t\t[ -N A|B|M ]     # Clear counter A/B/M\n");
2898 	DEBUG("\t\t[ -p num ]       # Set counter P\n");
2899 	DEBUG("\t\t[ -R ]           # Reset printer\n");
2900 	DEBUG("\t\t[ -s ]           # Query status\n");
2901 	DEBUG("\t\t[ -x num ]       # Set USB iSerialNumber Reporting (1 on, 0 off)\n");
2902 	DEBUG("\t\t[ -X ]           # Cancel current print job\n");
2903 }
2904 
dnpds40_cmdline_arg(void * vctx,int argc,char ** argv)2905 static int dnpds40_cmdline_arg(void *vctx, int argc, char **argv)
2906 {
2907 	struct dnpds40_ctx *ctx = vctx;
2908 	int i, j = 0;
2909 
2910 	if (!ctx)
2911 		return -1;
2912 
2913 	while ((i = getopt(argc, argv, GETOPT_LIST_GLOBAL "iIk:K:nN:p:Rsx:X")) >= 0) {
2914 		switch(i) {
2915 		GETOPT_PROCESS_GLOBAL
2916 		case 'i':
2917 			j = dnpds40_get_info(ctx);
2918 			break;
2919 		case 'I':
2920 			j = dnpds40_get_sensors(ctx);
2921 			break;
2922 		case 'k': {
2923 			int sleeptime = atoi(optarg);
2924 			if (!ctx->supports_standby) {
2925 				ERROR("Printer does not support standby\n");
2926 				j = -1;
2927 				break;
2928 			}
2929 			if (sleeptime < 0 || sleeptime > 99) {
2930 				ERROR("Value out of range (0-99)");
2931 				j = -1;
2932 				break;
2933 			}
2934 			j = dnpds620_standby_mode(ctx, sleeptime);
2935 			break;
2936 		}
2937 		case 'K': {
2938 			int keep = atoi(optarg);
2939 			if (!ctx->supports_standby) {
2940 				ERROR("Printer does not support media keep mode\n");
2941 				j = -1;
2942 				break;
2943 			}
2944 			if (keep < 0 || keep > 1) {
2945 				ERROR("Value out of range (0-1)");
2946 				j = -1;
2947 				break;
2948 			}
2949 			j = dnpds620_media_keep_mode(ctx, keep);
2950 			break;
2951 		}
2952 		case 'n':
2953 			j = dnpds40_get_counters(ctx);
2954 			break;
2955 		case 'N':
2956 			if (optarg[0] != 'A' &&
2957 			    optarg[0] != 'B' &&
2958 			    optarg[0] != 'M')
2959 				return CUPS_BACKEND_FAILED;
2960 			if (optarg[0] == 'M' && !ctx->supports_matte) {
2961 				ERROR("Printer FW does not support matte functions, please update!\n");
2962 				return CUPS_BACKEND_FAILED;
2963 			}
2964 			j = dnpds40_clear_counter(ctx, optarg[0]);
2965 			break;
2966 		case 'p':
2967 			if (!ctx->supports_counterp) {
2968 				ERROR("Printer FW dows not support P counter!\n");
2969 				return CUPS_BACKEND_FAILED;
2970 			}
2971 			j = dnpds40_set_counter_p(ctx, optarg);
2972 			break;
2973 		case 'R': {
2974 			j = dnpds40_reset_printer(ctx);
2975 			break;
2976 		}
2977 		case 's': {
2978 			j = dnpds40_get_status(ctx);
2979 			break;
2980 		}
2981 		case 'x': {
2982 			int enable = atoi(optarg);
2983 			if (!ctx->supports_iserial) {
2984 				ERROR("Printer does not support USB iSerialNumber reporting\n");
2985 				j = -1;
2986 				break;
2987 			}
2988 			if (enable < 0 || enable > 1) {
2989 				ERROR("Value out of range (0-1)");
2990 				j = -1;
2991 				break;
2992 			}
2993 			j = dnpds620_iserial_mode(ctx, enable);
2994 			break;
2995 		}
2996 		case 'X': {
2997 			j = dnpds40_cancel_job(ctx);
2998 			break;
2999 		}
3000 		default:
3001 			break;  /* Ignore completely */
3002 		}
3003 
3004 		if (j) return j;
3005 	}
3006 
3007 	return 0;
3008 }
3009 
dnpds40_query_markers(void * vctx,struct marker ** markers,int * count)3010 static int dnpds40_query_markers(void *vctx, struct marker **markers, int *count)
3011 {
3012 	struct dnpds40_ctx *ctx = vctx;
3013 
3014 	if (markers)
3015 		*markers = ctx->marker;
3016 	if (count)
3017 		*count = ctx->marker_count;
3018 
3019 	ctx->marker[0].levelnow = dnpds40_query_mqty(ctx);
3020 
3021 	if (ctx->marker[0].levelnow < 0)
3022 		return CUPS_BACKEND_FAILED;
3023 
3024         if (ctx->type == P_DNP_DS80D) {
3025 		if (dnpds80dx_query_paper(ctx))
3026 			return CUPS_BACKEND_FAILED;
3027 		switch (ctx->duplex_media_status) {
3028 		case DUPLEX_UNIT_PAPER_NONE:
3029 			ctx->marker[1].levelnow = 0;
3030 			break;
3031 		case DUPLEX_UNIT_PAPER_PROTECTIVE:
3032 			ctx->marker[1].levelnow = -1;
3033 			break;
3034 		case DUPLEX_UNIT_PAPER_PRESENT:
3035 			ctx->marker[1].levelnow = -3;
3036 			break;
3037 		}
3038 	}
3039 
3040 	return CUPS_BACKEND_OK;
3041 }
3042 
3043 static const char *dnpds40_prefixes[] = {
3044 	"dnp_citizen", "dnpds40",  // Family names, do *not* nuke.
3045 	"dnp-ds40", "dnp-ds80", "dnp-ds80dx", "dnp-ds620", "dnp-ds820", "dnp-dsrx1",
3046 	"citizen-cw-01", "citizen-cw-02", "citizen-cx-02",
3047 	// backwards compatibility
3048 	"dnpds80", "dnpds80dx", "dnpds620", "dnpds820", "dnprx1",
3049 	"citizencw01", "citizencw02", "citizencx02",
3050 	// These are all extras.
3051 	"citizen-cx", "citizen-cx-w", "citizen-cy", "citizen-cy-02",
3052 	"citizen-op900", "citizen-op900ii",
3053 	NULL
3054 };
3055 
3056 #define USB_VID_CITIZEN   0x1343
3057 #define USB_PID_DNP_DS40  0x0003 // Also Citizen CX
3058 #define USB_PID_DNP_DS80  0x0004 // Also Citizen CX-W and Mitsubishi CP-3800DW
3059 #define USB_PID_DNP_DSRX1 0x0005 // Also Citizen CY
3060 #define USB_PID_DNP_DS80D 0x0008
3061 
3062 #define USB_PID_CITIZEN_CW01 0x0002 // Maybe others?
3063 #define USB_PID_CITIZEN_CW02 0x0006 // Also OP900II
3064 #define USB_PID_CITIZEN_CX02 0x000A
3065 
3066 #define USB_VID_DNP       0x1452
3067 #define USB_PID_DNP_DS620 0x8b01
3068 #define USB_PID_DNP_DS820 0x9001
3069 
3070 /* Exported */
3071 struct dyesub_backend dnpds40_backend = {
3072 	.name = "DNP DS-series / Citizen C-series",
3073 	.version = "0.117",
3074 	.uri_prefixes = dnpds40_prefixes,
3075 	.flags = BACKEND_FLAG_JOBLIST,
3076 	.cmdline_usage = dnpds40_cmdline,
3077 	.cmdline_arg = dnpds40_cmdline_arg,
3078 	.init = dnpds40_init,
3079 	.attach = dnpds40_attach,
3080 	.teardown = dnpds40_teardown,
3081 	.read_parse = dnpds40_read_parse,
3082 	.cleanup_job = dnpds40_cleanup_job,
3083 	.main_loop = dnpds40_main_loop,
3084 	.query_serno = dnpds40_query_serno,
3085 	.query_markers = dnpds40_query_markers,
3086 	.devices = {
3087 		{ USB_VID_CITIZEN, USB_PID_DNP_DS40, P_DNP_DS40, NULL, "dnp-ds40"},  // Also Citizen CX
3088 		{ USB_VID_CITIZEN, USB_PID_DNP_DS80, P_DNP_DS80, NULL, "dnp-ds80"},  // Also Citizen CX-W and Mitsubishi CP-3800DW
3089 		{ USB_VID_CITIZEN, USB_PID_DNP_DS80D, P_DNP_DS80D, NULL, "dnp-ds80dx"},
3090 		{ USB_VID_CITIZEN, USB_PID_DNP_DSRX1, P_DNP_DSRX1, NULL, "dnp-dsrx1"}, // Also Citizen CY
3091 		{ USB_VID_DNP, USB_PID_DNP_DS620, P_DNP_DS620, NULL, "dnp-ds620"},
3092 		{ USB_VID_DNP, USB_PID_DNP_DS820, P_DNP_DS820, NULL, "dnp-ds820"},
3093 		{ USB_VID_CITIZEN, USB_PID_CITIZEN_CW01, P_CITIZEN_CW01, NULL, "citizen-cw-01"}, // Also OP900 ?
3094 		{ USB_VID_CITIZEN, USB_PID_CITIZEN_CW02, P_CITIZEN_OP900II, NULL, "citizen-cw-02"}, // Also OP900II
3095 		{ USB_VID_CITIZEN, USB_PID_CITIZEN_CX02, P_DNP_DS620, NULL, "citizen-cx-02"},
3096 		{ 0, 0, 0, NULL, NULL}
3097 	}
3098 };
3099 
3100 /* Windows spool file support */
legacy_spool_helper(struct dnpds40_printjob * job,int data_fd,int read_data,int hdrlen,uint32_t plane_len,int parse_dpi)3101 static int legacy_spool_helper(struct dnpds40_printjob *job, int data_fd,
3102 			       int read_data, int hdrlen, uint32_t plane_len,
3103 			       int parse_dpi)
3104 {
3105 	uint8_t *buf;
3106 	uint8_t bmp_hdr[14];
3107 	int i, remain;
3108 	uint32_t j;
3109 
3110 	/* Allocate a temp processing buffer */
3111 	remain = plane_len * 3;
3112 	buf = malloc(remain);
3113 	if (!buf) {
3114 		ERROR("Memory allocation failure!\n");
3115 		return CUPS_BACKEND_RETRY_CURRENT;
3116 	}
3117 
3118 	/* Copy over the post-jobhdr crap into our processing buffer */
3119 	j = read_data - hdrlen;
3120 	memcpy(buf, job->databuf + hdrlen, j);
3121 	remain -= j;
3122 
3123 	/* Read in the remaining spool data */
3124 	while (remain) {
3125 		i = read(data_fd, buf + j, remain);
3126 
3127 		if (i < 0) {
3128 			free(buf);
3129 			return i;
3130 		}
3131 
3132 		remain -= i;
3133 		j += i;
3134 	}
3135 
3136 	if (parse_dpi) {
3137 		/* Parse out Y DPI */
3138 		memcpy(&j, buf + 28, sizeof(j));
3139 		j = le32_to_cpu(j);
3140 		switch(j) {
3141 		case 11808:
3142 			job->dpi = 300;
3143 			break;
3144 		case 23615:
3145 		case 23616:
3146 			job->dpi = 600;
3147 			break;
3148 		default:
3149 			ERROR("Unrecognized printjob resolution (%u ppm)\n", j);
3150 			free(buf);
3151 			return CUPS_BACKEND_CANCEL;
3152 		}
3153 	}
3154 
3155 	/* Generate bitmap file header (same for all planes) */
3156 	j = cpu_to_le32(plane_len + 24);
3157 	memset(bmp_hdr, 0, sizeof(bmp_hdr));
3158 	bmp_hdr[0] = 0x42;
3159 	bmp_hdr[1] = 0x4d;
3160 	memcpy(bmp_hdr + 2, &j, sizeof(j));
3161 	bmp_hdr[10] = 0x40;
3162 	bmp_hdr[11] = 0x04;
3163 
3164 	/* Set up planes */
3165 	j = 0;
3166 
3167 	/* Y plane */
3168 	job->datalen += sprintf((char*)job->databuf + job->datalen,
3169 				"\033PIMAGE YPLANE          %08u", plane_len + 24);
3170 	memcpy(job->databuf + job->datalen, bmp_hdr, sizeof(bmp_hdr));
3171 	job->datalen += sizeof(bmp_hdr);
3172 	memcpy(job->databuf + job->datalen, buf + j, plane_len);
3173 	job->datalen += plane_len;
3174 	j += plane_len;
3175 	memset(job->databuf + job->datalen, 0, 10);
3176 	job->datalen += 10;
3177 
3178 	/* M plane */
3179 	job->datalen += sprintf((char*)job->databuf + job->datalen,
3180 				"\033PIMAGE MPLANE          %08u", plane_len + 24);
3181 	memcpy(job->databuf + job->datalen, bmp_hdr, sizeof(bmp_hdr));
3182 	job->datalen += sizeof(bmp_hdr);
3183 	memcpy(job->databuf + job->datalen, buf + j, plane_len);
3184 	job->datalen += plane_len;
3185 	j += plane_len;
3186 	memset(job->databuf + job->datalen, 0, 10);
3187 	job->datalen += 10;
3188 
3189 	/* C plane */
3190 	job->datalen += sprintf((char*)job->databuf + job->datalen,
3191 				"\033PIMAGE CPLANE          %08u", plane_len + 24);
3192 	memcpy(job->databuf + job->datalen, bmp_hdr, sizeof(bmp_hdr));
3193 	job->datalen += sizeof(bmp_hdr);
3194 	memcpy(job->databuf + job->datalen, buf + j, plane_len);
3195 	job->datalen += plane_len;
3196 	j += plane_len;
3197 	memset(job->databuf + job->datalen, 0, 10);
3198 	job->datalen += 10;
3199 
3200 	/* Start */
3201 	job->datalen += sprintf((char*)job->databuf + job->datalen,
3202 				"\033PCNTRL START                   ");
3203 
3204 	/* We're done */
3205 	free(buf);
3206 
3207 	return CUPS_BACKEND_OK;
3208 }
3209 
3210 struct cw01_spool_hdr {
3211 	uint8_t  type; /* TYPE_??? */
3212 	uint8_t  res; /* DPI_??? */
3213 	uint8_t  copies; /* number of prints */
3214 	uint8_t  null0;
3215 	uint32_t plane_len; /* LE */
3216 	uint8_t  null1[4];
3217 };
3218 
3219 #define DPI_334 0
3220 #define DPI_600 1
3221 
3222 #define TYPE_DSC  0
3223 #define TYPE_L    1
3224 #define TYPE_PC   2
3225 #define TYPE_2DSC 3
3226 #define TYPE_3L   4
3227 #define TYPE_A5   5
3228 #define TYPE_A6   6
3229 
legacy_cw01_read_parse(struct dnpds40_printjob * job,int data_fd,int read_data)3230 static int legacy_cw01_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data)
3231 {
3232 	struct cw01_spool_hdr hdr;
3233 	uint32_t plane_len;
3234 
3235 	/* get original header out of structure */
3236 	memcpy(&hdr, job->databuf + job->datalen, sizeof(hdr));
3237 
3238 	/* Early parsing and sanity checking */
3239 	plane_len = le32_to_cpu(hdr.plane_len);
3240 
3241 	if (hdr.type > TYPE_A6 ||
3242 	    hdr.res > DPI_600 ||
3243 	    hdr.null1[0] || hdr.null1[1] || hdr.null1[2] || hdr.null1[3]) {
3244 		ERROR("Unrecognized header data format @%d!\n", job->datalen);
3245 		return CUPS_BACKEND_CANCEL;
3246 	}
3247 	job->dpi = (hdr.res == DPI_600) ? 600 : 334;
3248 	job->cutter = 0;
3249 
3250 	return legacy_spool_helper(job, data_fd, read_data,
3251 				   sizeof(hdr), plane_len, 0);
3252 }
3253 
3254 struct rx1_spool_hdr {
3255 	uint8_t  type; /* equals MULTICUT_?? - 1 */
3256 	uint8_t  null0;
3257 	uint8_t  copies; /* number of copies, always fixed at 01.. */
3258 	uint8_t  null1;
3259 	uint32_t plane_len; /* LE */
3260         uint8_t  flags; /* combination of FLAG_?? */
3261 	uint8_t  null2[3];
3262 };
3263 
3264 #define FLAG_MATTE     0x02
3265 #define FLAG_NORETRY   0x08
3266 #define FLAG_2INCH     0x10
3267 
legacy_dnp_read_parse(struct dnpds40_printjob * job,int data_fd,int read_data)3268 static int legacy_dnp_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data)
3269 {
3270 	struct rx1_spool_hdr hdr;
3271 	uint32_t plane_len;
3272 
3273 	/* get original header out of structure */
3274 	memcpy(&hdr, job->databuf + job->datalen, sizeof(hdr));
3275 
3276 	/* Early parsing and sanity checking */
3277 	plane_len = le32_to_cpu(hdr.plane_len);
3278 
3279 	if (hdr.type >= MULTICUT_A4x5X2 ||
3280 	    hdr.null2[0] || hdr.null2[1] || hdr.null2[2]) {
3281 		ERROR("Unrecognized header data format @%d!\n", job->datalen);
3282 		return CUPS_BACKEND_CANCEL;
3283 	}
3284 
3285 	/* Don't bother with FW version checks for legacy stuff */
3286 	job->multicut = hdr.type + 1;
3287 	job->matte = (hdr.flags & FLAG_MATTE) ? 1 : 0;
3288 	job->cutter = (hdr.flags & FLAG_2INCH) ? 120 : 0;
3289 
3290 	return legacy_spool_helper(job, data_fd, read_data,
3291 				   sizeof(hdr), plane_len, 1);
3292 }
3293 
3294 struct ds620_spool_hdr {
3295 	uint8_t  type; /* MULTICUT_?? -1, but >0x90 is a flag for rewind */
3296 	uint8_t  copies; /* Always fixed at 01..*/
3297 	uint8_t  null0[2];
3298 	uint8_t  quality;  /* 0x02 is HQ, 0x00 is HS.  Equivalent to DPI. */
3299 	uint8_t  unk[3];   /* Always 00 01 00 */
3300 	uint32_t plane_len; /* LE */
3301 	uint8_t  flags; /* FLAG_?? */
3302 	uint8_t  null1[3];
3303 };
3304 #define FLAG_LUSTER    0x04
3305 #define FLAG_FINEMATTE 0x06
3306 
legacy_dnp620_read_parse(struct dnpds40_printjob * job,int data_fd,int read_data)3307 static int legacy_dnp620_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data)
3308 {
3309 	struct ds620_spool_hdr hdr;
3310 	uint32_t plane_len;
3311 
3312 	/* get original header out of structure */
3313 	memcpy(&hdr, job->databuf + job->datalen, sizeof(hdr));
3314 
3315 	/* Early parsing and sanity checking */
3316 	plane_len = le32_to_cpu(hdr.plane_len);
3317 
3318 	/* Used to signify rewind request.  Backend handles automatically. */
3319 	if (hdr.type > 0x90)
3320 		hdr.type -= 0x90;
3321 
3322 	if (hdr.type > MULTICUT_A4x5X2 ||
3323 	    hdr.null1[0] || hdr.null1[1] || hdr.null1[2]) {
3324 		ERROR("Unrecognized header data format @%d!\n", job->datalen);
3325 		return CUPS_BACKEND_CANCEL;
3326 	}
3327 
3328 	/* Don't bother with FW version checks for legacy stuff */
3329 	job->multicut = hdr.type + 1;
3330 	if ((hdr.flags & FLAG_FINEMATTE) == FLAG_FINEMATTE)
3331 		job->matte = 21;
3332 	else if (hdr.flags & FLAG_LUSTER)
3333 		job->matte = 22;
3334 	else if (hdr.flags & FLAG_MATTE)
3335 		job->matte = 1;
3336 
3337 	job->cutter = (hdr.flags & FLAG_2INCH) ? 120 : 0;
3338 
3339 	return legacy_spool_helper(job, data_fd, read_data,
3340 				   sizeof(hdr), plane_len, 1);
3341 }
3342 
3343 #define FLAG_820_HD     0x80
3344 #define FLAG_820_RETRY  0x20
3345 #define FLAG_820_LUSTER 0x06
3346 #define FLAG_820_FMATTE 0x04
3347 #define FLAG_820_MATTE  0x02
3348 
legacy_dnp820_read_parse(struct dnpds40_printjob * job,int data_fd,int read_data)3349 static int legacy_dnp820_read_parse(struct dnpds40_printjob *job, int data_fd, int read_data)
3350 {
3351 	struct ds620_spool_hdr hdr;
3352 	uint32_t plane_len;
3353 
3354 	/* get original header out of structure */
3355 	memcpy(&hdr, job->databuf + job->datalen, sizeof(hdr));
3356 
3357 	/* Early parsing and sanity checking */
3358 	plane_len = le32_to_cpu(hdr.plane_len);
3359 
3360 	/* Used to signify rewind request.  Backend handles automatically. */
3361 	if (hdr.type > 0x90)
3362 		hdr.type -= 0x90;
3363 
3364 	if (hdr.type > MULTICUT_A4x5X2 ||
3365 	    hdr.null1[0] || hdr.null1[1] || hdr.null1[2]) {
3366 		ERROR("Unrecognized header data format @%d!\n", job->datalen);
3367 		return CUPS_BACKEND_CANCEL;
3368 	}
3369 
3370 	/* Don't bother with FW version checks for legacy stuff */
3371 	job->multicut = hdr.type + 1;
3372 	if ((hdr.flags & FLAG_820_FMATTE) == FLAG_820_FMATTE)
3373 		job->matte = 21;
3374 	else if (hdr.flags & FLAG_820_LUSTER)
3375 		job->matte = 22;
3376 	else if (hdr.flags & FLAG_820_MATTE)
3377 		job->matte = 1;
3378 
3379 	if (hdr.flags & FLAG_820_HD)
3380 		job->printspeed = 3;
3381 
3382 	return legacy_spool_helper(job, data_fd, read_data,
3383 				   sizeof(hdr), plane_len, 1);
3384 }
3385 
3386 
3387 /*
3388 
3389 Basic spool file format for CW01
3390 
3391 TT RR NN 00 XX XX XX XX  00 00 00 00              <- FILE header.
3392 
3393   NN          : copies (0x01 or more)
3394   RR          : resolution; 0 == 334 dpi, 1 == 600dpi
3395   TT          : type 0x02 == 4x6, 0x01 == 5x3.5, 0x06 = 6x9
3396   XX XX XX XX : plane length (LE)
3397                 plane length * 3 + 12 == file length.
3398 
3399 Followed by three planes, each with this 40b header:
3400 
3401 28 00 00 00 00 08 00 00  RR RR 00 00 01 00 08 00
3402 00 00 00 00 00 00 00 00  5a 33 00 00 YY YY 00 00
3403 00 01 00 00 00 00 00 00
3404 
3405   RR RR       : rows in LE format
3406   YY YY       : 0x335a (334dpi) or 0x5c40 (600dpi)
3407 
3408 Followed by 1024 bytes of color tables:
3409 
3410  ff ff ff 00 ... 00 00 00 00
3411 
3412 1024+40 = 1064 bytes of header per plane.
3413 
3414 Always have 2048 columns of data.
3415 
3416 followed by (2048 * rows) bytes of data.
3417 
3418 */
3419