From 3daf7743aad339be63ab9d4014f9e529aef43726 Mon Sep 17 00:00:00 2001 From: John Floren Date: Wed, 1 Jun 2011 17:53:56 +0300 Subject: [PATCH] kvm tools: Add support for PS/2 keyboard system Add support for PS/2 keyboard system with AUX device (aka mouse). The device works with vnc, the guest must be started with the '--vnc' parameter for the device to be initialized. Signed-off-by: John Floren [ turn into patch and clean up code ] Signed-off-by: Sasha Levin Signed-off-by: Pekka Enberg --- tools/kvm/Makefile | 1 + tools/kvm/hw/i8042.c | 475 ++++++++++++++++++++++++++++++++++ tools/kvm/hw/vesa.c | 3 + tools/kvm/include/kvm/i8042.h | 19 ++ tools/kvm/ioport.c | 4 - tools/kvm/kvm-run.c | 5 +- 6 files changed, 502 insertions(+), 5 deletions(-) create mode 100644 tools/kvm/hw/i8042.c create mode 100644 tools/kvm/include/kvm/i8042.h diff --git a/tools/kvm/Makefile b/tools/kvm/Makefile index e7ceb5c3905f..8fa7ca83c3de 100644 --- a/tools/kvm/Makefile +++ b/tools/kvm/Makefile @@ -64,6 +64,7 @@ has_vncserver := $(call try-cc,$(SOURCE_VNCSERVER),$(FLAGS_VNCSERVER)) ifeq ($(has_vncserver),y) CFLAGS += -DCONFIG_HAS_VNCSERVER OBJS += hw/vesa.o + OBJS += hw/i8042.o LIBS += -lvncserver endif diff --git a/tools/kvm/hw/i8042.c b/tools/kvm/hw/i8042.c new file mode 100644 index 000000000000..80f0d18e8470 --- /dev/null +++ b/tools/kvm/hw/i8042.c @@ -0,0 +1,475 @@ +#include "kvm/read-write.h" +#include "kvm/ioport.h" +#include "kvm/mutex.h" +#include "kvm/util.h" +#include "kvm/term.h" +#include "kvm/kvm.h" +#include "kvm/i8042.h" + +#include +#include +#include + +#define KBD_IRQ 1 +#define AUX_IRQ 12 + +#define CMD_READ_MODE 0x20 +#define CMD_WRITE_MODE 0x60 +#define CMD_WRITE_AUX_BUF 0xD3 +#define CMD_WRITE_AUX 0xD4 +#define CMD_TEST_AUX 0xA9 +#define CMD_DISABLE_AUX 0xA7 +#define CMD_ENABLE_AUX 0xA8 + +#define RESPONSE_ACK 0xFA + +#define MODE_DISABLE_AUX 0x20 + +#define AUX_ENABLE_REPORTING 0x20 +#define AUX_SCALING_FLAG 0x10 +#define AUX_DEFAULT_RESOLUTION 0x2 +#define AUX_DEFAULT_SAMPLE 100 + +#define KBD_STATUS_SYS 0x4 +#define KBD_STATUS_A2 0x8 +#define KBD_STATUS_INH 0x10 +#define KBD_STATUS_OBF 0x01 +#define KBD_STATUS_AUX_OBF 0x20 + +#define KBD_MODE_KBD_INT 0x01 +#define KBD_MODE_SYS 0x02 + +#define QUEUE_SIZE 128 + +/* + * This represents the current state of the PS/2 keyboard system, + * including the AUX device (the mouse) + */ +struct kbd_state { + struct kvm *kvm; + + char kq[QUEUE_SIZE]; /* Keyboard queue */ + int kread, kwrite; /* Indexes into the queue */ + int kcount; /* number of elements in queue */ + + char mq[QUEUE_SIZE]; + int mread, mwrite; + int mcount; + + u8 mstatus; /* Mouse status byte */ + u8 mres; /* Current mouse resolution */ + u8 msample; /* Current mouse samples/second */ + + u8 mode; /* i8042 mode register */ + u8 status; /* i8042 status register */ + /* + * Some commands (on port 0x64) have arguments; + * we store the command here while we wait for the argument + */ + u32 write_cmd; +}; + +static struct kbd_state state; + +/* + * If there are packets to be read, set the appropriate IRQs high + */ +static void kbd_update_irq(void) +{ + u8 klevel = 0; + u8 mlevel = 0; + + /* First, clear the kbd and aux output buffer full bits */ + state.status &= ~(KBD_STATUS_OBF | KBD_STATUS_AUX_OBF); + + if (state.kcount > 0) { + state.status |= KBD_STATUS_OBF; + klevel = 1; + } + + /* Keyboard has higher priority than mouse */ + if (klevel == 0 && state.mcount != 0) { + state.status |= KBD_STATUS_OBF | KBD_STATUS_AUX_OBF; + mlevel = 1; + } + + kvm__irq_line(state.kvm, KBD_IRQ, klevel); + kvm__irq_line(state.kvm, AUX_IRQ, mlevel); +} + +/* + * Add a byte to the mouse queue, then set IRQs + */ +static void mouse_queue(u8 c) +{ + if (state.mcount >= QUEUE_SIZE) + return; + + state.mq[state.mwrite++ % QUEUE_SIZE] = c; + + state.mcount++; + kbd_update_irq(); +} + +/* + * Add a byte to the keyboard queue, then set IRQs + */ +static void kbd_queue(u8 c) +{ + if (state.kcount >= QUEUE_SIZE) + return; + + state.kq[state.kwrite++ % QUEUE_SIZE] = c; + + state.kcount++; + kbd_update_irq(); +} + +/* + * This function is called when the OS issues a write to port 0x64 + */ +static void kbd_write_command(u32 val) +{ + switch (val) { + case CMD_READ_MODE: + kbd_queue(state.mode); + break; + case CMD_WRITE_MODE: + case CMD_WRITE_AUX: + case CMD_WRITE_AUX_BUF: + state.write_cmd = val; + break; + case CMD_TEST_AUX: + /* 0 means we're a normal PS/2 mouse */ + mouse_queue(0); + break; + case CMD_DISABLE_AUX: + state.mode |= MODE_DISABLE_AUX; + break; + case CMD_ENABLE_AUX: + state.mode &= ~MODE_DISABLE_AUX; + break; + default: + break; + } +} + +/* + * Called when the OS reads from port 0x60 (PS/2 data) + */ +static u32 kbd_read_data(void) +{ + u32 ret; + int i; + + if (state.kcount != 0) { + /* Keyboard data gets read first */ + ret = state.kq[state.kread++ % QUEUE_SIZE]; + state.kcount--; + kvm__irq_line(state.kvm, KBD_IRQ, 0); + kbd_update_irq(); + } else if (state.mcount > 0) { + /* Followed by the mouse */ + ret = state.mq[state.mread++ % QUEUE_SIZE]; + state.mcount--; + kvm__irq_line(state.kvm, AUX_IRQ, 0); + kbd_update_irq(); + } else if (state.kcount == 0) { + i = state.kread - 1; + if (i < 0) + i = QUEUE_SIZE; + ret = state.kq[i]; + } + return ret; +} + +/* + * Called when the OS read from port 0x64, the command port + */ +static u32 kbd_read_status(void) +{ + return (u32)state.status; +} + +/* + * Called when the OS writes to port 0x60 (data port) + * Things written here are generally arguments to commands previously + * written to port 0x64 and stored in state.write_cmd + */ +static void kbd_write_data(u32 val) +{ + switch (state.write_cmd) { + case CMD_WRITE_MODE: + state.mode = val; + kbd_update_irq(); + break; + case CMD_WRITE_AUX_BUF: + mouse_queue(val); + mouse_queue(RESPONSE_ACK); + break; + case CMD_WRITE_AUX: + /* The OS wants to send a command to the mouse */ + mouse_queue(RESPONSE_ACK); + switch (val) { + case 0xe6: + /* set scaling = 1:1 */ + state.mstatus &= ~AUX_SCALING_FLAG; + break; + case 0xe8: + /* set resolution */ + state.mres = val; + break; + case 0xe9: + /* Report mouse status/config */ + mouse_queue(state.mstatus); + mouse_queue(state.mres); + mouse_queue(state.msample); + break; + case 0xf2: + /* send ID */ + mouse_queue(0); /* normal mouse */ + break; + case 0xf3: + /* set sample rate */ + state.msample = val; + break; + case 0xf4: + /* enable reporting */ + state.mstatus |= AUX_ENABLE_REPORTING; + break; + case 0xf5: + state.mstatus &= ~AUX_ENABLE_REPORTING; + break; + case 0xf6: + /* set defaults, just fall through to reset */ + case 0xff: + /* reset */ + state.mstatus = 0x0; + state.mres = AUX_DEFAULT_RESOLUTION; + state.msample = AUX_DEFAULT_SAMPLE; + break; + default: + break; + } + break; + case 0: + /* Just send the ID */ + kbd_queue(RESPONSE_ACK); + kbd_queue(0xab); + kbd_queue(0x41); + kbd_update_irq(); + break; + default: + /* Yeah whatever */ + break; + } + state.write_cmd = 0; +} + +static void kbd_reset(void) +{ + state = (struct kbd_state) { + .status = KBD_STATUS_SYS | KBD_STATUS_A2 | KBD_STATUS_INH, /* 0x1c */ + .mode = KBD_MODE_KBD_INT | KBD_MODE_SYS, /* 0x3 */ + .mres = AUX_DEFAULT_RESOLUTION, + .msample = AUX_DEFAULT_SAMPLE, + }; +} + +/* + * We can map the letters and numbers without a fuss, + * but the other characters not so much. + */ +static char letters[26] = { + 0x1c, 0x32, 0x21, 0x23, 0x24, /* a-e */ + 0x2b, 0x34, 0x33, 0x43, 0x3b, /* f-j */ + 0x42, 0x4b, 0x3a, 0x31, 0x44, /* k-o */ + 0x4d, 0x15, 0x2d, 0x1b, 0x2c, /* p-t */ + 0x3c, 0x2a, 0x1d, 0x22, 0x35, /* u-y */ + 0x1a, +}; + +static char num[10] = { + 0x45, 0x16, 0x1e, 0x26, 0x2e, 0x23, 0x36, 0x3d, 0x3e, 0x46, +}; + +/* + * This is called when the VNC server receives a key event + * The reason this function is such a beast is that we have + * to convert from ASCII characters (which is what VNC gets) + * to PC keyboard scancodes, which is what Linux expects to + * get from its keyboard. ASCII and the scancode set don't + * really seem to mesh in any good way beyond some basics with + * the letters and numbers. + */ +void kbd_handle_key(rfbBool down, rfbKeySym key, rfbClientPtr cl) +{ + char tosend = 0; + + if (key >= 0x41 && key <= 0x5a) + key += 0x20; /* convert to lowercase */ + + if (key >= 0x61 && key <= 0x7a) /* a-z */ + tosend = letters[key - 0x61]; + + if (key >= 0x30 && key <= 0x39) + tosend = num[key - 0x30]; + + switch (key) { + case XK_Insert: kbd_queue(0xe0); tosend = 0x70; break; + case XK_Delete: kbd_queue(0xe0); tosend = 0x71; break; + case XK_Up: kbd_queue(0xe0); tosend = 0x75; break; + case XK_Down: kbd_queue(0xe0); tosend = 0x72; break; + case XK_Left: kbd_queue(0xe0); tosend = 0x6b; break; + case XK_Right: kbd_queue(0xe0); tosend = 0x74; break; + case XK_Page_Up: kbd_queue(0xe0); tosend = 0x7d; break; + case XK_Page_Down: kbd_queue(0xe0); tosend = 0x7a; break; + case XK_Home: kbd_queue(0xe0); tosend = 0x6c; break; + case XK_BackSpace: tosend = 0x66; break; + case XK_Tab: tosend = 0x0d; break; + case XK_Return: tosend = 0x5a; break; + case XK_Escape: tosend = 0x76; break; + case XK_End: tosend = 0x69; break; + case XK_Shift_L: tosend = 0x12; break; + case XK_Shift_R: tosend = 0x59; break; + case XK_Control_R: kbd_queue(0xe0); + case XK_Control_L: tosend = 0x14; break; + case XK_Alt_R: kbd_queue(0xe0); + case XK_Alt_L: tosend = 0x11; break; + case XK_quoteleft: tosend = 0x0e; break; + case XK_minus: tosend = 0x4e; break; + case XK_equal: tosend = 0x55; break; + case XK_bracketleft: tosend = 0x54; break; + case XK_bracketright: tosend = 0x5b; break; + case XK_backslash: tosend = 0x5d; break; + case XK_Caps_Lock: tosend = 0x58; break; + case XK_semicolon: tosend = 0x4c; break; + case XK_quoteright: tosend = 0x52; break; + case XK_comma: tosend = 0x41; break; + case XK_period: tosend = 0x49; break; + case XK_slash: tosend = 0x4a; break; + case XK_space: tosend = 0x29; break; + + /* + * This is where I handle the shifted characters. + * They don't really map nicely the way A-Z maps to a-z, + * so I'm doing it manually + */ + case XK_exclam: tosend = 0x16; break; + case XK_quotedbl: tosend = 0x52; break; + case XK_numbersign: tosend = 0x26; break; + case XK_dollar: tosend = 0x25; break; + case XK_percent: tosend = 0x2e; break; + case XK_ampersand: tosend = 0x3d; break; + case XK_parenleft: tosend = 0x46; break; + case XK_parenright: tosend = 0x45; break; + case XK_asterisk: tosend = 0x3e; break; + case XK_plus: tosend = 0x55; break; + case XK_colon: tosend = 0x4c; break; + case XK_less: tosend = 0x41; break; + case XK_greater: tosend = 0x49; break; + case XK_question: tosend = 0x4a; break; + case XK_at: tosend = 0x1e; break; + case XK_asciicircum: tosend = 0x36; break; + case XK_underscore: tosend = 0x4e; break; + case XK_braceleft: tosend = 0x54; break; + case XK_braceright: tosend = 0x5b; break; + case XK_bar: tosend = 0x5d; break; + case XK_asciitilde: tosend = 0x0e; break; + default: break; + } + + /* + * If this is a "key up" event (the user has released the key, we + * need to send 0xf0 first. + */ + if (!down && tosend != 0x0) + kbd_queue(0xf0); + + if (tosend) + kbd_queue(tosend); +} + +/* The previous X and Y coordinates of the mouse */ +static int xlast, ylast = -1; + +/* + * This function is called by the VNC server whenever a mouse event occurs. + */ +void kbd_handle_ptr(int buttonMask, int x, int y, rfbClientPtr cl) +{ + int dx, dy; + char b1 = 0x8; + + /* The VNC mask and the PS/2 button encoding are the same */ + b1 |= buttonMask; + + if (xlast >= 0 && ylast >= 0) { + /* The PS/2 mouse sends deltas, not absolutes */ + dx = x - xlast; + dy = ylast - y; + + /* Set overflow bits if needed */ + if (dy > 255) + b1 |= 0x80; + if (dx > 255) + b1 |= 0x40; + + /* Set negative bits if needed */ + if (dy < 0) + b1 |= 0x20; + if (dx < 0) + b1 |= 0x10; + + mouse_queue(b1); + mouse_queue(dx); + mouse_queue(dy); + } + + xlast = x; + ylast = y; + rfbDefaultPtrAddEvent(buttonMask, x, y, cl); +} + +/* + * Called when the OS has written to one of the keyboard's ports (0x60 or 0x64) + */ +static bool kbd_in(struct ioport *ioport, struct kvm *kvm, u16 port, void *data, int size, u32 count) +{ + u32 result; + + if (port == 0x64) { + result = kbd_read_status(); + ioport__write8(data, (char)result); + } else { + result = kbd_read_data(); + ioport__write32(data, result); + } + return true; +} + +/* + * Called when the OS attempts to read from a keyboard port (0x60 or 0x64) + */ +static bool kbd_out(struct ioport *ioport, struct kvm *kvm, u16 port, void *data, int size, u32 count) +{ + if (port == 0x64) + kbd_write_command(*((u32 *)data)); + else + kbd_write_data(*((u32 *)data)); + + return true; +} + +static struct ioport_operations kbd_ops = { + .io_in = kbd_in, + .io_out = kbd_out, +}; + +void kbd__init(struct kvm *kvm) +{ + kbd_reset(); + state.kvm = kvm; + ioport__register(0x60, &kbd_ops, 2, NULL); + ioport__register(0x64, &kbd_ops, 2, NULL); +} diff --git a/tools/kvm/hw/vesa.c b/tools/kvm/hw/vesa.c index 85fe1a90d140..b99f2de8ff9a 100644 --- a/tools/kvm/hw/vesa.c +++ b/tools/kvm/hw/vesa.c @@ -7,6 +7,7 @@ #include "kvm/irq.h" #include "kvm/kvm.h" #include "kvm/pci.h" +#include "kvm/i8042.h" #include #include @@ -99,6 +100,8 @@ void *vesa__dovnc(void *v) server = rfbGetScreen(&argc, (char **) argv, VESA_WIDTH, VESA_HEIGHT, 8, 3, 4); server->frameBuffer = videomem; server->alwaysShared = TRUE; + server->kbdAddEvent = kbd_handle_key; + server->ptrAddEvent = kbd_handle_ptr; rfbInitServer(server); while (rfbIsActive(server)) { diff --git a/tools/kvm/include/kvm/i8042.h b/tools/kvm/include/kvm/i8042.h new file mode 100644 index 000000000000..3416b64e113d --- /dev/null +++ b/tools/kvm/include/kvm/i8042.h @@ -0,0 +1,19 @@ +#ifndef KVM__PCKBD_H +#define KVM__PCKBD_H + +void kbd__init(struct kvm *kvm); + +#ifdef CONFIG_HAS_VNCSERVER +#include +#include + +void kbd_handle_key(rfbBool, rfbKeySym, rfbClientPtr); +void kbd_handle_ptr(int, int, int, rfbClientPtr); + +#else + +void kbd__init(struct kvm *kvm) { } + +#endif + +#endif diff --git a/tools/kvm/ioport.c b/tools/kvm/ioport.c index e00fb59ed151..2d69ae4c3397 100644 --- a/tools/kvm/ioport.c +++ b/tools/kvm/ioport.c @@ -168,10 +168,6 @@ void ioport__setup_legacy(void) /* PORT 0040-005F - PIT - PROGRAMMABLE INTERVAL TIMER (8253, 8254) */ ioport__register(0x0040, &dummy_read_write_ioport_ops, 4, NULL); - /* PORT 0060-006F - KEYBOARD CONTROLLER 804x (8041, 8042) (or PPI (8255) on PC,XT) */ - ioport__register(0x0060, &dummy_read_write_ioport_ops, 2, NULL); - ioport__register(0x0064, &dummy_read_write_ioport_ops, 1, NULL); - /* 0x00A0 - 0x00AF - 8259A PIC 2 */ ioport__register(0x00A0, &dummy_read_write_ioport_ops, 2, NULL); diff --git a/tools/kvm/kvm-run.c b/tools/kvm/kvm-run.c index b0ef33388584..034a3ba2ef48 100644 --- a/tools/kvm/kvm-run.c +++ b/tools/kvm/kvm-run.c @@ -30,6 +30,7 @@ #include #include #include +#include /* header files for gitish interface */ #include @@ -626,8 +627,10 @@ int kvm_cmd_run(int argc, const char **argv, const char *prefix) kvm__init_ram(kvm); - if (vnc) + if (vnc) { + kbd__init(kvm); vesa__init(kvm); + } thread_pool__init(nr_online_cpus); ioeventfd__start(); -- 2.39.5