1 /*
2  * ps2mouse.c -- PS/2 mouse on userport emulation
3  *
4  * Written by
5  *  Hannu Nuotio <hannu.nuotio@tut.fi>
6  * Based on code by
7  *  Andreas Boose <viceteam@t-online.de>
8  *
9  * This file is part of VICE, the Versatile Commodore Emulator.
10  * See README for copyright notice.
11  *
12  *  This program is free software; you can redistribute it and/or modify
13  *  it under the terms of the GNU General Public License as published by
14  *  the Free Software Foundation; either version 2 of the License, or
15  *  (at your option) any later version.
16  *
17  *  This program is distributed in the hope that it will be useful,
18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20  *  GNU General Public License for more details.
21  *
22  *  You should have received a copy of the GNU General Public License
23  *  along with this program; if not, write to the Free Software
24  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
25  *  02111-1307  USA.
26  *
27  */
28 
29 #include "vice.h"
30 
31 #include "cmdline.h"
32 #include "resources.h"
33 #include "log.h"
34 #include "ps2mouse.h"
35 #include "alarm.h"
36 #include "maincpu.h"
37 #include "mousedrv.h"
38 
39 static void mouse_button_left(int pressed);
40 static void mouse_button_right(int pressed);
41 static void mouse_button_middle(int pressed);
42 static void mouse_button_up(int pressed);
43 static void mouse_button_down(int pressed);
44 
45 static log_t ps2mouse_log = LOG_ERR;
46 
47 #if 0
48 #define PS2MOUSE_DEBUG(args ...) log_message(ps2mouse_log, args)
49 #define PS2MOUSE_DEBUG_ENABLED
50 #endif
51 
52 /* PS/2 mouse port bits */
53 #define PS2_CLK_BIT 0x40
54 #define PS2_DATA_BIT 0x80
55 
56 /* PS/2 mouse timing */
57 #define PS2_BIT_DELAY_CLK 75
58 
59 /* PS/2 mouse commands & replies (TODO: support more commands) */
60 #define PS2_REPLY_OK 0xfa
61 #define PS2_REPLY_ERROR 0xfc
62 #define PS2_CMD_SET_REMOTE_MODE 0xf0
63 #define PS2_CMD_READ_DATA 0xeb
64 #define PS2_CMD_GET_DEV_ID 0xf2
65 #define PS2_REPLY_DEV_ID 0x00
66 
67 /* PS/2 mouse movement packet bits */
68 #define PS2_MDATA_YO 0x80
69 #define PS2_MDATA_XO 0x40
70 #define PS2_MDATA_YS 0x20
71 #define PS2_MDATA_XS 0x10
72 #define PS2_MDATA_A1 0x08
73 #define PS2_MDATA_MB 0x04
74 #define PS2_MDATA_RB 0x02
75 #define PS2_MDATA_LB 0x01
76 
77 /* PS/2 mouse variables */
78 static uint8_t ps2mouse_value;
79 static uint8_t ps2mouse_in;
80 static uint8_t ps2mouse_out;
81 static uint8_t ps2mouse_prev;
82 static uint8_t ps2mouse_parity;
83 static int16_t ps2mouse_lastx;
84 static int16_t ps2mouse_lasty;
85 static uint8_t ps2mouse_buttons;
86 
87 /* PS/2 transmission state */
88 enum {
89     PS2_FROMTO_IDLE=0,
90     PS2_FROM_START, /* mouse <- cpu */
91     PS2_FROM_D0,
92     PS2_FROM_D1,
93     PS2_FROM_D2,
94     PS2_FROM_D3,
95     PS2_FROM_D4,
96     PS2_FROM_D5,
97     PS2_FROM_D6,
98     PS2_FROM_D7,
99     PS2_FROM_PARITY,
100     PS2_FROM_STOP,
101     PS2_FROM_ACK,
102     PS2_CHECK_SEND, /* mouse -> cpu */
103     PS2_TO_D0,
104     PS2_TO_D1,
105     PS2_TO_D2,
106     PS2_TO_D3,
107     PS2_TO_D4,
108     PS2_TO_D5,
109     PS2_TO_D6,
110     PS2_TO_D7,
111     PS2_TO_PARITY,
112     PS2_TO_STOP
113 } ps2mouse_xmit_state = PS2_FROMTO_IDLE;
114 
115 /* Output buffer */
116 #define PS2_QUEUE_SIZE 8
117 uint8_t ps2mouse_queue[PS2_QUEUE_SIZE];
118 uint8_t ps2mouse_queue_head;
119 uint8_t ps2mouse_queue_tail;
120 
ps2mouse_queue_put(uint8_t value)121 static int ps2mouse_queue_put(uint8_t value)
122 {
123     uint8_t new_head = (ps2mouse_queue_head + 1) & (PS2_QUEUE_SIZE - 1);
124     if (new_head == ps2mouse_queue_tail) {
125 #ifdef PS2MOUSE_DEBUG_ENABLED
126         PS2MOUSE_DEBUG("queue full!");
127 #endif
128         return 0;
129     }
130     ps2mouse_queue[ps2mouse_queue_head] = value;
131     ps2mouse_queue_head = new_head;
132     return 1;
133 }
134 
ps2mouse_queue_empty(void)135 static int ps2mouse_queue_empty(void)
136 {
137     return (ps2mouse_queue_head == ps2mouse_queue_tail);
138 }
139 
ps2mouse_queue_get(void)140 static uint8_t ps2mouse_queue_get(void)
141 {
142     uint8_t retval = ps2mouse_queue[ps2mouse_queue_tail];
143     ++ps2mouse_queue_tail;
144     ps2mouse_queue_tail &= (PS2_QUEUE_SIZE - 1);
145     return retval;
146 }
147 
148 /* ------------------------------------------------------------------------- */
149 
150 
ps2mouse_handle_command(uint8_t value)151 static int ps2mouse_handle_command(uint8_t value)
152 {
153     int16_t diff_x, diff_y, new_x, new_y;
154     uint8_t new_buttons;
155 #ifdef PS2MOUSE_DEBUG_ENABLED
156     PS2MOUSE_DEBUG("cmd: got %02x", value);
157 #endif
158     ps2mouse_xmit_state = PS2_CHECK_SEND;
159 
160     if (ps2mouse_parity) {
161 #ifdef PS2MOUSE_DEBUG_ENABLED
162         PS2MOUSE_DEBUG("parity error");
163 #endif
164         return ps2mouse_queue_put(PS2_REPLY_ERROR);
165     }
166 
167     switch (value) {
168         case PS2_CMD_GET_DEV_ID:
169             return (ps2mouse_queue_put(PS2_REPLY_OK)
170                     && ps2mouse_queue_put(PS2_REPLY_DEV_ID));
171             break;
172 
173         case PS2_CMD_SET_REMOTE_MODE:
174 #ifdef HAVE_MOUSE
175             mouse_get_int16(&ps2mouse_lastx, &ps2mouse_lasty);
176 #endif
177             return (ps2mouse_queue_put(PS2_REPLY_OK));
178             break;
179 
180         case PS2_CMD_READ_DATA:
181             new_buttons = ps2mouse_buttons;
182 #ifdef HAVE_MOUSE
183             mouse_get_int16(&new_x, &new_y);
184             diff_x = (new_x - ps2mouse_lastx);
185             if (diff_x < 0) {
186                 new_buttons |= PS2_MDATA_XS;
187             }
188             if (diff_x < -256 || diff_x > 255) {
189                 new_buttons |= PS2_MDATA_XO;
190             }
191             ps2mouse_lastx = new_x;
192 
193             diff_y = (int16_t)(new_y - ps2mouse_lasty);
194             if (diff_y < 0) {
195                 new_buttons |= PS2_MDATA_YS;
196             }
197             if (diff_y < -256 || diff_y > 255) {
198                 new_buttons |= PS2_MDATA_YO;
199             }
200             ps2mouse_lasty = new_y;
201 #else
202             diff_x = 0;
203             diff_y = 0;
204 #endif
205 #ifdef PS2MOUSE_DEBUG_ENABLED
206             PS2MOUSE_DEBUG("x/y/b: %02x, %02x, %02x", diff_x, diff_y, new_buttons);
207 #endif
208             return (ps2mouse_queue_put(PS2_REPLY_OK)
209                     && ps2mouse_queue_put(new_buttons)
210                     && ps2mouse_queue_put((uint8_t)diff_x)
211                     && ps2mouse_queue_put((uint8_t)diff_y));
212             break;
213 
214         default:
215 #ifdef PS2MOUSE_DEBUG_ENABLED
216             PS2MOUSE_DEBUG("unsupported command %02x", value);
217 #endif
218             return (ps2mouse_queue_put(PS2_REPLY_ERROR) & 0);
219             break;
220     }
221     return 0;
222 }
223 
224 
225 struct alarm_s *c64dtv_ps2mouse_alarm;
226 
c64dtv_ps2mouse_alarm_handler(CLOCK offset,void * data)227 static void c64dtv_ps2mouse_alarm_handler(CLOCK offset, void *data)
228 {
229     int another_alarm = 1;
230 
231     alarm_unset(c64dtv_ps2mouse_alarm);
232 
233     ps2mouse_out ^= PS2_CLK_BIT;
234     ps2mouse_out &= ~PS2_DATA_BIT;
235 
236     switch (ps2mouse_xmit_state) {
237         case PS2_FROM_START:
238             ps2mouse_value = 0;
239             ps2mouse_parity = 0;
240             ps2mouse_out |= (ps2mouse_in & PS2_DATA_BIT);
241 #ifdef PS2MOUSE_DEBUG_ENABLED
242             PS2MOUSE_DEBUG("start: clk/data = %i/%i", (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
243 #endif
244             if ((ps2mouse_out & PS2_CLK_BIT) == 0) {
245                 ++ps2mouse_xmit_state;
246             }
247             break;
248 
249         case PS2_FROM_D0:
250         case PS2_FROM_D1:
251         case PS2_FROM_D2:
252         case PS2_FROM_D3:
253         case PS2_FROM_D4:
254         case PS2_FROM_D5:
255         case PS2_FROM_D6:
256         case PS2_FROM_D7:
257             if (ps2mouse_out & PS2_CLK_BIT) {
258                 ps2mouse_value >>= 1;
259                 if (ps2mouse_in & PS2_DATA_BIT) {
260                     ps2mouse_value |= 0x80;
261                     ps2mouse_parity ^= PS2_DATA_BIT;
262                 }
263             }
264             ps2mouse_out |= (ps2mouse_in & PS2_DATA_BIT);
265 #ifdef PS2MOUSE_DEBUG_ENABLED
266             PS2MOUSE_DEBUG("d%i: clk/data = %i/%i", ps2mouse_xmit_state - PS2_FROM_D0, (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
267 #endif
268             if ((ps2mouse_out & PS2_CLK_BIT) == 0) {
269                 ++ps2mouse_xmit_state;
270             }
271             break;
272 
273         case PS2_FROM_PARITY:
274             if (ps2mouse_out & PS2_CLK_BIT) {
275                 if (ps2mouse_in & PS2_DATA_BIT) {
276                     ps2mouse_parity ^= PS2_DATA_BIT;
277                 }
278             }
279             ps2mouse_out |= PS2_DATA_BIT;
280 #ifdef PS2MOUSE_DEBUG_ENABLED
281             PS2MOUSE_DEBUG("parity: clk/data = %i/%i", (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
282 #endif
283             if ((ps2mouse_out & PS2_CLK_BIT) == 0) {
284                 ++ps2mouse_xmit_state;
285             }
286             break;
287 
288         case PS2_FROM_STOP:
289             if (ps2mouse_out & PS2_CLK_BIT) {
290                 if (ps2mouse_in & PS2_DATA_BIT) {
291                     ps2mouse_parity ^= PS2_DATA_BIT;
292                 }
293                 ps2mouse_out |= PS2_DATA_BIT;
294             } else {
295                 ps2mouse_out |= ps2mouse_parity;
296             }
297 #ifdef PS2MOUSE_DEBUG_ENABLED
298             PS2MOUSE_DEBUG("stop: clk/data = %i/%i", (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
299 #endif
300             if ((ps2mouse_out & PS2_CLK_BIT) == 0) {
301                 ++ps2mouse_xmit_state;
302             }
303             break;
304 
305         case PS2_FROM_ACK:
306             ps2mouse_out |= PS2_DATA_BIT; /* ps2mouse_parity; */
307 #ifdef PS2MOUSE_DEBUG_ENABLED
308             PS2MOUSE_DEBUG("got %02x, parity: %02x, clk/data = %i/%i", ps2mouse_value, ps2mouse_parity, (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
309 #endif
310             another_alarm = ps2mouse_handle_command(ps2mouse_value);
311             break;
312 
313         case PS2_CHECK_SEND:
314             if (ps2mouse_queue_empty()) {
315                 ps2mouse_out |= (PS2_DATA_BIT | PS2_CLK_BIT);
316                 another_alarm = 0;
317                 ps2mouse_xmit_state = PS2_FROMTO_IDLE;
318 #ifdef PS2MOUSE_DEBUG_ENABLED
319                 PS2MOUSE_DEBUG("all sent. clk/data = %i/%i", (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
320 #endif
321                 break;
322             }
323             if ((ps2mouse_in & PS2_CLK_BIT) == 0) {
324                 ps2mouse_out &= ~PS2_CLK_BIT;
325 #ifdef PS2MOUSE_DEBUG_ENABLED
326                 PS2MOUSE_DEBUG("hold! clk/data = %i/%i", (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
327 #endif
328                 break;
329             }
330 
331             if ((ps2mouse_out & PS2_CLK_BIT) == 0) {
332                 ps2mouse_parity = PS2_DATA_BIT;
333                 ps2mouse_value = ps2mouse_queue_get();
334 #ifdef PS2MOUSE_DEBUG_ENABLED
335                 PS2MOUSE_DEBUG("sending %02x, clk/data = %i/%i", ps2mouse_value, (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
336 #endif
337                 ps2mouse_xmit_state = PS2_TO_D0;
338             }
339             break;
340 
341         case PS2_TO_D0:
342         case PS2_TO_D1:
343         case PS2_TO_D2:
344         case PS2_TO_D3:
345         case PS2_TO_D4:
346         case PS2_TO_D5:
347         case PS2_TO_D6:
348         case PS2_TO_D7:
349             if (ps2mouse_out & PS2_CLK_BIT) {
350                 ps2mouse_out |= (ps2mouse_value & 1) ? PS2_DATA_BIT : 0;
351                 ps2mouse_prev = ps2mouse_out;
352                 ps2mouse_parity ^= (ps2mouse_value & 1) ? PS2_DATA_BIT : 0;
353                 ps2mouse_value >>= 1;
354             } else {
355                 ps2mouse_out |= (ps2mouse_prev & PS2_DATA_BIT);
356             }
357 #ifdef PS2MOUSE_DEBUG_ENABLED
358             PS2MOUSE_DEBUG("to_d%i: clk/data = %i/%i", ps2mouse_xmit_state - PS2_TO_D0, (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
359 #endif
360             if ((ps2mouse_out & PS2_CLK_BIT) == 0) {
361                 ++ps2mouse_xmit_state;
362             }
363             break;
364 
365         case PS2_TO_PARITY:
366             if (ps2mouse_out & PS2_CLK_BIT) {
367                 ps2mouse_out |= ps2mouse_parity;
368                 ps2mouse_prev = ps2mouse_out;
369             } else {
370                 ps2mouse_out |= (ps2mouse_prev & PS2_DATA_BIT);
371             }
372 #ifdef PS2MOUSE_DEBUG_ENABLED
373             PS2MOUSE_DEBUG("to_parity: clk/data = %i/%i", (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
374 #endif
375             if ((ps2mouse_out & PS2_CLK_BIT) == 0) {
376                 ++ps2mouse_xmit_state;
377             }
378             break;
379 
380         case PS2_TO_STOP:
381             ps2mouse_out |= PS2_DATA_BIT;
382 #ifdef PS2MOUSE_DEBUG_ENABLED
383             PS2MOUSE_DEBUG("stop: clk/data = %i/%i", (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
384 #endif
385             if ((ps2mouse_out & PS2_CLK_BIT) == 0) {
386                 ps2mouse_xmit_state = PS2_CHECK_SEND;
387             }
388             break;
389 
390         default:
391             ps2mouse_out |= (PS2_DATA_BIT | PS2_CLK_BIT);
392             another_alarm = 0;
393             ps2mouse_xmit_state = PS2_FROMTO_IDLE;
394 #ifdef PS2MOUSE_DEBUG_ENABLED
395             PS2MOUSE_DEBUG("bug! state %i. clk/data = %i/%i", ps2mouse_xmit_state, (ps2mouse_out >> 6) & 1, (ps2mouse_out >> 7) & 1);
396 #endif
397             break;
398     }
399 
400     if (another_alarm) {
401         alarm_set(c64dtv_ps2mouse_alarm, maincpu_clk + PS2_BIT_DELAY_CLK);
402     }
403 }
404 
405 
ps2mouse_store(uint8_t value)406 void ps2mouse_store(uint8_t value)
407 {
408     ps2mouse_in = value;
409     if (((ps2mouse_prev & PS2_CLK_BIT) == 0) && (value & PS2_CLK_BIT)
410         && (ps2mouse_xmit_state == PS2_FROMTO_IDLE) && ((value & PS2_DATA_BIT) == 0)) {
411         ps2mouse_xmit_state = PS2_FROM_START;
412         ps2mouse_out = value;
413         alarm_set(c64dtv_ps2mouse_alarm, maincpu_clk + PS2_BIT_DELAY_CLK);
414     }
415     ps2mouse_prev = value;
416     return;
417 }
418 
ps2mouse_read()419 uint8_t ps2mouse_read()
420 {
421     return ps2mouse_out;
422 }
423 
424 /* ------------------------------------------------------------------------- */
425 
426 
c64dtv_ps2mouse_alarm_init(void)427 static void c64dtv_ps2mouse_alarm_init(void)
428 {
429     c64dtv_ps2mouse_alarm = alarm_new(maincpu_alarm_context, "PS2MOUSEAlarm",
430                                       c64dtv_ps2mouse_alarm_handler, NULL);
431 }
432 
ps2mouse_reset(void)433 void ps2mouse_reset(void)
434 {
435     ps2mouse_in = 0xff;
436     ps2mouse_out = 0xff;
437     ps2mouse_prev = 0xff;
438     ps2mouse_xmit_state = PS2_FROMTO_IDLE;
439     ps2mouse_parity = 0;
440     ps2mouse_lastx = 0;
441     ps2mouse_lasty = 0;
442     ps2mouse_buttons = PS2_MDATA_A1;
443     ps2mouse_queue_head = 0;
444     ps2mouse_queue_tail = 0;
445     return;
446 }
447 
448 /* ------------------------------------------------------------------------- */
449 
450 int ps2mouse_enabled = 0;
451 
set_ps2mouse_enable(int val,void * param)452 static int set_ps2mouse_enable(int val, void *param)
453 {
454     ps2mouse_enabled = (unsigned int)(val ? 1 : 0);
455 
456     return 0;
457 }
458 
459 static const resource_int_t resources_int[] = {
460     { "ps2mouse", 0, RES_EVENT_SAME, NULL,
461       &ps2mouse_enabled, set_ps2mouse_enable, NULL },
462     RESOURCE_INT_LIST_END
463 };
464 
465 static const cmdline_option_t cmdline_options[] =
466 {
467     { "-ps2mouse", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
468       NULL, NULL, "PS2Mouse", (void *)1,
469       NULL, "Enable PS/2 mouse on userport" },
470     { "+ps2mouse", SET_RESOURCE, CMDLINE_ATTRIB_NONE,
471       NULL, NULL, "PS2Mouse", (void *)0,
472       NULL, "Disable PS/2 mouse on userport" },
473     CMDLINE_LIST_END
474 };
475 
476 /* ------------------------------------------------------------------------- */
477 
478 static mouse_func_t mouse_funcs =
479 {
480     mouse_button_left,
481     mouse_button_right,
482     mouse_button_middle,
483     mouse_button_up,
484     mouse_button_down
485 };
486 
mouse_ps2_resources_init(void)487 int mouse_ps2_resources_init(void)
488 {
489     if (resources_register_int(resources_int) < 0) {
490         return -1;
491     }
492 
493     return mousedrv_resources_init(&mouse_funcs);
494 }
495 
mouse_ps2_cmdline_options_init(void)496 int mouse_ps2_cmdline_options_init(void)
497 {
498     if (cmdline_register_options(cmdline_options) < 0) {
499         return -1;
500     }
501 
502     return mousedrv_cmdline_options_init();
503 }
504 
mouse_ps2_init(void)505 void mouse_ps2_init(void)
506 {
507     if (ps2mouse_log == LOG_ERR) {
508         ps2mouse_log = log_open("ps2mouse");
509     }
510 
511     c64dtv_ps2mouse_alarm_init();
512 
513     ps2mouse_reset();
514     mousedrv_init();
515 }
516 
mouse_ps2_shutdown(void)517 void mouse_ps2_shutdown(void)
518 {
519 }
520 
mouse_button_left(int pressed)521 static void mouse_button_left(int pressed)
522 {
523     if (pressed) {
524         ps2mouse_buttons |= PS2_MDATA_LB;
525     } else {
526         ps2mouse_buttons &= ~PS2_MDATA_LB;
527     }
528 }
529 
mouse_button_middle(int pressed)530 static void mouse_button_middle(int pressed)
531 {
532     if (pressed) {
533         ps2mouse_buttons |= PS2_MDATA_MB;
534     } else {
535         ps2mouse_buttons &= ~PS2_MDATA_MB;
536     }
537 }
538 
mouse_button_right(int pressed)539 static void mouse_button_right(int pressed)
540 {
541     if (pressed) {
542         ps2mouse_buttons |= PS2_MDATA_RB;
543     } else {
544         ps2mouse_buttons &= ~PS2_MDATA_RB;
545     }
546 }
547 
mouse_button_up(int pressed)548 static void mouse_button_up(int pressed)
549 {
550 }
551 
mouse_button_down(int pressed)552 static void mouse_button_down(int pressed)
553 {
554 }
555