blob: c787568435df03f55bd94b4eb08b7dfb8070186f [file] [log] [blame] [raw]
/*
* QEMU PS/2 keyboard/mouse emulation
*
* Copyright (c) 2003 Fabrice Bellard
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <inttypes.h>
#include <assert.h>
#include "cutils.h"
#include "iomem.h"
#include "ps2.h"
/* debug PC keyboard */
//#define DEBUG_KBD
/* debug PC keyboard : only mouse */
//#define DEBUG_MOUSE
/* Keyboard Commands */
#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */
#define KBD_CMD_ECHO 0xEE
#define KBD_CMD_GET_ID 0xF2 /* get keyboard ID */
#define KBD_CMD_SET_RATE 0xF3 /* Set typematic rate */
#define KBD_CMD_ENABLE 0xF4 /* Enable scanning */
#define KBD_CMD_RESET_DISABLE 0xF5 /* reset and disable scanning */
#define KBD_CMD_RESET_ENABLE 0xF6 /* reset and enable scanning */
#define KBD_CMD_RESET 0xFF /* Reset */
/* Keyboard Replies */
#define KBD_REPLY_POR 0xAA /* Power on reset */
#define KBD_REPLY_ACK 0xFA /* Command ACK */
#define KBD_REPLY_RESEND 0xFE /* Command NACK, send the cmd again */
/* Mouse Commands */
#define AUX_SET_SCALE11 0xE6 /* Set 1:1 scaling */
#define AUX_SET_SCALE21 0xE7 /* Set 2:1 scaling */
#define AUX_SET_RES 0xE8 /* Set resolution */
#define AUX_GET_SCALE 0xE9 /* Get scaling factor */
#define AUX_SET_STREAM 0xEA /* Set stream mode */
#define AUX_POLL 0xEB /* Poll */
#define AUX_RESET_WRAP 0xEC /* Reset wrap mode */
#define AUX_SET_WRAP 0xEE /* Set wrap mode */
#define AUX_SET_REMOTE 0xF0 /* Set remote mode */
#define AUX_GET_TYPE 0xF2 /* Get type */
#define AUX_SET_SAMPLE 0xF3 /* Set sample rate */
#define AUX_ENABLE_DEV 0xF4 /* Enable aux device */
#define AUX_DISABLE_DEV 0xF5 /* Disable aux device */
#define AUX_SET_DEFAULT 0xF6
#define AUX_RESET 0xFF /* Reset aux device */
#define AUX_ACK 0xFA /* Command byte ACK. */
#define MOUSE_STATUS_REMOTE 0x40
#define MOUSE_STATUS_ENABLED 0x20
#define MOUSE_STATUS_SCALE21 0x10
#define PS2_QUEUE_SIZE 256
typedef struct {
uint8_t data[PS2_QUEUE_SIZE];
int rptr, wptr, count;
} PS2Queue;
typedef struct {
PS2Queue queue;
int32_t write_cmd;
void (*update_irq)(void *, int);
void *update_arg;
} PS2State;
struct PS2KbdState {
PS2State common;
int scan_enabled;
/* Qemu uses translated PC scancodes internally. To avoid multiple
conversions we do the translation (if any) in the PS/2 emulation
not the keyboard controller. */
int translate;
};
struct PS2MouseState {
PS2State common;
uint8_t mouse_status;
uint8_t mouse_resolution;
uint8_t mouse_sample_rate;
uint8_t mouse_wrap;
uint8_t mouse_type; /* 0 = PS2, 3 = IMPS/2, 4 = IMEX */
uint8_t mouse_detect_state;
int mouse_dx; /* current values, needed for 'poll' mode */
int mouse_dy;
int mouse_dz;
uint8_t mouse_buttons;
};
void ps2_queue(void *opaque, int b)
{
PS2State *s = (PS2State *)opaque;
PS2Queue *q = &s->queue;
if (q->count >= PS2_QUEUE_SIZE)
return;
q->data[q->wptr] = b;
if (++q->wptr == PS2_QUEUE_SIZE)
q->wptr = 0;
q->count++;
s->update_irq(s->update_arg, 1);
}
#define INPUT_MAKE_KEY_MIN 96
#define INPUT_MAKE_KEY_MAX 127
static const uint8_t linux_input_to_keycode_set1[INPUT_MAKE_KEY_MAX - INPUT_MAKE_KEY_MIN + 1] = {
0x1c, 0x1d, 0x35, 0x00, 0x38, 0x00, 0x47, 0x48,
0x49, 0x4b, 0x4d, 0x4f, 0x50, 0x51, 0x52, 0x53,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x5b, 0x5c, 0x5d,
};
/* keycode is a Linux input layer keycode. We only support the PS/2
keycode set 1 */
void ps2_put_keycode(PS2KbdState *s, BOOL is_down, int keycode)
{
if (keycode >= INPUT_MAKE_KEY_MIN) {
if (keycode > INPUT_MAKE_KEY_MAX)
return;
keycode = linux_input_to_keycode_set1[keycode - INPUT_MAKE_KEY_MIN];
if (keycode == 0)
return;
ps2_queue(&s->common, 0xe0);
}
ps2_queue(&s->common, keycode | ((!is_down) << 7));
}
uint32_t ps2_read_data(void *opaque)
{
PS2State *s = (PS2State *)opaque;
PS2Queue *q;
int val, index;
q = &s->queue;
if (q->count == 0) {
/* NOTE: if no data left, we return the last keyboard one
(needed for EMM386) */
/* XXX: need a timer to do things correctly */
index = q->rptr - 1;
if (index < 0)
index = PS2_QUEUE_SIZE - 1;
val = q->data[index];
} else {
val = q->data[q->rptr];
if (++q->rptr == PS2_QUEUE_SIZE)
q->rptr = 0;
q->count--;
/* reading deasserts IRQ */
s->update_irq(s->update_arg, 0);
/* reassert IRQs if data left */
s->update_irq(s->update_arg, q->count != 0);
}
return val;
}
static void ps2_reset_keyboard(PS2KbdState *s)
{
s->scan_enabled = 1;
}
void ps2_write_keyboard(void *opaque, int val)
{
PS2KbdState *s = (PS2KbdState *)opaque;
switch(s->common.write_cmd) {
default:
case -1:
switch(val) {
case 0x00:
ps2_queue(&s->common, KBD_REPLY_ACK);
break;
case 0x05:
ps2_queue(&s->common, KBD_REPLY_RESEND);
break;
case KBD_CMD_GET_ID:
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_queue(&s->common, 0xab);
ps2_queue(&s->common, 0x83);
break;
case KBD_CMD_ECHO:
ps2_queue(&s->common, KBD_CMD_ECHO);
break;
case KBD_CMD_ENABLE:
s->scan_enabled = 1;
ps2_queue(&s->common, KBD_REPLY_ACK);
break;
case KBD_CMD_SET_LEDS:
case KBD_CMD_SET_RATE:
s->common.write_cmd = val;
ps2_queue(&s->common, KBD_REPLY_ACK);
break;
case KBD_CMD_RESET_DISABLE:
ps2_reset_keyboard(s);
s->scan_enabled = 0;
ps2_queue(&s->common, KBD_REPLY_ACK);
break;
case KBD_CMD_RESET_ENABLE:
ps2_reset_keyboard(s);
s->scan_enabled = 1;
ps2_queue(&s->common, KBD_REPLY_ACK);
break;
case KBD_CMD_RESET:
ps2_reset_keyboard(s);
ps2_queue(&s->common, KBD_REPLY_ACK);
ps2_queue(&s->common, KBD_REPLY_POR);
break;
default:
ps2_queue(&s->common, KBD_REPLY_ACK);
break;
}
break;
case KBD_CMD_SET_LEDS:
ps2_queue(&s->common, KBD_REPLY_ACK);
s->common.write_cmd = -1;
break;
case KBD_CMD_SET_RATE:
ps2_queue(&s->common, KBD_REPLY_ACK);
s->common.write_cmd = -1;
break;
}
}
/* Set the scancode translation mode.
0 = raw scancodes.
1 = translated scancodes (used by qemu internally). */
void ps2_keyboard_set_translation(void *opaque, int mode)
{
PS2KbdState *s = (PS2KbdState *)opaque;
s->translate = mode;
}
static void ps2_mouse_send_packet(PS2MouseState *s)
{
unsigned int b;
int dx1, dy1, dz1;
dx1 = s->mouse_dx;
dy1 = s->mouse_dy;
dz1 = s->mouse_dz;
/* XXX: increase range to 8 bits ? */
if (dx1 > 127)
dx1 = 127;
else if (dx1 < -127)
dx1 = -127;
if (dy1 > 127)
dy1 = 127;
else if (dy1 < -127)
dy1 = -127;
b = 0x08 | ((dx1 < 0) << 4) | ((dy1 < 0) << 5) | (s->mouse_buttons & 0x07);
ps2_queue(&s->common, b);
ps2_queue(&s->common, dx1 & 0xff);
ps2_queue(&s->common, dy1 & 0xff);
/* extra byte for IMPS/2 or IMEX */
switch(s->mouse_type) {
default:
break;
case 3:
if (dz1 > 127)
dz1 = 127;
else if (dz1 < -127)
dz1 = -127;
ps2_queue(&s->common, dz1 & 0xff);
break;
case 4:
if (dz1 > 7)
dz1 = 7;
else if (dz1 < -7)
dz1 = -7;
b = (dz1 & 0x0f) | ((s->mouse_buttons & 0x18) << 1);
ps2_queue(&s->common, b);
break;
}
/* update deltas */
s->mouse_dx -= dx1;
s->mouse_dy -= dy1;
s->mouse_dz -= dz1;
}
void ps2_mouse_event(PS2MouseState *s,
int dx, int dy, int dz, int buttons_state)
{
/* check if deltas are recorded when disabled */
if (!(s->mouse_status & MOUSE_STATUS_ENABLED))
return;
s->mouse_dx += dx;
s->mouse_dy -= dy;
s->mouse_dz += dz;
/* XXX: SDL sometimes generates nul events: we delete them */
if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0 &&
s->mouse_buttons == buttons_state)
return;
s->mouse_buttons = buttons_state;
if (!(s->mouse_status & MOUSE_STATUS_REMOTE) &&
(s->common.queue.count < (PS2_QUEUE_SIZE - 16))) {
for(;;) {
/* if not remote, send event. Multiple events are sent if
too big deltas */
ps2_mouse_send_packet(s);
if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0)
break;
}
}
}
void ps2_write_mouse(void *opaque, int val)
{
PS2MouseState *s = (PS2MouseState *)opaque;
#ifdef DEBUG_MOUSE
printf("kbd: write mouse 0x%02x\n", val);
#endif
switch(s->common.write_cmd) {
default:
case -1:
/* mouse command */
if (s->mouse_wrap) {
if (val == AUX_RESET_WRAP) {
s->mouse_wrap = 0;
ps2_queue(&s->common, AUX_ACK);
return;
} else if (val != AUX_RESET) {
ps2_queue(&s->common, val);
return;
}
}
switch(val) {
case AUX_SET_SCALE11:
s->mouse_status &= ~MOUSE_STATUS_SCALE21;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_SET_SCALE21:
s->mouse_status |= MOUSE_STATUS_SCALE21;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_SET_STREAM:
s->mouse_status &= ~MOUSE_STATUS_REMOTE;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_SET_WRAP:
s->mouse_wrap = 1;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_SET_REMOTE:
s->mouse_status |= MOUSE_STATUS_REMOTE;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_GET_TYPE:
ps2_queue(&s->common, AUX_ACK);
ps2_queue(&s->common, s->mouse_type);
break;
case AUX_SET_RES:
case AUX_SET_SAMPLE:
s->common.write_cmd = val;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_GET_SCALE:
ps2_queue(&s->common, AUX_ACK);
ps2_queue(&s->common, s->mouse_status);
ps2_queue(&s->common, s->mouse_resolution);
ps2_queue(&s->common, s->mouse_sample_rate);
break;
case AUX_POLL:
ps2_queue(&s->common, AUX_ACK);
ps2_mouse_send_packet(s);
break;
case AUX_ENABLE_DEV:
s->mouse_status |= MOUSE_STATUS_ENABLED;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_DISABLE_DEV:
s->mouse_status &= ~MOUSE_STATUS_ENABLED;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_SET_DEFAULT:
s->mouse_sample_rate = 100;
s->mouse_resolution = 2;
s->mouse_status = 0;
ps2_queue(&s->common, AUX_ACK);
break;
case AUX_RESET:
s->mouse_sample_rate = 100;
s->mouse_resolution = 2;
s->mouse_status = 0;
s->mouse_type = 0;
ps2_queue(&s->common, AUX_ACK);
ps2_queue(&s->common, 0xaa);
ps2_queue(&s->common, s->mouse_type);
break;
default:
break;
}
break;
case AUX_SET_SAMPLE:
s->mouse_sample_rate = val;
/* detect IMPS/2 or IMEX */
switch(s->mouse_detect_state) {
default:
case 0:
if (val == 200)
s->mouse_detect_state = 1;
break;
case 1:
if (val == 100)
s->mouse_detect_state = 2;
else if (val == 200)
s->mouse_detect_state = 3;
else
s->mouse_detect_state = 0;
break;
case 2:
if (val == 80)
s->mouse_type = 3; /* IMPS/2 */
s->mouse_detect_state = 0;
break;
case 3:
if (val == 80)
s->mouse_type = 4; /* IMEX */
s->mouse_detect_state = 0;
break;
}
ps2_queue(&s->common, AUX_ACK);
s->common.write_cmd = -1;
break;
case AUX_SET_RES:
s->mouse_resolution = val;
ps2_queue(&s->common, AUX_ACK);
s->common.write_cmd = -1;
break;
}
}
static void ps2_reset(void *opaque)
{
PS2State *s = (PS2State *)opaque;
PS2Queue *q;
s->write_cmd = -1;
q = &s->queue;
q->rptr = 0;
q->wptr = 0;
q->count = 0;
}
PS2KbdState *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg)
{
PS2KbdState *s = (PS2KbdState *)mallocz(sizeof(PS2KbdState));
s->common.update_irq = update_irq;
s->common.update_arg = update_arg;
ps2_reset(&s->common);
return s;
}
PS2MouseState *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg)
{
PS2MouseState *s = (PS2MouseState *)mallocz(sizeof(PS2MouseState));
s->common.update_irq = update_irq;
s->common.update_arg = update_arg;
ps2_reset(&s->common);
return s;
}