5a37532d08
Add a linked list of keyboard handlers. Added handlers will go to the head of the list. Removed handlers will be zapped from the list. The head of the list will be used for events. This fixes the keyboard-dead-after-usb-kbd-unplug issue, key events will be re-routed to the ps/2 kbd instead of being discarded. [ v2: fix cut+paste bug found my Markus ] Signed-off-by: Gerd Hoffmann <kraxel@redhat.com> Message-id: 1366798118-3248-3-git-send-email-kraxel@redhat.com Signed-off-by: Anthony Liguori <aliguori@us.ibm.com>
499 lines
14 KiB
C
499 lines
14 KiB
C
/*
|
|
* QEMU HID devices
|
|
*
|
|
* Copyright (c) 2005 Fabrice Bellard
|
|
* Copyright (c) 2007 OpenMoko, Inc. (andrew@openedhand.com)
|
|
*
|
|
* 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 "hw/hw.h"
|
|
#include "ui/console.h"
|
|
#include "qemu/timer.h"
|
|
#include "hw/input/hid.h"
|
|
|
|
#define HID_USAGE_ERROR_ROLLOVER 0x01
|
|
#define HID_USAGE_POSTFAIL 0x02
|
|
#define HID_USAGE_ERROR_UNDEFINED 0x03
|
|
|
|
/* Indices are QEMU keycodes, values are from HID Usage Table. Indices
|
|
* above 0x80 are for keys that come after 0xe0 or 0xe1+0x1d or 0xe1+0x9d. */
|
|
static const uint8_t hid_usage_keys[0x100] = {
|
|
0x00, 0x29, 0x1e, 0x1f, 0x20, 0x21, 0x22, 0x23,
|
|
0x24, 0x25, 0x26, 0x27, 0x2d, 0x2e, 0x2a, 0x2b,
|
|
0x14, 0x1a, 0x08, 0x15, 0x17, 0x1c, 0x18, 0x0c,
|
|
0x12, 0x13, 0x2f, 0x30, 0x28, 0xe0, 0x04, 0x16,
|
|
0x07, 0x09, 0x0a, 0x0b, 0x0d, 0x0e, 0x0f, 0x33,
|
|
0x34, 0x35, 0xe1, 0x31, 0x1d, 0x1b, 0x06, 0x19,
|
|
0x05, 0x11, 0x10, 0x36, 0x37, 0x38, 0xe5, 0x55,
|
|
0xe2, 0x2c, 0x32, 0x3a, 0x3b, 0x3c, 0x3d, 0x3e,
|
|
0x3f, 0x40, 0x41, 0x42, 0x43, 0x53, 0x47, 0x5f,
|
|
0x60, 0x61, 0x56, 0x5c, 0x5d, 0x5e, 0x57, 0x59,
|
|
0x5a, 0x5b, 0x62, 0x63, 0x00, 0x00, 0x00, 0x44,
|
|
0x45, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e,
|
|
0xe8, 0xe9, 0x71, 0x72, 0x73, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x85, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65,
|
|
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x58, 0xe4, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x46,
|
|
0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x4a,
|
|
0x52, 0x4b, 0x00, 0x50, 0x00, 0x4f, 0x00, 0x4d,
|
|
0x51, 0x4e, 0x49, 0x4c, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
};
|
|
|
|
bool hid_has_events(HIDState *hs)
|
|
{
|
|
return hs->n > 0 || hs->idle_pending;
|
|
}
|
|
|
|
static void hid_idle_timer(void *opaque)
|
|
{
|
|
HIDState *hs = opaque;
|
|
|
|
hs->idle_pending = true;
|
|
hs->event(hs);
|
|
}
|
|
|
|
static void hid_del_idle_timer(HIDState *hs)
|
|
{
|
|
if (hs->idle_timer) {
|
|
qemu_del_timer(hs->idle_timer);
|
|
qemu_free_timer(hs->idle_timer);
|
|
hs->idle_timer = NULL;
|
|
}
|
|
}
|
|
|
|
void hid_set_next_idle(HIDState *hs)
|
|
{
|
|
if (hs->idle) {
|
|
uint64_t expire_time = qemu_get_clock_ns(vm_clock) +
|
|
get_ticks_per_sec() * hs->idle * 4 / 1000;
|
|
if (!hs->idle_timer) {
|
|
hs->idle_timer = qemu_new_timer_ns(vm_clock, hid_idle_timer, hs);
|
|
}
|
|
qemu_mod_timer_ns(hs->idle_timer, expire_time);
|
|
} else {
|
|
hid_del_idle_timer(hs);
|
|
}
|
|
}
|
|
|
|
static void hid_pointer_event_clear(HIDPointerEvent *e, int buttons)
|
|
{
|
|
e->xdx = e->ydy = e->dz = 0;
|
|
e->buttons_state = buttons;
|
|
}
|
|
|
|
static void hid_pointer_event_combine(HIDPointerEvent *e, int xyrel,
|
|
int x1, int y1, int z1) {
|
|
if (xyrel) {
|
|
e->xdx += x1;
|
|
e->ydy += y1;
|
|
} else {
|
|
e->xdx = x1;
|
|
e->ydy = y1;
|
|
/* Windows drivers do not like the 0/0 position and ignore such
|
|
* events. */
|
|
if (!(x1 | y1)) {
|
|
e->xdx = 1;
|
|
}
|
|
}
|
|
e->dz += z1;
|
|
}
|
|
|
|
static void hid_pointer_event(void *opaque,
|
|
int x1, int y1, int z1, int buttons_state)
|
|
{
|
|
HIDState *hs = opaque;
|
|
unsigned use_slot = (hs->head + hs->n - 1) & QUEUE_MASK;
|
|
unsigned previous_slot = (use_slot - 1) & QUEUE_MASK;
|
|
|
|
/* We combine events where feasible to keep the queue small. We shouldn't
|
|
* combine anything with the first event of a particular button state, as
|
|
* that would change the location of the button state change. When the
|
|
* queue is empty, a second event is needed because we don't know if
|
|
* the first event changed the button state. */
|
|
if (hs->n == QUEUE_LENGTH) {
|
|
/* Queue full. Discard old button state, combine motion normally. */
|
|
hs->ptr.queue[use_slot].buttons_state = buttons_state;
|
|
} else if (hs->n < 2 ||
|
|
hs->ptr.queue[use_slot].buttons_state != buttons_state ||
|
|
hs->ptr.queue[previous_slot].buttons_state !=
|
|
hs->ptr.queue[use_slot].buttons_state) {
|
|
/* Cannot or should not combine, so add an empty item to the queue. */
|
|
QUEUE_INCR(use_slot);
|
|
hs->n++;
|
|
hid_pointer_event_clear(&hs->ptr.queue[use_slot], buttons_state);
|
|
}
|
|
hid_pointer_event_combine(&hs->ptr.queue[use_slot],
|
|
hs->kind == HID_MOUSE,
|
|
x1, y1, z1);
|
|
hs->event(hs);
|
|
}
|
|
|
|
static void hid_keyboard_event(void *opaque, int keycode)
|
|
{
|
|
HIDState *hs = opaque;
|
|
int slot;
|
|
|
|
if (hs->n == QUEUE_LENGTH) {
|
|
fprintf(stderr, "usb-kbd: warning: key event queue full\n");
|
|
return;
|
|
}
|
|
slot = (hs->head + hs->n) & QUEUE_MASK; hs->n++;
|
|
hs->kbd.keycodes[slot] = keycode;
|
|
hs->event(hs);
|
|
}
|
|
|
|
static void hid_keyboard_process_keycode(HIDState *hs)
|
|
{
|
|
uint8_t hid_code, key;
|
|
int i, keycode, slot;
|
|
|
|
if (hs->n == 0) {
|
|
return;
|
|
}
|
|
slot = hs->head & QUEUE_MASK; QUEUE_INCR(hs->head); hs->n--;
|
|
keycode = hs->kbd.keycodes[slot];
|
|
|
|
key = keycode & 0x7f;
|
|
hid_code = hid_usage_keys[key | ((hs->kbd.modifiers >> 1) & (1 << 7))];
|
|
hs->kbd.modifiers &= ~(1 << 8);
|
|
|
|
switch (hid_code) {
|
|
case 0x00:
|
|
return;
|
|
|
|
case 0xe0:
|
|
if (hs->kbd.modifiers & (1 << 9)) {
|
|
hs->kbd.modifiers ^= 3 << 8;
|
|
return;
|
|
}
|
|
case 0xe1 ... 0xe7:
|
|
if (keycode & (1 << 7)) {
|
|
hs->kbd.modifiers &= ~(1 << (hid_code & 0x0f));
|
|
return;
|
|
}
|
|
case 0xe8 ... 0xef:
|
|
hs->kbd.modifiers |= 1 << (hid_code & 0x0f);
|
|
return;
|
|
}
|
|
|
|
if (keycode & (1 << 7)) {
|
|
for (i = hs->kbd.keys - 1; i >= 0; i--) {
|
|
if (hs->kbd.key[i] == hid_code) {
|
|
hs->kbd.key[i] = hs->kbd.key[-- hs->kbd.keys];
|
|
hs->kbd.key[hs->kbd.keys] = 0x00;
|
|
break;
|
|
}
|
|
}
|
|
if (i < 0) {
|
|
return;
|
|
}
|
|
} else {
|
|
for (i = hs->kbd.keys - 1; i >= 0; i--) {
|
|
if (hs->kbd.key[i] == hid_code) {
|
|
break;
|
|
}
|
|
}
|
|
if (i < 0) {
|
|
if (hs->kbd.keys < sizeof(hs->kbd.key)) {
|
|
hs->kbd.key[hs->kbd.keys++] = hid_code;
|
|
}
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
static inline int int_clamp(int val, int vmin, int vmax)
|
|
{
|
|
if (val < vmin) {
|
|
return vmin;
|
|
} else if (val > vmax) {
|
|
return vmax;
|
|
} else {
|
|
return val;
|
|
}
|
|
}
|
|
|
|
void hid_pointer_activate(HIDState *hs)
|
|
{
|
|
if (!hs->ptr.mouse_grabbed) {
|
|
qemu_activate_mouse_event_handler(hs->ptr.eh_entry);
|
|
hs->ptr.mouse_grabbed = 1;
|
|
}
|
|
}
|
|
|
|
int hid_pointer_poll(HIDState *hs, uint8_t *buf, int len)
|
|
{
|
|
int dx, dy, dz, b, l;
|
|
int index;
|
|
HIDPointerEvent *e;
|
|
|
|
hs->idle_pending = false;
|
|
|
|
hid_pointer_activate(hs);
|
|
|
|
/* When the buffer is empty, return the last event. Relative
|
|
movements will all be zero. */
|
|
index = (hs->n ? hs->head : hs->head - 1);
|
|
e = &hs->ptr.queue[index & QUEUE_MASK];
|
|
|
|
if (hs->kind == HID_MOUSE) {
|
|
dx = int_clamp(e->xdx, -127, 127);
|
|
dy = int_clamp(e->ydy, -127, 127);
|
|
e->xdx -= dx;
|
|
e->ydy -= dy;
|
|
} else {
|
|
dx = e->xdx;
|
|
dy = e->ydy;
|
|
}
|
|
dz = int_clamp(e->dz, -127, 127);
|
|
e->dz -= dz;
|
|
|
|
b = 0;
|
|
if (e->buttons_state & MOUSE_EVENT_LBUTTON) {
|
|
b |= 0x01;
|
|
}
|
|
if (e->buttons_state & MOUSE_EVENT_RBUTTON) {
|
|
b |= 0x02;
|
|
}
|
|
if (e->buttons_state & MOUSE_EVENT_MBUTTON) {
|
|
b |= 0x04;
|
|
}
|
|
|
|
if (hs->n &&
|
|
!e->dz &&
|
|
(hs->kind == HID_TABLET || (!e->xdx && !e->ydy))) {
|
|
/* that deals with this event */
|
|
QUEUE_INCR(hs->head);
|
|
hs->n--;
|
|
}
|
|
|
|
/* Appears we have to invert the wheel direction */
|
|
dz = 0 - dz;
|
|
l = 0;
|
|
switch (hs->kind) {
|
|
case HID_MOUSE:
|
|
if (len > l) {
|
|
buf[l++] = b;
|
|
}
|
|
if (len > l) {
|
|
buf[l++] = dx;
|
|
}
|
|
if (len > l) {
|
|
buf[l++] = dy;
|
|
}
|
|
if (len > l) {
|
|
buf[l++] = dz;
|
|
}
|
|
break;
|
|
|
|
case HID_TABLET:
|
|
if (len > l) {
|
|
buf[l++] = b;
|
|
}
|
|
if (len > l) {
|
|
buf[l++] = dx & 0xff;
|
|
}
|
|
if (len > l) {
|
|
buf[l++] = dx >> 8;
|
|
}
|
|
if (len > l) {
|
|
buf[l++] = dy & 0xff;
|
|
}
|
|
if (len > l) {
|
|
buf[l++] = dy >> 8;
|
|
}
|
|
if (len > l) {
|
|
buf[l++] = dz;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
abort();
|
|
}
|
|
|
|
return l;
|
|
}
|
|
|
|
int hid_keyboard_poll(HIDState *hs, uint8_t *buf, int len)
|
|
{
|
|
hs->idle_pending = false;
|
|
|
|
if (len < 2) {
|
|
return 0;
|
|
}
|
|
|
|
hid_keyboard_process_keycode(hs);
|
|
|
|
buf[0] = hs->kbd.modifiers & 0xff;
|
|
buf[1] = 0;
|
|
if (hs->kbd.keys > 6) {
|
|
memset(buf + 2, HID_USAGE_ERROR_ROLLOVER, MIN(8, len) - 2);
|
|
} else {
|
|
memcpy(buf + 2, hs->kbd.key, MIN(8, len) - 2);
|
|
}
|
|
|
|
return MIN(8, len);
|
|
}
|
|
|
|
int hid_keyboard_write(HIDState *hs, uint8_t *buf, int len)
|
|
{
|
|
if (len > 0) {
|
|
int ledstate = 0;
|
|
/* 0x01: Num Lock LED
|
|
* 0x02: Caps Lock LED
|
|
* 0x04: Scroll Lock LED
|
|
* 0x08: Compose LED
|
|
* 0x10: Kana LED */
|
|
hs->kbd.leds = buf[0];
|
|
if (hs->kbd.leds & 0x04) {
|
|
ledstate |= QEMU_SCROLL_LOCK_LED;
|
|
}
|
|
if (hs->kbd.leds & 0x01) {
|
|
ledstate |= QEMU_NUM_LOCK_LED;
|
|
}
|
|
if (hs->kbd.leds & 0x02) {
|
|
ledstate |= QEMU_CAPS_LOCK_LED;
|
|
}
|
|
kbd_put_ledstate(ledstate);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
void hid_reset(HIDState *hs)
|
|
{
|
|
switch (hs->kind) {
|
|
case HID_KEYBOARD:
|
|
memset(hs->kbd.keycodes, 0, sizeof(hs->kbd.keycodes));
|
|
memset(hs->kbd.key, 0, sizeof(hs->kbd.key));
|
|
hs->kbd.keys = 0;
|
|
break;
|
|
case HID_MOUSE:
|
|
case HID_TABLET:
|
|
memset(hs->ptr.queue, 0, sizeof(hs->ptr.queue));
|
|
break;
|
|
}
|
|
hs->head = 0;
|
|
hs->n = 0;
|
|
hs->protocol = 1;
|
|
hs->idle = 0;
|
|
hs->idle_pending = false;
|
|
hid_del_idle_timer(hs);
|
|
}
|
|
|
|
void hid_free(HIDState *hs)
|
|
{
|
|
switch (hs->kind) {
|
|
case HID_KEYBOARD:
|
|
qemu_remove_kbd_event_handler(hs->kbd.eh_entry);
|
|
break;
|
|
case HID_MOUSE:
|
|
case HID_TABLET:
|
|
qemu_remove_mouse_event_handler(hs->ptr.eh_entry);
|
|
break;
|
|
}
|
|
hid_del_idle_timer(hs);
|
|
}
|
|
|
|
void hid_init(HIDState *hs, int kind, HIDEventFunc event)
|
|
{
|
|
hs->kind = kind;
|
|
hs->event = event;
|
|
|
|
if (hs->kind == HID_KEYBOARD) {
|
|
hs->kbd.eh_entry = qemu_add_kbd_event_handler(hid_keyboard_event, hs);
|
|
} else if (hs->kind == HID_MOUSE) {
|
|
hs->ptr.eh_entry = qemu_add_mouse_event_handler(hid_pointer_event, hs,
|
|
0, "QEMU HID Mouse");
|
|
} else if (hs->kind == HID_TABLET) {
|
|
hs->ptr.eh_entry = qemu_add_mouse_event_handler(hid_pointer_event, hs,
|
|
1, "QEMU HID Tablet");
|
|
}
|
|
}
|
|
|
|
static int hid_post_load(void *opaque, int version_id)
|
|
{
|
|
HIDState *s = opaque;
|
|
|
|
hid_set_next_idle(s);
|
|
return 0;
|
|
}
|
|
|
|
static const VMStateDescription vmstate_hid_ptr_queue = {
|
|
.name = "HIDPointerEventQueue",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_INT32(xdx, HIDPointerEvent),
|
|
VMSTATE_INT32(ydy, HIDPointerEvent),
|
|
VMSTATE_INT32(dz, HIDPointerEvent),
|
|
VMSTATE_INT32(buttons_state, HIDPointerEvent),
|
|
VMSTATE_END_OF_LIST()
|
|
}
|
|
};
|
|
|
|
const VMStateDescription vmstate_hid_ptr_device = {
|
|
.name = "HIDPointerDevice",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.post_load = hid_post_load,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_STRUCT_ARRAY(ptr.queue, HIDState, QUEUE_LENGTH, 0,
|
|
vmstate_hid_ptr_queue, HIDPointerEvent),
|
|
VMSTATE_UINT32(head, HIDState),
|
|
VMSTATE_UINT32(n, HIDState),
|
|
VMSTATE_INT32(protocol, HIDState),
|
|
VMSTATE_UINT8(idle, HIDState),
|
|
VMSTATE_END_OF_LIST(),
|
|
}
|
|
};
|
|
|
|
const VMStateDescription vmstate_hid_keyboard_device = {
|
|
.name = "HIDKeyboardDevice",
|
|
.version_id = 1,
|
|
.minimum_version_id = 1,
|
|
.post_load = hid_post_load,
|
|
.fields = (VMStateField[]) {
|
|
VMSTATE_UINT32_ARRAY(kbd.keycodes, HIDState, QUEUE_LENGTH),
|
|
VMSTATE_UINT32(head, HIDState),
|
|
VMSTATE_UINT32(n, HIDState),
|
|
VMSTATE_UINT16(kbd.modifiers, HIDState),
|
|
VMSTATE_UINT8(kbd.leds, HIDState),
|
|
VMSTATE_UINT8_ARRAY(kbd.key, HIDState, 16),
|
|
VMSTATE_INT32(kbd.keys, HIDState),
|
|
VMSTATE_INT32(protocol, HIDState),
|
|
VMSTATE_UINT8(idle, HIDState),
|
|
VMSTATE_END_OF_LIST(),
|
|
}
|
|
};
|