qemu-e2k/hw/input/hid.c

612 lines
18 KiB
C
Raw Normal View History

/*
* 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, 0x39, 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) {
timer_del(hs->idle_timer);
timer_free(hs->idle_timer);
hs->idle_timer = NULL;
}
}
void hid_set_next_idle(HIDState *hs)
{
if (hs->idle) {
uint64_t expire_time = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) +
get_ticks_per_sec() * hs->idle * 4 / 1000;
if (!hs->idle_timer) {
hs->idle_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, hid_idle_timer, hs);
}
timer_mod_ns(hs->idle_timer, expire_time);
} else {
hid_del_idle_timer(hs);
}
}
static void hid_pointer_event(DeviceState *dev, QemuConsole *src,
InputEvent *evt)
{
static const int bmap[INPUT_BUTTON__MAX] = {
[INPUT_BUTTON_LEFT] = 0x01,
[INPUT_BUTTON_RIGHT] = 0x02,
[INPUT_BUTTON_MIDDLE] = 0x04,
};
HIDState *hs = (HIDState *)dev;
HIDPointerEvent *e;
assert(hs->n < QUEUE_LENGTH);
e = &hs->ptr.queue[(hs->head + hs->n) & QUEUE_MASK];
switch (evt->type) {
case INPUT_EVENT_KIND_REL:
if (evt->u.rel->axis == INPUT_AXIS_X) {
e->xdx += evt->u.rel->value;
} else if (evt->u.rel->axis == INPUT_AXIS_Y) {
e->ydy += evt->u.rel->value;
}
break;
case INPUT_EVENT_KIND_ABS:
if (evt->u.rel->axis == INPUT_AXIS_X) {
e->xdx = evt->u.rel->value;
} else if (evt->u.rel->axis == INPUT_AXIS_Y) {
e->ydy = evt->u.rel->value;
}
break;
case INPUT_EVENT_KIND_BTN:
if (evt->u.btn->down) {
e->buttons_state |= bmap[evt->u.btn->button];
qapi: Change munging of CamelCase enum values When munging enum values, the fact that we were passing the entire prefix + value through camel_to_upper() meant that enum values spelled with CamelCase could be turned into CAMEL_CASE. However, this provides a potential collision (both OneTwo and One-Two would munge into ONE_TWO) for enum types, when the same two names are valid side-by-side as QAPI member names. By changing the generation of enum constants to always be prefix + '_' + c_name(value, False).upper(), and ensuring that there are no case collisions (in the next patches), we no longer have to worry about names that would be distinct as QAPI members but collide as variant tag names, without having to think about what munging the heuristics in camel_to_upper() will actually perform on an enum value. Making the change will affect enums that did not follow coding conventions, using 'CamelCase' rather than desired 'lower-case'. Thankfully, there are only two culprits: InputButton and ErrorClass. We already tweaked ErrorClass to make it an alias of QapiErrorClass, where only the alias needs changing rather than the whole tree. So the bulk of this change is modifying INPUT_BUTTON_WHEEL_UP to the new INPUT_BUTTON_WHEELUP (and likewise for WHEELDOWN). That part of this commit may later need reverting if we rename the enum constants from 'WheelUp' to 'wheel-up' as part of moving x-input-send-event to a stable interface; but at least we have documentation bread crumbs in place to remind us (commit 513e7cd), and it matches the fact that SDL constants are also spelled SDL_BUTTON_WHEELUP. Suggested by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Eric Blake <eblake@redhat.com> Message-Id: <1447836791-369-27-git-send-email-eblake@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 09:53:01 +01:00
if (evt->u.btn->button == INPUT_BUTTON_WHEELUP) {
e->dz--;
qapi: Change munging of CamelCase enum values When munging enum values, the fact that we were passing the entire prefix + value through camel_to_upper() meant that enum values spelled with CamelCase could be turned into CAMEL_CASE. However, this provides a potential collision (both OneTwo and One-Two would munge into ONE_TWO) for enum types, when the same two names are valid side-by-side as QAPI member names. By changing the generation of enum constants to always be prefix + '_' + c_name(value, False).upper(), and ensuring that there are no case collisions (in the next patches), we no longer have to worry about names that would be distinct as QAPI members but collide as variant tag names, without having to think about what munging the heuristics in camel_to_upper() will actually perform on an enum value. Making the change will affect enums that did not follow coding conventions, using 'CamelCase' rather than desired 'lower-case'. Thankfully, there are only two culprits: InputButton and ErrorClass. We already tweaked ErrorClass to make it an alias of QapiErrorClass, where only the alias needs changing rather than the whole tree. So the bulk of this change is modifying INPUT_BUTTON_WHEEL_UP to the new INPUT_BUTTON_WHEELUP (and likewise for WHEELDOWN). That part of this commit may later need reverting if we rename the enum constants from 'WheelUp' to 'wheel-up' as part of moving x-input-send-event to a stable interface; but at least we have documentation bread crumbs in place to remind us (commit 513e7cd), and it matches the fact that SDL constants are also spelled SDL_BUTTON_WHEELUP. Suggested by: Markus Armbruster <armbru@redhat.com> Signed-off-by: Eric Blake <eblake@redhat.com> Message-Id: <1447836791-369-27-git-send-email-eblake@redhat.com> [Commit message tweaked] Signed-off-by: Markus Armbruster <armbru@redhat.com>
2015-11-18 09:53:01 +01:00
} else if (evt->u.btn->button == INPUT_BUTTON_WHEELDOWN) {
e->dz++;
}
} else {
e->buttons_state &= ~bmap[evt->u.btn->button];
}
break;
default:
/* keep gcc happy */
break;
}
}
static void hid_pointer_sync(DeviceState *dev)
{
HIDState *hs = (HIDState *)dev;
HIDPointerEvent *prev, *curr, *next;
bool event_compression = false;
if (hs->n == QUEUE_LENGTH-1) {
/*
* Queue full. We are losing information, but we at least
* keep track of most recent button state.
*/
return;
}
prev = &hs->ptr.queue[(hs->head + hs->n - 1) & QUEUE_MASK];
curr = &hs->ptr.queue[(hs->head + hs->n) & QUEUE_MASK];
next = &hs->ptr.queue[(hs->head + hs->n + 1) & QUEUE_MASK];
if (hs->n > 0) {
/*
* No button state change between previous and current event
* (and previous wasn't seen by the guest yet), so there is
* motion information only and we can combine the two event
* into one.
*/
if (curr->buttons_state == prev->buttons_state) {
event_compression = true;
}
}
if (event_compression) {
/* add current motion to previous, clear current */
if (hs->kind == HID_MOUSE) {
prev->xdx += curr->xdx;
curr->xdx = 0;
prev->ydy += curr->ydy;
curr->ydy = 0;
} else {
prev->xdx = curr->xdx;
prev->ydy = curr->ydy;
}
prev->dz += curr->dz;
curr->dz = 0;
} else {
/* prepate next (clear rel, copy abs + btns) */
if (hs->kind == HID_MOUSE) {
next->xdx = 0;
next->ydy = 0;
} else {
next->xdx = curr->xdx;
next->ydy = curr->ydy;
}
next->dz = 0;
next->buttons_state = curr->buttons_state;
/* make current guest visible, notify guest */
hs->n++;
hs->event(hs);
}
}
static void hid_keyboard_event(DeviceState *dev, QemuConsole *src,
InputEvent *evt)
{
HIDState *hs = (HIDState *)dev;
int scancodes[3], i, count;
int slot;
count = qemu_input_key_value_to_scancode(evt->u.key->key,
evt->u.key->down,
scancodes);
if (hs->n + count > QUEUE_LENGTH) {
fprintf(stderr, "usb-kbd: warning: key event queue full\n");
return;
}
for (i = 0; i < count; i++) {
slot = (hs->head + hs->n) & QUEUE_MASK; hs->n++;
hs->kbd.keycodes[slot] = scancodes[i];
}
hs->event(hs);
}
static void hid_keyboard_process_keycode(HIDState *hs)
{
uint8_t hid_code, index, 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;
index = key | ((hs->kbd.modifiers & (1 << 8)) >> 1);
hid_code = hid_usage_keys[index];
hs->kbd.modifiers &= ~(1 << 8);
switch (hid_code) {
case 0x00:
return;
case 0xe0:
assert(key == 0x1d);
if (hs->kbd.modifiers & (1 << 9)) {
/* The hid_codes for the 0xe1/0x1d scancode sequence are 0xe9/0xe0.
* Here we're processing the second hid_code. By dropping bit 9
* and setting bit 8, the scancode after 0x1d will access the
* second half of the table.
*/
hs->kbd.modifiers ^= (1 << 8) | (1 << 9);
return;
}
/* fall through to process Ctrl_L */
case 0xe1 ... 0xe7:
/* Ctrl_L/Ctrl_R, Shift_L/Shift_R, Alt_L/Alt_R, Win_L/Win_R.
* Handle releases here, or fall through to process presses.
*/
if (keycode & (1 << 7)) {
hs->kbd.modifiers &= ~(1 << (hid_code & 0x0f));
return;
}
/* fall through */
case 0xe8 ... 0xe9:
/* USB modifiers are just 1 byte long. Bits 8 and 9 of
* hs->kbd.modifiers implement a state machine that detects the
* 0xe0 and 0xe1/0x1d sequences. These bits do not follow the
* usual rules where bit 7 marks released keys; they are cleared
* elsewhere in the function as the state machine dictates.
*/
hs->kbd.modifiers |= 1 << (hid_code & 0x0f);
return;
case 0xea ... 0xef:
abort();
default:
break;
}
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_input_handler_activate(hs->s);
hs->ptr.mouse_grabbed = 1;
}
}
int hid_pointer_poll(HIDState *hs, uint8_t *buf, int len)
{
int dx, dy, dz, 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;
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++] = e->buttons_state;
}
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++] = e->buttons_state;
}
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)
{
qemu_input_handler_unregister(hs->s);
hid_del_idle_timer(hs);
}
static QemuInputHandler hid_keyboard_handler = {
.name = "QEMU HID Keyboard",
.mask = INPUT_EVENT_MASK_KEY,
.event = hid_keyboard_event,
};
static QemuInputHandler hid_mouse_handler = {
.name = "QEMU HID Mouse",
.mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL,
.event = hid_pointer_event,
.sync = hid_pointer_sync,
};
static QemuInputHandler hid_tablet_handler = {
.name = "QEMU HID Tablet",
.mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS,
.event = hid_pointer_event,
.sync = hid_pointer_sync,
};
void hid_init(HIDState *hs, int kind, HIDEventFunc event)
{
hs->kind = kind;
hs->event = event;
if (hs->kind == HID_KEYBOARD) {
hs->s = qemu_input_handler_register((DeviceState *)hs,
&hid_keyboard_handler);
qemu_input_handler_activate(hs->s);
} else if (hs->kind == HID_MOUSE) {
hs->s = qemu_input_handler_register((DeviceState *)hs,
&hid_mouse_handler);
} else if (hs->kind == HID_TABLET) {
hs->s = qemu_input_handler_register((DeviceState *)hs,
&hid_tablet_handler);
}
}
static int hid_post_load(void *opaque, int version_id)
{
HIDState *s = opaque;
hid_set_next_idle(s);
if (s->n == QUEUE_LENGTH && (s->kind == HID_TABLET ||
s->kind == HID_MOUSE)) {
/*
* Handle ptr device migration from old qemu with full queue.
*
* Throw away everything but the last event, so we propagate
* at least the current button state to the guest. Also keep
* current position for the tablet, signal "no motion" for the
* mouse.
*/
HIDPointerEvent evt;
evt = s->ptr.queue[(s->head+s->n) & QUEUE_MASK];
if (s->kind == HID_MOUSE) {
evt.xdx = 0;
evt.ydy = 0;
}
s->ptr.queue[0] = evt;
s->head = 0;
s->n = 1;
}
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(),
}
};