1 /* Hey EMACS -*- linux-c -*- */
2 /* $Id$ */
3 
4 /*  libticalcs - Ti Calculator library, a part of the TiLP project
5  *  Copyright (C) 1999-2005  Romain Liévin
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License as published by
9  *  the Free Software Foundation; either version 2 of the License, or
10  *  (at your option) any later version.
11  *
12  *  This program is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  *  GNU General Public License for more details.
16  *
17  *  You should have received a copy of the GNU General Public License
18  *  along with this program; if not, write to the Free Software Foundation,
19  *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20  */
21 
22 /*
23 	Probing support.
24 */
25 
26 #include <stdio.h>
27 #include <stdlib.h>
28 #include <string.h>
29 
30 #include "ticalcs.h"
31 #include "internal.h"
32 #include "logging.h"
33 #include "dbus_pkt.h"
34 #include "error.h"
35 #include "gettext.h"
36 #include "calc_xx.h"
37 #include "dusb_vpkt.h"
38 #include "dusb_cmd.h"
39 
40 #define DEAD_TIME	250
41 
42 /*
43 	Get the first byte sent by the calc (Machine ID)
44 */
tixx_recv_ACK(CalcHandle * handle,uint8_t * mid)45 static int tixx_recv_ACK(CalcHandle* handle, uint8_t* mid)
46 {
47 	uint8_t host, cmd;
48 	uint16_t length;
49 	uint8_t buffer[5];
50 	int ret;
51 
52 	ret = dbus_recv(handle, &host, &cmd, &length, buffer);
53 	if (!ret)
54 	{
55 		ticalcs_info(" TI->PC: ACK");
56 
57 		*mid = host;
58 
59 		if (cmd == DBUS_CMD_SKP)
60 		{
61 			ret = ERR_VAR_REJECTED;
62 		}
63 	}
64 
65 	return ret;
66 }
67 
68 /**
69  * ticalcs_probe_calc_2:
70  * @handle: a previously allocated handle
71  * @type: the calculator model
72  *
73  * This function tries and detect the calculator type for non-silent models
74  * by requesting a screedump and analyzing the Machine ID.
75  * It supposes your calc is on and plugged.
76  *
77  * PC: 08  6D 00 00		PC request a screen dump
78  * TI: MId 56 00 00		TI reply OK
79  *
80  * Beware: the call sequence is very important: 86, 85, 73, 83, 82 !!!
81  *
82  * Return value: 0 if successful, an error code otherwise.
83  **/
ticalcs_probe_calc_2(CalcHandle * handle,CalcModel * model)84 static int ticalcs_probe_calc_2(CalcHandle* handle, CalcModel* model)
85 {
86 	int err;
87 	uint8_t data;
88 
89 	do
90 	{
91 		ticalcs_info(_("Probing calculator...\n"));
92 		*model = CALC_NONE;
93 
94 		/* Test for a TI86 before a TI85 */
95 		ticalcs_info(_("Check for TI86... "));
96 		err = dbus_send(handle, DBUS_MID_PC_TI86, DBUS_CMD_SCR, 2, NULL);
97 		if (err)
98 		{
99 			break;
100 		}
101 		err = tixx_recv_ACK(handle, &data);
102 
103 		ticalcs_info("<%02X-%02X> ", DBUS_MID_PC_TI86, data);
104 
105 		if (!err && (data == DBUS_MID_TI86_PC))
106 		{
107 			ticalcs_info("OK !\n");
108 			*model = CALC_TI86;
109 			break;
110 		}
111 		else
112 		{
113 			ticalcs_info("NOK.\n");
114 			ticables_cable_reset(handle->cable);
115 			PAUSE(DEAD_TIME);
116 		}
117 
118 		/* Test for a TI85 */
119 		ticalcs_info(_("Check for TI85... "));
120 		err = dbus_send(handle, DBUS_MID_PC_TI85, DBUS_CMD_SCR, 2, NULL);
121 		if (err)
122 		{
123 			break;
124 		}
125 		err = tixx_recv_ACK(handle, &data);
126 
127 		ticalcs_info("<%02X-%02X> ", DBUS_MID_PC_TI85, data);
128 
129 		if (!err && (data == DBUS_MID_TI85_PC))
130 		{
131 			ticalcs_info("OK !\n");
132 			*model = CALC_TI85;
133 			break;
134 		}
135 		else
136 		{
137 			ticalcs_info("NOK.\n");
138 			ticables_cable_reset(handle->cable);
139 			PAUSE(DEAD_TIME);
140 		}
141 
142 		/* Test for a TI73 before a TI83 */
143 		ticalcs_info(_("Check for TI73... "));
144 		err = dbus_send(handle, DBUS_MID_PC_TI73, DBUS_CMD_SCR, 2, NULL);
145 		if (err)
146 		{
147 			break;
148 		}
149 		err = tixx_recv_ACK(handle, &data);
150 
151 		ticalcs_info("<%02X-%02X> ", DBUS_MID_PC_TI73, data);
152 
153 		if (!err && (data == DBUS_MID_TI73_PC))
154 		{
155 			ticalcs_info("OK !\n");
156 			*model = CALC_TI73;
157 			break;
158 		}
159 		else
160 		{
161 			ticalcs_info("NOK.\n");
162 			ticables_cable_reset(handle->cable);
163 			PAUSE(DEAD_TIME);
164 		}
165 
166 		/* Test for a TI83 before a TI82 */
167 		ticalcs_info(_("Check for TI83... "));
168 		err = dbus_send(handle, DBUS_MID_PC_TI83, DBUS_CMD_SCR, 2, NULL);
169 		if (err)
170 		{
171 			break;
172 		}
173 		err = tixx_recv_ACK(handle, &data);
174 
175 		ticalcs_info("<%02X-%02X> ", DBUS_MID_PC_TI83, data);
176 
177 		if (!err && (data == DBUS_MID_TI83_PC))
178 		{
179 			ticalcs_info("OK !\n");
180 			*model = CALC_TI83;
181 			break;
182 		}
183 		else
184 		{
185 			ticalcs_info("NOK.\n");
186 			ticables_cable_reset(handle->cable);
187 			PAUSE(DEAD_TIME);
188 		}
189 
190 		/* Test for a TI82 */
191 		ticalcs_info(_("Check for TI82... "));
192 		err = dbus_send(handle, DBUS_MID_PC_TI83, DBUS_CMD_SCR, 2, NULL);
193 		if (err)
194 		{
195 			break;
196 		}
197 		err = tixx_recv_ACK(handle, &data);
198 
199 		ticalcs_info("<%02X-%02X> ", DBUS_MID_PC_TI82, data);
200 
201 		if (!err && (data == DBUS_MID_TI82_PC))
202 		{
203 			ticalcs_info("OK !\n");
204 			*model = CALC_TI82;
205 			break;
206 		}
207 		else
208 		{
209 			ticalcs_info("NOK.\n");
210 			ticables_cable_reset(handle->cable);
211 			PAUSE(DEAD_TIME);
212 		}
213 
214 #if 0
215 		/* Test for a TI80 */
216 #warning TI-80 DETECTION FAILS
217 		ticalcs_info(_("Check for TI80... "));
218 		err = dbus_send(handle, DBUS_MID_PC_TI80, DBUS_CMD_SCR, 0, NULL);
219 		if (err)
220 		{
221 			break;
222 		}
223 		err = tixx_recv_ACK(handle, &data);
224 
225 		ticalcs_info("<%02X-%02X> ", DBUS_MID_PC_TI80, data);
226 
227 		if (!err && (data == DBUS_MID_TI80_PC))
228 		{
229 			ticalcs_info("OK !\n");
230 			*model = CALC_TI80;
231 			break;
232 		}
233 		else
234 		{
235 			ticalcs_info("NOK.\n");
236 			ticables_cable_reset(handle->cable);
237 			PAUSE(DEAD_TIME);
238 		}
239 #endif
240 	} while(0);
241 
242 	return (*model == CALC_NONE) ? ERR_NO_CALC : 0;
243 }
244 
245 /**
246  * ticalcs_probe_calc_1:
247  * @handle: a previously allocated handle
248  * @type: the calculator model
249  *
250  * Check if the calculator is ready and detect the type.
251  * Works only on FLASH calculators with an AMS2.08 or OS2.00 by requesting the
252  * version. A previous version was based on MID but TI83+/84+, TI89/TI89t, TI92+/V200
253  * could not be distinguished ;-(
254  *
255  * Return value: 0 if successful, an error code otherwise.
256  **/
ticalcs_probe_calc_1(CalcHandle * handle,CalcModel * model)257 static int ticalcs_probe_calc_1(CalcHandle* handle, CalcModel* model)
258 {
259 	uint8_t host, cmd;
260 	uint16_t status;
261 	uint8_t buffer[256];
262 	int i, ret;
263 
264 	// init value
265 	*model = CALC_NONE;
266 
267 	do
268 	{
269 		CalcInfos infos;
270 
271 		// test for FLASH hand-helds (00 68 00 00 -> XX 56 00 00)
272 		// where XX is 0x98: TI89/89t, 0x88: TI92+/V200, 0x73: TI83+/84+, 0x74: TI73
273 		ticalcs_info(_("Check for TIXX... "));
274 		for (i = 0; i < 2; i++)
275 		{
276 			ticalcs_info(" PC->TI: RDY?");
277 			ret = dbus_send(handle, DBUS_MID_PC_TIXX, DBUS_CMD_RDY, 2, NULL);
278 			if (ret)
279 			{
280 				continue;
281 			}
282 
283 			ret = dbus_recv(handle, &host, &cmd, &status, buffer);
284 			ticalcs_info(" TI->PC: ACK");
285 			if (ret)
286 			{
287 				continue;
288 			}
289 
290 			break;
291 		}
292 
293 		// test for TI73
294 		if (!ret)
295 		{
296 			if (host == DBUS_MID_TI73_PC)
297 			{
298 				*model = CALC_TI73;
299 				break;
300 			}
301 			else if (host == DBUS_MID_TI92_PC)
302 			{
303 				*model = CALC_TI92;
304 				break;
305 			}
306 		}
307 
308 		// test for TI92 (09 68 00 00 -> 89 56 00 00)
309 		else if (ret)
310 		{
311 			ticalcs_info(_("Check for TI92... "));
312 			ticables_cable_reset(handle->cable);
313 			PAUSE(DEAD_TIME);	// needed !
314 
315 			for (i = 0; i < 2; i++)
316 			{
317 				ticalcs_info(" PC->TI: RDY?");
318 				ret = dbus_send(handle, DBUS_MID_PC_TI92, DBUS_CMD_RDY, 2, NULL);
319 				if (ret)
320 				{
321 					continue;
322 				}
323 
324 				ret = dbus_recv(handle, &host, &cmd, &status, buffer);
325 				ticalcs_info(" TI->PC: ACK");
326 				if (ret)
327 				{
328 					continue;
329 				}
330 
331 				break;
332 			}
333 
334 			if (!ret)
335 			{
336 				*model = CALC_TI92;
337 			}
338 		}
339 
340 		if (cmd != DBUS_CMD_ACK)
341 		{
342 			ret = ERR_INVALID_CMD;
343 			break;
344 		}
345 
346 		if ((status & 1) != 0)
347 		{
348 			ret = ERR_NOT_READY;
349 			break;
350 		}
351 
352 		// test for TI9x FLASH hand-helds again (request version and analyze HW_ID)
353 		if(!ret && (host != DBUS_MID_TI73_PC) && (host != DBUS_MID_TI83p_PC))
354 		{
355 			ticalcs_info(_("Check for TI9X... "));
356 
357 			handle->model = CALC_TI89;
358 			handle->calc = (CalcFncts *)&calc_89;
359 
360 			memset(&infos, 0, sizeof(CalcInfos));
361 			ret = ticalcs_calc_get_version(handle, &infos);
362 			if (ret)
363 			{
364 				break;
365 			}
366 			*model = infos.model;
367 		}
368 		else
369 		{
370 			ticalcs_info(_("Check for TI8X... "));
371 
372 			handle->model = CALC_TI83P;
373 			handle->calc = (CalcFncts *)&calc_83p;
374 
375 			memset(&infos, 0, sizeof(CalcInfos));
376 			ret = ticalcs_calc_get_version(handle, &infos);
377 			if (ret)
378 			{
379 				break;
380 			}
381 			*model = infos.model;
382 		}
383 	} while(0);
384 
385 	ticalcs_info(_("Calculator type: %s"), tifiles_model_to_string(*model));
386 
387 	if (!ret)
388 	{
389 		ret = (*model == CALC_NONE) ? ERR_NO_CALC : 0;
390 	}
391 
392 	return ret;
393 }
394 
395 extern const CalcUpdate default_update;
396 
397 /**
398  * ticalcs_probe_calc:
399  * @cable: a valid (=opened/attached) link cable handle
400  * @model: the calculator model which have been detected
401  *
402  * This function attempts to detect the calculator model plugged onto the cable.
403  * It works in a heuristic fashion.
404  *
405  * Return value: 0 if successful, an error code otherwise.
406  **/
ticalcs_probe_calc(CableHandle * cable,CalcModel * model)407 TIEXPORT3 int TICALL ticalcs_probe_calc(CableHandle* cable, CalcModel* model)
408 {
409 	CalcHandle calc;
410 	int ret = 0;
411 
412 	VALIDATE_NONNULL(cable);
413 	VALIDATE_NONNULL(model);
414 
415 	do
416 	{
417 		// Hack: we construct the structure here because we don't really need it.
418 		// I want to use ticalcs functions with a non-fixed calculator
419 		memset(&calc, 0, sizeof(CalcHandle));
420 		calc.model = *model = CALC_NONE;
421 		calc.updat = (CalcUpdate *)&default_update;
422 		calc.buffer = (uint8_t *)g_malloc(65536 + 6);
423 		calc.cable = cable;
424 		calc.open = !0;
425 
426 		// first: search for FLASH hand-helds (fast)
427 		ret = ticalcs_probe_calc_1(&calc, model);
428 		if (!ret && (*model != CALC_NONE))
429 		{
430 			g_free(calc.buffer);
431 			break;
432 		}
433 
434 		// second: search for other calcs (slow)
435 		ret = ticalcs_probe_calc_2(&calc, model);
436 		g_free(calc.buffer);
437 	} while(0);
438 
439 	if (!ret)
440 	{
441 		ret = (*model == CALC_NONE) ? ERR_NO_CALC : 0;
442 	}
443 
444 	return ret;
445 }
446 
447 /**
448  * ticalcs_probe_usb_calc:
449  * @cable: a valid (=opened/attached) link cable handle
450  * @model: the calculator model which have been detected
451  *
452  * This function attempts to detect the calculator model plugged onto the cable.
453  * It works in a heuristic fashion and with FLASH hand-helds only.
454  *
455  * Return value: 0 if successful, an error code otherwise.
456  **/
ticalcs_probe_usb_calc(CableHandle * cable,CalcModel * model)457 TIEXPORT3 int TICALL ticalcs_probe_usb_calc(CableHandle* cable, CalcModel* model)
458 {
459 	CalcHandle calc;
460 	int ret = ERR_NO_CALC;
461 
462 	VALIDATE_NONNULL(cable);
463 	VALIDATE_NONNULL(model);
464 
465 	do
466 	{
467 		// Hack: we construct the structure here because we don't really need it.
468 		// I want to use ticalcs functions with a non-fixed calculator
469 		memset(&calc, 0, sizeof(CalcHandle));
470 		calc.model = *model = CALC_NONE;
471 		calc.updat = (CalcUpdate *)&default_update;
472 		calc.buffer = (uint8_t *)g_malloc(65536 + 6);
473 		calc.cable = cable;
474 		calc.open = !0;
475 
476 		if (cable->model == CABLE_SLV)
477 		{
478 			ret = ticalcs_probe_calc_1(&calc, model);
479 		}
480 		else if (cable->model == CABLE_USB)
481 		{
482 			CableDeviceInfo info;
483 
484 			ret = ticables_cable_get_device_info(cable, &info);
485 			if (!ret)
486 			{
487 				*model = ticalcs_device_info_to_model(&info);
488 			}
489 		}
490 
491 		g_free(calc.buffer);
492 	} while(0);
493 
494 	return ret;
495 }
496 
497 /**
498  * ticalcs_probe:
499  * @c_model: link cable model
500  * @c_port: link cable port
501  * @model: hand-held model detected/found [out]
502  * @all: which hand-helds to detect (FLASH only or all)
503  *
504  * This function attempts to detect the calculator model plugged onto a
505  * given link cable model/port. It works in a heuristic fashion.
506  * This function handles device opening/closing for you.
507  *
508  * Return value: 0 if successful, an error code otherwise.
509  **/
ticalcs_probe(CableModel c_model,CablePort c_port,CalcModel * model,int all)510 TIEXPORT3 int TICALL ticalcs_probe(CableModel c_model, CablePort c_port, CalcModel* model, int all)
511 {
512 	CableHandle *handle;
513 	int ret = 0;
514 	CalcHandle calc;
515 
516 	VALIDATE_NONNULL(model);
517 
518 	// get handle
519 	handle = ticables_handle_new(c_model, c_port);
520 	ticables_options_set_timeout(handle, 10);
521 
522 	// hack: we construct the structure here because we don't really need it.
523 	// I want to use ticalcs functions with a non-fixed calculator
524 	memset(&calc, 0, sizeof(CalcHandle));
525 	calc.model = *model = CALC_NONE;
526 	calc.updat = (CalcUpdate *)&default_update;
527 	calc.buffer = (uint8_t *)g_malloc(65536 + 6);
528 	calc.cable = handle;
529 	calc.open = !0;
530 
531 	do
532 	{
533 		// open cable
534 		ret = ticables_cable_open(handle);
535 		if (ret)
536 		{
537 			ticables_handle_del(handle);
538 			break;
539 		}
540 
541 
542 		// probe
543 		if (c_model == CABLE_USB)
544 		{
545 			ret = ticalcs_probe_usb_calc(handle, model);
546 		}
547 		else
548 		{
549 			if (all)
550 			{
551 				ret = ticalcs_probe_calc(handle, model);
552 			}
553 			else
554 			{
555 				ret = ticalcs_probe_calc_1(&calc, model);
556 			}
557 		}
558 
559 		// close
560 		ticables_cable_close(handle);
561 		ticables_handle_del(handle);
562 	} while(0);
563 
564 	return ret;
565 }
566 
567 /**
568  * ticalcs_device_info_to_model:
569  * @info: pointer to device info
570  *
571  * Converts a cable device info to a CalcModel, if possible.
572  *
573  * Return value: != CALC_NONE if a precise calculator model can be determined through the cable+device info, CALC_NONE if invalid argument or SilverLink cable.
574  */
ticalcs_device_info_to_model(const CableDeviceInfo * info)575 TIEXPORT3 CalcModel TICALL ticalcs_device_info_to_model(const CableDeviceInfo *info)
576 {
577 	CalcModel model = CALC_NONE;
578 
579 	if (info != NULL)
580 	{
581 		if (info->family == CABLE_FAMILY_DBUS)
582 		{
583 			// Can't determine calc model through the sole cable model. That's a job for the upper layers.
584 			// Fall through.
585 		}
586 		else if (info->family == CABLE_FAMILY_USB_TI8X)
587 		{
588 			if (info->variant == CABLE_VARIANT_TI84P || info->variant == CABLE_VARIANT_TI84PSE)
589 			{
590 				model = CALC_TI84P_USB;
591 			}
592 			else if (info->variant == CABLE_VARIANT_TI84PCSE)
593 			{
594 				model = CALC_TI84PC_USB;
595 			}
596 			else if (info->variant == CABLE_VARIANT_TI84PCE)
597 			{
598 				model = CALC_TI84PCE_USB;
599 			}
600 			else if (info->variant == CABLE_VARIANT_TI83PCE)
601 			{
602 				model = CALC_TI83PCE_USB;
603 			}
604 			else if (info->variant == CABLE_VARIANT_TI82A)
605 			{
606 				model = CALC_TI82A_USB;
607 			}
608 			else if (info->variant == CABLE_VARIANT_TI84PT)
609 			{
610 				model = CALC_TI84PT_USB;
611 			}
612 			else
613 			{
614 				ticalcs_warning("Unexpected variant for TI-(e)Z80 USB cable, assuming 84+CE");
615 				model = CALC_TI84PCE_USB;
616 			}
617 		}
618 		else if (info->family == CABLE_FAMILY_USB_TI9X)
619 		{
620 			// This cable family has a single corresponding calc model.
621 			if (info->variant != CABLE_VARIANT_TI89TM)
622 			{
623 				ticalcs_warning("Unexpected variant for TI-68k USB cable");
624 			}
625 			model = CALC_TI89T_USB;
626 		}
627 		else if (info->family == CABLE_FAMILY_USB_NSPIRE)
628 		{
629 			// This cable family has a single corresponding calc model.
630 			if (info->variant != CABLE_VARIANT_NSPIRE)
631 			{
632 				ticalcs_warning("Unexpected variant for Nspire USB cable");
633 			}
634 			model = CALC_NSPIRE;
635 		}
636 		else
637 		{
638 			ticalcs_critical("Unexpected cable family");
639 			// Fall through.
640 		}
641 	}
642 	// else fall through.
643 
644 	return model;
645 }
646 
647 /**
648  * ticalcs_remap_model_from_usb:
649  * @cable: a cable model model taken in #CableModel.
650  * @cable: a calc model model taken in #CalcModel.
651  *
652  * This function remaps a CALC_*_USB model to the corresponding CALC_* model, if said CALC_* model exists and the selected cable is USB.
653  *
654  * Return value: the new calculator model, which can be the same as the passed one.
655  **/
ticalcs_remap_model_from_usb(CableModel cable,CalcModel calc)656 TIEXPORT3 CalcModel TICALL ticalcs_remap_model_from_usb(CableModel cable, CalcModel calc)
657 {
658 	if (cable == CABLE_USB)
659 	{
660 		if (calc == CALC_TI84P_USB)
661 		{
662 			return CALC_TI84P;
663 		}
664 		else if (calc == CALC_TI84PC_USB)
665 		{
666 			return CALC_TI84PC;
667 		}
668 		else if (calc == CALC_TI89T_USB)
669 		{
670 			return CALC_TI89T;
671 		}
672 		// else fall through.
673 	}
674 	// else fall through.
675 
676 	return calc;
677 }
678 
679 /**
680  * ticalcs_remap_model_from_usb:
681  * @cable: a cable model model taken in #CableModel.
682  * @cable: a calc model model taken in #CalcModel.
683  *
684  * This function remaps a CALC_* model to the corresponding CALC_*_USB model, if said CALC_*_USB model exists and the selected cable is USB.
685  *
686  * Return value: the new calculator model, which can be the same as the passed one.
687  **/
ticalcs_remap_model_to_usb(CableModel cable,CalcModel calc)688 TIEXPORT3 CalcModel TICALL ticalcs_remap_model_to_usb(CableModel cable, CalcModel calc)
689 {
690 	if (cable == CABLE_USB)
691 	{
692 		if (calc == CALC_TI84P)
693 		{
694 			return CALC_TI84P_USB;
695 		}
696 		else if (calc == CALC_TI84PC)
697 		{
698 			return CALC_TI84PC_USB;
699 		}
700 		else if (calc== CALC_TI89T)
701 		{
702 			return CALC_TI89T_USB;
703 		}
704 		// else fall through.
705 	}
706 	// else fall through.
707 
708 	return calc;
709 }
710