diff options
author | 2023-10-10 11:40:56 +0000 | |
---|---|---|
committer | 2023-10-10 11:40:56 +0000 | |
commit | e02cda008591317b1625707ff8e115a4841aa889 (patch) | |
tree | aee302e3cf8b59ec2d32ec481be3d1afddfc8968 /hw/input | |
parent | cc668e6b7e0ffd8c9d130513d12053cf5eda1d3b (diff) |
Introduce Virtio-loopback epsilon release:
Epsilon release introduces a new compatibility layer which make virtio-loopback
design to work with QEMU and rust-vmm vhost-user backend without require any
changes.
Signed-off-by: Timos Ampelikiotis <t.ampelikiotis@virtualopensystems.com>
Change-Id: I52e57563e08a7d0bdc002f8e928ee61ba0c53dd9
Diffstat (limited to 'hw/input')
-rw-r--r-- | hw/input/Kconfig | 48 | ||||
-rw-r--r-- | hw/input/adb-internal.h | 53 | ||||
-rw-r--r-- | hw/input/adb-kbd.c | 408 | ||||
-rw-r--r-- | hw/input/adb-mouse.c | 279 | ||||
-rw-r--r-- | hw/input/adb.c | 324 | ||||
-rw-r--r-- | hw/input/ads7846.c | 186 | ||||
-rw-r--r-- | hw/input/hid.c | 624 | ||||
-rw-r--r-- | hw/input/lasips2.c | 288 | ||||
-rw-r--r-- | hw/input/lm832x.c | 528 | ||||
-rw-r--r-- | hw/input/meson.build | 18 | ||||
-rw-r--r-- | hw/input/pckbd.c | 814 | ||||
-rw-r--r-- | hw/input/pl050.c | 210 | ||||
-rw-r--r-- | hw/input/ps2.c | 1220 | ||||
-rw-r--r-- | hw/input/pxa2xx_keypad.c | 331 | ||||
-rw-r--r-- | hw/input/stellaris_input.c | 93 | ||||
-rw-r--r-- | hw/input/trace-events | 60 | ||||
-rw-r--r-- | hw/input/trace.h | 1 | ||||
-rw-r--r-- | hw/input/tsc2005.c | 559 | ||||
-rw-r--r-- | hw/input/tsc210x.c | 1253 | ||||
-rw-r--r-- | hw/input/vhost-user-input.c | 130 | ||||
-rw-r--r-- | hw/input/virtio-input-hid.c | 522 | ||||
-rw-r--r-- | hw/input/virtio-input-host.c | 260 | ||||
-rw-r--r-- | hw/input/virtio-input.c | 343 |
23 files changed, 8552 insertions, 0 deletions
diff --git a/hw/input/Kconfig b/hw/input/Kconfig new file mode 100644 index 000000000..55865bb38 --- /dev/null +++ b/hw/input/Kconfig @@ -0,0 +1,48 @@ +config ADB + bool + +config ADS7846 + bool + +config LM832X + bool + depends on I2C + +config PCKBD + bool + select PS2 + depends on ISA_BUS + +config PL050 + bool + select PS2 + +config PS2 + bool + +config STELLARIS_INPUT + bool + +config TSC2005 + bool + +config VIRTIO_INPUT + bool + default y + depends on VIRTIO + +config VIRTIO_INPUT_HOST + bool + default y + depends on VIRTIO_INPUT && LINUX + +config VHOST_USER_INPUT + bool + default y + depends on VIRTIO_INPUT && VHOST_USER + +config TSC210X + bool + +config LASIPS2 + select PS2 diff --git a/hw/input/adb-internal.h b/hw/input/adb-internal.h new file mode 100644 index 000000000..8d92165c4 --- /dev/null +++ b/hw/input/adb-internal.h @@ -0,0 +1,53 @@ +/* + * QEMU ADB support + * + * Copyright (c) 2004 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. + */ + +#ifndef HW_INPUT_ADB_INTERNAL_H +#define HW_INPUT_ADB_INTERNAL_H + +/* ADB commands */ + +#define ADB_BUSRESET 0x00 +#define ADB_FLUSH 0x01 +#define ADB_WRITEREG 0x08 +#define ADB_READREG 0x0c + +/* ADB device commands */ + +#define ADB_CMD_SELF_TEST 0xff +#define ADB_CMD_CHANGE_ID 0xfe +#define ADB_CMD_CHANGE_ID_AND_ACT 0xfd +#define ADB_CMD_CHANGE_ID_AND_ENABLE 0x00 + +/* ADB default device IDs (upper 4 bits of ADB command byte) */ + +#define ADB_DEVID_DONGLE 1 +#define ADB_DEVID_KEYBOARD 2 +#define ADB_DEVID_MOUSE 3 +#define ADB_DEVID_TABLET 4 +#define ADB_DEVID_MODEM 5 +#define ADB_DEVID_MISC 7 + +extern const VMStateDescription vmstate_adb_device; + +#endif diff --git a/hw/input/adb-kbd.c b/hw/input/adb-kbd.c new file mode 100644 index 000000000..a9088c910 --- /dev/null +++ b/hw/input/adb-kbd.c @@ -0,0 +1,408 @@ +/* + * QEMU ADB keyboard support + * + * Copyright (c) 2004 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 "qemu/osdep.h" +#include "hw/input/adb.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "ui/input.h" +#include "hw/input/adb-keys.h" +#include "adb-internal.h" +#include "trace.h" +#include "qom/object.h" + +OBJECT_DECLARE_TYPE(KBDState, ADBKeyboardClass, ADB_KEYBOARD) + +struct KBDState { + /*< private >*/ + ADBDevice parent_obj; + /*< public >*/ + + uint8_t data[128]; + int rptr, wptr, count; +}; + + +struct ADBKeyboardClass { + /*< private >*/ + ADBDeviceClass parent_class; + /*< public >*/ + + DeviceRealize parent_realize; +}; + +/* The adb keyboard doesn't have every key imaginable */ +#define NO_KEY 0xff + +int qcode_to_adb_keycode[] = { + /* Make sure future additions are automatically set to NO_KEY */ + [0 ... 0xff] = NO_KEY, + + [Q_KEY_CODE_SHIFT] = ADB_KEY_LEFT_SHIFT, + [Q_KEY_CODE_SHIFT_R] = ADB_KEY_RIGHT_SHIFT, + [Q_KEY_CODE_ALT] = ADB_KEY_LEFT_OPTION, + [Q_KEY_CODE_ALT_R] = ADB_KEY_RIGHT_OPTION, + [Q_KEY_CODE_CTRL] = ADB_KEY_LEFT_CONTROL, + [Q_KEY_CODE_CTRL_R] = ADB_KEY_RIGHT_CONTROL, + [Q_KEY_CODE_META_L] = ADB_KEY_COMMAND, + [Q_KEY_CODE_META_R] = ADB_KEY_COMMAND, + [Q_KEY_CODE_SPC] = ADB_KEY_SPACEBAR, + + [Q_KEY_CODE_ESC] = ADB_KEY_ESC, + [Q_KEY_CODE_1] = ADB_KEY_1, + [Q_KEY_CODE_2] = ADB_KEY_2, + [Q_KEY_CODE_3] = ADB_KEY_3, + [Q_KEY_CODE_4] = ADB_KEY_4, + [Q_KEY_CODE_5] = ADB_KEY_5, + [Q_KEY_CODE_6] = ADB_KEY_6, + [Q_KEY_CODE_7] = ADB_KEY_7, + [Q_KEY_CODE_8] = ADB_KEY_8, + [Q_KEY_CODE_9] = ADB_KEY_9, + [Q_KEY_CODE_0] = ADB_KEY_0, + [Q_KEY_CODE_MINUS] = ADB_KEY_MINUS, + [Q_KEY_CODE_EQUAL] = ADB_KEY_EQUAL, + [Q_KEY_CODE_BACKSPACE] = ADB_KEY_DELETE, + [Q_KEY_CODE_TAB] = ADB_KEY_TAB, + [Q_KEY_CODE_Q] = ADB_KEY_Q, + [Q_KEY_CODE_W] = ADB_KEY_W, + [Q_KEY_CODE_E] = ADB_KEY_E, + [Q_KEY_CODE_R] = ADB_KEY_R, + [Q_KEY_CODE_T] = ADB_KEY_T, + [Q_KEY_CODE_Y] = ADB_KEY_Y, + [Q_KEY_CODE_U] = ADB_KEY_U, + [Q_KEY_CODE_I] = ADB_KEY_I, + [Q_KEY_CODE_O] = ADB_KEY_O, + [Q_KEY_CODE_P] = ADB_KEY_P, + [Q_KEY_CODE_BRACKET_LEFT] = ADB_KEY_LEFT_BRACKET, + [Q_KEY_CODE_BRACKET_RIGHT] = ADB_KEY_RIGHT_BRACKET, + [Q_KEY_CODE_RET] = ADB_KEY_RETURN, + [Q_KEY_CODE_A] = ADB_KEY_A, + [Q_KEY_CODE_S] = ADB_KEY_S, + [Q_KEY_CODE_D] = ADB_KEY_D, + [Q_KEY_CODE_F] = ADB_KEY_F, + [Q_KEY_CODE_G] = ADB_KEY_G, + [Q_KEY_CODE_H] = ADB_KEY_H, + [Q_KEY_CODE_J] = ADB_KEY_J, + [Q_KEY_CODE_K] = ADB_KEY_K, + [Q_KEY_CODE_L] = ADB_KEY_L, + [Q_KEY_CODE_SEMICOLON] = ADB_KEY_SEMICOLON, + [Q_KEY_CODE_APOSTROPHE] = ADB_KEY_APOSTROPHE, + [Q_KEY_CODE_GRAVE_ACCENT] = ADB_KEY_GRAVE_ACCENT, + [Q_KEY_CODE_BACKSLASH] = ADB_KEY_BACKSLASH, + [Q_KEY_CODE_Z] = ADB_KEY_Z, + [Q_KEY_CODE_X] = ADB_KEY_X, + [Q_KEY_CODE_C] = ADB_KEY_C, + [Q_KEY_CODE_V] = ADB_KEY_V, + [Q_KEY_CODE_B] = ADB_KEY_B, + [Q_KEY_CODE_N] = ADB_KEY_N, + [Q_KEY_CODE_M] = ADB_KEY_M, + [Q_KEY_CODE_COMMA] = ADB_KEY_COMMA, + [Q_KEY_CODE_DOT] = ADB_KEY_PERIOD, + [Q_KEY_CODE_SLASH] = ADB_KEY_FORWARD_SLASH, + [Q_KEY_CODE_ASTERISK] = ADB_KEY_KP_MULTIPLY, + [Q_KEY_CODE_CAPS_LOCK] = ADB_KEY_CAPS_LOCK, + + [Q_KEY_CODE_F1] = ADB_KEY_F1, + [Q_KEY_CODE_F2] = ADB_KEY_F2, + [Q_KEY_CODE_F3] = ADB_KEY_F3, + [Q_KEY_CODE_F4] = ADB_KEY_F4, + [Q_KEY_CODE_F5] = ADB_KEY_F5, + [Q_KEY_CODE_F6] = ADB_KEY_F6, + [Q_KEY_CODE_F7] = ADB_KEY_F7, + [Q_KEY_CODE_F8] = ADB_KEY_F8, + [Q_KEY_CODE_F9] = ADB_KEY_F9, + [Q_KEY_CODE_F10] = ADB_KEY_F10, + [Q_KEY_CODE_F11] = ADB_KEY_F11, + [Q_KEY_CODE_F12] = ADB_KEY_F12, + [Q_KEY_CODE_PRINT] = ADB_KEY_F13, + [Q_KEY_CODE_SYSRQ] = ADB_KEY_F13, + [Q_KEY_CODE_SCROLL_LOCK] = ADB_KEY_F14, + [Q_KEY_CODE_PAUSE] = ADB_KEY_F15, + + [Q_KEY_CODE_NUM_LOCK] = ADB_KEY_KP_CLEAR, + [Q_KEY_CODE_KP_EQUALS] = ADB_KEY_KP_EQUAL, + [Q_KEY_CODE_KP_DIVIDE] = ADB_KEY_KP_DIVIDE, + [Q_KEY_CODE_KP_MULTIPLY] = ADB_KEY_KP_MULTIPLY, + [Q_KEY_CODE_KP_SUBTRACT] = ADB_KEY_KP_SUBTRACT, + [Q_KEY_CODE_KP_ADD] = ADB_KEY_KP_PLUS, + [Q_KEY_CODE_KP_ENTER] = ADB_KEY_KP_ENTER, + [Q_KEY_CODE_KP_DECIMAL] = ADB_KEY_KP_PERIOD, + [Q_KEY_CODE_KP_0] = ADB_KEY_KP_0, + [Q_KEY_CODE_KP_1] = ADB_KEY_KP_1, + [Q_KEY_CODE_KP_2] = ADB_KEY_KP_2, + [Q_KEY_CODE_KP_3] = ADB_KEY_KP_3, + [Q_KEY_CODE_KP_4] = ADB_KEY_KP_4, + [Q_KEY_CODE_KP_5] = ADB_KEY_KP_5, + [Q_KEY_CODE_KP_6] = ADB_KEY_KP_6, + [Q_KEY_CODE_KP_7] = ADB_KEY_KP_7, + [Q_KEY_CODE_KP_8] = ADB_KEY_KP_8, + [Q_KEY_CODE_KP_9] = ADB_KEY_KP_9, + + [Q_KEY_CODE_UP] = ADB_KEY_UP, + [Q_KEY_CODE_DOWN] = ADB_KEY_DOWN, + [Q_KEY_CODE_LEFT] = ADB_KEY_LEFT, + [Q_KEY_CODE_RIGHT] = ADB_KEY_RIGHT, + + [Q_KEY_CODE_HELP] = ADB_KEY_HELP, + [Q_KEY_CODE_INSERT] = ADB_KEY_HELP, + [Q_KEY_CODE_DELETE] = ADB_KEY_FORWARD_DELETE, + [Q_KEY_CODE_HOME] = ADB_KEY_HOME, + [Q_KEY_CODE_END] = ADB_KEY_END, + [Q_KEY_CODE_PGUP] = ADB_KEY_PAGE_UP, + [Q_KEY_CODE_PGDN] = ADB_KEY_PAGE_DOWN, + + [Q_KEY_CODE_POWER] = ADB_KEY_POWER +}; + +static void adb_kbd_put_keycode(void *opaque, int keycode) +{ + KBDState *s = opaque; + + if (s->count < sizeof(s->data)) { + s->data[s->wptr] = keycode; + if (++s->wptr == sizeof(s->data)) { + s->wptr = 0; + } + s->count++; + } +} + +static int adb_kbd_poll(ADBDevice *d, uint8_t *obuf) +{ + KBDState *s = ADB_KEYBOARD(d); + int keycode; + + if (s->count == 0) { + return 0; + } + keycode = s->data[s->rptr]; + s->rptr++; + if (s->rptr == sizeof(s->data)) { + s->rptr = 0; + } + s->count--; + /* + * The power key is the only two byte value key, so it is a special case. + * Since 0x7f is not a used keycode for ADB we overload it to indicate the + * power button when we're storing keycodes in our internal buffer, and + * expand it out to two bytes when we send to the guest. + */ + if (keycode == 0x7f) { + obuf[0] = 0x7f; + obuf[1] = 0x7f; + } else { + obuf[0] = keycode; + /* NOTE: the power key key-up is the two byte sequence 0xff 0xff; + * otherwise we could in theory send a second keycode in the second + * byte, but choose not to bother. + */ + obuf[1] = 0xff; + } + + return 2; +} + +static int adb_kbd_request(ADBDevice *d, uint8_t *obuf, + const uint8_t *buf, int len) +{ + KBDState *s = ADB_KEYBOARD(d); + int cmd, reg, olen; + + if ((buf[0] & 0x0f) == ADB_FLUSH) { + /* flush keyboard fifo */ + s->wptr = s->rptr = s->count = 0; + return 0; + } + + cmd = buf[0] & 0xc; + reg = buf[0] & 0x3; + olen = 0; + switch (cmd) { + case ADB_WRITEREG: + trace_adb_device_kbd_writereg(reg, buf[1]); + switch (reg) { + case 2: + /* LED status */ + break; + case 3: + switch (buf[2]) { + case ADB_CMD_SELF_TEST: + break; + case ADB_CMD_CHANGE_ID: + case ADB_CMD_CHANGE_ID_AND_ACT: + case ADB_CMD_CHANGE_ID_AND_ENABLE: + d->devaddr = buf[1] & 0xf; + trace_adb_device_kbd_request_change_addr(d->devaddr); + break; + default: + d->devaddr = buf[1] & 0xf; + /* + * we support handlers: + * 1: Apple Standard Keyboard + * 2: Apple Extended Keyboard (LShift = RShift) + * 3: Apple Extended Keyboard (LShift != RShift) + */ + if (buf[2] == 1 || buf[2] == 2 || buf[2] == 3) { + d->handler = buf[2]; + } + + trace_adb_device_kbd_request_change_addr_and_handler( + d->devaddr, d->handler); + break; + } + } + break; + case ADB_READREG: + switch (reg) { + case 0: + olen = adb_kbd_poll(d, obuf); + break; + case 1: + break; + case 2: + obuf[0] = 0x00; /* XXX: check this */ + obuf[1] = 0x07; /* led status */ + olen = 2; + break; + case 3: + obuf[0] = d->devaddr; + obuf[1] = d->handler; + olen = 2; + break; + } + trace_adb_device_kbd_readreg(reg, obuf[0], obuf[1]); + break; + } + return olen; +} + +static bool adb_kbd_has_data(ADBDevice *d) +{ + KBDState *s = ADB_KEYBOARD(d); + + return s->count > 0; +} + +/* This is where keyboard events enter this file */ +static void adb_keyboard_event(DeviceState *dev, QemuConsole *src, + InputEvent *evt) +{ + KBDState *s = (KBDState *)dev; + int qcode, keycode; + + qcode = qemu_input_key_value_to_qcode(evt->u.key.data->key); + if (qcode >= ARRAY_SIZE(qcode_to_adb_keycode)) { + return; + } + /* FIXME: take handler into account when translating qcode */ + keycode = qcode_to_adb_keycode[qcode]; + if (keycode == NO_KEY) { /* We don't want to send this to the guest */ + trace_adb_device_kbd_no_key(); + return; + } + if (evt->u.key.data->down == false) { /* if key release event */ + keycode = keycode | 0x80; /* create keyboard break code */ + } + + adb_kbd_put_keycode(s, keycode); +} + +static const VMStateDescription vmstate_adb_kbd = { + .name = "adb_kbd", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(parent_obj, KBDState, 0, vmstate_adb_device, ADBDevice), + VMSTATE_BUFFER(data, KBDState), + VMSTATE_INT32(rptr, KBDState), + VMSTATE_INT32(wptr, KBDState), + VMSTATE_INT32(count, KBDState), + VMSTATE_END_OF_LIST() + } +}; + +static void adb_kbd_reset(DeviceState *dev) +{ + ADBDevice *d = ADB_DEVICE(dev); + KBDState *s = ADB_KEYBOARD(dev); + + d->handler = 1; + d->devaddr = ADB_DEVID_KEYBOARD; + memset(s->data, 0, sizeof(s->data)); + s->rptr = 0; + s->wptr = 0; + s->count = 0; +} + +static QemuInputHandler adb_keyboard_handler = { + .name = "QEMU ADB Keyboard", + .mask = INPUT_EVENT_MASK_KEY, + .event = adb_keyboard_event, +}; + +static void adb_kbd_realizefn(DeviceState *dev, Error **errp) +{ + ADBKeyboardClass *akc = ADB_KEYBOARD_GET_CLASS(dev); + akc->parent_realize(dev, errp); + qemu_input_handler_register(dev, &adb_keyboard_handler); +} + +static void adb_kbd_initfn(Object *obj) +{ + ADBDevice *d = ADB_DEVICE(obj); + + d->devaddr = ADB_DEVID_KEYBOARD; +} + +static void adb_kbd_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ADBDeviceClass *adc = ADB_DEVICE_CLASS(oc); + ADBKeyboardClass *akc = ADB_KEYBOARD_CLASS(oc); + + device_class_set_parent_realize(dc, adb_kbd_realizefn, + &akc->parent_realize); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + + adc->devreq = adb_kbd_request; + adc->devhasdata = adb_kbd_has_data; + dc->reset = adb_kbd_reset; + dc->vmsd = &vmstate_adb_kbd; +} + +static const TypeInfo adb_kbd_type_info = { + .name = TYPE_ADB_KEYBOARD, + .parent = TYPE_ADB_DEVICE, + .instance_size = sizeof(KBDState), + .instance_init = adb_kbd_initfn, + .class_init = adb_kbd_class_init, + .class_size = sizeof(ADBKeyboardClass), +}; + +static void adb_kbd_register_types(void) +{ + type_register_static(&adb_kbd_type_info); +} + +type_init(adb_kbd_register_types) diff --git a/hw/input/adb-mouse.c b/hw/input/adb-mouse.c new file mode 100644 index 000000000..e6b341f02 --- /dev/null +++ b/hw/input/adb-mouse.c @@ -0,0 +1,279 @@ +/* + * QEMU ADB mouse support + * + * Copyright (c) 2004 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 "qemu/osdep.h" +#include "ui/console.h" +#include "hw/input/adb.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "adb-internal.h" +#include "trace.h" +#include "qom/object.h" + +OBJECT_DECLARE_TYPE(MouseState, ADBMouseClass, ADB_MOUSE) + +struct MouseState { + /*< public >*/ + ADBDevice parent_obj; + /*< private >*/ + + int buttons_state, last_buttons_state; + int dx, dy, dz; +}; + + +struct ADBMouseClass { + /*< public >*/ + ADBDeviceClass parent_class; + /*< private >*/ + + DeviceRealize parent_realize; +}; + +static void adb_mouse_event(void *opaque, + int dx1, int dy1, int dz1, int buttons_state) +{ + MouseState *s = opaque; + + s->dx += dx1; + s->dy += dy1; + s->dz += dz1; + s->buttons_state = buttons_state; +} + + +static int adb_mouse_poll(ADBDevice *d, uint8_t *obuf) +{ + MouseState *s = ADB_MOUSE(d); + int dx, dy; + + if (s->last_buttons_state == s->buttons_state && + s->dx == 0 && s->dy == 0) { + return 0; + } + + dx = s->dx; + if (dx < -63) { + dx = -63; + } else if (dx > 63) { + dx = 63; + } + + dy = s->dy; + if (dy < -63) { + dy = -63; + } else if (dy > 63) { + dy = 63; + } + + s->dx -= dx; + s->dy -= dy; + s->last_buttons_state = s->buttons_state; + + dx &= 0x7f; + dy &= 0x7f; + + if (!(s->buttons_state & MOUSE_EVENT_LBUTTON)) { + dy |= 0x80; + } + if (!(s->buttons_state & MOUSE_EVENT_RBUTTON)) { + dx |= 0x80; + } + + obuf[0] = dy; + obuf[1] = dx; + return 2; +} + +static int adb_mouse_request(ADBDevice *d, uint8_t *obuf, + const uint8_t *buf, int len) +{ + MouseState *s = ADB_MOUSE(d); + int cmd, reg, olen; + + if ((buf[0] & 0x0f) == ADB_FLUSH) { + /* flush mouse fifo */ + s->buttons_state = s->last_buttons_state; + s->dx = 0; + s->dy = 0; + s->dz = 0; + trace_adb_device_mouse_flush(); + return 0; + } + + cmd = buf[0] & 0xc; + reg = buf[0] & 0x3; + olen = 0; + switch (cmd) { + case ADB_WRITEREG: + trace_adb_device_mouse_writereg(reg, buf[1]); + switch (reg) { + case 2: + break; + case 3: + /* + * MacOS 9 has a bug in its ADB driver whereby after configuring + * the ADB bus devices it sends another write of invalid length + * to reg 3. Make sure we ignore it to prevent an address clash + * with the previous device. + */ + if (len != 3) { + return 0; + } + + switch (buf[2]) { + case ADB_CMD_SELF_TEST: + break; + case ADB_CMD_CHANGE_ID: + case ADB_CMD_CHANGE_ID_AND_ACT: + case ADB_CMD_CHANGE_ID_AND_ENABLE: + d->devaddr = buf[1] & 0xf; + trace_adb_device_mouse_request_change_addr(d->devaddr); + break; + default: + d->devaddr = buf[1] & 0xf; + /* + * we support handlers: + * 0x01: Classic Apple Mouse Protocol / 100 cpi operations + * 0x02: Classic Apple Mouse Protocol / 200 cpi operations + * we don't support handlers (at least): + * 0x03: Mouse systems A3 trackball + * 0x04: Extended Apple Mouse Protocol + * 0x2f: Microspeed mouse + * 0x42: Macally + * 0x5f: Microspeed mouse + * 0x66: Microspeed mouse + */ + if (buf[2] == 1 || buf[2] == 2) { + d->handler = buf[2]; + } + + trace_adb_device_mouse_request_change_addr_and_handler( + d->devaddr, d->handler); + break; + } + } + break; + case ADB_READREG: + switch (reg) { + case 0: + olen = adb_mouse_poll(d, obuf); + break; + case 1: + break; + case 3: + obuf[0] = d->devaddr; + obuf[1] = d->handler; + olen = 2; + break; + } + trace_adb_device_mouse_readreg(reg, obuf[0], obuf[1]); + break; + } + return olen; +} + +static bool adb_mouse_has_data(ADBDevice *d) +{ + MouseState *s = ADB_MOUSE(d); + + return !(s->last_buttons_state == s->buttons_state && + s->dx == 0 && s->dy == 0); +} + +static void adb_mouse_reset(DeviceState *dev) +{ + ADBDevice *d = ADB_DEVICE(dev); + MouseState *s = ADB_MOUSE(dev); + + d->handler = 2; + d->devaddr = ADB_DEVID_MOUSE; + s->last_buttons_state = s->buttons_state = 0; + s->dx = s->dy = s->dz = 0; +} + +static const VMStateDescription vmstate_adb_mouse = { + .name = "adb_mouse", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(parent_obj, MouseState, 0, vmstate_adb_device, + ADBDevice), + VMSTATE_INT32(buttons_state, MouseState), + VMSTATE_INT32(last_buttons_state, MouseState), + VMSTATE_INT32(dx, MouseState), + VMSTATE_INT32(dy, MouseState), + VMSTATE_INT32(dz, MouseState), + VMSTATE_END_OF_LIST() + } +}; + +static void adb_mouse_realizefn(DeviceState *dev, Error **errp) +{ + MouseState *s = ADB_MOUSE(dev); + ADBMouseClass *amc = ADB_MOUSE_GET_CLASS(dev); + + amc->parent_realize(dev, errp); + + qemu_add_mouse_event_handler(adb_mouse_event, s, 0, "QEMU ADB Mouse"); +} + +static void adb_mouse_initfn(Object *obj) +{ + ADBDevice *d = ADB_DEVICE(obj); + + d->devaddr = ADB_DEVID_MOUSE; +} + +static void adb_mouse_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + ADBDeviceClass *adc = ADB_DEVICE_CLASS(oc); + ADBMouseClass *amc = ADB_MOUSE_CLASS(oc); + + device_class_set_parent_realize(dc, adb_mouse_realizefn, + &amc->parent_realize); + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + + adc->devreq = adb_mouse_request; + adc->devhasdata = adb_mouse_has_data; + dc->reset = adb_mouse_reset; + dc->vmsd = &vmstate_adb_mouse; +} + +static const TypeInfo adb_mouse_type_info = { + .name = TYPE_ADB_MOUSE, + .parent = TYPE_ADB_DEVICE, + .instance_size = sizeof(MouseState), + .instance_init = adb_mouse_initfn, + .class_init = adb_mouse_class_init, + .class_size = sizeof(ADBMouseClass), +}; + +static void adb_mouse_register_types(void) +{ + type_register_static(&adb_mouse_type_info); +} + +type_init(adb_mouse_register_types) diff --git a/hw/input/adb.c b/hw/input/adb.c new file mode 100644 index 000000000..84331b9fc --- /dev/null +++ b/hw/input/adb.c @@ -0,0 +1,324 @@ +/* + * QEMU ADB support + * + * Copyright (c) 2004 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 "qemu/osdep.h" +#include "hw/input/adb.h" +#include "hw/qdev-properties.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "adb-internal.h" +#include "trace.h" + +/* error codes */ +#define ADB_RET_NOTPRESENT (-2) + +static const char *adb_commands[] = { + "RESET", "FLUSH", "(Reserved 0x2)", "(Reserved 0x3)", + "Reserved (0x4)", "(Reserved 0x5)", "(Reserved 0x6)", "(Reserved 0x7)", + "LISTEN r0", "LISTEN r1", "LISTEN r2", "LISTEN r3", + "TALK r0", "TALK r1", "TALK r2", "TALK r3", +}; + +static void adb_device_reset(ADBDevice *d) +{ + qdev_reset_all(DEVICE(d)); +} + +static int do_adb_request(ADBBusState *s, uint8_t *obuf, const uint8_t *buf, + int len) +{ + ADBDevice *d; + ADBDeviceClass *adc; + int devaddr, cmd, olen, i; + + cmd = buf[0] & 0xf; + if (cmd == ADB_BUSRESET) { + for (i = 0; i < s->nb_devices; i++) { + d = s->devices[i]; + adb_device_reset(d); + } + s->status = 0; + return 0; + } + + s->pending = 0; + for (i = 0; i < s->nb_devices; i++) { + d = s->devices[i]; + adc = ADB_DEVICE_GET_CLASS(d); + + if (adc->devhasdata(d)) { + s->pending |= (1 << d->devaddr); + } + } + + s->status = 0; + devaddr = buf[0] >> 4; + for (i = 0; i < s->nb_devices; i++) { + d = s->devices[i]; + adc = ADB_DEVICE_GET_CLASS(d); + + if (d->devaddr == devaddr) { + olen = adc->devreq(d, obuf, buf, len); + if (!olen) { + s->status |= ADB_STATUS_BUSTIMEOUT; + } + return olen; + } + } + + s->status |= ADB_STATUS_BUSTIMEOUT; + return ADB_RET_NOTPRESENT; +} + +int adb_request(ADBBusState *s, uint8_t *obuf, const uint8_t *buf, int len) +{ + int ret; + + trace_adb_bus_request(buf[0] >> 4, adb_commands[buf[0] & 0xf], len); + + assert(s->autopoll_blocked); + + ret = do_adb_request(s, obuf, buf, len); + + trace_adb_bus_request_done(buf[0] >> 4, adb_commands[buf[0] & 0xf], ret); + return ret; +} + +int adb_poll(ADBBusState *s, uint8_t *obuf, uint16_t poll_mask) +{ + ADBDevice *d; + int olen, i; + uint8_t buf[1]; + + olen = 0; + for (i = 0; i < s->nb_devices; i++) { + if (s->poll_index >= s->nb_devices) { + s->poll_index = 0; + } + d = s->devices[s->poll_index]; + if ((1 << d->devaddr) & poll_mask) { + buf[0] = ADB_READREG | (d->devaddr << 4); + olen = do_adb_request(s, obuf + 1, buf, 1); + /* if there is data, we poll again the same device */ + if (olen > 0) { + s->status |= ADB_STATUS_POLLREPLY; + obuf[0] = buf[0]; + olen++; + return olen; + } + } + s->poll_index++; + } + return olen; +} + +void adb_set_autopoll_enabled(ADBBusState *s, bool enabled) +{ + if (s->autopoll_enabled != enabled) { + s->autopoll_enabled = enabled; + if (s->autopoll_enabled) { + timer_mod(s->autopoll_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->autopoll_rate_ms); + } else { + timer_del(s->autopoll_timer); + } + } +} + +void adb_set_autopoll_rate_ms(ADBBusState *s, int rate_ms) +{ + s->autopoll_rate_ms = rate_ms; + + if (s->autopoll_enabled) { + timer_mod(s->autopoll_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->autopoll_rate_ms); + } +} + +void adb_set_autopoll_mask(ADBBusState *s, uint16_t mask) +{ + if (s->autopoll_mask != mask) { + s->autopoll_mask = mask; + if (s->autopoll_enabled && s->autopoll_mask) { + timer_mod(s->autopoll_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->autopoll_rate_ms); + } else { + timer_del(s->autopoll_timer); + } + } +} + +void adb_autopoll_block(ADBBusState *s) +{ + s->autopoll_blocked = true; + trace_adb_bus_autopoll_block(s->autopoll_blocked); + + if (s->autopoll_enabled) { + timer_del(s->autopoll_timer); + } +} + +void adb_autopoll_unblock(ADBBusState *s) +{ + s->autopoll_blocked = false; + trace_adb_bus_autopoll_block(s->autopoll_blocked); + + if (s->autopoll_enabled) { + timer_mod(s->autopoll_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->autopoll_rate_ms); + } +} + +static void adb_autopoll(void *opaque) +{ + ADBBusState *s = opaque; + + if (!s->autopoll_blocked) { + trace_adb_bus_autopoll_cb(s->autopoll_mask); + s->autopoll_cb(s->autopoll_cb_opaque); + trace_adb_bus_autopoll_cb_done(s->autopoll_mask); + } + + timer_mod(s->autopoll_timer, + qemu_clock_get_ms(QEMU_CLOCK_VIRTUAL) + + s->autopoll_rate_ms); +} + +void adb_register_autopoll_callback(ADBBusState *s, void (*cb)(void *opaque), + void *opaque) +{ + s->autopoll_cb = cb; + s->autopoll_cb_opaque = opaque; +} + +static const VMStateDescription vmstate_adb_bus = { + .name = "adb_bus", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_TIMER_PTR(autopoll_timer, ADBBusState), + VMSTATE_BOOL(autopoll_enabled, ADBBusState), + VMSTATE_UINT8(autopoll_rate_ms, ADBBusState), + VMSTATE_UINT16(autopoll_mask, ADBBusState), + VMSTATE_BOOL(autopoll_blocked, ADBBusState), + VMSTATE_END_OF_LIST() + } +}; + +static void adb_bus_reset(BusState *qbus) +{ + ADBBusState *adb_bus = ADB_BUS(qbus); + + adb_bus->autopoll_enabled = false; + adb_bus->autopoll_mask = 0xffff; + adb_bus->autopoll_rate_ms = 20; +} + +static void adb_bus_realize(BusState *qbus, Error **errp) +{ + ADBBusState *adb_bus = ADB_BUS(qbus); + + adb_bus->autopoll_timer = timer_new_ms(QEMU_CLOCK_VIRTUAL, adb_autopoll, + adb_bus); + + vmstate_register(NULL, -1, &vmstate_adb_bus, adb_bus); +} + +static void adb_bus_unrealize(BusState *qbus) +{ + ADBBusState *adb_bus = ADB_BUS(qbus); + + timer_del(adb_bus->autopoll_timer); + + vmstate_unregister(NULL, &vmstate_adb_bus, adb_bus); +} + +static void adb_bus_class_init(ObjectClass *klass, void *data) +{ + BusClass *k = BUS_CLASS(klass); + + k->realize = adb_bus_realize; + k->unrealize = adb_bus_unrealize; + k->reset = adb_bus_reset; +} + +static const TypeInfo adb_bus_type_info = { + .name = TYPE_ADB_BUS, + .parent = TYPE_BUS, + .instance_size = sizeof(ADBBusState), + .class_init = adb_bus_class_init, +}; + +const VMStateDescription vmstate_adb_device = { + .name = "adb_device", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_INT32(devaddr, ADBDevice), + VMSTATE_INT32(handler, ADBDevice), + VMSTATE_END_OF_LIST() + } +}; + +static void adb_device_realizefn(DeviceState *dev, Error **errp) +{ + ADBDevice *d = ADB_DEVICE(dev); + ADBBusState *bus = ADB_BUS(qdev_get_parent_bus(dev)); + + if (bus->nb_devices >= MAX_ADB_DEVICES) { + return; + } + + bus->devices[bus->nb_devices++] = d; +} + +static void adb_device_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = adb_device_realizefn; + dc->bus_type = TYPE_ADB_BUS; +} + +static const TypeInfo adb_device_type_info = { + .name = TYPE_ADB_DEVICE, + .parent = TYPE_DEVICE, + .class_size = sizeof(ADBDeviceClass), + .instance_size = sizeof(ADBDevice), + .abstract = true, + .class_init = adb_device_class_init, +}; + +static void adb_register_types(void) +{ + type_register_static(&adb_bus_type_info); + type_register_static(&adb_device_type_info); +} + +type_init(adb_register_types) diff --git a/hw/input/ads7846.c b/hw/input/ads7846.c new file mode 100644 index 000000000..1d4e04a2d --- /dev/null +++ b/hw/input/ads7846.c @@ -0,0 +1,186 @@ +/* + * TI ADS7846 / TSC2046 chip emulation. + * + * Copyright (c) 2006 Openedhand Ltd. + * Written by Andrzej Zaborowski <balrog@zabor.org> + * + * This code is licensed under the GNU GPL v2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "hw/irq.h" +#include "hw/ssi/ssi.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "ui/console.h" +#include "qom/object.h" + +struct ADS7846State { + SSIPeripheral ssidev; + qemu_irq interrupt; + + int input[8]; + int pressure; + int noise; + + int cycle; + int output; +}; + +#define TYPE_ADS7846 "ads7846" +OBJECT_DECLARE_SIMPLE_TYPE(ADS7846State, ADS7846) + +/* Control-byte bitfields */ +#define CB_PD0 (1 << 0) +#define CB_PD1 (1 << 1) +#define CB_SER (1 << 2) +#define CB_MODE (1 << 3) +#define CB_A0 (1 << 4) +#define CB_A1 (1 << 5) +#define CB_A2 (1 << 6) +#define CB_START (1 << 7) + +#define X_AXIS_DMAX 3470 +#define X_AXIS_MIN 290 +#define Y_AXIS_DMAX 3450 +#define Y_AXIS_MIN 200 + +#define ADS_VBAT 2000 +#define ADS_VAUX 2000 +#define ADS_TEMP0 2000 +#define ADS_TEMP1 3000 +#define ADS_XPOS(x, y) (X_AXIS_MIN + ((X_AXIS_DMAX * (x)) >> 15)) +#define ADS_YPOS(x, y) (Y_AXIS_MIN + ((Y_AXIS_DMAX * (y)) >> 15)) +#define ADS_Z1POS(x, y) 600 +#define ADS_Z2POS(x, y) (600 + 6000 / ADS_XPOS(x, y)) + +static void ads7846_int_update(ADS7846State *s) +{ + if (s->interrupt) + qemu_set_irq(s->interrupt, s->pressure == 0); +} + +static uint32_t ads7846_transfer(SSIPeripheral *dev, uint32_t value) +{ + ADS7846State *s = ADS7846(dev); + + switch (s->cycle ++) { + case 0: + if (!(value & CB_START)) { + s->cycle = 0; + break; + } + + s->output = s->input[(value >> 4) & 7]; + + /* Imitate the ADC noise, some drivers expect this. */ + s->noise = (s->noise + 3) & 7; + switch ((value >> 4) & 7) { + case 1: s->output += s->noise ^ 2; break; + case 3: s->output += s->noise ^ 0; break; + case 4: s->output += s->noise ^ 7; break; + case 5: s->output += s->noise ^ 5; break; + } + + if (value & CB_MODE) + s->output >>= 4; /* 8 bits instead of 12 */ + + break; + case 1: + s->cycle = 0; + break; + } + return s->output; +} + +static void ads7846_ts_event(void *opaque, + int x, int y, int z, int buttons_state) +{ + ADS7846State *s = opaque; + + if (buttons_state) { + x = 0x7fff - x; + s->input[1] = ADS_XPOS(x, y); + s->input[3] = ADS_Z1POS(x, y); + s->input[4] = ADS_Z2POS(x, y); + s->input[5] = ADS_YPOS(x, y); + } + + if (s->pressure == !buttons_state) { + s->pressure = !!buttons_state; + + ads7846_int_update(s); + } +} + +static int ads7856_post_load(void *opaque, int version_id) +{ + ADS7846State *s = opaque; + + s->pressure = 0; + ads7846_int_update(s); + return 0; +} + +static const VMStateDescription vmstate_ads7846 = { + .name = "ads7846", + .version_id = 1, + .minimum_version_id = 1, + .post_load = ads7856_post_load, + .fields = (VMStateField[]) { + VMSTATE_SSI_PERIPHERAL(ssidev, ADS7846State), + VMSTATE_INT32_ARRAY(input, ADS7846State, 8), + VMSTATE_INT32(noise, ADS7846State), + VMSTATE_INT32(cycle, ADS7846State), + VMSTATE_INT32(output, ADS7846State), + VMSTATE_END_OF_LIST() + } +}; + +static void ads7846_realize(SSIPeripheral *d, Error **errp) +{ + DeviceState *dev = DEVICE(d); + ADS7846State *s = ADS7846(d); + + qdev_init_gpio_out(dev, &s->interrupt, 1); + + s->input[0] = ADS_TEMP0; /* TEMP0 */ + s->input[2] = ADS_VBAT; /* VBAT */ + s->input[6] = ADS_VAUX; /* VAUX */ + s->input[7] = ADS_TEMP1; /* TEMP1 */ + + /* We want absolute coordinates */ + qemu_add_mouse_event_handler(ads7846_ts_event, s, 1, + "QEMU ADS7846-driven Touchscreen"); + + ads7846_int_update(s); + + vmstate_register(NULL, VMSTATE_INSTANCE_ID_ANY, &vmstate_ads7846, s); +} + +static void ads7846_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + SSIPeripheralClass *k = SSI_PERIPHERAL_CLASS(klass); + + k->realize = ads7846_realize; + k->transfer = ads7846_transfer; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo ads7846_info = { + .name = TYPE_ADS7846, + .parent = TYPE_SSI_PERIPHERAL, + .instance_size = sizeof(ADS7846State), + .class_init = ads7846_class_init, +}; + +static void ads7846_register_types(void) +{ + type_register_static(&ads7846_info); +} + +type_init(ads7846_register_types) diff --git a/hw/input/hid.c b/hw/input/hid.c new file mode 100644 index 000000000..8aab0521f --- /dev/null +++ b/hw/input/hid.c @@ -0,0 +1,624 @@ +/* + * 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 "qemu/osdep.h" +#include "ui/console.h" +#include "qemu/timer.h" +#include "hw/input/hid.h" +#include "migration/vmstate.h" +#include "trace.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, 0x46, 0x00, 0x64, 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, + 0x88, 0x00, 0x00, 0x87, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x8a, 0x00, 0x8b, 0x00, 0x89, 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, + 0x7f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x81, 0x00, + 0x80, 0x00, 0x00, 0x00, 0x00, 0x54, 0x00, 0x46, + 0xe6, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x48, 0x4a, + 0x52, 0x4b, 0x00, 0x50, 0x00, 0x4f, 0x00, 0x4d, + 0x51, 0x4e, 0x49, 0x4c, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xe3, 0xe7, 0x65, 0x66, 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_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) + + NANOSECONDS_PER_SECOND * 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; + InputMoveEvent *move; + InputBtnEvent *btn; + + assert(hs->n < QUEUE_LENGTH); + e = &hs->ptr.queue[(hs->head + hs->n) & QUEUE_MASK]; + + switch (evt->type) { + case INPUT_EVENT_KIND_REL: + move = evt->u.rel.data; + if (move->axis == INPUT_AXIS_X) { + e->xdx += move->value; + } else if (move->axis == INPUT_AXIS_Y) { + e->ydy += move->value; + } + break; + + case INPUT_EVENT_KIND_ABS: + move = evt->u.abs.data; + if (move->axis == INPUT_AXIS_X) { + e->xdx = move->value; + } else if (move->axis == INPUT_AXIS_Y) { + e->ydy = move->value; + } + break; + + case INPUT_EVENT_KIND_BTN: + btn = evt->u.btn.data; + if (btn->down) { + e->buttons_state |= bmap[btn->button]; + if (btn->button == INPUT_BUTTON_WHEEL_UP) { + e->dz--; + } else if (btn->button == INPUT_BUTTON_WHEEL_DOWN) { + e->dz++; + } + } else { + e->buttons_state &= ~bmap[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; + InputKeyEvent *key = evt->u.key.data; + + count = qemu_input_key_value_to_scancode(key->key, + key->down, + scancodes); + if (hs->n + count > QUEUE_LENGTH) { + trace_hid_kbd_queue_full(); + 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]; + + if (!hs->n) { + trace_hid_kbd_queue_empty(); + } + + 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; + hs->kbd.modifiers = 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(), + } +}; diff --git a/hw/input/lasips2.c b/hw/input/lasips2.c new file mode 100644 index 000000000..68d741d34 --- /dev/null +++ b/hw/input/lasips2.c @@ -0,0 +1,288 @@ +/* + * QEMU HP Lasi PS/2 interface emulation + * + * Copyright (c) 2019 Sven Schnelle + * + * 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 "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/qdev-properties.h" +#include "hw/input/ps2.h" +#include "hw/input/lasips2.h" +#include "exec/hwaddr.h" +#include "trace.h" +#include "exec/address-spaces.h" +#include "migration/vmstate.h" +#include "hw/irq.h" + + +struct LASIPS2State; +typedef struct LASIPS2Port { + struct LASIPS2State *parent; + MemoryRegion reg; + void *dev; + uint8_t id; + uint8_t control; + uint8_t buf; + bool loopback_rbne; + bool irq; +} LASIPS2Port; + +typedef struct LASIPS2State { + LASIPS2Port kbd; + LASIPS2Port mouse; + qemu_irq irq; +} LASIPS2State; + +static const VMStateDescription vmstate_lasips2 = { + .name = "lasips2", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(kbd.control, LASIPS2State), + VMSTATE_UINT8(kbd.id, LASIPS2State), + VMSTATE_BOOL(kbd.irq, LASIPS2State), + VMSTATE_UINT8(mouse.control, LASIPS2State), + VMSTATE_UINT8(mouse.id, LASIPS2State), + VMSTATE_BOOL(mouse.irq, LASIPS2State), + VMSTATE_END_OF_LIST() + } +}; + +typedef enum { + REG_PS2_ID = 0, + REG_PS2_RCVDATA = 4, + REG_PS2_CONTROL = 8, + REG_PS2_STATUS = 12, +} lasips2_read_reg_t; + +typedef enum { + REG_PS2_RESET = 0, + REG_PS2_XMTDATA = 4, +} lasips2_write_reg_t; + +typedef enum { + LASIPS2_CONTROL_ENABLE = 0x01, + LASIPS2_CONTROL_LOOPBACK = 0x02, + LASIPS2_CONTROL_DIAG = 0x20, + LASIPS2_CONTROL_DATDIR = 0x40, + LASIPS2_CONTROL_CLKDIR = 0x80, +} lasips2_control_reg_t; + +typedef enum { + LASIPS2_STATUS_RBNE = 0x01, + LASIPS2_STATUS_TBNE = 0x02, + LASIPS2_STATUS_TERR = 0x04, + LASIPS2_STATUS_PERR = 0x08, + LASIPS2_STATUS_CMPINTR = 0x10, + LASIPS2_STATUS_DATSHD = 0x40, + LASIPS2_STATUS_CLKSHD = 0x80, +} lasips2_status_reg_t; + +static const char *lasips2_read_reg_name(uint64_t addr) +{ + switch (addr & 0xc) { + case REG_PS2_ID: + return " PS2_ID"; + + case REG_PS2_RCVDATA: + return " PS2_RCVDATA"; + + case REG_PS2_CONTROL: + return " PS2_CONTROL"; + + case REG_PS2_STATUS: + return " PS2_STATUS"; + + default: + return ""; + } +} + +static const char *lasips2_write_reg_name(uint64_t addr) +{ + switch (addr & 0x0c) { + case REG_PS2_RESET: + return " PS2_RESET"; + + case REG_PS2_XMTDATA: + return " PS2_XMTDATA"; + + case REG_PS2_CONTROL: + return " PS2_CONTROL"; + + default: + return ""; + } +} + +static void lasips2_update_irq(LASIPS2State *s) +{ + trace_lasips2_intr(s->kbd.irq | s->mouse.irq); + qemu_set_irq(s->irq, s->kbd.irq | s->mouse.irq); +} + +static void lasips2_reg_write(void *opaque, hwaddr addr, uint64_t val, + unsigned size) +{ + LASIPS2Port *port = opaque; + + trace_lasips2_reg_write(size, port->id, addr, + lasips2_write_reg_name(addr), val); + + switch (addr & 0xc) { + case REG_PS2_CONTROL: + port->control = val; + break; + + case REG_PS2_XMTDATA: + if (port->control & LASIPS2_CONTROL_LOOPBACK) { + port->buf = val; + port->irq = true; + port->loopback_rbne = true; + lasips2_update_irq(port->parent); + break; + } + + if (port->id) { + ps2_write_mouse(port->dev, val); + } else { + ps2_write_keyboard(port->dev, val); + } + break; + + case REG_PS2_RESET: + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: unknown register 0x%02" HWADDR_PRIx "\n", + __func__, addr); + break; + } +} + +static uint64_t lasips2_reg_read(void *opaque, hwaddr addr, unsigned size) +{ + LASIPS2Port *port = opaque; + uint64_t ret = 0; + + switch (addr & 0xc) { + case REG_PS2_ID: + ret = port->id; + break; + + case REG_PS2_RCVDATA: + if (port->control & LASIPS2_CONTROL_LOOPBACK) { + port->irq = false; + port->loopback_rbne = false; + lasips2_update_irq(port->parent); + ret = port->buf; + break; + } + + ret = ps2_read_data(port->dev); + break; + + case REG_PS2_CONTROL: + ret = port->control; + break; + + case REG_PS2_STATUS: + + ret = LASIPS2_STATUS_DATSHD | LASIPS2_STATUS_CLKSHD; + + if (port->control & LASIPS2_CONTROL_DIAG) { + if (!(port->control & LASIPS2_CONTROL_DATDIR)) { + ret &= ~LASIPS2_STATUS_DATSHD; + } + + if (!(port->control & LASIPS2_CONTROL_CLKDIR)) { + ret &= ~LASIPS2_STATUS_CLKSHD; + } + } + + if (port->control & LASIPS2_CONTROL_LOOPBACK) { + if (port->loopback_rbne) { + ret |= LASIPS2_STATUS_RBNE; + } + } else { + if (!ps2_queue_empty(port->dev)) { + ret |= LASIPS2_STATUS_RBNE; + } + } + + if (port->parent->kbd.irq || port->parent->mouse.irq) { + ret |= LASIPS2_STATUS_CMPINTR; + } + break; + + default: + qemu_log_mask(LOG_UNIMP, "%s: unknown register 0x%02" HWADDR_PRIx "\n", + __func__, addr); + break; + } + trace_lasips2_reg_read(size, port->id, addr, + lasips2_read_reg_name(addr), ret); + + return ret; +} + +static const MemoryRegionOps lasips2_reg_ops = { + .read = lasips2_reg_read, + .write = lasips2_reg_write, + .impl = { + .min_access_size = 1, + .max_access_size = 4, + }, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void ps2dev_update_irq(void *opaque, int level) +{ + LASIPS2Port *port = opaque; + port->irq = level; + lasips2_update_irq(port->parent); +} + +void lasips2_init(MemoryRegion *address_space, + hwaddr base, qemu_irq irq) +{ + LASIPS2State *s; + + s = g_malloc0(sizeof(LASIPS2State)); + + s->irq = irq; + s->mouse.id = 1; + s->kbd.parent = s; + s->mouse.parent = s; + + vmstate_register(NULL, base, &vmstate_lasips2, s); + + s->kbd.dev = ps2_kbd_init(ps2dev_update_irq, &s->kbd); + s->mouse.dev = ps2_mouse_init(ps2dev_update_irq, &s->mouse); + + memory_region_init_io(&s->kbd.reg, NULL, &lasips2_reg_ops, &s->kbd, + "lasips2-kbd", 0x100); + memory_region_add_subregion(address_space, base, &s->kbd.reg); + + memory_region_init_io(&s->mouse.reg, NULL, &lasips2_reg_ops, &s->mouse, + "lasips2-mouse", 0x100); + memory_region_add_subregion(address_space, base + 0x100, &s->mouse.reg); +} diff --git a/hw/input/lm832x.c b/hw/input/lm832x.c new file mode 100644 index 000000000..19a646d9b --- /dev/null +++ b/hw/input/lm832x.c @@ -0,0 +1,528 @@ +/* + * National Semiconductor LM8322/8323 GPIO keyboard & PWM chips. + * + * Copyright (C) 2008 Nokia Corporation + * Written by Andrzej Zaborowski <andrew@openedhand.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "hw/input/lm832x.h" +#include "hw/i2c/i2c.h" +#include "hw/irq.h" +#include "migration/vmstate.h" +#include "qemu/module.h" +#include "qemu/timer.h" +#include "ui/console.h" +#include "qom/object.h" + +OBJECT_DECLARE_SIMPLE_TYPE(LM823KbdState, LM8323) + +struct LM823KbdState { + I2CSlave parent_obj; + + uint8_t i2c_dir; + uint8_t i2c_cycle; + uint8_t reg; + + qemu_irq nirq; + uint16_t model; + + struct { + qemu_irq out[2]; + int in[2][2]; + } mux; + + uint8_t config; + uint8_t status; + uint8_t acttime; + uint8_t error; + uint8_t clock; + + struct { + uint16_t pull; + uint16_t mask; + uint16_t dir; + uint16_t level; + qemu_irq out[16]; + } gpio; + + struct { + uint8_t dbnctime; + uint8_t size; + uint8_t start; + uint8_t len; + uint8_t fifo[16]; + } kbd; + + struct { + uint16_t file[256]; + uint8_t faddr; + uint8_t addr[3]; + QEMUTimer *tm[3]; + } pwm; +}; + +#define INT_KEYPAD (1 << 0) +#define INT_ERROR (1 << 3) +#define INT_NOINIT (1 << 4) +#define INT_PWMEND(n) (1 << (5 + n)) + +#define ERR_BADPAR (1 << 0) +#define ERR_CMDUNK (1 << 1) +#define ERR_KEYOVR (1 << 2) +#define ERR_FIFOOVR (1 << 6) + +static void lm_kbd_irq_update(LM823KbdState *s) +{ + qemu_set_irq(s->nirq, !s->status); +} + +static void lm_kbd_gpio_update(LM823KbdState *s) +{ +} + +static void lm_kbd_reset(DeviceState *dev) +{ + LM823KbdState *s = LM8323(dev); + + s->config = 0x80; + s->status = INT_NOINIT; + s->acttime = 125; + s->kbd.dbnctime = 3; + s->kbd.size = 0x33; + s->clock = 0x08; + + lm_kbd_irq_update(s); + lm_kbd_gpio_update(s); +} + +static void lm_kbd_error(LM823KbdState *s, int err) +{ + s->error |= err; + s->status |= INT_ERROR; + lm_kbd_irq_update(s); +} + +static void lm_kbd_pwm_tick(LM823KbdState *s, int line) +{ +} + +static void lm_kbd_pwm_start(LM823KbdState *s, int line) +{ + lm_kbd_pwm_tick(s, line); +} + +static void lm_kbd_pwm0_tick(void *opaque) +{ + lm_kbd_pwm_tick(opaque, 0); +} +static void lm_kbd_pwm1_tick(void *opaque) +{ + lm_kbd_pwm_tick(opaque, 1); +} +static void lm_kbd_pwm2_tick(void *opaque) +{ + lm_kbd_pwm_tick(opaque, 2); +} + +enum { + LM832x_CMD_READ_ID = 0x80, /* Read chip ID. */ + LM832x_CMD_WRITE_CFG = 0x81, /* Set configuration item. */ + LM832x_CMD_READ_INT = 0x82, /* Get interrupt status. */ + LM832x_CMD_RESET = 0x83, /* Reset, same as external one */ + LM823x_CMD_WRITE_PULL_DOWN = 0x84, /* Select GPIO pull-up/down. */ + LM832x_CMD_WRITE_PORT_SEL = 0x85, /* Select GPIO in/out. */ + LM832x_CMD_WRITE_PORT_STATE = 0x86, /* Set GPIO pull-up/down. */ + LM832x_CMD_READ_PORT_SEL = 0x87, /* Get GPIO in/out. */ + LM832x_CMD_READ_PORT_STATE = 0x88, /* Get GPIO pull-up/down. */ + LM832x_CMD_READ_FIFO = 0x89, /* Read byte from FIFO. */ + LM832x_CMD_RPT_READ_FIFO = 0x8a, /* Read FIFO (no increment). */ + LM832x_CMD_SET_ACTIVE = 0x8b, /* Set active time. */ + LM832x_CMD_READ_ERROR = 0x8c, /* Get error status. */ + LM832x_CMD_READ_ROTATOR = 0x8e, /* Read rotator status. */ + LM832x_CMD_SET_DEBOUNCE = 0x8f, /* Set debouncing time. */ + LM832x_CMD_SET_KEY_SIZE = 0x90, /* Set keypad size. */ + LM832x_CMD_READ_KEY_SIZE = 0x91, /* Get keypad size. */ + LM832x_CMD_READ_CFG = 0x92, /* Get configuration item. */ + LM832x_CMD_WRITE_CLOCK = 0x93, /* Set clock config. */ + LM832x_CMD_READ_CLOCK = 0x94, /* Get clock config. */ + LM832x_CMD_PWM_WRITE = 0x95, /* Write PWM script. */ + LM832x_CMD_PWM_START = 0x96, /* Start PWM engine. */ + LM832x_CMD_PWM_STOP = 0x97, /* Stop PWM engine. */ + LM832x_GENERAL_ERROR = 0xff, /* There was one error. + Previously was represented by -1 + This is not a command */ +}; + +#define LM832x_MAX_KPX 8 +#define LM832x_MAX_KPY 12 + +static uint8_t lm_kbd_read(LM823KbdState *s, int reg, int byte) +{ + int ret; + + switch (reg) { + case LM832x_CMD_READ_ID: + ret = 0x0400; + break; + + case LM832x_CMD_READ_INT: + ret = s->status; + if (!(s->status & INT_NOINIT)) { + s->status = 0; + lm_kbd_irq_update(s); + } + break; + + case LM832x_CMD_READ_PORT_SEL: + ret = s->gpio.dir; + break; + case LM832x_CMD_READ_PORT_STATE: + ret = s->gpio.mask; + break; + + case LM832x_CMD_READ_FIFO: + if (s->kbd.len <= 1) + return 0x00; + + /* Example response from the two commands after a INT_KEYPAD + * interrupt caused by the key 0x3c being pressed: + * RPT_READ_FIFO: 55 bc 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01 + * READ_FIFO: bc 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01 + * RPT_READ_FIFO: bc 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 01 + * + * 55 is the code of the key release event serviced in the previous + * interrupt handling. + * + * TODO: find out whether the FIFO is advanced a single character + * before reading every byte or the whole size of the FIFO at the + * last LM832x_CMD_READ_FIFO. This affects LM832x_CMD_RPT_READ_FIFO + * output in cases where there are more than one event in the FIFO. + * Assume 0xbc and 0x3c events are in the FIFO: + * RPT_READ_FIFO: 55 bc 3c 00 4e ff 0a 50 08 00 29 d9 08 01 c9 + * READ_FIFO: bc 3c 00 00 4e ff 0a 50 08 00 29 d9 08 01 c9 + * Does RPT_READ_FIFO now return 0xbc and 0x3c or only 0x3c? + */ + s->kbd.start ++; + s->kbd.start &= sizeof(s->kbd.fifo) - 1; + s->kbd.len --; + + return s->kbd.fifo[s->kbd.start]; + case LM832x_CMD_RPT_READ_FIFO: + if (byte >= s->kbd.len) + return 0x00; + + return s->kbd.fifo[(s->kbd.start + byte) & (sizeof(s->kbd.fifo) - 1)]; + + case LM832x_CMD_READ_ERROR: + return s->error; + + case LM832x_CMD_READ_ROTATOR: + return 0; + + case LM832x_CMD_READ_KEY_SIZE: + return s->kbd.size; + + case LM832x_CMD_READ_CFG: + return s->config & 0xf; + + case LM832x_CMD_READ_CLOCK: + return (s->clock & 0xfc) | 2; + + default: + lm_kbd_error(s, ERR_CMDUNK); + fprintf(stderr, "%s: unknown command %02x\n", __func__, reg); + return 0x00; + } + + return ret >> (byte << 3); +} + +static void lm_kbd_write(LM823KbdState *s, int reg, int byte, uint8_t value) +{ + switch (reg) { + case LM832x_CMD_WRITE_CFG: + s->config = value; + /* This must be done whenever s->mux.in is updated (never). */ + if ((s->config >> 1) & 1) /* MUX1EN */ + qemu_set_irq(s->mux.out[0], s->mux.in[0][(s->config >> 0) & 1]); + if ((s->config >> 3) & 1) /* MUX2EN */ + qemu_set_irq(s->mux.out[0], s->mux.in[0][(s->config >> 2) & 1]); + /* TODO: check that this is issued only following the chip reset + * and not in the middle of operation and that it is followed by + * the GPIO ports re-resablishing through WRITE_PORT_SEL and + * WRITE_PORT_STATE (using a timer perhaps) and otherwise output + * warnings. */ + s->status = 0; + lm_kbd_irq_update(s); + s->kbd.len = 0; + s->kbd.start = 0; + s->reg = LM832x_GENERAL_ERROR; + break; + + case LM832x_CMD_RESET: + if (value == 0xaa) + lm_kbd_reset(DEVICE(s)); + else + lm_kbd_error(s, ERR_BADPAR); + s->reg = LM832x_GENERAL_ERROR; + break; + + case LM823x_CMD_WRITE_PULL_DOWN: + if (!byte) + s->gpio.pull = value; + else { + s->gpio.pull |= value << 8; + lm_kbd_gpio_update(s); + s->reg = LM832x_GENERAL_ERROR; + } + break; + case LM832x_CMD_WRITE_PORT_SEL: + if (!byte) + s->gpio.dir = value; + else { + s->gpio.dir |= value << 8; + lm_kbd_gpio_update(s); + s->reg = LM832x_GENERAL_ERROR; + } + break; + case LM832x_CMD_WRITE_PORT_STATE: + if (!byte) + s->gpio.mask = value; + else { + s->gpio.mask |= value << 8; + lm_kbd_gpio_update(s); + s->reg = LM832x_GENERAL_ERROR; + } + break; + + case LM832x_CMD_SET_ACTIVE: + s->acttime = value; + s->reg = LM832x_GENERAL_ERROR; + break; + + case LM832x_CMD_SET_DEBOUNCE: + s->kbd.dbnctime = value; + s->reg = LM832x_GENERAL_ERROR; + if (!value) + lm_kbd_error(s, ERR_BADPAR); + break; + + case LM832x_CMD_SET_KEY_SIZE: + s->kbd.size = value; + s->reg = LM832x_GENERAL_ERROR; + if ( + (value & 0xf) < 3 || (value & 0xf) > LM832x_MAX_KPY || + (value >> 4) < 3 || (value >> 4) > LM832x_MAX_KPX) + lm_kbd_error(s, ERR_BADPAR); + break; + + case LM832x_CMD_WRITE_CLOCK: + s->clock = value; + s->reg = LM832x_GENERAL_ERROR; + if ((value & 3) && (value & 3) != 3) { + lm_kbd_error(s, ERR_BADPAR); + fprintf(stderr, "%s: invalid clock setting in RCPWM\n", + __func__); + } + /* TODO: Validate that the command is only issued once */ + break; + + case LM832x_CMD_PWM_WRITE: + if (byte == 0) { + if (!(value & 3) || (value >> 2) > 59) { + lm_kbd_error(s, ERR_BADPAR); + s->reg = LM832x_GENERAL_ERROR; + break; + } + + s->pwm.faddr = value; + s->pwm.file[s->pwm.faddr] = 0; + } else if (byte == 1) { + s->pwm.file[s->pwm.faddr] |= value << 8; + } else if (byte == 2) { + s->pwm.file[s->pwm.faddr] |= value << 0; + s->reg = LM832x_GENERAL_ERROR; + } + break; + case LM832x_CMD_PWM_START: + s->reg = LM832x_GENERAL_ERROR; + if (!(value & 3) || (value >> 2) > 59) { + lm_kbd_error(s, ERR_BADPAR); + break; + } + + s->pwm.addr[(value & 3) - 1] = value >> 2; + lm_kbd_pwm_start(s, (value & 3) - 1); + break; + case LM832x_CMD_PWM_STOP: + s->reg = LM832x_GENERAL_ERROR; + if (!(value & 3)) { + lm_kbd_error(s, ERR_BADPAR); + break; + } + + timer_del(s->pwm.tm[(value & 3) - 1]); + break; + + case LM832x_GENERAL_ERROR: + lm_kbd_error(s, ERR_BADPAR); + break; + default: + lm_kbd_error(s, ERR_CMDUNK); + fprintf(stderr, "%s: unknown command %02x\n", __func__, reg); + break; + } +} + +static int lm_i2c_event(I2CSlave *i2c, enum i2c_event event) +{ + LM823KbdState *s = LM8323(i2c); + + switch (event) { + case I2C_START_RECV: + case I2C_START_SEND: + s->i2c_cycle = 0; + s->i2c_dir = (event == I2C_START_SEND); + break; + + default: + break; + } + + return 0; +} + +static uint8_t lm_i2c_rx(I2CSlave *i2c) +{ + LM823KbdState *s = LM8323(i2c); + + return lm_kbd_read(s, s->reg, s->i2c_cycle ++); +} + +static int lm_i2c_tx(I2CSlave *i2c, uint8_t data) +{ + LM823KbdState *s = LM8323(i2c); + + if (!s->i2c_cycle) + s->reg = data; + else + lm_kbd_write(s, s->reg, s->i2c_cycle - 1, data); + s->i2c_cycle ++; + + return 0; +} + +static int lm_kbd_post_load(void *opaque, int version_id) +{ + LM823KbdState *s = opaque; + + lm_kbd_irq_update(s); + lm_kbd_gpio_update(s); + + return 0; +} + +static const VMStateDescription vmstate_lm_kbd = { + .name = "LM8323", + .version_id = 0, + .minimum_version_id = 0, + .post_load = lm_kbd_post_load, + .fields = (VMStateField[]) { + VMSTATE_I2C_SLAVE(parent_obj, LM823KbdState), + VMSTATE_UINT8(i2c_dir, LM823KbdState), + VMSTATE_UINT8(i2c_cycle, LM823KbdState), + VMSTATE_UINT8(reg, LM823KbdState), + VMSTATE_UINT8(config, LM823KbdState), + VMSTATE_UINT8(status, LM823KbdState), + VMSTATE_UINT8(acttime, LM823KbdState), + VMSTATE_UINT8(error, LM823KbdState), + VMSTATE_UINT8(clock, LM823KbdState), + VMSTATE_UINT16(gpio.pull, LM823KbdState), + VMSTATE_UINT16(gpio.mask, LM823KbdState), + VMSTATE_UINT16(gpio.dir, LM823KbdState), + VMSTATE_UINT16(gpio.level, LM823KbdState), + VMSTATE_UINT8(kbd.dbnctime, LM823KbdState), + VMSTATE_UINT8(kbd.size, LM823KbdState), + VMSTATE_UINT8(kbd.start, LM823KbdState), + VMSTATE_UINT8(kbd.len, LM823KbdState), + VMSTATE_BUFFER(kbd.fifo, LM823KbdState), + VMSTATE_UINT16_ARRAY(pwm.file, LM823KbdState, 256), + VMSTATE_UINT8(pwm.faddr, LM823KbdState), + VMSTATE_BUFFER(pwm.addr, LM823KbdState), + VMSTATE_TIMER_PTR_ARRAY(pwm.tm, LM823KbdState, 3), + VMSTATE_END_OF_LIST() + } +}; + + +static void lm8323_realize(DeviceState *dev, Error **errp) +{ + LM823KbdState *s = LM8323(dev); + + s->model = 0x8323; + s->pwm.tm[0] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm0_tick, s); + s->pwm.tm[1] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm1_tick, s); + s->pwm.tm[2] = timer_new_ns(QEMU_CLOCK_VIRTUAL, lm_kbd_pwm2_tick, s); + qdev_init_gpio_out(dev, &s->nirq, 1); +} + +void lm832x_key_event(DeviceState *dev, int key, int state) +{ + LM823KbdState *s = LM8323(dev); + + if ((s->status & INT_ERROR) && (s->error & ERR_FIFOOVR)) + return; + + if (s->kbd.len >= sizeof(s->kbd.fifo)) { + lm_kbd_error(s, ERR_FIFOOVR); + return; + } + + s->kbd.fifo[(s->kbd.start + s->kbd.len ++) & (sizeof(s->kbd.fifo) - 1)] = + key | (state << 7); + + /* We never set ERR_KEYOVR because we support multiple keys fine. */ + s->status |= INT_KEYPAD; + lm_kbd_irq_update(s); +} + +static void lm8323_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + I2CSlaveClass *k = I2C_SLAVE_CLASS(klass); + + dc->reset = lm_kbd_reset; + dc->realize = lm8323_realize; + k->event = lm_i2c_event; + k->recv = lm_i2c_rx; + k->send = lm_i2c_tx; + dc->vmsd = &vmstate_lm_kbd; +} + +static const TypeInfo lm8323_info = { + .name = TYPE_LM8323, + .parent = TYPE_I2C_SLAVE, + .instance_size = sizeof(LM823KbdState), + .class_init = lm8323_class_init, +}; + +static void lm832x_register_types(void) +{ + type_register_static(&lm8323_info); +} + +type_init(lm832x_register_types) diff --git a/hw/input/meson.build b/hw/input/meson.build new file mode 100644 index 000000000..8deb011d4 --- /dev/null +++ b/hw/input/meson.build @@ -0,0 +1,18 @@ +softmmu_ss.add(files('hid.c')) +softmmu_ss.add(when: 'CONFIG_ADB', if_true: files('adb.c', 'adb-mouse.c', 'adb-kbd.c')) +softmmu_ss.add(when: 'CONFIG_ADS7846', if_true: files('ads7846.c')) +softmmu_ss.add(when: 'CONFIG_LM832X', if_true: files('lm832x.c')) +softmmu_ss.add(when: 'CONFIG_PCKBD', if_true: files('pckbd.c')) +softmmu_ss.add(when: 'CONFIG_PL050', if_true: files('pl050.c')) +softmmu_ss.add(when: 'CONFIG_PS2', if_true: files('ps2.c')) +softmmu_ss.add(when: 'CONFIG_STELLARIS_INPUT', if_true: files('stellaris_input.c')) +softmmu_ss.add(when: 'CONFIG_TSC2005', if_true: files('tsc2005.c')) + +softmmu_ss.add(when: 'CONFIG_VIRTIO_INPUT', if_true: files('virtio-input.c')) +softmmu_ss.add(when: 'CONFIG_VIRTIO_INPUT', if_true: files('virtio-input-hid.c')) +softmmu_ss.add(when: 'CONFIG_VIRTIO_INPUT_HOST', if_true: files('virtio-input-host.c')) +softmmu_ss.add(when: 'CONFIG_VHOST_USER_INPUT', if_true: files('vhost-user-input.c')) + +softmmu_ss.add(when: 'CONFIG_PXA2XX', if_true: files('pxa2xx_keypad.c')) +softmmu_ss.add(when: 'CONFIG_TSC210X', if_true: files('tsc210x.c')) +softmmu_ss.add(when: 'CONFIG_LASIPS2', if_true: files('lasips2.c')) diff --git a/hw/input/pckbd.c b/hw/input/pckbd.c new file mode 100644 index 000000000..baba62f35 --- /dev/null +++ b/hw/input/pckbd.c @@ -0,0 +1,814 @@ +/* + * QEMU PC keyboard 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 "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "hw/isa/isa.h" +#include "migration/vmstate.h" +#include "hw/acpi/aml-build.h" +#include "hw/input/ps2.h" +#include "hw/irq.h" +#include "hw/input/i8042.h" +#include "hw/qdev-properties.h" +#include "sysemu/reset.h" +#include "sysemu/runstate.h" + +#include "trace.h" + +/* Keyboard Controller Commands */ +#define KBD_CCMD_READ_MODE 0x20 /* Read mode bits */ +#define KBD_CCMD_WRITE_MODE 0x60 /* Write mode bits */ +#define KBD_CCMD_GET_VERSION 0xA1 /* Get controller version */ +#define KBD_CCMD_MOUSE_DISABLE 0xA7 /* Disable mouse interface */ +#define KBD_CCMD_MOUSE_ENABLE 0xA8 /* Enable mouse interface */ +#define KBD_CCMD_TEST_MOUSE 0xA9 /* Mouse interface test */ +#define KBD_CCMD_SELF_TEST 0xAA /* Controller self test */ +#define KBD_CCMD_KBD_TEST 0xAB /* Keyboard interface test */ +#define KBD_CCMD_KBD_DISABLE 0xAD /* Keyboard interface disable */ +#define KBD_CCMD_KBD_ENABLE 0xAE /* Keyboard interface enable */ +#define KBD_CCMD_READ_INPORT 0xC0 /* read input port */ +#define KBD_CCMD_READ_OUTPORT 0xD0 /* read output port */ +#define KBD_CCMD_WRITE_OUTPORT 0xD1 /* write output port */ +#define KBD_CCMD_WRITE_OBUF 0xD2 +#define KBD_CCMD_WRITE_AUX_OBUF 0xD3 /* Write to output buffer as if + initiated by the auxiliary device */ +#define KBD_CCMD_WRITE_MOUSE 0xD4 /* Write the following byte to the mouse */ +#define KBD_CCMD_DISABLE_A20 0xDD /* HP vectra only ? */ +#define KBD_CCMD_ENABLE_A20 0xDF /* HP vectra only ? */ +#define KBD_CCMD_PULSE_BITS_3_0 0xF0 /* Pulse bits 3-0 of the output port P2. */ +#define KBD_CCMD_RESET 0xFE /* Pulse bit 0 of the output port P2 = CPU reset. */ +#define KBD_CCMD_NO_OP 0xFF /* Pulse no bits of the output port P2. */ + +/* Status Register Bits */ +#define KBD_STAT_OBF 0x01 /* Keyboard output buffer full */ +#define KBD_STAT_IBF 0x02 /* Keyboard input buffer full */ +#define KBD_STAT_SELFTEST 0x04 /* Self test successful */ +#define KBD_STAT_CMD 0x08 /* Last write was a command write (0=data) */ +#define KBD_STAT_UNLOCKED 0x10 /* Zero if keyboard locked */ +#define KBD_STAT_MOUSE_OBF 0x20 /* Mouse output buffer full */ +#define KBD_STAT_GTO 0x40 /* General receive/xmit timeout */ +#define KBD_STAT_PERR 0x80 /* Parity error */ + +/* Controller Mode Register Bits */ +#define KBD_MODE_KBD_INT 0x01 /* Keyboard data generate IRQ1 */ +#define KBD_MODE_MOUSE_INT 0x02 /* Mouse data generate IRQ12 */ +#define KBD_MODE_SYS 0x04 /* The system flag (?) */ +#define KBD_MODE_NO_KEYLOCK 0x08 /* The keylock doesn't affect the keyboard if set */ +#define KBD_MODE_DISABLE_KBD 0x10 /* Disable keyboard interface */ +#define KBD_MODE_DISABLE_MOUSE 0x20 /* Disable mouse interface */ +#define KBD_MODE_KCC 0x40 /* Scan code conversion to PC format */ +#define KBD_MODE_RFU 0x80 + +/* Output Port Bits */ +#define KBD_OUT_RESET 0x01 /* 1=normal mode, 0=reset */ +#define KBD_OUT_A20 0x02 /* x86 only */ +#define KBD_OUT_OBF 0x10 /* Keyboard output buffer full */ +#define KBD_OUT_MOUSE_OBF 0x20 /* Mouse output buffer full */ + +/* OSes typically write 0xdd/0xdf to turn the A20 line off and on. + * We make the default value of the outport include these four bits, + * so that the subsection is rarely necessary. + */ +#define KBD_OUT_ONES 0xcc + +#define KBD_PENDING_KBD_COMPAT 0x01 +#define KBD_PENDING_AUX_COMPAT 0x02 +#define KBD_PENDING_CTRL_KBD 0x04 +#define KBD_PENDING_CTRL_AUX 0x08 +#define KBD_PENDING_KBD KBD_MODE_DISABLE_KBD /* 0x10 */ +#define KBD_PENDING_AUX KBD_MODE_DISABLE_MOUSE /* 0x20 */ + +#define KBD_MIGR_TIMER_PENDING 0x1 + +#define KBD_OBSRC_KBD 0x01 +#define KBD_OBSRC_MOUSE 0x02 +#define KBD_OBSRC_CTRL 0x04 + +typedef struct KBDState { + uint8_t write_cmd; /* if non zero, write data to port 60 is expected */ + uint8_t status; + uint8_t mode; + uint8_t outport; + uint32_t migration_flags; + uint32_t obsrc; + bool outport_present; + bool extended_state; + bool extended_state_loaded; + /* Bitmask of devices with data available. */ + uint8_t pending; + uint8_t obdata; + uint8_t cbdata; + uint8_t pending_tmp; + void *kbd; + void *mouse; + QEMUTimer *throttle_timer; + + qemu_irq irq_kbd; + qemu_irq irq_mouse; + qemu_irq a20_out; + hwaddr mask; +} KBDState; + +/* XXX: not generating the irqs if KBD_MODE_DISABLE_KBD is set may be + incorrect, but it avoids having to simulate exact delays */ +static void kbd_update_irq_lines(KBDState *s) +{ + int irq_kbd_level, irq_mouse_level; + + irq_kbd_level = 0; + irq_mouse_level = 0; + + if (s->status & KBD_STAT_OBF) { + if (s->status & KBD_STAT_MOUSE_OBF) { + if (s->mode & KBD_MODE_MOUSE_INT) { + irq_mouse_level = 1; + } + } else { + if ((s->mode & KBD_MODE_KBD_INT) && + !(s->mode & KBD_MODE_DISABLE_KBD)) { + irq_kbd_level = 1; + } + } + } + qemu_set_irq(s->irq_kbd, irq_kbd_level); + qemu_set_irq(s->irq_mouse, irq_mouse_level); +} + +static void kbd_deassert_irq(KBDState *s) +{ + s->status &= ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF); + s->outport &= ~(KBD_OUT_OBF | KBD_OUT_MOUSE_OBF); + kbd_update_irq_lines(s); +} + +static uint8_t kbd_pending(KBDState *s) +{ + if (s->extended_state) { + return s->pending & (~s->mode | ~(KBD_PENDING_KBD | KBD_PENDING_AUX)); + } else { + return s->pending; + } +} + +/* update irq and KBD_STAT_[MOUSE_]OBF */ +static void kbd_update_irq(KBDState *s) +{ + uint8_t pending = kbd_pending(s); + + s->status &= ~(KBD_STAT_OBF | KBD_STAT_MOUSE_OBF); + s->outport &= ~(KBD_OUT_OBF | KBD_OUT_MOUSE_OBF); + if (pending) { + s->status |= KBD_STAT_OBF; + s->outport |= KBD_OUT_OBF; + if (pending & KBD_PENDING_CTRL_KBD) { + s->obsrc = KBD_OBSRC_CTRL; + } else if (pending & KBD_PENDING_CTRL_AUX) { + s->status |= KBD_STAT_MOUSE_OBF; + s->outport |= KBD_OUT_MOUSE_OBF; + s->obsrc = KBD_OBSRC_CTRL; + } else if (pending & KBD_PENDING_KBD) { + s->obsrc = KBD_OBSRC_KBD; + } else { + s->status |= KBD_STAT_MOUSE_OBF; + s->outport |= KBD_OUT_MOUSE_OBF; + s->obsrc = KBD_OBSRC_MOUSE; + } + } + kbd_update_irq_lines(s); +} + +static void kbd_safe_update_irq(KBDState *s) +{ + /* + * with KBD_STAT_OBF set, a call to kbd_read_data() will eventually call + * kbd_update_irq() + */ + if (s->status & KBD_STAT_OBF) { + return; + } + /* the throttle timer is pending and will call kbd_update_irq() */ + if (s->throttle_timer && timer_pending(s->throttle_timer)) { + return; + } + if (kbd_pending(s)) { + kbd_update_irq(s); + } +} + +static void kbd_update_kbd_irq(void *opaque, int level) +{ + KBDState *s = opaque; + + if (level) { + s->pending |= KBD_PENDING_KBD; + } else { + s->pending &= ~KBD_PENDING_KBD; + } + kbd_safe_update_irq(s); +} + +static void kbd_update_aux_irq(void *opaque, int level) +{ + KBDState *s = opaque; + + if (level) { + s->pending |= KBD_PENDING_AUX; + } else { + s->pending &= ~KBD_PENDING_AUX; + } + kbd_safe_update_irq(s); +} + +static void kbd_throttle_timeout(void *opaque) +{ + KBDState *s = opaque; + + if (kbd_pending(s)) { + kbd_update_irq(s); + } +} + +static uint64_t kbd_read_status(void *opaque, hwaddr addr, + unsigned size) +{ + KBDState *s = opaque; + int val; + val = s->status; + trace_pckbd_kbd_read_status(val); + return val; +} + +static void kbd_queue(KBDState *s, int b, int aux) +{ + if (s->extended_state) { + s->cbdata = b; + s->pending &= ~KBD_PENDING_CTRL_KBD & ~KBD_PENDING_CTRL_AUX; + s->pending |= aux ? KBD_PENDING_CTRL_AUX : KBD_PENDING_CTRL_KBD; + kbd_safe_update_irq(s); + } else { + ps2_queue(aux ? s->mouse : s->kbd, b); + } +} + +static uint8_t kbd_dequeue(KBDState *s) +{ + uint8_t b = s->cbdata; + + s->pending &= ~KBD_PENDING_CTRL_KBD & ~KBD_PENDING_CTRL_AUX; + if (kbd_pending(s)) { + kbd_update_irq(s); + } + return b; +} + +static void outport_write(KBDState *s, uint32_t val) +{ + trace_pckbd_outport_write(val); + s->outport = val; + qemu_set_irq(s->a20_out, (val >> 1) & 1); + if (!(val & 1)) { + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); + } +} + +static void kbd_write_command(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + KBDState *s = opaque; + + trace_pckbd_kbd_write_command(val); + + /* Bits 3-0 of the output port P2 of the keyboard controller may be pulsed + * low for approximately 6 micro seconds. Bits 3-0 of the KBD_CCMD_PULSE + * command specify the output port bits to be pulsed. + * 0: Bit should be pulsed. 1: Bit should not be modified. + * The only useful version of this command is pulsing bit 0, + * which does a CPU reset. + */ + if((val & KBD_CCMD_PULSE_BITS_3_0) == KBD_CCMD_PULSE_BITS_3_0) { + if(!(val & 1)) + val = KBD_CCMD_RESET; + else + val = KBD_CCMD_NO_OP; + } + + switch(val) { + case KBD_CCMD_READ_MODE: + kbd_queue(s, s->mode, 0); + break; + case KBD_CCMD_WRITE_MODE: + case KBD_CCMD_WRITE_OBUF: + case KBD_CCMD_WRITE_AUX_OBUF: + case KBD_CCMD_WRITE_MOUSE: + case KBD_CCMD_WRITE_OUTPORT: + s->write_cmd = val; + break; + case KBD_CCMD_MOUSE_DISABLE: + s->mode |= KBD_MODE_DISABLE_MOUSE; + break; + case KBD_CCMD_MOUSE_ENABLE: + s->mode &= ~KBD_MODE_DISABLE_MOUSE; + kbd_safe_update_irq(s); + break; + case KBD_CCMD_TEST_MOUSE: + kbd_queue(s, 0x00, 0); + break; + case KBD_CCMD_SELF_TEST: + s->status |= KBD_STAT_SELFTEST; + kbd_queue(s, 0x55, 0); + break; + case KBD_CCMD_KBD_TEST: + kbd_queue(s, 0x00, 0); + break; + case KBD_CCMD_KBD_DISABLE: + s->mode |= KBD_MODE_DISABLE_KBD; + break; + case KBD_CCMD_KBD_ENABLE: + s->mode &= ~KBD_MODE_DISABLE_KBD; + kbd_safe_update_irq(s); + break; + case KBD_CCMD_READ_INPORT: + kbd_queue(s, 0x80, 0); + break; + case KBD_CCMD_READ_OUTPORT: + kbd_queue(s, s->outport, 0); + break; + case KBD_CCMD_ENABLE_A20: + qemu_irq_raise(s->a20_out); + s->outport |= KBD_OUT_A20; + break; + case KBD_CCMD_DISABLE_A20: + qemu_irq_lower(s->a20_out); + s->outport &= ~KBD_OUT_A20; + break; + case KBD_CCMD_RESET: + qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET); + break; + case KBD_CCMD_NO_OP: + /* ignore that */ + break; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "unsupported keyboard cmd=0x%02" PRIx64 "\n", val); + break; + } +} + +static uint64_t kbd_read_data(void *opaque, hwaddr addr, + unsigned size) +{ + KBDState *s = opaque; + + if (s->status & KBD_STAT_OBF) { + kbd_deassert_irq(s); + if (s->obsrc & KBD_OBSRC_KBD) { + if (s->throttle_timer) { + timer_mod(s->throttle_timer, + qemu_clock_get_us(QEMU_CLOCK_VIRTUAL) + 1000); + } + s->obdata = ps2_read_data(s->kbd); + } else if (s->obsrc & KBD_OBSRC_MOUSE) { + s->obdata = ps2_read_data(s->mouse); + } else if (s->obsrc & KBD_OBSRC_CTRL) { + s->obdata = kbd_dequeue(s); + } + } + + trace_pckbd_kbd_read_data(s->obdata); + return s->obdata; +} + +static void kbd_write_data(void *opaque, hwaddr addr, + uint64_t val, unsigned size) +{ + KBDState *s = opaque; + + trace_pckbd_kbd_write_data(val); + + switch(s->write_cmd) { + case 0: + ps2_write_keyboard(s->kbd, val); + /* sending data to the keyboard reenables PS/2 communication */ + s->mode &= ~KBD_MODE_DISABLE_KBD; + kbd_safe_update_irq(s); + break; + case KBD_CCMD_WRITE_MODE: + s->mode = val; + ps2_keyboard_set_translation(s->kbd, (s->mode & KBD_MODE_KCC) != 0); + /* + * a write to the mode byte interrupt enable flags directly updates + * the irq lines + */ + kbd_update_irq_lines(s); + /* + * a write to the mode byte disable interface flags may raise + * an irq if there is pending data in the PS/2 queues. + */ + kbd_safe_update_irq(s); + break; + case KBD_CCMD_WRITE_OBUF: + kbd_queue(s, val, 0); + break; + case KBD_CCMD_WRITE_AUX_OBUF: + kbd_queue(s, val, 1); + break; + case KBD_CCMD_WRITE_OUTPORT: + outport_write(s, val); + break; + case KBD_CCMD_WRITE_MOUSE: + ps2_write_mouse(s->mouse, val); + /* sending data to the mouse reenables PS/2 communication */ + s->mode &= ~KBD_MODE_DISABLE_MOUSE; + kbd_safe_update_irq(s); + break; + default: + break; + } + s->write_cmd = 0; +} + +static void kbd_reset(void *opaque) +{ + KBDState *s = opaque; + + s->mode = KBD_MODE_KBD_INT | KBD_MODE_MOUSE_INT; + s->status = KBD_STAT_CMD | KBD_STAT_UNLOCKED; + s->outport = KBD_OUT_RESET | KBD_OUT_A20 | KBD_OUT_ONES; + s->pending = 0; + kbd_deassert_irq(s); + if (s->throttle_timer) { + timer_del(s->throttle_timer); + } +} + +static uint8_t kbd_outport_default(KBDState *s) +{ + return KBD_OUT_RESET | KBD_OUT_A20 | KBD_OUT_ONES + | (s->status & KBD_STAT_OBF ? KBD_OUT_OBF : 0) + | (s->status & KBD_STAT_MOUSE_OBF ? KBD_OUT_MOUSE_OBF : 0); +} + +static int kbd_outport_post_load(void *opaque, int version_id) +{ + KBDState *s = opaque; + s->outport_present = true; + return 0; +} + +static bool kbd_outport_needed(void *opaque) +{ + KBDState *s = opaque; + return s->outport != kbd_outport_default(s); +} + +static const VMStateDescription vmstate_kbd_outport = { + .name = "pckbd_outport", + .version_id = 1, + .minimum_version_id = 1, + .post_load = kbd_outport_post_load, + .needed = kbd_outport_needed, + .fields = (VMStateField[]) { + VMSTATE_UINT8(outport, KBDState), + VMSTATE_END_OF_LIST() + } +}; + +static int kbd_extended_state_pre_save(void *opaque) +{ + KBDState *s = opaque; + + s->migration_flags = 0; + if (s->throttle_timer && timer_pending(s->throttle_timer)) { + s->migration_flags |= KBD_MIGR_TIMER_PENDING; + } + + return 0; +} + +static int kbd_extended_state_post_load(void *opaque, int version_id) +{ + KBDState *s = opaque; + + if (s->migration_flags & KBD_MIGR_TIMER_PENDING) { + kbd_throttle_timeout(s); + } + s->extended_state_loaded = true; + + return 0; +} + +static bool kbd_extended_state_needed(void *opaque) +{ + KBDState *s = opaque; + + return s->extended_state; +} + +static const VMStateDescription vmstate_kbd_extended_state = { + .name = "pckbd/extended_state", + .post_load = kbd_extended_state_post_load, + .pre_save = kbd_extended_state_pre_save, + .needed = kbd_extended_state_needed, + .fields = (VMStateField[]) { + VMSTATE_UINT32(migration_flags, KBDState), + VMSTATE_UINT32(obsrc, KBDState), + VMSTATE_UINT8(obdata, KBDState), + VMSTATE_UINT8(cbdata, KBDState), + VMSTATE_END_OF_LIST() + } +}; + +static int kbd_pre_save(void *opaque) +{ + KBDState *s = opaque; + + if (s->extended_state) { + s->pending_tmp = s->pending; + } else { + s->pending_tmp = 0; + if (s->pending & KBD_PENDING_KBD) { + s->pending_tmp |= KBD_PENDING_KBD_COMPAT; + } + if (s->pending & KBD_PENDING_AUX) { + s->pending_tmp |= KBD_PENDING_AUX_COMPAT; + } + } + return 0; +} + +static int kbd_pre_load(void *opaque) +{ + KBDState *s = opaque; + + s->outport_present = false; + s->extended_state_loaded = false; + return 0; +} + +static int kbd_post_load(void *opaque, int version_id) +{ + KBDState *s = opaque; + if (!s->outport_present) { + s->outport = kbd_outport_default(s); + } + s->pending = s->pending_tmp; + if (!s->extended_state_loaded) { + s->obsrc = s->status & KBD_STAT_OBF ? + (s->status & KBD_STAT_MOUSE_OBF ? KBD_OBSRC_MOUSE : KBD_OBSRC_KBD) : + 0; + if (s->pending & KBD_PENDING_KBD_COMPAT) { + s->pending |= KBD_PENDING_KBD; + } + if (s->pending & KBD_PENDING_AUX_COMPAT) { + s->pending |= KBD_PENDING_AUX; + } + } + /* clear all unused flags */ + s->pending &= KBD_PENDING_CTRL_KBD | KBD_PENDING_CTRL_AUX | + KBD_PENDING_KBD | KBD_PENDING_AUX; + return 0; +} + +static const VMStateDescription vmstate_kbd = { + .name = "pckbd", + .version_id = 3, + .minimum_version_id = 3, + .pre_load = kbd_pre_load, + .post_load = kbd_post_load, + .pre_save = kbd_pre_save, + .fields = (VMStateField[]) { + VMSTATE_UINT8(write_cmd, KBDState), + VMSTATE_UINT8(status, KBDState), + VMSTATE_UINT8(mode, KBDState), + VMSTATE_UINT8(pending_tmp, KBDState), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription*[]) { + &vmstate_kbd_outport, + &vmstate_kbd_extended_state, + NULL + } +}; + +/* Memory mapped interface */ +static uint64_t kbd_mm_readfn(void *opaque, hwaddr addr, unsigned size) +{ + KBDState *s = opaque; + + if (addr & s->mask) + return kbd_read_status(s, 0, 1) & 0xff; + else + return kbd_read_data(s, 0, 1) & 0xff; +} + +static void kbd_mm_writefn(void *opaque, hwaddr addr, + uint64_t value, unsigned size) +{ + KBDState *s = opaque; + + if (addr & s->mask) + kbd_write_command(s, 0, value & 0xff, 1); + else + kbd_write_data(s, 0, value & 0xff, 1); +} + + +static const MemoryRegionOps i8042_mmio_ops = { + .read = kbd_mm_readfn, + .write = kbd_mm_writefn, + .valid.min_access_size = 1, + .valid.max_access_size = 4, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +void i8042_mm_init(qemu_irq kbd_irq, qemu_irq mouse_irq, + MemoryRegion *region, ram_addr_t size, + hwaddr mask) +{ + KBDState *s = g_malloc0(sizeof(KBDState)); + + s->irq_kbd = kbd_irq; + s->irq_mouse = mouse_irq; + s->mask = mask; + + s->extended_state = true; + + vmstate_register(NULL, 0, &vmstate_kbd, s); + + memory_region_init_io(region, NULL, &i8042_mmio_ops, s, "i8042", size); + + s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s); + s->mouse = ps2_mouse_init(kbd_update_aux_irq, s); + qemu_register_reset(kbd_reset, s); +} + +struct ISAKBDState { + ISADevice parent_obj; + + KBDState kbd; + bool kbd_throttle; + MemoryRegion io[2]; +}; + +void i8042_isa_mouse_fake_event(ISAKBDState *isa) +{ + KBDState *s = &isa->kbd; + + ps2_mouse_fake_event(s->mouse); +} + +void i8042_setup_a20_line(ISADevice *dev, qemu_irq a20_out) +{ + qdev_connect_gpio_out_named(DEVICE(dev), I8042_A20_LINE, 0, a20_out); +} + +static const VMStateDescription vmstate_kbd_isa = { + .name = "pckbd", + .version_id = 3, + .minimum_version_id = 3, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(kbd, ISAKBDState, 0, vmstate_kbd, KBDState), + VMSTATE_END_OF_LIST() + } +}; + +static const MemoryRegionOps i8042_data_ops = { + .read = kbd_read_data, + .write = kbd_write_data, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static const MemoryRegionOps i8042_cmd_ops = { + .read = kbd_read_status, + .write = kbd_write_command, + .impl = { + .min_access_size = 1, + .max_access_size = 1, + }, + .endianness = DEVICE_LITTLE_ENDIAN, +}; + +static void i8042_initfn(Object *obj) +{ + ISAKBDState *isa_s = I8042(obj); + KBDState *s = &isa_s->kbd; + + memory_region_init_io(isa_s->io + 0, obj, &i8042_data_ops, s, + "i8042-data", 1); + memory_region_init_io(isa_s->io + 1, obj, &i8042_cmd_ops, s, + "i8042-cmd", 1); + + qdev_init_gpio_out_named(DEVICE(obj), &s->a20_out, I8042_A20_LINE, 1); +} + +static void i8042_realizefn(DeviceState *dev, Error **errp) +{ + ISADevice *isadev = ISA_DEVICE(dev); + ISAKBDState *isa_s = I8042(dev); + KBDState *s = &isa_s->kbd; + + isa_init_irq(isadev, &s->irq_kbd, 1); + isa_init_irq(isadev, &s->irq_mouse, 12); + + isa_register_ioport(isadev, isa_s->io + 0, 0x60); + isa_register_ioport(isadev, isa_s->io + 1, 0x64); + + s->kbd = ps2_kbd_init(kbd_update_kbd_irq, s); + s->mouse = ps2_mouse_init(kbd_update_aux_irq, s); + if (isa_s->kbd_throttle && !isa_s->kbd.extended_state) { + warn_report(TYPE_I8042 ": can't enable kbd-throttle without" + " extended-state, disabling kbd-throttle"); + } else if (isa_s->kbd_throttle) { + s->throttle_timer = timer_new_us(QEMU_CLOCK_VIRTUAL, + kbd_throttle_timeout, s); + } + qemu_register_reset(kbd_reset, s); +} + +static void i8042_build_aml(ISADevice *isadev, Aml *scope) +{ + Aml *kbd; + Aml *mou; + Aml *crs; + + crs = aml_resource_template(); + aml_append(crs, aml_io(AML_DECODE16, 0x0060, 0x0060, 0x01, 0x01)); + aml_append(crs, aml_io(AML_DECODE16, 0x0064, 0x0064, 0x01, 0x01)); + aml_append(crs, aml_irq_no_flags(1)); + + kbd = aml_device("KBD"); + aml_append(kbd, aml_name_decl("_HID", aml_eisaid("PNP0303"))); + aml_append(kbd, aml_name_decl("_STA", aml_int(0xf))); + aml_append(kbd, aml_name_decl("_CRS", crs)); + + crs = aml_resource_template(); + aml_append(crs, aml_irq_no_flags(12)); + + mou = aml_device("MOU"); + aml_append(mou, aml_name_decl("_HID", aml_eisaid("PNP0F13"))); + aml_append(mou, aml_name_decl("_STA", aml_int(0xf))); + aml_append(mou, aml_name_decl("_CRS", crs)); + + aml_append(scope, kbd); + aml_append(scope, mou); +} + +static Property i8042_properties[] = { + DEFINE_PROP_BOOL("extended-state", ISAKBDState, kbd.extended_state, true), + DEFINE_PROP_BOOL("kbd-throttle", ISAKBDState, kbd_throttle, false), + DEFINE_PROP_END_OF_LIST(), +}; + +static void i8042_class_initfn(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + ISADeviceClass *isa = ISA_DEVICE_CLASS(klass); + + device_class_set_props(dc, i8042_properties); + dc->realize = i8042_realizefn; + dc->vmsd = &vmstate_kbd_isa; + isa->build_aml = i8042_build_aml; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); +} + +static const TypeInfo i8042_info = { + .name = TYPE_I8042, + .parent = TYPE_ISA_DEVICE, + .instance_size = sizeof(ISAKBDState), + .instance_init = i8042_initfn, + .class_init = i8042_class_initfn, +}; + +static void i8042_register_types(void) +{ + type_register_static(&i8042_info); +} + +type_init(i8042_register_types) diff --git a/hw/input/pl050.c b/hw/input/pl050.c new file mode 100644 index 000000000..d279b6c14 --- /dev/null +++ b/hw/input/pl050.c @@ -0,0 +1,210 @@ +/* + * Arm PrimeCell PL050 Keyboard / Mouse Interface + * + * Copyright (c) 2006-2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "qemu/osdep.h" +#include "hw/sysbus.h" +#include "migration/vmstate.h" +#include "hw/input/ps2.h" +#include "hw/irq.h" +#include "qemu/log.h" +#include "qemu/module.h" +#include "qom/object.h" + +#define TYPE_PL050 "pl050" +OBJECT_DECLARE_SIMPLE_TYPE(PL050State, PL050) + +struct PL050State { + SysBusDevice parent_obj; + + MemoryRegion iomem; + void *dev; + uint32_t cr; + uint32_t clk; + uint32_t last; + int pending; + qemu_irq irq; + bool is_mouse; +}; + +static const VMStateDescription vmstate_pl050 = { + .name = "pl050", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_UINT32(cr, PL050State), + VMSTATE_UINT32(clk, PL050State), + VMSTATE_UINT32(last, PL050State), + VMSTATE_INT32(pending, PL050State), + VMSTATE_END_OF_LIST() + } +}; + +#define PL050_TXEMPTY (1 << 6) +#define PL050_TXBUSY (1 << 5) +#define PL050_RXFULL (1 << 4) +#define PL050_RXBUSY (1 << 3) +#define PL050_RXPARITY (1 << 2) +#define PL050_KMIC (1 << 1) +#define PL050_KMID (1 << 0) + +static const unsigned char pl050_id[] = +{ 0x50, 0x10, 0x04, 0x00, 0x0d, 0xf0, 0x05, 0xb1 }; + +static void pl050_update(void *opaque, int level) +{ + PL050State *s = (PL050State *)opaque; + int raise; + + s->pending = level; + raise = (s->pending && (s->cr & 0x10) != 0) + || (s->cr & 0x08) != 0; + qemu_set_irq(s->irq, raise); +} + +static uint64_t pl050_read(void *opaque, hwaddr offset, + unsigned size) +{ + PL050State *s = (PL050State *)opaque; + if (offset >= 0xfe0 && offset < 0x1000) + return pl050_id[(offset - 0xfe0) >> 2]; + + switch (offset >> 2) { + case 0: /* KMICR */ + return s->cr; + case 1: /* KMISTAT */ + { + uint8_t val; + uint32_t stat; + + val = s->last; + val = val ^ (val >> 4); + val = val ^ (val >> 2); + val = (val ^ (val >> 1)) & 1; + + stat = PL050_TXEMPTY; + if (val) + stat |= PL050_RXPARITY; + if (s->pending) + stat |= PL050_RXFULL; + + return stat; + } + case 2: /* KMIDATA */ + if (s->pending) + s->last = ps2_read_data(s->dev); + return s->last; + case 3: /* KMICLKDIV */ + return s->clk; + case 4: /* KMIIR */ + return s->pending | 2; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl050_read: Bad offset %x\n", (int)offset); + return 0; + } +} + +static void pl050_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PL050State *s = (PL050State *)opaque; + switch (offset >> 2) { + case 0: /* KMICR */ + s->cr = value; + pl050_update(s, s->pending); + /* ??? Need to implement the enable/disable bit. */ + break; + case 2: /* KMIDATA */ + /* ??? This should toggle the TX interrupt line. */ + /* ??? This means kbd/mouse can block each other. */ + if (s->is_mouse) { + ps2_write_mouse(s->dev, value); + } else { + ps2_write_keyboard(s->dev, value); + } + break; + case 3: /* KMICLKDIV */ + s->clk = value; + return; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "pl050_write: Bad offset %x\n", (int)offset); + } +} +static const MemoryRegionOps pl050_ops = { + .read = pl050_read, + .write = pl050_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static void pl050_realize(DeviceState *dev, Error **errp) +{ + PL050State *s = PL050(dev); + SysBusDevice *sbd = SYS_BUS_DEVICE(dev); + + memory_region_init_io(&s->iomem, OBJECT(s), &pl050_ops, s, "pl050", 0x1000); + sysbus_init_mmio(sbd, &s->iomem); + sysbus_init_irq(sbd, &s->irq); + if (s->is_mouse) { + s->dev = ps2_mouse_init(pl050_update, s); + } else { + s->dev = ps2_kbd_init(pl050_update, s); + } +} + +static void pl050_keyboard_init(Object *obj) +{ + PL050State *s = PL050(obj); + + s->is_mouse = false; +} + +static void pl050_mouse_init(Object *obj) +{ + PL050State *s = PL050(obj); + + s->is_mouse = true; +} + +static const TypeInfo pl050_kbd_info = { + .name = "pl050_keyboard", + .parent = TYPE_PL050, + .instance_init = pl050_keyboard_init, +}; + +static const TypeInfo pl050_mouse_info = { + .name = "pl050_mouse", + .parent = TYPE_PL050, + .instance_init = pl050_mouse_init, +}; + +static void pl050_class_init(ObjectClass *oc, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(oc); + + dc->realize = pl050_realize; + dc->vmsd = &vmstate_pl050; +} + +static const TypeInfo pl050_type_info = { + .name = TYPE_PL050, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(PL050State), + .abstract = true, + .class_init = pl050_class_init, +}; + +static void pl050_register_types(void) +{ + type_register_static(&pl050_type_info); + type_register_static(&pl050_kbd_info); + type_register_static(&pl050_mouse_info); +} + +type_init(pl050_register_types) diff --git a/hw/input/ps2.c b/hw/input/ps2.c new file mode 100644 index 000000000..9376a8f4c --- /dev/null +++ b/hw/input/ps2.c @@ -0,0 +1,1220 @@ +/* + * 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 "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/input/ps2.h" +#include "migration/vmstate.h" +#include "ui/console.h" +#include "ui/input.h" +#include "sysemu/reset.h" +#include "sysemu/runstate.h" + +#include "trace.h" + +/* Keyboard Commands */ +#define KBD_CMD_SET_LEDS 0xED /* Set keyboard leds */ +#define KBD_CMD_ECHO 0xEE +#define KBD_CMD_SCANCODE 0xF0 /* Get/set scancode set */ +#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 */ +#define KBD_CMD_SET_MAKE_BREAK 0xFC /* Set Make and Break mode */ +#define KBD_CMD_SET_TYPEMATIC 0xFA /* Set Typematic Make and Break mode */ + +/* Keyboard Replies */ +#define KBD_REPLY_POR 0xAA /* Power on reset */ +#define KBD_REPLY_ID 0xAB /* Keyboard ID */ +#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 + +/* + * PS/2 buffer size. Keep 256 bytes for compatibility with + * older QEMU versions. + */ +#define PS2_BUFFER_SIZE 256 +#define PS2_QUEUE_SIZE 16 /* Queue size required by PS/2 protocol */ +#define PS2_QUEUE_HEADROOM 8 /* Queue size for keyboard command replies */ + +/* Bits for 'modifiers' field in PS2KbdState */ +#define MOD_CTRL_L (1 << 0) +#define MOD_SHIFT_L (1 << 1) +#define MOD_ALT_L (1 << 2) +#define MOD_CTRL_R (1 << 3) +#define MOD_SHIFT_R (1 << 4) +#define MOD_ALT_R (1 << 5) + +typedef struct { + uint8_t data[PS2_BUFFER_SIZE]; + int rptr, wptr, cwptr, count; +} PS2Queue; + +struct PS2State { + PS2Queue queue; + int32_t write_cmd; + void (*update_irq)(void *, int); + void *update_arg; +}; + +typedef struct { + PS2State common; + int scan_enabled; + int translate; + int scancode_set; /* 1=XT, 2=AT, 3=PS/2 */ + int ledstate; + bool need_high_bit; + unsigned int modifiers; /* bitmask of MOD_* constants above */ +} PS2KbdState; + +typedef struct { + 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; +} PS2MouseState; + +static uint8_t translate_table[256] = { + 0xff, 0x43, 0x41, 0x3f, 0x3d, 0x3b, 0x3c, 0x58, + 0x64, 0x44, 0x42, 0x40, 0x3e, 0x0f, 0x29, 0x59, + 0x65, 0x38, 0x2a, 0x70, 0x1d, 0x10, 0x02, 0x5a, + 0x66, 0x71, 0x2c, 0x1f, 0x1e, 0x11, 0x03, 0x5b, + 0x67, 0x2e, 0x2d, 0x20, 0x12, 0x05, 0x04, 0x5c, + 0x68, 0x39, 0x2f, 0x21, 0x14, 0x13, 0x06, 0x5d, + 0x69, 0x31, 0x30, 0x23, 0x22, 0x15, 0x07, 0x5e, + 0x6a, 0x72, 0x32, 0x24, 0x16, 0x08, 0x09, 0x5f, + 0x6b, 0x33, 0x25, 0x17, 0x18, 0x0b, 0x0a, 0x60, + 0x6c, 0x34, 0x35, 0x26, 0x27, 0x19, 0x0c, 0x61, + 0x6d, 0x73, 0x28, 0x74, 0x1a, 0x0d, 0x62, 0x6e, + 0x3a, 0x36, 0x1c, 0x1b, 0x75, 0x2b, 0x63, 0x76, + 0x55, 0x56, 0x77, 0x78, 0x79, 0x7a, 0x0e, 0x7b, + 0x7c, 0x4f, 0x7d, 0x4b, 0x47, 0x7e, 0x7f, 0x6f, + 0x52, 0x53, 0x50, 0x4c, 0x4d, 0x48, 0x01, 0x45, + 0x57, 0x4e, 0x51, 0x4a, 0x37, 0x49, 0x46, 0x54, + 0x80, 0x81, 0x82, 0x41, 0x54, 0x85, 0x86, 0x87, + 0x88, 0x89, 0x8a, 0x8b, 0x8c, 0x8d, 0x8e, 0x8f, + 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, + 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, + 0xa0, 0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, + 0xa8, 0xa9, 0xaa, 0xab, 0xac, 0xad, 0xae, 0xaf, + 0xb0, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, + 0xb8, 0xb9, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf, + 0xc0, 0xc1, 0xc2, 0xc3, 0xc4, 0xc5, 0xc6, 0xc7, + 0xc8, 0xc9, 0xca, 0xcb, 0xcc, 0xcd, 0xce, 0xcf, + 0xd0, 0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, + 0xd8, 0xd9, 0xda, 0xdb, 0xdc, 0xdd, 0xde, 0xdf, + 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, + 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef, + 0xf0, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, + 0xf8, 0xf9, 0xfa, 0xfb, 0xfc, 0xfd, 0xfe, 0xff, +}; + +static unsigned int ps2_modifier_bit(QKeyCode key) +{ + switch (key) { + case Q_KEY_CODE_CTRL: + return MOD_CTRL_L; + case Q_KEY_CODE_CTRL_R: + return MOD_CTRL_R; + case Q_KEY_CODE_SHIFT: + return MOD_SHIFT_L; + case Q_KEY_CODE_SHIFT_R: + return MOD_SHIFT_R; + case Q_KEY_CODE_ALT: + return MOD_ALT_L; + case Q_KEY_CODE_ALT_R: + return MOD_ALT_R; + default: + return 0; + } +} + +static void ps2_reset_queue(PS2State *s) +{ + PS2Queue *q = &s->queue; + + q->rptr = 0; + q->wptr = 0; + q->cwptr = -1; + q->count = 0; +} + +int ps2_queue_empty(PS2State *s) +{ + return s->queue.count == 0; +} + +void ps2_queue_noirq(PS2State *s, int b) +{ + PS2Queue *q = &s->queue; + + if (q->count >= PS2_QUEUE_SIZE) { + return; + } + + q->data[q->wptr] = b; + if (++q->wptr == PS2_BUFFER_SIZE) { + q->wptr = 0; + } + q->count++; +} + +void ps2_raise_irq(PS2State *s) +{ + s->update_irq(s->update_arg, 1); +} + +void ps2_queue(PS2State *s, int b) +{ + if (PS2_QUEUE_SIZE - s->queue.count < 1) { + return; + } + + ps2_queue_noirq(s, b); + ps2_raise_irq(s); +} + +void ps2_queue_2(PS2State *s, int b1, int b2) +{ + if (PS2_QUEUE_SIZE - s->queue.count < 2) { + return; + } + + ps2_queue_noirq(s, b1); + ps2_queue_noirq(s, b2); + ps2_raise_irq(s); +} + +void ps2_queue_3(PS2State *s, int b1, int b2, int b3) +{ + if (PS2_QUEUE_SIZE - s->queue.count < 3) { + return; + } + + ps2_queue_noirq(s, b1); + ps2_queue_noirq(s, b2); + ps2_queue_noirq(s, b3); + ps2_raise_irq(s); +} + +void ps2_queue_4(PS2State *s, int b1, int b2, int b3, int b4) +{ + if (PS2_QUEUE_SIZE - s->queue.count < 4) { + return; + } + + ps2_queue_noirq(s, b1); + ps2_queue_noirq(s, b2); + ps2_queue_noirq(s, b3); + ps2_queue_noirq(s, b4); + ps2_raise_irq(s); +} + +static void ps2_cqueue_data(PS2Queue *q, int b) +{ + q->data[q->cwptr] = b; + if (++q->cwptr >= PS2_BUFFER_SIZE) { + q->cwptr = 0; + } + q->count++; +} + +static void ps2_cqueue_1(PS2State *s, int b1) +{ + PS2Queue *q = &s->queue; + + q->rptr = (q->rptr - 1) & (PS2_BUFFER_SIZE - 1); + q->cwptr = q->rptr; + ps2_cqueue_data(q, b1); + ps2_raise_irq(s); +} + +static void ps2_cqueue_2(PS2State *s, int b1, int b2) +{ + PS2Queue *q = &s->queue; + + q->rptr = (q->rptr - 2) & (PS2_BUFFER_SIZE - 1); + q->cwptr = q->rptr; + ps2_cqueue_data(q, b1); + ps2_cqueue_data(q, b2); + ps2_raise_irq(s); +} + +static void ps2_cqueue_3(PS2State *s, int b1, int b2, int b3) +{ + PS2Queue *q = &s->queue; + + q->rptr = (q->rptr - 3) & (PS2_BUFFER_SIZE - 1); + q->cwptr = q->rptr; + ps2_cqueue_data(q, b1); + ps2_cqueue_data(q, b2); + ps2_cqueue_data(q, b3); + ps2_raise_irq(s); +} + +static void ps2_cqueue_reset(PS2State *s) +{ + PS2Queue *q = &s->queue; + int ccount; + + if (q->cwptr == -1) { + return; + } + + ccount = (q->cwptr - q->rptr) & (PS2_BUFFER_SIZE - 1); + q->count -= ccount; + q->rptr = q->cwptr; + q->cwptr = -1; +} + +/* keycode is the untranslated scancode in the current scancode set. */ +static void ps2_put_keycode(void *opaque, int keycode) +{ + PS2KbdState *s = opaque; + + trace_ps2_put_keycode(opaque, keycode); + qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL); + + if (s->translate) { + if (keycode == 0xf0) { + s->need_high_bit = true; + } else if (s->need_high_bit) { + ps2_queue(&s->common, translate_table[keycode] | 0x80); + s->need_high_bit = false; + } else { + ps2_queue(&s->common, translate_table[keycode]); + } + } else { + ps2_queue(&s->common, keycode); + } +} + +static void ps2_keyboard_event(DeviceState *dev, QemuConsole *src, + InputEvent *evt) +{ + PS2KbdState *s = (PS2KbdState *)dev; + InputKeyEvent *key = evt->u.key.data; + int qcode; + uint16_t keycode = 0; + int mod; + + /* do not process events while disabled to prevent stream corruption */ + if (!s->scan_enabled) { + return; + } + + qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL); + assert(evt->type == INPUT_EVENT_KIND_KEY); + qcode = qemu_input_key_value_to_qcode(key->key); + + mod = ps2_modifier_bit(qcode); + trace_ps2_keyboard_event(s, qcode, key->down, mod, + s->modifiers, s->scancode_set, s->translate); + if (key->down) { + s->modifiers |= mod; + } else { + s->modifiers &= ~mod; + } + + if (s->scancode_set == 1) { + if (qcode == Q_KEY_CODE_PAUSE) { + if (s->modifiers & (MOD_CTRL_L | MOD_CTRL_R)) { + if (key->down) { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x46); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xc6); + } + } else { + if (key->down) { + ps2_put_keycode(s, 0xe1); + ps2_put_keycode(s, 0x1d); + ps2_put_keycode(s, 0x45); + ps2_put_keycode(s, 0xe1); + ps2_put_keycode(s, 0x9d); + ps2_put_keycode(s, 0xc5); + } + } + } else if (qcode == Q_KEY_CODE_PRINT) { + if (s->modifiers & MOD_ALT_L) { + if (key->down) { + ps2_put_keycode(s, 0xb8); + ps2_put_keycode(s, 0x38); + ps2_put_keycode(s, 0x54); + } else { + ps2_put_keycode(s, 0xd4); + ps2_put_keycode(s, 0xb8); + ps2_put_keycode(s, 0x38); + } + } else if (s->modifiers & MOD_ALT_R) { + if (key->down) { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xb8); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x38); + ps2_put_keycode(s, 0x54); + } else { + ps2_put_keycode(s, 0xd4); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xb8); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x38); + } + } else if (s->modifiers & (MOD_SHIFT_L | MOD_CTRL_L | + MOD_SHIFT_R | MOD_CTRL_R)) { + if (key->down) { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x37); + } else { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xb7); + } + } else { + if (key->down) { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x2a); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x37); + } else { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xb7); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xaa); + } + } + } else { + if (qcode < qemu_input_map_qcode_to_atset1_len) + keycode = qemu_input_map_qcode_to_atset1[qcode]; + if (keycode) { + if (keycode & 0xff00) { + ps2_put_keycode(s, keycode >> 8); + } + if (!key->down) { + keycode |= 0x80; + } + ps2_put_keycode(s, keycode & 0xff); + } else { + qemu_log_mask(LOG_UNIMP, + "ps2: ignoring key with qcode %d\n", qcode); + } + } + } else if (s->scancode_set == 2) { + if (qcode == Q_KEY_CODE_PAUSE) { + if (s->modifiers & (MOD_CTRL_L | MOD_CTRL_R)) { + if (key->down) { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x7e); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x7e); + } + } else { + if (key->down) { + ps2_put_keycode(s, 0xe1); + ps2_put_keycode(s, 0x14); + ps2_put_keycode(s, 0x77); + ps2_put_keycode(s, 0xe1); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x14); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x77); + } + } + } else if (qcode == Q_KEY_CODE_PRINT) { + if (s->modifiers & MOD_ALT_L) { + if (key->down) { + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x11); + ps2_put_keycode(s, 0x11); + ps2_put_keycode(s, 0x84); + } else { + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x84); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x11); + ps2_put_keycode(s, 0x11); + } + } else if (s->modifiers & MOD_ALT_R) { + if (key->down) { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x11); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x11); + ps2_put_keycode(s, 0x84); + } else { + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x84); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x11); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x11); + } + } else if (s->modifiers & (MOD_SHIFT_L | MOD_CTRL_L | + MOD_SHIFT_R | MOD_CTRL_R)) { + if (key->down) { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x7c); + } else { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x7c); + } + } else { + if (key->down) { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x12); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0x7c); + } else { + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x7c); + ps2_put_keycode(s, 0xe0); + ps2_put_keycode(s, 0xf0); + ps2_put_keycode(s, 0x12); + } + } + } else { + if (qcode < qemu_input_map_qcode_to_atset2_len) + keycode = qemu_input_map_qcode_to_atset2[qcode]; + if (keycode) { + if (keycode & 0xff00) { + ps2_put_keycode(s, keycode >> 8); + } + if (!key->down) { + ps2_put_keycode(s, 0xf0); + } + ps2_put_keycode(s, keycode & 0xff); + } else { + qemu_log_mask(LOG_UNIMP, + "ps2: ignoring key with qcode %d\n", qcode); + } + } + } else if (s->scancode_set == 3) { + if (qcode < qemu_input_map_qcode_to_atset3_len) + keycode = qemu_input_map_qcode_to_atset3[qcode]; + if (keycode) { + /* FIXME: break code should be configured on a key by key basis */ + if (!key->down) { + ps2_put_keycode(s, 0xf0); + } + ps2_put_keycode(s, keycode); + } else { + qemu_log_mask(LOG_UNIMP, + "ps2: ignoring key with qcode %d\n", qcode); + } + } +} + +uint32_t ps2_read_data(PS2State *s) +{ + PS2Queue *q; + int val, index; + + trace_ps2_read_data(s); + 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_BUFFER_SIZE - 1; + } + val = q->data[index]; + } else { + val = q->data[q->rptr]; + if (++q->rptr == PS2_BUFFER_SIZE) { + q->rptr = 0; + } + q->count--; + if (q->rptr == q->cwptr) { + /* command reply queue is empty */ + q->cwptr = -1; + } + /* reading deasserts IRQ */ + s->update_irq(s->update_arg, 0); + /* reassert IRQs if data left */ + if (q->count) { + s->update_irq(s->update_arg, 1); + } + } + return val; +} + +static void ps2_set_ledstate(PS2KbdState *s, int ledstate) +{ + trace_ps2_set_ledstate(s, ledstate); + s->ledstate = ledstate; + kbd_put_ledstate(ledstate); +} + +static void ps2_reset_keyboard(PS2KbdState *s) +{ + trace_ps2_reset_keyboard(s); + s->scan_enabled = 1; + s->scancode_set = 2; + ps2_reset_queue(&s->common); + ps2_set_ledstate(s, 0); +} + +void ps2_write_keyboard(void *opaque, int val) +{ + PS2KbdState *s = (PS2KbdState *)opaque; + + trace_ps2_write_keyboard(opaque, val); + ps2_cqueue_reset(&s->common); + switch(s->common.write_cmd) { + default: + case -1: + switch(val) { + case 0x00: + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + break; + case 0x05: + ps2_cqueue_1(&s->common, KBD_REPLY_RESEND); + break; + case KBD_CMD_GET_ID: + /* We emulate a MF2 AT keyboard here */ + ps2_cqueue_3(&s->common, KBD_REPLY_ACK, KBD_REPLY_ID, + s->translate ? 0x41 : 0x83); + break; + case KBD_CMD_ECHO: + ps2_cqueue_1(&s->common, KBD_CMD_ECHO); + break; + case KBD_CMD_ENABLE: + s->scan_enabled = 1; + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_SCANCODE: + case KBD_CMD_SET_LEDS: + case KBD_CMD_SET_RATE: + case KBD_CMD_SET_MAKE_BREAK: + s->common.write_cmd = val; + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET_DISABLE: + ps2_reset_keyboard(s); + s->scan_enabled = 0; + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET_ENABLE: + ps2_reset_keyboard(s); + s->scan_enabled = 1; + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + break; + case KBD_CMD_RESET: + ps2_reset_keyboard(s); + ps2_cqueue_2(&s->common, + KBD_REPLY_ACK, + KBD_REPLY_POR); + break; + case KBD_CMD_SET_TYPEMATIC: + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + break; + default: + ps2_cqueue_1(&s->common, KBD_REPLY_RESEND); + break; + } + break; + case KBD_CMD_SET_MAKE_BREAK: + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + s->common.write_cmd = -1; + break; + case KBD_CMD_SCANCODE: + if (val == 0) { + ps2_cqueue_2(&s->common, KBD_REPLY_ACK, s->translate ? + translate_table[s->scancode_set] : s->scancode_set); + } else if (val >= 1 && val <= 3) { + s->scancode_set = val; + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + } else { + ps2_cqueue_1(&s->common, KBD_REPLY_RESEND); + } + s->common.write_cmd = -1; + break; + case KBD_CMD_SET_LEDS: + ps2_set_ledstate(s, val); + ps2_cqueue_1(&s->common, KBD_REPLY_ACK); + s->common.write_cmd = -1; + break; + case KBD_CMD_SET_RATE: + ps2_cqueue_1(&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; + trace_ps2_keyboard_set_translation(opaque, mode); + s->translate = mode; +} + +static int ps2_mouse_send_packet(PS2MouseState *s) +{ + /* IMPS/2 and IMEX send 4 bytes, PS2 sends 3 bytes */ + const int needed = s->mouse_type ? 4 : 3; + unsigned int b; + int dx1, dy1, dz1; + + if (PS2_QUEUE_SIZE - s->common.queue.count < needed) { + return 0; + } + + 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_noirq(&s->common, b); + ps2_queue_noirq(&s->common, dx1 & 0xff); + ps2_queue_noirq(&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_noirq(&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_noirq(&s->common, b); + break; + } + + ps2_raise_irq(&s->common); + + trace_ps2_mouse_send_packet(s, dx1, dy1, dz1, b); + /* update deltas */ + s->mouse_dx -= dx1; + s->mouse_dy -= dy1; + s->mouse_dz -= dz1; + + return 1; +} + +static void ps2_mouse_event(DeviceState *dev, QemuConsole *src, + InputEvent *evt) +{ + static const int bmap[INPUT_BUTTON__MAX] = { + [INPUT_BUTTON_LEFT] = PS2_MOUSE_BUTTON_LEFT, + [INPUT_BUTTON_MIDDLE] = PS2_MOUSE_BUTTON_MIDDLE, + [INPUT_BUTTON_RIGHT] = PS2_MOUSE_BUTTON_RIGHT, + [INPUT_BUTTON_SIDE] = PS2_MOUSE_BUTTON_SIDE, + [INPUT_BUTTON_EXTRA] = PS2_MOUSE_BUTTON_EXTRA, + }; + PS2MouseState *s = (PS2MouseState *)dev; + InputMoveEvent *move; + InputBtnEvent *btn; + + /* check if deltas are recorded when disabled */ + if (!(s->mouse_status & MOUSE_STATUS_ENABLED)) + return; + + switch (evt->type) { + case INPUT_EVENT_KIND_REL: + move = evt->u.rel.data; + if (move->axis == INPUT_AXIS_X) { + s->mouse_dx += move->value; + } else if (move->axis == INPUT_AXIS_Y) { + s->mouse_dy -= move->value; + } + break; + + case INPUT_EVENT_KIND_BTN: + btn = evt->u.btn.data; + if (btn->down) { + s->mouse_buttons |= bmap[btn->button]; + if (btn->button == INPUT_BUTTON_WHEEL_UP) { + s->mouse_dz--; + } else if (btn->button == INPUT_BUTTON_WHEEL_DOWN) { + s->mouse_dz++; + } + } else { + s->mouse_buttons &= ~bmap[btn->button]; + } + break; + + default: + /* keep gcc happy */ + break; + } +} + +static void ps2_mouse_sync(DeviceState *dev) +{ + PS2MouseState *s = (PS2MouseState *)dev; + + /* do not sync while disabled to prevent stream corruption */ + if (!(s->mouse_status & MOUSE_STATUS_ENABLED)) { + return; + } + + if (s->mouse_buttons) { + qemu_system_wakeup_request(QEMU_WAKEUP_REASON_OTHER, NULL); + } + if (!(s->mouse_status & MOUSE_STATUS_REMOTE)) { + /* if not remote, send event. Multiple events are sent if + too big deltas */ + while (ps2_mouse_send_packet(s)) { + if (s->mouse_dx == 0 && s->mouse_dy == 0 && s->mouse_dz == 0) + break; + } + } +} + +void ps2_mouse_fake_event(void *opaque) +{ + PS2MouseState *s = opaque; + trace_ps2_mouse_fake_event(opaque); + s->mouse_dx++; + ps2_mouse_sync(opaque); +} + +void ps2_write_mouse(void *opaque, int val) +{ + PS2MouseState *s = (PS2MouseState *)opaque; + + trace_ps2_write_mouse(opaque, val); + 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_2(&s->common, + AUX_ACK, + 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_4(&s->common, + AUX_ACK, + s->mouse_status, + s->mouse_resolution, + 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_reset_queue(&s->common); + ps2_queue_3(&s->common, + AUX_ACK, + 0xaa, + 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_common_reset(PS2State *s) +{ + s->write_cmd = -1; + ps2_reset_queue(s); + s->update_irq(s->update_arg, 0); +} + +static void ps2_common_post_load(PS2State *s) +{ + PS2Queue *q = &s->queue; + int ccount = 0; + + /* limit the number of queued command replies to PS2_QUEUE_HEADROOM */ + if (q->cwptr != -1) { + ccount = (q->cwptr - q->rptr) & (PS2_BUFFER_SIZE - 1); + if (ccount > PS2_QUEUE_HEADROOM) { + ccount = PS2_QUEUE_HEADROOM; + } + } + + /* limit the scancode queue size to PS2_QUEUE_SIZE */ + if (q->count < ccount) { + q->count = ccount; + } else if (q->count > ccount + PS2_QUEUE_SIZE) { + q->count = ccount + PS2_QUEUE_SIZE; + } + + /* sanitize rptr and recalculate wptr and cwptr */ + q->rptr = q->rptr & (PS2_BUFFER_SIZE - 1); + q->wptr = (q->rptr + q->count) & (PS2_BUFFER_SIZE - 1); + q->cwptr = ccount ? (q->rptr + ccount) & (PS2_BUFFER_SIZE - 1) : -1; +} + +static void ps2_kbd_reset(void *opaque) +{ + PS2KbdState *s = (PS2KbdState *) opaque; + + trace_ps2_kbd_reset(opaque); + ps2_common_reset(&s->common); + s->scan_enabled = 1; + s->translate = 0; + s->scancode_set = 2; + s->modifiers = 0; +} + +static void ps2_mouse_reset(void *opaque) +{ + PS2MouseState *s = (PS2MouseState *) opaque; + + trace_ps2_mouse_reset(opaque); + ps2_common_reset(&s->common); + s->mouse_status = 0; + s->mouse_resolution = 0; + s->mouse_sample_rate = 0; + s->mouse_wrap = 0; + s->mouse_type = 0; + s->mouse_detect_state = 0; + s->mouse_dx = 0; + s->mouse_dy = 0; + s->mouse_dz = 0; + s->mouse_buttons = 0; +} + +static const VMStateDescription vmstate_ps2_common = { + .name = "PS2 Common State", + .version_id = 3, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_INT32(write_cmd, PS2State), + VMSTATE_INT32(queue.rptr, PS2State), + VMSTATE_INT32(queue.wptr, PS2State), + VMSTATE_INT32(queue.count, PS2State), + VMSTATE_BUFFER(queue.data, PS2State), + VMSTATE_END_OF_LIST() + } +}; + +static bool ps2_keyboard_ledstate_needed(void *opaque) +{ + PS2KbdState *s = opaque; + + return s->ledstate != 0; /* 0 is default state */ +} + +static int ps2_kbd_ledstate_post_load(void *opaque, int version_id) +{ + PS2KbdState *s = opaque; + + kbd_put_ledstate(s->ledstate); + return 0; +} + +static const VMStateDescription vmstate_ps2_keyboard_ledstate = { + .name = "ps2kbd/ledstate", + .version_id = 3, + .minimum_version_id = 2, + .post_load = ps2_kbd_ledstate_post_load, + .needed = ps2_keyboard_ledstate_needed, + .fields = (VMStateField[]) { + VMSTATE_INT32(ledstate, PS2KbdState), + VMSTATE_END_OF_LIST() + } +}; + +static bool ps2_keyboard_need_high_bit_needed(void *opaque) +{ + PS2KbdState *s = opaque; + return s->need_high_bit != 0; /* 0 is the usual state */ +} + +static const VMStateDescription vmstate_ps2_keyboard_need_high_bit = { + .name = "ps2kbd/need_high_bit", + .version_id = 1, + .minimum_version_id = 1, + .needed = ps2_keyboard_need_high_bit_needed, + .fields = (VMStateField[]) { + VMSTATE_BOOL(need_high_bit, PS2KbdState), + VMSTATE_END_OF_LIST() + } +}; + +static bool ps2_keyboard_cqueue_needed(void *opaque) +{ + PS2KbdState *s = opaque; + + return s->common.queue.cwptr != -1; /* the queue is mostly empty */ +} + +static const VMStateDescription vmstate_ps2_keyboard_cqueue = { + .name = "ps2kbd/command_reply_queue", + .needed = ps2_keyboard_cqueue_needed, + .fields = (VMStateField[]) { + VMSTATE_INT32(common.queue.cwptr, PS2KbdState), + VMSTATE_END_OF_LIST() + } +}; + +static int ps2_kbd_post_load(void* opaque, int version_id) +{ + PS2KbdState *s = (PS2KbdState*)opaque; + PS2State *ps2 = &s->common; + + if (version_id == 2) + s->scancode_set=2; + + ps2_common_post_load(ps2); + + return 0; +} + +static const VMStateDescription vmstate_ps2_keyboard = { + .name = "ps2kbd", + .version_id = 3, + .minimum_version_id = 2, + .post_load = ps2_kbd_post_load, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(common, PS2KbdState, 0, vmstate_ps2_common, PS2State), + VMSTATE_INT32(scan_enabled, PS2KbdState), + VMSTATE_INT32(translate, PS2KbdState), + VMSTATE_INT32_V(scancode_set, PS2KbdState,3), + VMSTATE_END_OF_LIST() + }, + .subsections = (const VMStateDescription*[]) { + &vmstate_ps2_keyboard_ledstate, + &vmstate_ps2_keyboard_need_high_bit, + &vmstate_ps2_keyboard_cqueue, + NULL + } +}; + +static int ps2_mouse_post_load(void *opaque, int version_id) +{ + PS2MouseState *s = (PS2MouseState *)opaque; + PS2State *ps2 = &s->common; + + ps2_common_post_load(ps2); + + return 0; +} + +static const VMStateDescription vmstate_ps2_mouse = { + .name = "ps2mouse", + .version_id = 2, + .minimum_version_id = 2, + .post_load = ps2_mouse_post_load, + .fields = (VMStateField[]) { + VMSTATE_STRUCT(common, PS2MouseState, 0, vmstate_ps2_common, PS2State), + VMSTATE_UINT8(mouse_status, PS2MouseState), + VMSTATE_UINT8(mouse_resolution, PS2MouseState), + VMSTATE_UINT8(mouse_sample_rate, PS2MouseState), + VMSTATE_UINT8(mouse_wrap, PS2MouseState), + VMSTATE_UINT8(mouse_type, PS2MouseState), + VMSTATE_UINT8(mouse_detect_state, PS2MouseState), + VMSTATE_INT32(mouse_dx, PS2MouseState), + VMSTATE_INT32(mouse_dy, PS2MouseState), + VMSTATE_INT32(mouse_dz, PS2MouseState), + VMSTATE_UINT8(mouse_buttons, PS2MouseState), + VMSTATE_END_OF_LIST() + } +}; + +static QemuInputHandler ps2_keyboard_handler = { + .name = "QEMU PS/2 Keyboard", + .mask = INPUT_EVENT_MASK_KEY, + .event = ps2_keyboard_event, +}; + +void *ps2_kbd_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2KbdState *s = (PS2KbdState *)g_malloc0(sizeof(PS2KbdState)); + + trace_ps2_kbd_init(s); + s->common.update_irq = update_irq; + s->common.update_arg = update_arg; + s->scancode_set = 2; + vmstate_register(NULL, 0, &vmstate_ps2_keyboard, s); + qemu_input_handler_register((DeviceState *)s, + &ps2_keyboard_handler); + qemu_register_reset(ps2_kbd_reset, s); + return s; +} + +static QemuInputHandler ps2_mouse_handler = { + .name = "QEMU PS/2 Mouse", + .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL, + .event = ps2_mouse_event, + .sync = ps2_mouse_sync, +}; + +void *ps2_mouse_init(void (*update_irq)(void *, int), void *update_arg) +{ + PS2MouseState *s = (PS2MouseState *)g_malloc0(sizeof(PS2MouseState)); + + trace_ps2_mouse_init(s); + s->common.update_irq = update_irq; + s->common.update_arg = update_arg; + vmstate_register(NULL, 0, &vmstate_ps2_mouse, s); + qemu_input_handler_register((DeviceState *)s, + &ps2_mouse_handler); + qemu_register_reset(ps2_mouse_reset, s); + return s; +} diff --git a/hw/input/pxa2xx_keypad.c b/hw/input/pxa2xx_keypad.c new file mode 100644 index 000000000..7f2f739fb --- /dev/null +++ b/hw/input/pxa2xx_keypad.c @@ -0,0 +1,331 @@ +/* + * Intel PXA27X Keypad Controller emulation. + * + * Copyright (c) 2007 MontaVista Software, Inc + * Written by Armin Kuster <akuster@kama-aina.net> + * or <Akuster@mvista.com> + * + * This code is licensed under the GPLv2. + * + * Contributions after 2012-01-13 are licensed under the terms of the + * GNU GPL, version 2 or (at your option) any later version. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/irq.h" +#include "migration/vmstate.h" +#include "hw/arm/pxa.h" +#include "ui/console.h" + +/* + * Keypad + */ +#define KPC 0x00 /* Keypad Interface Control register */ +#define KPDK 0x08 /* Keypad Interface Direct Key register */ +#define KPREC 0x10 /* Keypad Interface Rotary Encoder register */ +#define KPMK 0x18 /* Keypad Interface Matrix Key register */ +#define KPAS 0x20 /* Keypad Interface Automatic Scan register */ +#define KPASMKP0 0x28 /* Keypad Interface Automatic Scan Multiple + Key Presser register 0 */ +#define KPASMKP1 0x30 /* Keypad Interface Automatic Scan Multiple + Key Presser register 1 */ +#define KPASMKP2 0x38 /* Keypad Interface Automatic Scan Multiple + Key Presser register 2 */ +#define KPASMKP3 0x40 /* Keypad Interface Automatic Scan Multiple + Key Presser register 3 */ +#define KPKDI 0x48 /* Keypad Interface Key Debounce Interval + register */ + +/* Keypad defines */ +#define KPC_AS (0x1 << 30) /* Automatic Scan bit */ +#define KPC_ASACT (0x1 << 29) /* Automatic Scan on Activity */ +#define KPC_MI (0x1 << 22) /* Matrix interrupt bit */ +#define KPC_IMKP (0x1 << 21) /* Ignore Multiple Key Press */ +#define KPC_MS7 (0x1 << 20) /* Matrix scan line 7 */ +#define KPC_MS6 (0x1 << 19) /* Matrix scan line 6 */ +#define KPC_MS5 (0x1 << 18) /* Matrix scan line 5 */ +#define KPC_MS4 (0x1 << 17) /* Matrix scan line 4 */ +#define KPC_MS3 (0x1 << 16) /* Matrix scan line 3 */ +#define KPC_MS2 (0x1 << 15) /* Matrix scan line 2 */ +#define KPC_MS1 (0x1 << 14) /* Matrix scan line 1 */ +#define KPC_MS0 (0x1 << 13) /* Matrix scan line 0 */ +#define KPC_ME (0x1 << 12) /* Matrix Keypad Enable */ +#define KPC_MIE (0x1 << 11) /* Matrix Interrupt Enable */ +#define KPC_DK_DEB_SEL (0x1 << 9) /* Direct Keypad Debounce Select */ +#define KPC_DI (0x1 << 5) /* Direct key interrupt bit */ +#define KPC_RE_ZERO_DEB (0x1 << 4) /* Rotary Encoder Zero Debounce */ +#define KPC_REE1 (0x1 << 3) /* Rotary Encoder1 Enable */ +#define KPC_REE0 (0x1 << 2) /* Rotary Encoder0 Enable */ +#define KPC_DE (0x1 << 1) /* Direct Keypad Enable */ +#define KPC_DIE (0x1 << 0) /* Direct Keypad interrupt Enable */ + +#define KPDK_DKP (0x1 << 31) +#define KPDK_DK7 (0x1 << 7) +#define KPDK_DK6 (0x1 << 6) +#define KPDK_DK5 (0x1 << 5) +#define KPDK_DK4 (0x1 << 4) +#define KPDK_DK3 (0x1 << 3) +#define KPDK_DK2 (0x1 << 2) +#define KPDK_DK1 (0x1 << 1) +#define KPDK_DK0 (0x1 << 0) + +#define KPREC_OF1 (0x1 << 31) +#define KPREC_UF1 (0x1 << 30) +#define KPREC_OF0 (0x1 << 15) +#define KPREC_UF0 (0x1 << 14) + +#define KPMK_MKP (0x1 << 31) +#define KPAS_SO (0x1 << 31) +#define KPASMKPx_SO (0x1 << 31) + + +#define KPASMKPx_MKC(row, col) (1 << (row + 16 * (col % 2))) + +#define PXAKBD_MAXROW 8 +#define PXAKBD_MAXCOL 8 + +struct PXA2xxKeyPadState { + MemoryRegion iomem; + qemu_irq irq; + const struct keymap *map; + int pressed_cnt; + int alt_code; + + uint32_t kpc; + uint32_t kpdk; + uint32_t kprec; + uint32_t kpmk; + uint32_t kpas; + uint32_t kpasmkp[4]; + uint32_t kpkdi; +}; + +static void pxa27x_keypad_find_pressed_key(PXA2xxKeyPadState *kp, int *row, int *col) +{ + int i; + for (i = 0; i < 4; i++) + { + *col = i * 2; + for (*row = 0; *row < 8; (*row)++) { + if (kp->kpasmkp[i] & (1 << *row)) + return; + } + *col = i * 2 + 1; + for (*row = 0; *row < 8; (*row)++) { + if (kp->kpasmkp[i] & (1 << (*row + 16))) + return; + } + } +} + +static void pxa27x_keyboard_event (PXA2xxKeyPadState *kp, int keycode) +{ + int row, col, rel, assert_irq = 0; + uint32_t val; + + if (keycode == 0xe0) { + kp->alt_code = 1; + return; + } + + if(!(kp->kpc & KPC_ME)) /* skip if not enabled */ + return; + + rel = (keycode & 0x80) ? 1 : 0; /* key release from qemu */ + keycode &= ~0x80; /* strip qemu key release bit */ + if (kp->alt_code) { + keycode |= 0x80; + kp->alt_code = 0; + } + + row = kp->map[keycode].row; + col = kp->map[keycode].column; + if (row == -1 || col == -1) { + return; + } + + val = KPASMKPx_MKC(row, col); + if (rel) { + if (kp->kpasmkp[col / 2] & val) { + kp->kpasmkp[col / 2] &= ~val; + kp->pressed_cnt--; + assert_irq = 1; + } + } else { + if (!(kp->kpasmkp[col / 2] & val)) { + kp->kpasmkp[col / 2] |= val; + kp->pressed_cnt++; + assert_irq = 1; + } + } + kp->kpas = ((kp->pressed_cnt & 0x1f) << 26) | (0xf << 4) | 0xf; + if (kp->pressed_cnt == 1) { + kp->kpas &= ~((0xf << 4) | 0xf); + if (rel) { + pxa27x_keypad_find_pressed_key(kp, &row, &col); + } + kp->kpas |= ((row & 0xf) << 4) | (col & 0xf); + } + + if (!(kp->kpc & (KPC_AS | KPC_ASACT))) + assert_irq = 0; + + if (assert_irq && (kp->kpc & KPC_MIE)) { + kp->kpc |= KPC_MI; + qemu_irq_raise(kp->irq); + } +} + +static uint64_t pxa2xx_keypad_read(void *opaque, hwaddr offset, + unsigned size) +{ + PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque; + uint32_t tmp; + + switch (offset) { + case KPC: + tmp = s->kpc; + if(tmp & KPC_MI) + s->kpc &= ~(KPC_MI); + if(tmp & KPC_DI) + s->kpc &= ~(KPC_DI); + qemu_irq_lower(s->irq); + return tmp; + case KPDK: + return s->kpdk; + case KPREC: + tmp = s->kprec; + if(tmp & KPREC_OF1) + s->kprec &= ~(KPREC_OF1); + if(tmp & KPREC_UF1) + s->kprec &= ~(KPREC_UF1); + if(tmp & KPREC_OF0) + s->kprec &= ~(KPREC_OF0); + if(tmp & KPREC_UF0) + s->kprec &= ~(KPREC_UF0); + return tmp; + case KPMK: + tmp = s->kpmk; + if(tmp & KPMK_MKP) + s->kpmk &= ~(KPMK_MKP); + return tmp; + case KPAS: + return s->kpas; + case KPASMKP0: + return s->kpasmkp[0]; + case KPASMKP1: + return s->kpasmkp[1]; + case KPASMKP2: + return s->kpasmkp[2]; + case KPASMKP3: + return s->kpasmkp[3]; + case KPKDI: + return s->kpkdi; + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad read offset 0x%"HWADDR_PRIx"\n", + __func__, offset); + } + + return 0; +} + +static void pxa2xx_keypad_write(void *opaque, hwaddr offset, + uint64_t value, unsigned size) +{ + PXA2xxKeyPadState *s = (PXA2xxKeyPadState *) opaque; + + switch (offset) { + case KPC: + s->kpc = value; + if (s->kpc & KPC_AS) { + s->kpc &= ~(KPC_AS); + } + break; + case KPDK: + s->kpdk = value; + break; + case KPREC: + s->kprec = value; + break; + case KPMK: + s->kpmk = value; + break; + case KPAS: + s->kpas = value; + break; + case KPASMKP0: + s->kpasmkp[0] = value; + break; + case KPASMKP1: + s->kpasmkp[1] = value; + break; + case KPASMKP2: + s->kpasmkp[2] = value; + break; + case KPASMKP3: + s->kpasmkp[3] = value; + break; + case KPKDI: + s->kpkdi = value; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: Bad write offset 0x%"HWADDR_PRIx"\n", + __func__, offset); + } +} + +static const MemoryRegionOps pxa2xx_keypad_ops = { + .read = pxa2xx_keypad_read, + .write = pxa2xx_keypad_write, + .endianness = DEVICE_NATIVE_ENDIAN, +}; + +static const VMStateDescription vmstate_pxa2xx_keypad = { + .name = "pxa2xx_keypad", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT32(kpc, PXA2xxKeyPadState), + VMSTATE_UINT32(kpdk, PXA2xxKeyPadState), + VMSTATE_UINT32(kprec, PXA2xxKeyPadState), + VMSTATE_UINT32(kpmk, PXA2xxKeyPadState), + VMSTATE_UINT32(kpas, PXA2xxKeyPadState), + VMSTATE_UINT32_ARRAY(kpasmkp, PXA2xxKeyPadState, 4), + VMSTATE_UINT32(kpkdi, PXA2xxKeyPadState), + VMSTATE_END_OF_LIST() + } +}; + +PXA2xxKeyPadState *pxa27x_keypad_init(MemoryRegion *sysmem, + hwaddr base, + qemu_irq irq) +{ + PXA2xxKeyPadState *s; + + s = (PXA2xxKeyPadState *) g_malloc0(sizeof(PXA2xxKeyPadState)); + s->irq = irq; + + memory_region_init_io(&s->iomem, NULL, &pxa2xx_keypad_ops, s, + "pxa2xx-keypad", 0x00100000); + memory_region_add_subregion(sysmem, base, &s->iomem); + + vmstate_register(NULL, 0, &vmstate_pxa2xx_keypad, s); + + return s; +} + +void pxa27x_register_keypad(PXA2xxKeyPadState *kp, + const struct keymap *map, int size) +{ + if(!map || size < 0x80) { + fprintf(stderr, "%s - No PXA keypad map defined\n", __func__); + exit(-1); + } + + kp->map = map; + qemu_add_kbd_event_handler((QEMUPutKBDEvent *) pxa27x_keyboard_event, kp); +} diff --git a/hw/input/stellaris_input.c b/hw/input/stellaris_input.c new file mode 100644 index 000000000..e6ee5e11f --- /dev/null +++ b/hw/input/stellaris_input.c @@ -0,0 +1,93 @@ +/* + * Gamepad style buttons connected to IRQ/GPIO lines + * + * Copyright (c) 2007 CodeSourcery. + * Written by Paul Brook + * + * This code is licensed under the GPL. + */ + +#include "qemu/osdep.h" +#include "hw/input/gamepad.h" +#include "hw/irq.h" +#include "migration/vmstate.h" +#include "ui/console.h" + +typedef struct { + qemu_irq irq; + int keycode; + uint8_t pressed; +} gamepad_button; + +typedef struct { + gamepad_button *buttons; + int num_buttons; + int extension; +} gamepad_state; + +static void stellaris_gamepad_put_key(void * opaque, int keycode) +{ + gamepad_state *s = (gamepad_state *)opaque; + int i; + int down; + + if (keycode == 0xe0 && !s->extension) { + s->extension = 0x80; + return; + } + + down = (keycode & 0x80) == 0; + keycode = (keycode & 0x7f) | s->extension; + + for (i = 0; i < s->num_buttons; i++) { + if (s->buttons[i].keycode == keycode + && s->buttons[i].pressed != down) { + s->buttons[i].pressed = down; + qemu_set_irq(s->buttons[i].irq, down); + } + } + + s->extension = 0; +} + +static const VMStateDescription vmstate_stellaris_button = { + .name = "stellaris_button", + .version_id = 0, + .minimum_version_id = 0, + .fields = (VMStateField[]) { + VMSTATE_UINT8(pressed, gamepad_button), + VMSTATE_END_OF_LIST() + } +}; + +static const VMStateDescription vmstate_stellaris_gamepad = { + .name = "stellaris_gamepad", + .version_id = 2, + .minimum_version_id = 2, + .fields = (VMStateField[]) { + VMSTATE_INT32(extension, gamepad_state), + VMSTATE_STRUCT_VARRAY_POINTER_INT32(buttons, gamepad_state, + num_buttons, + vmstate_stellaris_button, + gamepad_button), + VMSTATE_END_OF_LIST() + } +}; + +/* Returns an array of 5 output slots. */ +void stellaris_gamepad_init(int n, qemu_irq *irq, const int *keycode) +{ + gamepad_state *s; + int i; + + s = g_new0(gamepad_state, 1); + s->buttons = g_new0(gamepad_button, n); + for (i = 0; i < n; i++) { + s->buttons[i].irq = irq[i]; + s->buttons[i].keycode = keycode[i]; + } + s->num_buttons = n; + qemu_add_kbd_event_handler(stellaris_gamepad_put_key, s); + vmstate_register(NULL, VMSTATE_INSTANCE_ID_ANY, + &vmstate_stellaris_gamepad, s); +} diff --git a/hw/input/trace-events b/hw/input/trace-events new file mode 100644 index 000000000..e0bfe7f3e --- /dev/null +++ b/hw/input/trace-events @@ -0,0 +1,60 @@ +# See docs/devel/tracing.rst for syntax documentation. + +# adb-kbd.c +adb_device_kbd_no_key(void) "Ignoring NO_KEY" +adb_device_kbd_writereg(int reg, uint8_t val) "reg %d val 0x%2.2x" +adb_device_kbd_readreg(int reg, uint8_t val0, uint8_t val1) "reg %d obuf[0] 0x%2.2x obuf[1] 0x%2.2x" +adb_device_kbd_request_change_addr(int devaddr) "change addr to 0x%x" +adb_device_kbd_request_change_addr_and_handler(int devaddr, int handler) "change addr and handler to 0x%x, 0x%x" + +# adb-mouse.c +adb_device_mouse_flush(void) "flush" +adb_device_mouse_writereg(int reg, uint8_t val) "reg %d val 0x%2.2x" +adb_device_mouse_readreg(int reg, uint8_t val0, uint8_t val1) "reg %d obuf[0] 0x%2.2x obuf[1] 0x%2.2x" +adb_device_mouse_request_change_addr(int devaddr) "change addr to 0x%x" +adb_device_mouse_request_change_addr_and_handler(int devaddr, int handler) "change addr and handler to 0x%x, 0x%x" + +# adb.c +adb_bus_request(uint8_t addr, const char *cmd, int size) "device 0x%x %s cmdsize=%d" +adb_bus_request_done(uint8_t addr, const char *cmd, int size) "device 0x%x %s replysize=%d" +adb_bus_autopoll_block(bool blocked) "blocked: %d" +adb_bus_autopoll_cb(uint16_t mask) "executing autopoll_cb with autopoll mask 0x%x" +adb_bus_autopoll_cb_done(uint16_t mask) "done executing autopoll_cb with autopoll mask 0x%x" + +# pckbd.c +pckbd_kbd_read_data(uint32_t val) "0x%02x" +pckbd_kbd_read_status(int status) "0x%02x" +pckbd_outport_write(uint32_t val) "0x%02x" +pckbd_kbd_write_command(uint64_t val) "0x%02"PRIx64 +pckbd_kbd_write_data(uint64_t val) "0x%02"PRIx64 + +# ps2.c +ps2_put_keycode(void *opaque, int keycode) "%p keycode 0x%02x" +ps2_keyboard_event(void *opaque, int qcode, int down, unsigned int modifier, unsigned int modifiers, int set, int xlate) "%p qcode %d down %d modifier 0x%x modifiers 0x%x set %d xlate %d" +ps2_read_data(void *opaque) "%p" +ps2_set_ledstate(void *s, int ledstate) "%p ledstate %d" +ps2_reset_keyboard(void *s) "%p" +ps2_write_keyboard(void *opaque, int val) "%p val %d" +ps2_keyboard_set_translation(void *opaque, int mode) "%p mode %d" +ps2_mouse_send_packet(void *s, int dx1, int dy1, int dz1, int b) "%p x %d y %d z %d bs 0x%x" +ps2_mouse_fake_event(void *opaque) "%p" +ps2_write_mouse(void *opaque, int val) "%p val %d" +ps2_kbd_reset(void *opaque) "%p" +ps2_mouse_reset(void *opaque) "%p" +ps2_kbd_init(void *s) "%p" +ps2_mouse_init(void *s) "%p" + +# hid.c +hid_kbd_queue_full(void) "queue full" +hid_kbd_queue_empty(void) "queue empty" + +# tsc2005.c +tsc2005_sense(const char *state) "touchscreen sense %s" + +# virtio-input.c +virtio_input_queue_full(void) "queue full" + +# lasips2.c +lasips2_reg_read(unsigned int size, int id, uint64_t addr, const char *name, uint64_t val) "%u %d addr 0x%"PRIx64 "%s -> 0x%"PRIx64 +lasips2_reg_write(unsigned int size, int id, uint64_t addr, const char *name, uint64_t val) "%u %d addr 0x%"PRIx64 "%s <- 0x%"PRIx64 +lasips2_intr(unsigned int val) "%d" diff --git a/hw/input/trace.h b/hw/input/trace.h new file mode 100644 index 000000000..d1cc5d924 --- /dev/null +++ b/hw/input/trace.h @@ -0,0 +1 @@ +#include "trace/trace-hw_input.h" diff --git a/hw/input/tsc2005.c b/hw/input/tsc2005.c new file mode 100644 index 000000000..55d61cc84 --- /dev/null +++ b/hw/input/tsc2005.c @@ -0,0 +1,559 @@ +/* + * TI TSC2005 emulator. + * + * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org> + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qemu/timer.h" +#include "sysemu/reset.h" +#include "ui/console.h" +#include "hw/input/tsc2xxx.h" +#include "hw/irq.h" +#include "migration/vmstate.h" +#include "trace.h" + +#define TSC_CUT_RESOLUTION(value, p) ((value) >> (16 - (p ? 12 : 10))) + +typedef struct { + qemu_irq pint; /* Combination of the nPENIRQ and DAV signals */ + QEMUTimer *timer; + uint16_t model; + + int32_t x, y; + bool pressure; + + uint8_t reg, state; + bool irq, command; + uint16_t data, dav; + + bool busy; + bool enabled; + bool host_mode; + int8_t function; + int8_t nextfunction; + bool precision; + bool nextprecision; + uint16_t filter; + uint8_t pin_func; + uint16_t timing[2]; + uint8_t noise; + bool reset; + bool pdst; + bool pnd0; + uint16_t temp_thr[2]; + uint16_t aux_thr[2]; + + int32_t tr[8]; +} TSC2005State; + +enum { + TSC_MODE_XYZ_SCAN = 0x0, + TSC_MODE_XY_SCAN, + TSC_MODE_X, + TSC_MODE_Y, + TSC_MODE_Z, + TSC_MODE_AUX, + TSC_MODE_TEMP1, + TSC_MODE_TEMP2, + TSC_MODE_AUX_SCAN, + TSC_MODE_X_TEST, + TSC_MODE_Y_TEST, + TSC_MODE_TS_TEST, + TSC_MODE_RESERVED, + TSC_MODE_XX_DRV, + TSC_MODE_YY_DRV, + TSC_MODE_YX_DRV, +}; + +static const uint16_t mode_regs[16] = { + 0xf000, /* X, Y, Z scan */ + 0xc000, /* X, Y scan */ + 0x8000, /* X */ + 0x4000, /* Y */ + 0x3000, /* Z */ + 0x0800, /* AUX */ + 0x0400, /* TEMP1 */ + 0x0200, /* TEMP2 */ + 0x0800, /* AUX scan */ + 0x0040, /* X test */ + 0x0020, /* Y test */ + 0x0080, /* Short-circuit test */ + 0x0000, /* Reserved */ + 0x0000, /* X+, X- drivers */ + 0x0000, /* Y+, Y- drivers */ + 0x0000, /* Y+, X- drivers */ +}; + +#define X_TRANSFORM(s) \ + ((s->y * s->tr[0] - s->x * s->tr[1]) / s->tr[2] + s->tr[3]) +#define Y_TRANSFORM(s) \ + ((s->y * s->tr[4] - s->x * s->tr[5]) / s->tr[6] + s->tr[7]) +#define Z1_TRANSFORM(s) \ + ((400 - ((s)->x >> 7) + ((s)->pressure << 10)) << 4) +#define Z2_TRANSFORM(s) \ + ((4000 + ((s)->y >> 7) - ((s)->pressure << 10)) << 4) + +#define AUX_VAL (700 << 4) /* +/- 3 at 12-bit */ +#define TEMP1_VAL (1264 << 4) /* +/- 5 at 12-bit */ +#define TEMP2_VAL (1531 << 4) /* +/- 5 at 12-bit */ + +static uint16_t tsc2005_read(TSC2005State *s, int reg) +{ + uint16_t ret; + + switch (reg) { + case 0x0: /* X */ + s->dav &= ~mode_regs[TSC_MODE_X]; + return TSC_CUT_RESOLUTION(X_TRANSFORM(s), s->precision) + + (s->noise & 3); + case 0x1: /* Y */ + s->dav &= ~mode_regs[TSC_MODE_Y]; + s->noise ++; + return TSC_CUT_RESOLUTION(Y_TRANSFORM(s), s->precision) ^ + (s->noise & 3); + case 0x2: /* Z1 */ + s->dav &= 0xdfff; + return TSC_CUT_RESOLUTION(Z1_TRANSFORM(s), s->precision) - + (s->noise & 3); + case 0x3: /* Z2 */ + s->dav &= 0xefff; + return TSC_CUT_RESOLUTION(Z2_TRANSFORM(s), s->precision) | + (s->noise & 3); + + case 0x4: /* AUX */ + s->dav &= ~mode_regs[TSC_MODE_AUX]; + return TSC_CUT_RESOLUTION(AUX_VAL, s->precision); + + case 0x5: /* TEMP1 */ + s->dav &= ~mode_regs[TSC_MODE_TEMP1]; + return TSC_CUT_RESOLUTION(TEMP1_VAL, s->precision) - + (s->noise & 5); + case 0x6: /* TEMP2 */ + s->dav &= 0xdfff; + s->dav &= ~mode_regs[TSC_MODE_TEMP2]; + return TSC_CUT_RESOLUTION(TEMP2_VAL, s->precision) ^ + (s->noise & 3); + + case 0x7: /* Status */ + ret = s->dav | (s->reset << 7) | (s->pdst << 2) | 0x0; + s->dav &= ~(mode_regs[TSC_MODE_X_TEST] | mode_regs[TSC_MODE_Y_TEST] | + mode_regs[TSC_MODE_TS_TEST]); + s->reset = true; + return ret; + + case 0x8: /* AUX high treshold */ + return s->aux_thr[1]; + case 0x9: /* AUX low treshold */ + return s->aux_thr[0]; + + case 0xa: /* TEMP high treshold */ + return s->temp_thr[1]; + case 0xb: /* TEMP low treshold */ + return s->temp_thr[0]; + + case 0xc: /* CFR0 */ + return (s->pressure << 15) | ((!s->busy) << 14) | + (s->nextprecision << 13) | s->timing[0]; + case 0xd: /* CFR1 */ + return s->timing[1]; + case 0xe: /* CFR2 */ + return (s->pin_func << 14) | s->filter; + + case 0xf: /* Function select status */ + return s->function >= 0 ? 1 << s->function : 0; + } + + /* Never gets here */ + return 0xffff; +} + +static void tsc2005_write(TSC2005State *s, int reg, uint16_t data) +{ + switch (reg) { + case 0x8: /* AUX high treshold */ + s->aux_thr[1] = data; + break; + case 0x9: /* AUX low treshold */ + s->aux_thr[0] = data; + break; + + case 0xa: /* TEMP high treshold */ + s->temp_thr[1] = data; + break; + case 0xb: /* TEMP low treshold */ + s->temp_thr[0] = data; + break; + + case 0xc: /* CFR0 */ + s->host_mode = (data >> 15) != 0; + if (s->enabled != !(data & 0x4000)) { + s->enabled = !(data & 0x4000); + trace_tsc2005_sense(s->enabled ? "enabled" : "disabled"); + if (s->busy && !s->enabled) + timer_del(s->timer); + s->busy = s->busy && s->enabled; + } + s->nextprecision = (data >> 13) & 1; + s->timing[0] = data & 0x1fff; + if ((s->timing[0] >> 11) == 3) { + qemu_log_mask(LOG_GUEST_ERROR, + "tsc2005_write: illegal conversion clock setting\n"); + } + break; + case 0xd: /* CFR1 */ + s->timing[1] = data & 0xf07; + break; + case 0xe: /* CFR2 */ + s->pin_func = (data >> 14) & 3; + s->filter = data & 0x3fff; + break; + + default: + qemu_log_mask(LOG_GUEST_ERROR, + "%s: write into read-only register 0x%x\n", + __func__, reg); + } +} + +/* This handles most of the chip's logic. */ +static void tsc2005_pin_update(TSC2005State *s) +{ + int64_t expires; + bool pin_state; + + switch (s->pin_func) { + case 0: + pin_state = !s->pressure && !!s->dav; + break; + case 1: + case 3: + default: + pin_state = !s->dav; + break; + case 2: + pin_state = !s->pressure; + } + + if (pin_state != s->irq) { + s->irq = pin_state; + qemu_set_irq(s->pint, s->irq); + } + + switch (s->nextfunction) { + case TSC_MODE_XYZ_SCAN: + case TSC_MODE_XY_SCAN: + if (!s->host_mode && s->dav) + s->enabled = false; + if (!s->pressure) + return; + /* Fall through */ + case TSC_MODE_AUX_SCAN: + break; + + case TSC_MODE_X: + case TSC_MODE_Y: + case TSC_MODE_Z: + if (!s->pressure) + return; + /* Fall through */ + case TSC_MODE_AUX: + case TSC_MODE_TEMP1: + case TSC_MODE_TEMP2: + case TSC_MODE_X_TEST: + case TSC_MODE_Y_TEST: + case TSC_MODE_TS_TEST: + if (s->dav) + s->enabled = false; + break; + + case TSC_MODE_RESERVED: + case TSC_MODE_XX_DRV: + case TSC_MODE_YY_DRV: + case TSC_MODE_YX_DRV: + default: + return; + } + + if (!s->enabled || s->busy) + return; + + s->busy = true; + s->precision = s->nextprecision; + s->function = s->nextfunction; + s->pdst = !s->pnd0; /* Synchronised on internal clock */ + expires = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + (NANOSECONDS_PER_SECOND >> 7); + timer_mod(s->timer, expires); +} + +static void tsc2005_reset(TSC2005State *s) +{ + s->state = 0; + s->pin_func = 0; + s->enabled = false; + s->busy = false; + s->nextprecision = false; + s->nextfunction = 0; + s->timing[0] = 0; + s->timing[1] = 0; + s->irq = false; + s->dav = 0; + s->reset = false; + s->pdst = true; + s->pnd0 = false; + s->function = -1; + s->temp_thr[0] = 0x000; + s->temp_thr[1] = 0xfff; + s->aux_thr[0] = 0x000; + s->aux_thr[1] = 0xfff; + + tsc2005_pin_update(s); +} + +static uint8_t tsc2005_txrx_word(void *opaque, uint8_t value) +{ + TSC2005State *s = opaque; + uint32_t ret = 0; + + switch (s->state ++) { + case 0: + if (value & 0x80) { + /* Command */ + if (value & (1 << 1)) + tsc2005_reset(s); + else { + s->nextfunction = (value >> 3) & 0xf; + s->nextprecision = (value >> 2) & 1; + if (s->enabled != !(value & 1)) { + s->enabled = !(value & 1); + trace_tsc2005_sense(s->enabled ? "enabled" : "disabled"); + if (s->busy && !s->enabled) + timer_del(s->timer); + s->busy = s->busy && s->enabled; + } + tsc2005_pin_update(s); + } + + s->state = 0; + } else if (value) { + /* Data transfer */ + s->reg = (value >> 3) & 0xf; + s->pnd0 = (value >> 1) & 1; + s->command = value & 1; + + if (s->command) { + /* Read */ + s->data = tsc2005_read(s, s->reg); + tsc2005_pin_update(s); + } else + s->data = 0; + } else + s->state = 0; + break; + + case 1: + if (s->command) + ret = (s->data >> 8) & 0xff; + else + s->data |= value << 8; + break; + + case 2: + if (s->command) + ret = s->data & 0xff; + else { + s->data |= value; + tsc2005_write(s, s->reg, s->data); + tsc2005_pin_update(s); + } + + s->state = 0; + break; + } + + return ret; +} + +uint32_t tsc2005_txrx(void *opaque, uint32_t value, int len) +{ + uint32_t ret = 0; + + len &= ~7; + while (len > 0) { + len -= 8; + ret |= tsc2005_txrx_word(opaque, (value >> len) & 0xff) << len; + } + + return ret; +} + +static void tsc2005_timer_tick(void *opaque) +{ + TSC2005State *s = opaque; + + /* Timer ticked -- a set of conversions has been finished. */ + + if (!s->busy) + return; + + s->busy = false; + s->dav |= mode_regs[s->function]; + s->function = -1; + tsc2005_pin_update(s); +} + +static void tsc2005_touchscreen_event(void *opaque, + int x, int y, int z, int buttons_state) +{ + TSC2005State *s = opaque; + int p = s->pressure; + + if (buttons_state) { + s->x = x; + s->y = y; + } + s->pressure = !!buttons_state; + + /* + * Note: We would get better responsiveness in the guest by + * signaling TS events immediately, but for now we simulate + * the first conversion delay for sake of correctness. + */ + if (p != s->pressure) + tsc2005_pin_update(s); +} + +static int tsc2005_post_load(void *opaque, int version_id) +{ + TSC2005State *s = (TSC2005State *) opaque; + + s->busy = timer_pending(s->timer); + tsc2005_pin_update(s); + + return 0; +} + +static const VMStateDescription vmstate_tsc2005 = { + .name = "tsc2005", + .version_id = 2, + .minimum_version_id = 2, + .post_load = tsc2005_post_load, + .fields = (VMStateField []) { + VMSTATE_BOOL(pressure, TSC2005State), + VMSTATE_BOOL(irq, TSC2005State), + VMSTATE_BOOL(command, TSC2005State), + VMSTATE_BOOL(enabled, TSC2005State), + VMSTATE_BOOL(host_mode, TSC2005State), + VMSTATE_BOOL(reset, TSC2005State), + VMSTATE_BOOL(pdst, TSC2005State), + VMSTATE_BOOL(pnd0, TSC2005State), + VMSTATE_BOOL(precision, TSC2005State), + VMSTATE_BOOL(nextprecision, TSC2005State), + VMSTATE_UINT8(reg, TSC2005State), + VMSTATE_UINT8(state, TSC2005State), + VMSTATE_UINT16(data, TSC2005State), + VMSTATE_UINT16(dav, TSC2005State), + VMSTATE_UINT16(filter, TSC2005State), + VMSTATE_INT8(nextfunction, TSC2005State), + VMSTATE_INT8(function, TSC2005State), + VMSTATE_INT32(x, TSC2005State), + VMSTATE_INT32(y, TSC2005State), + VMSTATE_TIMER_PTR(timer, TSC2005State), + VMSTATE_UINT8(pin_func, TSC2005State), + VMSTATE_UINT16_ARRAY(timing, TSC2005State, 2), + VMSTATE_UINT8(noise, TSC2005State), + VMSTATE_UINT16_ARRAY(temp_thr, TSC2005State, 2), + VMSTATE_UINT16_ARRAY(aux_thr, TSC2005State, 2), + VMSTATE_INT32_ARRAY(tr, TSC2005State, 8), + VMSTATE_END_OF_LIST() + } +}; + +void *tsc2005_init(qemu_irq pintdav) +{ + TSC2005State *s; + + s = (TSC2005State *) + g_malloc0(sizeof(TSC2005State)); + s->x = 400; + s->y = 240; + s->pressure = false; + s->precision = s->nextprecision = false; + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc2005_timer_tick, s); + s->pint = pintdav; + s->model = 0x2005; + + s->tr[0] = 0; + s->tr[1] = 1; + s->tr[2] = 1; + s->tr[3] = 0; + s->tr[4] = 1; + s->tr[5] = 0; + s->tr[6] = 1; + s->tr[7] = 0; + + tsc2005_reset(s); + + qemu_add_mouse_event_handler(tsc2005_touchscreen_event, s, 1, + "QEMU TSC2005-driven Touchscreen"); + + qemu_register_reset((void *) tsc2005_reset, s); + vmstate_register(NULL, 0, &vmstate_tsc2005, s); + + return s; +} + +/* + * Use tslib generated calibration data to generate ADC input values + * from the touchscreen. Assuming 12-bit precision was used during + * tslib calibration. + */ +void tsc2005_set_transform(void *opaque, MouseTransformInfo *info) +{ + TSC2005State *s = (TSC2005State *) opaque; + + /* This version assumes touchscreen X & Y axis are parallel or + * perpendicular to LCD's X & Y axis in some way. */ + if (abs(info->a[0]) > abs(info->a[1])) { + s->tr[0] = 0; + s->tr[1] = -info->a[6] * info->x; + s->tr[2] = info->a[0]; + s->tr[3] = -info->a[2] / info->a[0]; + s->tr[4] = info->a[6] * info->y; + s->tr[5] = 0; + s->tr[6] = info->a[4]; + s->tr[7] = -info->a[5] / info->a[4]; + } else { + s->tr[0] = info->a[6] * info->y; + s->tr[1] = 0; + s->tr[2] = info->a[1]; + s->tr[3] = -info->a[2] / info->a[1]; + s->tr[4] = 0; + s->tr[5] = -info->a[6] * info->x; + s->tr[6] = info->a[3]; + s->tr[7] = -info->a[5] / info->a[3]; + } + + s->tr[0] >>= 11; + s->tr[1] >>= 11; + s->tr[3] <<= 4; + s->tr[4] >>= 11; + s->tr[5] >>= 11; + s->tr[7] <<= 4; +} diff --git a/hw/input/tsc210x.c b/hw/input/tsc210x.c new file mode 100644 index 000000000..182d3725f --- /dev/null +++ b/hw/input/tsc210x.c @@ -0,0 +1,1253 @@ +/* + * TI TSC2102 (touchscreen/sensors/audio controller) emulator. + * TI TSC2301 (touchscreen/sensors/keypad). + * + * Copyright (c) 2006 Andrzej Zaborowski <balrog@zabor.org> + * Copyright (C) 2008 Nokia Corporation + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 or + * (at your option) version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "hw/hw.h" +#include "audio/audio.h" +#include "qemu/timer.h" +#include "sysemu/reset.h" +#include "ui/console.h" +#include "hw/arm/omap.h" /* For I2SCodec */ +#include "hw/input/tsc2xxx.h" +#include "hw/irq.h" +#include "migration/vmstate.h" + +#define TSC_DATA_REGISTERS_PAGE 0x0 +#define TSC_CONTROL_REGISTERS_PAGE 0x1 +#define TSC_AUDIO_REGISTERS_PAGE 0x2 + +#define TSC_VERBOSE + +#define TSC_CUT_RESOLUTION(value, p) ((value) >> (16 - resolution[p])) + +typedef struct { + qemu_irq pint; + qemu_irq kbint; + qemu_irq davint; + QEMUTimer *timer; + QEMUSoundCard card; + uWireSlave chip; + I2SCodec codec; + uint8_t in_fifo[16384]; + uint8_t out_fifo[16384]; + uint16_t model; + + int32_t x, y; + bool pressure; + + uint8_t page, offset; + uint16_t dav; + + bool state; + bool irq; + bool command; + bool busy; + bool enabled; + bool host_mode; + uint8_t function, nextfunction; + uint8_t precision, nextprecision; + uint8_t filter; + uint8_t pin_func; + uint8_t ref; + uint8_t timing; + uint8_t noise; + + uint16_t audio_ctrl1; + uint16_t audio_ctrl2; + uint16_t audio_ctrl3; + uint16_t pll[3]; + uint16_t volume; + int64_t volume_change; + bool softstep; + uint16_t dac_power; + int64_t powerdown; + uint16_t filter_data[0x14]; + + const char *name; + SWVoiceIn *adc_voice[1]; + SWVoiceOut *dac_voice[1]; + int i2s_rx_rate; + int i2s_tx_rate; + + int tr[8]; + + struct { + uint16_t down; + uint16_t mask; + int scan; + int debounce; + int mode; + int intr; + } kb; + int64_t now; /* Time at migration */ +} TSC210xState; + +static const int resolution[4] = { 12, 8, 10, 12 }; + +#define TSC_MODE_NO_SCAN 0x0 +#define TSC_MODE_XY_SCAN 0x1 +#define TSC_MODE_XYZ_SCAN 0x2 +#define TSC_MODE_X 0x3 +#define TSC_MODE_Y 0x4 +#define TSC_MODE_Z 0x5 +#define TSC_MODE_BAT1 0x6 +#define TSC_MODE_BAT2 0x7 +#define TSC_MODE_AUX 0x8 +#define TSC_MODE_AUX_SCAN 0x9 +#define TSC_MODE_TEMP1 0xa +#define TSC_MODE_PORT_SCAN 0xb +#define TSC_MODE_TEMP2 0xc +#define TSC_MODE_XX_DRV 0xd +#define TSC_MODE_YY_DRV 0xe +#define TSC_MODE_YX_DRV 0xf + +static const uint16_t mode_regs[16] = { + 0x0000, /* No scan */ + 0x0600, /* X, Y scan */ + 0x0780, /* X, Y, Z scan */ + 0x0400, /* X */ + 0x0200, /* Y */ + 0x0180, /* Z */ + 0x0040, /* BAT1 */ + 0x0030, /* BAT2 */ + 0x0010, /* AUX */ + 0x0010, /* AUX scan */ + 0x0004, /* TEMP1 */ + 0x0070, /* Port scan */ + 0x0002, /* TEMP2 */ + 0x0000, /* X+, X- drivers */ + 0x0000, /* Y+, Y- drivers */ + 0x0000, /* Y+, X- drivers */ +}; + +#define X_TRANSFORM(s) \ + ((s->y * s->tr[0] - s->x * s->tr[1]) / s->tr[2] + s->tr[3]) +#define Y_TRANSFORM(s) \ + ((s->y * s->tr[4] - s->x * s->tr[5]) / s->tr[6] + s->tr[7]) +#define Z1_TRANSFORM(s) \ + ((400 - ((s)->x >> 7) + ((s)->pressure << 10)) << 4) +#define Z2_TRANSFORM(s) \ + ((4000 + ((s)->y >> 7) - ((s)->pressure << 10)) << 4) + +#define BAT1_VAL 0x8660 +#define BAT2_VAL 0x0000 +#define AUX1_VAL 0x35c0 +#define AUX2_VAL 0xffff +#define TEMP1_VAL 0x8c70 +#define TEMP2_VAL 0xa5b0 + +#define TSC_POWEROFF_DELAY 50 +#define TSC_SOFTSTEP_DELAY 50 + +static void tsc210x_reset(TSC210xState *s) +{ + s->state = false; + s->pin_func = 2; + s->enabled = false; + s->busy = false; + s->nextfunction = 0; + s->ref = 0; + s->timing = 0; + s->irq = false; + s->dav = 0; + + s->audio_ctrl1 = 0x0000; + s->audio_ctrl2 = 0x4410; + s->audio_ctrl3 = 0x0000; + s->pll[0] = 0x1004; + s->pll[1] = 0x0000; + s->pll[2] = 0x1fff; + s->volume = 0xffff; + s->dac_power = 0x8540; + s->softstep = true; + s->volume_change = 0; + s->powerdown = 0; + s->filter_data[0x00] = 0x6be3; + s->filter_data[0x01] = 0x9666; + s->filter_data[0x02] = 0x675d; + s->filter_data[0x03] = 0x6be3; + s->filter_data[0x04] = 0x9666; + s->filter_data[0x05] = 0x675d; + s->filter_data[0x06] = 0x7d83; + s->filter_data[0x07] = 0x84ee; + s->filter_data[0x08] = 0x7d83; + s->filter_data[0x09] = 0x84ee; + s->filter_data[0x0a] = 0x6be3; + s->filter_data[0x0b] = 0x9666; + s->filter_data[0x0c] = 0x675d; + s->filter_data[0x0d] = 0x6be3; + s->filter_data[0x0e] = 0x9666; + s->filter_data[0x0f] = 0x675d; + s->filter_data[0x10] = 0x7d83; + s->filter_data[0x11] = 0x84ee; + s->filter_data[0x12] = 0x7d83; + s->filter_data[0x13] = 0x84ee; + + s->i2s_tx_rate = 0; + s->i2s_rx_rate = 0; + + s->kb.scan = 1; + s->kb.debounce = 0; + s->kb.mask = 0x0000; + s->kb.mode = 3; + s->kb.intr = 0; + + qemu_set_irq(s->pint, !s->irq); + qemu_set_irq(s->davint, !s->dav); + qemu_irq_raise(s->kbint); +} + +typedef struct { + int rate; + int dsor; + int fsref; +} TSC210xRateInfo; + +/* { rate, dsor, fsref } */ +static const TSC210xRateInfo tsc2102_rates[] = { + /* Fsref / 6.0 */ + { 7350, 63, 1 }, + { 8000, 63, 0 }, + /* Fsref / 6.0 */ + { 7350, 54, 1 }, + { 8000, 54, 0 }, + /* Fsref / 5.0 */ + { 8820, 45, 1 }, + { 9600, 45, 0 }, + /* Fsref / 4.0 */ + { 11025, 36, 1 }, + { 12000, 36, 0 }, + /* Fsref / 3.0 */ + { 14700, 27, 1 }, + { 16000, 27, 0 }, + /* Fsref / 2.0 */ + { 22050, 18, 1 }, + { 24000, 18, 0 }, + /* Fsref / 1.5 */ + { 29400, 9, 1 }, + { 32000, 9, 0 }, + /* Fsref */ + { 44100, 0, 1 }, + { 48000, 0, 0 }, + + { 0, 0, 0 }, +}; + +static inline void tsc210x_out_flush(TSC210xState *s, int len) +{ + uint8_t *data = s->codec.out.fifo + s->codec.out.start; + uint8_t *end = data + len; + + while (data < end) + data += AUD_write(s->dac_voice[0], data, end - data) ?: (end - data); + + s->codec.out.len -= len; + if (s->codec.out.len) + memmove(s->codec.out.fifo, end, s->codec.out.len); + s->codec.out.start = 0; +} + +static void tsc210x_audio_out_cb(TSC210xState *s, int free_b) +{ + if (s->codec.out.len >= free_b) { + tsc210x_out_flush(s, free_b); + return; + } + + s->codec.out.size = MIN(free_b, 16384); + qemu_irq_raise(s->codec.tx_start); +} + +static void tsc2102_audio_rate_update(TSC210xState *s) +{ + const TSC210xRateInfo *rate; + + s->codec.tx_rate = 0; + s->codec.rx_rate = 0; + if (s->dac_power & (1 << 15)) /* PWDNC */ + return; + + for (rate = tsc2102_rates; rate->rate; rate ++) + if (rate->dsor == (s->audio_ctrl1 & 0x3f) && /* DACFS */ + rate->fsref == ((s->audio_ctrl3 >> 13) & 1))/* REFFS */ + break; + if (!rate->rate) { + printf("%s: unknown sampling rate configured\n", __func__); + return; + } + + s->codec.tx_rate = rate->rate; +} + +static void tsc2102_audio_output_update(TSC210xState *s) +{ + int enable; + struct audsettings fmt; + + if (s->dac_voice[0]) { + tsc210x_out_flush(s, s->codec.out.len); + s->codec.out.size = 0; + AUD_set_active_out(s->dac_voice[0], 0); + AUD_close_out(&s->card, s->dac_voice[0]); + s->dac_voice[0] = NULL; + } + s->codec.cts = 0; + + enable = + (~s->dac_power & (1 << 15)) && /* PWDNC */ + (~s->dac_power & (1 << 10)); /* DAPWDN */ + if (!enable || !s->codec.tx_rate) + return; + + /* Force our own sampling rate even in slave DAC mode */ + fmt.endianness = 0; + fmt.nchannels = 2; + fmt.freq = s->codec.tx_rate; + fmt.fmt = AUDIO_FORMAT_S16; + + s->dac_voice[0] = AUD_open_out(&s->card, s->dac_voice[0], + "tsc2102.sink", s, (void *) tsc210x_audio_out_cb, &fmt); + if (s->dac_voice[0]) { + s->codec.cts = 1; + AUD_set_active_out(s->dac_voice[0], 1); + } +} + +static uint16_t tsc2102_data_register_read(TSC210xState *s, int reg) +{ + switch (reg) { + case 0x00: /* X */ + s->dav &= 0xfbff; + return TSC_CUT_RESOLUTION(X_TRANSFORM(s), s->precision) + + (s->noise & 3); + + case 0x01: /* Y */ + s->noise ++; + s->dav &= 0xfdff; + return TSC_CUT_RESOLUTION(Y_TRANSFORM(s), s->precision) ^ + (s->noise & 3); + + case 0x02: /* Z1 */ + s->dav &= 0xfeff; + return TSC_CUT_RESOLUTION(Z1_TRANSFORM(s), s->precision) - + (s->noise & 3); + + case 0x03: /* Z2 */ + s->dav &= 0xff7f; + return TSC_CUT_RESOLUTION(Z2_TRANSFORM(s), s->precision) | + (s->noise & 3); + + case 0x04: /* KPData */ + if ((s->model & 0xff00) == 0x2300) { + if (s->kb.intr && (s->kb.mode & 2)) { + s->kb.intr = 0; + qemu_irq_raise(s->kbint); + } + return s->kb.down; + } + + return 0xffff; + + case 0x05: /* BAT1 */ + s->dav &= 0xffbf; + return TSC_CUT_RESOLUTION(BAT1_VAL, s->precision) + + (s->noise & 6); + + case 0x06: /* BAT2 */ + s->dav &= 0xffdf; + return TSC_CUT_RESOLUTION(BAT2_VAL, s->precision); + + case 0x07: /* AUX1 */ + s->dav &= 0xffef; + return TSC_CUT_RESOLUTION(AUX1_VAL, s->precision); + + case 0x08: /* AUX2 */ + s->dav &= 0xfff7; + return 0xffff; + + case 0x09: /* TEMP1 */ + s->dav &= 0xfffb; + return TSC_CUT_RESOLUTION(TEMP1_VAL, s->precision) - + (s->noise & 5); + + case 0x0a: /* TEMP2 */ + s->dav &= 0xfffd; + return TSC_CUT_RESOLUTION(TEMP2_VAL, s->precision) ^ + (s->noise & 3); + + case 0x0b: /* DAC */ + s->dav &= 0xfffe; + return 0xffff; + + default: +#ifdef TSC_VERBOSE + fprintf(stderr, "tsc2102_data_register_read: " + "no such register: 0x%02x\n", reg); +#endif + return 0xffff; + } +} + +static uint16_t tsc2102_control_register_read( + TSC210xState *s, int reg) +{ + switch (reg) { + case 0x00: /* TSC ADC */ + return (s->pressure << 15) | ((!s->busy) << 14) | + (s->nextfunction << 10) | (s->nextprecision << 8) | s->filter; + + case 0x01: /* Status / Keypad Control */ + if ((s->model & 0xff00) == 0x2100) + return (s->pin_func << 14) | ((!s->enabled) << 13) | + (s->host_mode << 12) | ((!!s->dav) << 11) | s->dav; + else + return (s->kb.intr << 15) | ((s->kb.scan || !s->kb.down) << 14) | + (s->kb.debounce << 11); + + case 0x02: /* DAC Control */ + if ((s->model & 0xff00) == 0x2300) + return s->dac_power & 0x8000; + else + goto bad_reg; + + case 0x03: /* Reference */ + return s->ref; + + case 0x04: /* Reset */ + return 0xffff; + + case 0x05: /* Configuration */ + return s->timing; + + case 0x06: /* Secondary configuration */ + if ((s->model & 0xff00) == 0x2100) + goto bad_reg; + return ((!s->dav) << 15) | ((s->kb.mode & 1) << 14) | s->pll[2]; + + case 0x10: /* Keypad Mask */ + if ((s->model & 0xff00) == 0x2100) + goto bad_reg; + return s->kb.mask; + + default: + bad_reg: +#ifdef TSC_VERBOSE + fprintf(stderr, "tsc2102_control_register_read: " + "no such register: 0x%02x\n", reg); +#endif + return 0xffff; + } +} + +static uint16_t tsc2102_audio_register_read(TSC210xState *s, int reg) +{ + int l_ch, r_ch; + uint16_t val; + + switch (reg) { + case 0x00: /* Audio Control 1 */ + return s->audio_ctrl1; + + case 0x01: + return 0xff00; + + case 0x02: /* DAC Volume Control */ + return s->volume; + + case 0x03: + return 0x8b00; + + case 0x04: /* Audio Control 2 */ + l_ch = 1; + r_ch = 1; + if (s->softstep && !(s->dac_power & (1 << 10))) { + l_ch = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) > + s->volume_change + TSC_SOFTSTEP_DELAY); + r_ch = (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) > + s->volume_change + TSC_SOFTSTEP_DELAY); + } + + return s->audio_ctrl2 | (l_ch << 3) | (r_ch << 2); + + case 0x05: /* Stereo DAC Power Control */ + return 0x2aa0 | s->dac_power | + (((s->dac_power & (1 << 10)) && + (qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) > + s->powerdown + TSC_POWEROFF_DELAY)) << 6); + + case 0x06: /* Audio Control 3 */ + val = s->audio_ctrl3 | 0x0001; + s->audio_ctrl3 &= 0xff3f; + return val; + + case 0x07: /* LCH_BASS_BOOST_N0 */ + case 0x08: /* LCH_BASS_BOOST_N1 */ + case 0x09: /* LCH_BASS_BOOST_N2 */ + case 0x0a: /* LCH_BASS_BOOST_N3 */ + case 0x0b: /* LCH_BASS_BOOST_N4 */ + case 0x0c: /* LCH_BASS_BOOST_N5 */ + case 0x0d: /* LCH_BASS_BOOST_D1 */ + case 0x0e: /* LCH_BASS_BOOST_D2 */ + case 0x0f: /* LCH_BASS_BOOST_D4 */ + case 0x10: /* LCH_BASS_BOOST_D5 */ + case 0x11: /* RCH_BASS_BOOST_N0 */ + case 0x12: /* RCH_BASS_BOOST_N1 */ + case 0x13: /* RCH_BASS_BOOST_N2 */ + case 0x14: /* RCH_BASS_BOOST_N3 */ + case 0x15: /* RCH_BASS_BOOST_N4 */ + case 0x16: /* RCH_BASS_BOOST_N5 */ + case 0x17: /* RCH_BASS_BOOST_D1 */ + case 0x18: /* RCH_BASS_BOOST_D2 */ + case 0x19: /* RCH_BASS_BOOST_D4 */ + case 0x1a: /* RCH_BASS_BOOST_D5 */ + return s->filter_data[reg - 0x07]; + + case 0x1b: /* PLL Programmability 1 */ + return s->pll[0]; + + case 0x1c: /* PLL Programmability 2 */ + return s->pll[1]; + + case 0x1d: /* Audio Control 4 */ + return (!s->softstep) << 14; + + default: +#ifdef TSC_VERBOSE + fprintf(stderr, "tsc2102_audio_register_read: " + "no such register: 0x%02x\n", reg); +#endif + return 0xffff; + } +} + +static void tsc2102_data_register_write( + TSC210xState *s, int reg, uint16_t value) +{ + switch (reg) { + case 0x00: /* X */ + case 0x01: /* Y */ + case 0x02: /* Z1 */ + case 0x03: /* Z2 */ + case 0x05: /* BAT1 */ + case 0x06: /* BAT2 */ + case 0x07: /* AUX1 */ + case 0x08: /* AUX2 */ + case 0x09: /* TEMP1 */ + case 0x0a: /* TEMP2 */ + return; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "tsc2102_data_register_write: " + "no such register: 0x%02x\n", reg); + } +} + +static void tsc2102_control_register_write( + TSC210xState *s, int reg, uint16_t value) +{ + switch (reg) { + case 0x00: /* TSC ADC */ + s->host_mode = value >> 15; + s->enabled = !(value & 0x4000); + if (s->busy && !s->enabled) + timer_del(s->timer); + s->busy = s->busy && s->enabled; + s->nextfunction = (value >> 10) & 0xf; + s->nextprecision = (value >> 8) & 3; + s->filter = value & 0xff; + return; + + case 0x01: /* Status / Keypad Control */ + if ((s->model & 0xff00) == 0x2100) + s->pin_func = value >> 14; + else { + s->kb.scan = (value >> 14) & 1; + s->kb.debounce = (value >> 11) & 7; + if (s->kb.intr && s->kb.scan) { + s->kb.intr = 0; + qemu_irq_raise(s->kbint); + } + } + return; + + case 0x02: /* DAC Control */ + if ((s->model & 0xff00) == 0x2300) { + s->dac_power &= 0x7fff; + s->dac_power |= 0x8000 & value; + } else + goto bad_reg; + break; + + case 0x03: /* Reference */ + s->ref = value & 0x1f; + return; + + case 0x04: /* Reset */ + if (value == 0xbb00) { + if (s->busy) + timer_del(s->timer); + tsc210x_reset(s); +#ifdef TSC_VERBOSE + } else { + fprintf(stderr, "tsc2102_control_register_write: " + "wrong value written into RESET\n"); +#endif + } + return; + + case 0x05: /* Configuration */ + s->timing = value & 0x3f; +#ifdef TSC_VERBOSE + if (value & ~0x3f) + fprintf(stderr, "tsc2102_control_register_write: " + "wrong value written into CONFIG\n"); +#endif + return; + + case 0x06: /* Secondary configuration */ + if ((s->model & 0xff00) == 0x2100) + goto bad_reg; + s->kb.mode = value >> 14; + s->pll[2] = value & 0x3ffff; + return; + + case 0x10: /* Keypad Mask */ + if ((s->model & 0xff00) == 0x2100) + goto bad_reg; + s->kb.mask = value; + return; + + default: + bad_reg: + qemu_log_mask(LOG_GUEST_ERROR, "tsc2102_control_register_write: " + "no such register: 0x%02x\n", reg); + } +} + +static void tsc2102_audio_register_write( + TSC210xState *s, int reg, uint16_t value) +{ + switch (reg) { + case 0x00: /* Audio Control 1 */ + s->audio_ctrl1 = value & 0x0f3f; +#ifdef TSC_VERBOSE + if ((value & ~0x0f3f) || ((value & 7) != ((value >> 3) & 7))) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into Audio 1\n"); +#endif + tsc2102_audio_rate_update(s); + tsc2102_audio_output_update(s); + return; + + case 0x01: +#ifdef TSC_VERBOSE + if (value != 0xff00) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into reg 0x01\n"); +#endif + return; + + case 0x02: /* DAC Volume Control */ + s->volume = value; + s->volume_change = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + return; + + case 0x03: +#ifdef TSC_VERBOSE + if (value != 0x8b00) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into reg 0x03\n"); +#endif + return; + + case 0x04: /* Audio Control 2 */ + s->audio_ctrl2 = value & 0xf7f2; +#ifdef TSC_VERBOSE + if (value & ~0xf7fd) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into Audio 2\n"); +#endif + return; + + case 0x05: /* Stereo DAC Power Control */ + if ((value & ~s->dac_power) & (1 << 10)) + s->powerdown = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + s->dac_power = value & 0x9543; +#ifdef TSC_VERBOSE + if ((value & ~0x9543) != 0x2aa0) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into Power\n"); +#endif + tsc2102_audio_rate_update(s); + tsc2102_audio_output_update(s); + return; + + case 0x06: /* Audio Control 3 */ + s->audio_ctrl3 &= 0x00c0; + s->audio_ctrl3 |= value & 0xf800; +#ifdef TSC_VERBOSE + if (value & ~0xf8c7) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into Audio 3\n"); +#endif + tsc2102_audio_output_update(s); + return; + + case 0x07: /* LCH_BASS_BOOST_N0 */ + case 0x08: /* LCH_BASS_BOOST_N1 */ + case 0x09: /* LCH_BASS_BOOST_N2 */ + case 0x0a: /* LCH_BASS_BOOST_N3 */ + case 0x0b: /* LCH_BASS_BOOST_N4 */ + case 0x0c: /* LCH_BASS_BOOST_N5 */ + case 0x0d: /* LCH_BASS_BOOST_D1 */ + case 0x0e: /* LCH_BASS_BOOST_D2 */ + case 0x0f: /* LCH_BASS_BOOST_D4 */ + case 0x10: /* LCH_BASS_BOOST_D5 */ + case 0x11: /* RCH_BASS_BOOST_N0 */ + case 0x12: /* RCH_BASS_BOOST_N1 */ + case 0x13: /* RCH_BASS_BOOST_N2 */ + case 0x14: /* RCH_BASS_BOOST_N3 */ + case 0x15: /* RCH_BASS_BOOST_N4 */ + case 0x16: /* RCH_BASS_BOOST_N5 */ + case 0x17: /* RCH_BASS_BOOST_D1 */ + case 0x18: /* RCH_BASS_BOOST_D2 */ + case 0x19: /* RCH_BASS_BOOST_D4 */ + case 0x1a: /* RCH_BASS_BOOST_D5 */ + s->filter_data[reg - 0x07] = value; + return; + + case 0x1b: /* PLL Programmability 1 */ + s->pll[0] = value & 0xfffc; +#ifdef TSC_VERBOSE + if (value & ~0xfffc) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into PLL 1\n"); +#endif + return; + + case 0x1c: /* PLL Programmability 2 */ + s->pll[1] = value & 0xfffc; +#ifdef TSC_VERBOSE + if (value & ~0xfffc) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into PLL 2\n"); +#endif + return; + + case 0x1d: /* Audio Control 4 */ + s->softstep = !(value & 0x4000); +#ifdef TSC_VERBOSE + if (value & ~0x4000) + fprintf(stderr, "tsc2102_audio_register_write: " + "wrong value written into Audio 4\n"); +#endif + return; + + default: + qemu_log_mask(LOG_GUEST_ERROR, "tsc2102_audio_register_write: " + "no such register: 0x%02x\n", reg); + } +} + +/* This handles most of the chip logic. */ +static void tsc210x_pin_update(TSC210xState *s) +{ + int64_t expires; + bool pin_state; + + switch (s->pin_func) { + case 0: + pin_state = s->pressure; + break; + case 1: + pin_state = !!s->dav; + break; + case 2: + default: + pin_state = s->pressure && !s->dav; + } + + if (!s->enabled) + pin_state = false; + + if (pin_state != s->irq) { + s->irq = pin_state; + qemu_set_irq(s->pint, !s->irq); + } + + switch (s->nextfunction) { + case TSC_MODE_XY_SCAN: + case TSC_MODE_XYZ_SCAN: + if (!s->pressure) + return; + break; + + case TSC_MODE_X: + case TSC_MODE_Y: + case TSC_MODE_Z: + if (!s->pressure) + return; + /* Fall through */ + case TSC_MODE_BAT1: + case TSC_MODE_BAT2: + case TSC_MODE_AUX: + case TSC_MODE_TEMP1: + case TSC_MODE_TEMP2: + if (s->dav) + s->enabled = false; + break; + + case TSC_MODE_AUX_SCAN: + case TSC_MODE_PORT_SCAN: + break; + + case TSC_MODE_NO_SCAN: + case TSC_MODE_XX_DRV: + case TSC_MODE_YY_DRV: + case TSC_MODE_YX_DRV: + default: + return; + } + + if (!s->enabled || s->busy || s->dav) + return; + + s->busy = true; + s->precision = s->nextprecision; + s->function = s->nextfunction; + expires = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + (NANOSECONDS_PER_SECOND >> 10); + timer_mod(s->timer, expires); +} + +static uint16_t tsc210x_read(TSC210xState *s) +{ + uint16_t ret = 0x0000; + + if (!s->command) + fprintf(stderr, "tsc210x_read: SPI underrun!\n"); + + switch (s->page) { + case TSC_DATA_REGISTERS_PAGE: + ret = tsc2102_data_register_read(s, s->offset); + if (!s->dav) + qemu_irq_raise(s->davint); + break; + case TSC_CONTROL_REGISTERS_PAGE: + ret = tsc2102_control_register_read(s, s->offset); + break; + case TSC_AUDIO_REGISTERS_PAGE: + ret = tsc2102_audio_register_read(s, s->offset); + break; + default: + hw_error("tsc210x_read: wrong memory page\n"); + } + + tsc210x_pin_update(s); + + /* Allow sequential reads. */ + s->offset ++; + s->state = false; + return ret; +} + +static void tsc210x_write(TSC210xState *s, uint16_t value) +{ + /* + * This is a two-state state machine for reading + * command and data every second time. + */ + if (!s->state) { + s->command = (value >> 15) != 0; + s->page = (value >> 11) & 0x0f; + s->offset = (value >> 5) & 0x3f; + s->state = true; + } else { + if (s->command) + fprintf(stderr, "tsc210x_write: SPI overrun!\n"); + else + switch (s->page) { + case TSC_DATA_REGISTERS_PAGE: + tsc2102_data_register_write(s, s->offset, value); + break; + case TSC_CONTROL_REGISTERS_PAGE: + tsc2102_control_register_write(s, s->offset, value); + break; + case TSC_AUDIO_REGISTERS_PAGE: + tsc2102_audio_register_write(s, s->offset, value); + break; + default: + hw_error("tsc210x_write: wrong memory page\n"); + } + + tsc210x_pin_update(s); + s->state = false; + } +} + +uint32_t tsc210x_txrx(void *opaque, uint32_t value, int len) +{ + TSC210xState *s = opaque; + uint32_t ret = 0; + + if (len != 16) + hw_error("%s: FIXME: bad SPI word width %i\n", __func__, len); + + /* TODO: sequential reads etc - how do we make sure the host doesn't + * unintentionally read out a conversion result from a register while + * transmitting the command word of the next command? */ + if (!value || (s->state && s->command)) + ret = tsc210x_read(s); + if (value || (s->state && !s->command)) + tsc210x_write(s, value); + + return ret; +} + +static void tsc210x_timer_tick(void *opaque) +{ + TSC210xState *s = opaque; + + /* Timer ticked -- a set of conversions has been finished. */ + + if (!s->busy) + return; + + s->busy = false; + s->dav |= mode_regs[s->function]; + tsc210x_pin_update(s); + qemu_irq_lower(s->davint); +} + +static void tsc210x_touchscreen_event(void *opaque, + int x, int y, int z, int buttons_state) +{ + TSC210xState *s = opaque; + int p = s->pressure; + + if (buttons_state) { + s->x = x; + s->y = y; + } + s->pressure = !!buttons_state; + + /* + * Note: We would get better responsiveness in the guest by + * signaling TS events immediately, but for now we simulate + * the first conversion delay for sake of correctness. + */ + if (p != s->pressure) + tsc210x_pin_update(s); +} + +static void tsc210x_i2s_swallow(TSC210xState *s) +{ + if (s->dac_voice[0]) + tsc210x_out_flush(s, s->codec.out.len); + else + s->codec.out.len = 0; +} + +static void tsc210x_i2s_set_rate(TSC210xState *s, int in, int out) +{ + s->i2s_tx_rate = out; + s->i2s_rx_rate = in; +} + +static int tsc210x_pre_save(void *opaque) +{ + TSC210xState *s = (TSC210xState *) opaque; + s->now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + return 0; +} + +static int tsc210x_post_load(void *opaque, int version_id) +{ + TSC210xState *s = (TSC210xState *) opaque; + int64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL); + + if (s->function >= ARRAY_SIZE(mode_regs)) { + return -EINVAL; + } + if (s->nextfunction >= ARRAY_SIZE(mode_regs)) { + return -EINVAL; + } + if (s->precision >= ARRAY_SIZE(resolution)) { + return -EINVAL; + } + if (s->nextprecision >= ARRAY_SIZE(resolution)) { + return -EINVAL; + } + + s->volume_change -= s->now; + s->volume_change += now; + s->powerdown -= s->now; + s->powerdown += now; + + s->busy = timer_pending(s->timer); + qemu_set_irq(s->pint, !s->irq); + qemu_set_irq(s->davint, !s->dav); + + return 0; +} + +static VMStateField vmstatefields_tsc210x[] = { + VMSTATE_BOOL(enabled, TSC210xState), + VMSTATE_BOOL(host_mode, TSC210xState), + VMSTATE_BOOL(irq, TSC210xState), + VMSTATE_BOOL(command, TSC210xState), + VMSTATE_BOOL(pressure, TSC210xState), + VMSTATE_BOOL(softstep, TSC210xState), + VMSTATE_BOOL(state, TSC210xState), + VMSTATE_UINT16(dav, TSC210xState), + VMSTATE_INT32(x, TSC210xState), + VMSTATE_INT32(y, TSC210xState), + VMSTATE_UINT8(offset, TSC210xState), + VMSTATE_UINT8(page, TSC210xState), + VMSTATE_UINT8(filter, TSC210xState), + VMSTATE_UINT8(pin_func, TSC210xState), + VMSTATE_UINT8(ref, TSC210xState), + VMSTATE_UINT8(timing, TSC210xState), + VMSTATE_UINT8(noise, TSC210xState), + VMSTATE_UINT8(function, TSC210xState), + VMSTATE_UINT8(nextfunction, TSC210xState), + VMSTATE_UINT8(precision, TSC210xState), + VMSTATE_UINT8(nextprecision, TSC210xState), + VMSTATE_UINT16(audio_ctrl1, TSC210xState), + VMSTATE_UINT16(audio_ctrl2, TSC210xState), + VMSTATE_UINT16(audio_ctrl3, TSC210xState), + VMSTATE_UINT16_ARRAY(pll, TSC210xState, 3), + VMSTATE_UINT16(volume, TSC210xState), + VMSTATE_UINT16(dac_power, TSC210xState), + VMSTATE_INT64(volume_change, TSC210xState), + VMSTATE_INT64(powerdown, TSC210xState), + VMSTATE_INT64(now, TSC210xState), + VMSTATE_UINT16_ARRAY(filter_data, TSC210xState, 0x14), + VMSTATE_TIMER_PTR(timer, TSC210xState), + VMSTATE_END_OF_LIST() +}; + +static const VMStateDescription vmstate_tsc2102 = { + .name = "tsc2102", + .version_id = 1, + .minimum_version_id = 1, + .pre_save = tsc210x_pre_save, + .post_load = tsc210x_post_load, + .fields = vmstatefields_tsc210x, +}; + +static const VMStateDescription vmstate_tsc2301 = { + .name = "tsc2301", + .version_id = 1, + .minimum_version_id = 1, + .pre_save = tsc210x_pre_save, + .post_load = tsc210x_post_load, + .fields = vmstatefields_tsc210x, +}; + +uWireSlave *tsc2102_init(qemu_irq pint) +{ + TSC210xState *s; + + s = g_new0(TSC210xState, 1); + s->x = 160; + s->y = 160; + s->pressure = 0; + s->precision = s->nextprecision = 0; + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc210x_timer_tick, s); + s->pint = pint; + s->model = 0x2102; + s->name = "tsc2102"; + + s->tr[0] = 0; + s->tr[1] = 1; + s->tr[2] = 1; + s->tr[3] = 0; + s->tr[4] = 1; + s->tr[5] = 0; + s->tr[6] = 1; + s->tr[7] = 0; + + s->chip.opaque = s; + s->chip.send = (void *) tsc210x_write; + s->chip.receive = (void *) tsc210x_read; + + s->codec.opaque = s; + s->codec.tx_swallow = (void *) tsc210x_i2s_swallow; + s->codec.set_rate = (void *) tsc210x_i2s_set_rate; + s->codec.in.fifo = s->in_fifo; + s->codec.out.fifo = s->out_fifo; + + tsc210x_reset(s); + + qemu_add_mouse_event_handler(tsc210x_touchscreen_event, s, 1, + "QEMU TSC2102-driven Touchscreen"); + + AUD_register_card(s->name, &s->card); + + qemu_register_reset((void *) tsc210x_reset, s); + vmstate_register(NULL, 0, &vmstate_tsc2102, s); + + return &s->chip; +} + +uWireSlave *tsc2301_init(qemu_irq penirq, qemu_irq kbirq, qemu_irq dav) +{ + TSC210xState *s; + + s = g_new0(TSC210xState, 1); + s->x = 400; + s->y = 240; + s->pressure = 0; + s->precision = s->nextprecision = 0; + s->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, tsc210x_timer_tick, s); + s->pint = penirq; + s->kbint = kbirq; + s->davint = dav; + s->model = 0x2301; + s->name = "tsc2301"; + + s->tr[0] = 0; + s->tr[1] = 1; + s->tr[2] = 1; + s->tr[3] = 0; + s->tr[4] = 1; + s->tr[5] = 0; + s->tr[6] = 1; + s->tr[7] = 0; + + s->chip.opaque = s; + s->chip.send = (void *) tsc210x_write; + s->chip.receive = (void *) tsc210x_read; + + s->codec.opaque = s; + s->codec.tx_swallow = (void *) tsc210x_i2s_swallow; + s->codec.set_rate = (void *) tsc210x_i2s_set_rate; + s->codec.in.fifo = s->in_fifo; + s->codec.out.fifo = s->out_fifo; + + tsc210x_reset(s); + + qemu_add_mouse_event_handler(tsc210x_touchscreen_event, s, 1, + "QEMU TSC2301-driven Touchscreen"); + + AUD_register_card(s->name, &s->card); + + qemu_register_reset((void *) tsc210x_reset, s); + vmstate_register(NULL, 0, &vmstate_tsc2301, s); + + return &s->chip; +} + +I2SCodec *tsc210x_codec(uWireSlave *chip) +{ + TSC210xState *s = (TSC210xState *) chip->opaque; + + return &s->codec; +} + +/* + * Use tslib generated calibration data to generate ADC input values + * from the touchscreen. Assuming 12-bit precision was used during + * tslib calibration. + */ +void tsc210x_set_transform(uWireSlave *chip, + MouseTransformInfo *info) +{ + TSC210xState *s = (TSC210xState *) chip->opaque; +#if 0 + int64_t ltr[8]; + + ltr[0] = (int64_t) info->a[1] * info->y; + ltr[1] = (int64_t) info->a[4] * info->x; + ltr[2] = (int64_t) info->a[1] * info->a[3] - + (int64_t) info->a[4] * info->a[0]; + ltr[3] = (int64_t) info->a[2] * info->a[4] - + (int64_t) info->a[5] * info->a[1]; + ltr[4] = (int64_t) info->a[0] * info->y; + ltr[5] = (int64_t) info->a[3] * info->x; + ltr[6] = (int64_t) info->a[4] * info->a[0] - + (int64_t) info->a[1] * info->a[3]; + ltr[7] = (int64_t) info->a[2] * info->a[3] - + (int64_t) info->a[5] * info->a[0]; + + /* Avoid integer overflow */ + s->tr[0] = ltr[0] >> 11; + s->tr[1] = ltr[1] >> 11; + s->tr[2] = muldiv64(ltr[2], 1, info->a[6]); + s->tr[3] = muldiv64(ltr[3], 1 << 4, ltr[2]); + s->tr[4] = ltr[4] >> 11; + s->tr[5] = ltr[5] >> 11; + s->tr[6] = muldiv64(ltr[6], 1, info->a[6]); + s->tr[7] = muldiv64(ltr[7], 1 << 4, ltr[6]); +#else + + /* This version assumes touchscreen X & Y axis are parallel or + * perpendicular to LCD's X & Y axis in some way. */ + if (abs(info->a[0]) > abs(info->a[1])) { + s->tr[0] = 0; + s->tr[1] = -info->a[6] * info->x; + s->tr[2] = info->a[0]; + s->tr[3] = -info->a[2] / info->a[0]; + s->tr[4] = info->a[6] * info->y; + s->tr[5] = 0; + s->tr[6] = info->a[4]; + s->tr[7] = -info->a[5] / info->a[4]; + } else { + s->tr[0] = info->a[6] * info->y; + s->tr[1] = 0; + s->tr[2] = info->a[1]; + s->tr[3] = -info->a[2] / info->a[1]; + s->tr[4] = 0; + s->tr[5] = -info->a[6] * info->x; + s->tr[6] = info->a[3]; + s->tr[7] = -info->a[5] / info->a[3]; + } + + s->tr[0] >>= 11; + s->tr[1] >>= 11; + s->tr[3] <<= 4; + s->tr[4] >>= 11; + s->tr[5] >>= 11; + s->tr[7] <<= 4; +#endif +} + +void tsc210x_key_event(uWireSlave *chip, int key, int down) +{ + TSC210xState *s = (TSC210xState *) chip->opaque; + + if (down) + s->kb.down |= 1 << key; + else + s->kb.down &= ~(1 << key); + + if (down && (s->kb.down & ~s->kb.mask) && !s->kb.intr) { + s->kb.intr = 1; + qemu_irq_lower(s->kbint); + } else if (s->kb.intr && !(s->kb.down & ~s->kb.mask) && + !(s->kb.mode & 1)) { + s->kb.intr = 0; + qemu_irq_raise(s->kbint); + } +} diff --git a/hw/input/vhost-user-input.c b/hw/input/vhost-user-input.c new file mode 100644 index 000000000..273e96a7b --- /dev/null +++ b/hw/input/vhost-user-input.c @@ -0,0 +1,130 @@ +/* + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/error-report.h" +#include "qapi/error.h" +#include "qemu-common.h" + +#include "hw/virtio/virtio-input.h" + +static int vhost_input_config_change(struct vhost_dev *dev) +{ + error_report("vhost-user-input: unhandled backend config change"); + return -1; +} + +static const VhostDevConfigOps config_ops = { + .vhost_dev_config_notifier = vhost_input_config_change, +}; + +static void vhost_input_realize(DeviceState *dev, Error **errp) +{ + VHostUserInput *vhi = VHOST_USER_INPUT(dev); + VirtIOInput *vinput = VIRTIO_INPUT(dev); + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + + vhost_dev_set_config_notifier(&vhi->vhost->dev, &config_ops); + vinput->cfg_size = sizeof_field(virtio_input_config, u); + if (vhost_user_backend_dev_init(vhi->vhost, vdev, 2, errp) == -1) { + return; + } +} + +static void vhost_input_change_active(VirtIOInput *vinput) +{ + VHostUserInput *vhi = VHOST_USER_INPUT(vinput); + + if (vinput->active) { + vhost_user_backend_start(vhi->vhost); + } else { + vhost_user_backend_stop(vhi->vhost); + } +} + +static void vhost_input_get_config(VirtIODevice *vdev, uint8_t *config_data) +{ + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + VHostUserInput *vhi = VHOST_USER_INPUT(vdev); + Error *local_err = NULL; + int ret; + + memset(config_data, 0, vinput->cfg_size); + + ret = vhost_dev_get_config(&vhi->vhost->dev, config_data, vinput->cfg_size, + &local_err); + if (ret) { + error_report_err(local_err); + return; + } +} + +static void vhost_input_set_config(VirtIODevice *vdev, + const uint8_t *config_data) +{ + VHostUserInput *vhi = VHOST_USER_INPUT(vdev); + int ret; + + ret = vhost_dev_set_config(&vhi->vhost->dev, config_data, + 0, sizeof(virtio_input_config), + VHOST_SET_CONFIG_TYPE_MASTER); + if (ret) { + error_report("vhost-user-input: set device config space failed"); + return; + } + + virtio_notify_config(vdev); +} + +static const VMStateDescription vmstate_vhost_input = { + .name = "vhost-user-input", + .unmigratable = 1, +}; + +static void vhost_input_class_init(ObjectClass *klass, void *data) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_vhost_input; + vdc->get_config = vhost_input_get_config; + vdc->set_config = vhost_input_set_config; + vic->realize = vhost_input_realize; + vic->change_active = vhost_input_change_active; +} + +static void vhost_input_init(Object *obj) +{ + VHostUserInput *vhi = VHOST_USER_INPUT(obj); + + vhi->vhost = VHOST_USER_BACKEND(object_new(TYPE_VHOST_USER_BACKEND)); + object_property_add_alias(obj, "chardev", + OBJECT(vhi->vhost), "chardev"); +} + +static void vhost_input_finalize(Object *obj) +{ + VHostUserInput *vhi = VHOST_USER_INPUT(obj); + + object_unref(OBJECT(vhi->vhost)); +} + +static const TypeInfo vhost_input_info = { + .name = TYPE_VHOST_USER_INPUT, + .parent = TYPE_VIRTIO_INPUT, + .instance_size = sizeof(VHostUserInput), + .instance_init = vhost_input_init, + .instance_finalize = vhost_input_finalize, + .class_init = vhost_input_class_init, +}; + +static void vhost_input_register_types(void) +{ + type_register_static(&vhost_input_info); +} + +type_init(vhost_input_register_types) diff --git a/hw/input/virtio-input-hid.c b/hw/input/virtio-input-hid.c new file mode 100644 index 000000000..a7a244a95 --- /dev/null +++ b/hw/input/virtio-input-hid.c @@ -0,0 +1,522 @@ +/* + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qemu/iov.h" +#include "qemu/module.h" + +#include "hw/virtio/virtio.h" +#include "hw/qdev-properties.h" +#include "hw/virtio/virtio-input.h" + +#include "ui/console.h" + +#include "standard-headers/linux/input.h" + +#define VIRTIO_ID_NAME_KEYBOARD "QEMU Virtio Keyboard" +#define VIRTIO_ID_NAME_MOUSE "QEMU Virtio Mouse" +#define VIRTIO_ID_NAME_TABLET "QEMU Virtio Tablet" + +/* ----------------------------------------------------------------- */ + +static const unsigned short keymap_button[INPUT_BUTTON__MAX] = { + [INPUT_BUTTON_LEFT] = BTN_LEFT, + [INPUT_BUTTON_RIGHT] = BTN_RIGHT, + [INPUT_BUTTON_MIDDLE] = BTN_MIDDLE, + [INPUT_BUTTON_WHEEL_UP] = BTN_GEAR_UP, + [INPUT_BUTTON_WHEEL_DOWN] = BTN_GEAR_DOWN, + [INPUT_BUTTON_SIDE] = BTN_SIDE, + [INPUT_BUTTON_EXTRA] = BTN_EXTRA, +}; + +static const unsigned short axismap_rel[INPUT_AXIS__MAX] = { + [INPUT_AXIS_X] = REL_X, + [INPUT_AXIS_Y] = REL_Y, +}; + +static const unsigned short axismap_abs[INPUT_AXIS__MAX] = { + [INPUT_AXIS_X] = ABS_X, + [INPUT_AXIS_Y] = ABS_Y, +}; + +/* ----------------------------------------------------------------- */ + +static void virtio_input_key_config(VirtIOInput *vinput, + const unsigned short *keymap, + size_t mapsize) +{ + virtio_input_config keys; + int i, bit, byte, bmax = 0; + + memset(&keys, 0, sizeof(keys)); + for (i = 0; i < mapsize; i++) { + bit = keymap[i]; + if (!bit) { + continue; + } + byte = bit / 8; + bit = bit % 8; + keys.u.bitmap[byte] |= (1 << bit); + if (bmax < byte+1) { + bmax = byte+1; + } + } + keys.select = VIRTIO_INPUT_CFG_EV_BITS; + keys.subsel = EV_KEY; + keys.size = bmax; + virtio_input_add_config(vinput, &keys); +} + +static void virtio_input_handle_event(DeviceState *dev, QemuConsole *src, + InputEvent *evt) +{ + VirtIOInputHID *vhid = VIRTIO_INPUT_HID(dev); + VirtIOInput *vinput = VIRTIO_INPUT(dev); + virtio_input_event event; + int qcode; + InputKeyEvent *key; + InputMoveEvent *move; + InputBtnEvent *btn; + + switch (evt->type) { + case INPUT_EVENT_KIND_KEY: + key = evt->u.key.data; + qcode = qemu_input_key_value_to_qcode(key->key); + if (qcode < qemu_input_map_qcode_to_linux_len && + qemu_input_map_qcode_to_linux[qcode]) { + event.type = cpu_to_le16(EV_KEY); + event.code = cpu_to_le16(qemu_input_map_qcode_to_linux[qcode]); + event.value = cpu_to_le32(key->down ? 1 : 0); + virtio_input_send(vinput, &event); + } else { + if (key->down) { + fprintf(stderr, "%s: unmapped key: %d [%s]\n", __func__, + qcode, QKeyCode_str(qcode)); + } + } + break; + case INPUT_EVENT_KIND_BTN: + btn = evt->u.btn.data; + if (vhid->wheel_axis && + (btn->button == INPUT_BUTTON_WHEEL_UP || + btn->button == INPUT_BUTTON_WHEEL_DOWN) && + btn->down) { + event.type = cpu_to_le16(EV_REL); + event.code = cpu_to_le16(REL_WHEEL); + event.value = cpu_to_le32(btn->button == INPUT_BUTTON_WHEEL_UP + ? 1 : -1); + virtio_input_send(vinput, &event); + } else if (keymap_button[btn->button]) { + event.type = cpu_to_le16(EV_KEY); + event.code = cpu_to_le16(keymap_button[btn->button]); + event.value = cpu_to_le32(btn->down ? 1 : 0); + virtio_input_send(vinput, &event); + } else { + if (btn->down) { + fprintf(stderr, "%s: unmapped button: %d [%s]\n", __func__, + btn->button, + InputButton_str(btn->button)); + } + } + break; + case INPUT_EVENT_KIND_REL: + move = evt->u.rel.data; + event.type = cpu_to_le16(EV_REL); + event.code = cpu_to_le16(axismap_rel[move->axis]); + event.value = cpu_to_le32(move->value); + virtio_input_send(vinput, &event); + break; + case INPUT_EVENT_KIND_ABS: + move = evt->u.abs.data; + event.type = cpu_to_le16(EV_ABS); + event.code = cpu_to_le16(axismap_abs[move->axis]); + event.value = cpu_to_le32(move->value); + virtio_input_send(vinput, &event); + break; + default: + /* keep gcc happy */ + break; + } +} + +static void virtio_input_handle_sync(DeviceState *dev) +{ + VirtIOInput *vinput = VIRTIO_INPUT(dev); + virtio_input_event event = { + .type = cpu_to_le16(EV_SYN), + .code = cpu_to_le16(SYN_REPORT), + .value = 0, + }; + + virtio_input_send(vinput, &event); +} + +static void virtio_input_hid_realize(DeviceState *dev, Error **errp) +{ + VirtIOInputHID *vhid = VIRTIO_INPUT_HID(dev); + + vhid->hs = qemu_input_handler_register(dev, vhid->handler); + if (vhid->display && vhid->hs) { + qemu_input_handler_bind(vhid->hs, vhid->display, vhid->head, NULL); + } +} + +static void virtio_input_hid_unrealize(DeviceState *dev) +{ + VirtIOInputHID *vhid = VIRTIO_INPUT_HID(dev); + qemu_input_handler_unregister(vhid->hs); +} + +static void virtio_input_hid_change_active(VirtIOInput *vinput) +{ + VirtIOInputHID *vhid = VIRTIO_INPUT_HID(vinput); + + if (vinput->active) { + qemu_input_handler_activate(vhid->hs); + } else { + qemu_input_handler_deactivate(vhid->hs); + } +} + +static void virtio_input_hid_handle_status(VirtIOInput *vinput, + virtio_input_event *event) +{ + VirtIOInputHID *vhid = VIRTIO_INPUT_HID(vinput); + int ledbit = 0; + + switch (le16_to_cpu(event->type)) { + case EV_LED: + if (event->code == LED_NUML) { + ledbit = QEMU_NUM_LOCK_LED; + } else if (event->code == LED_CAPSL) { + ledbit = QEMU_CAPS_LOCK_LED; + } else if (event->code == LED_SCROLLL) { + ledbit = QEMU_SCROLL_LOCK_LED; + } + if (event->value) { + vhid->ledstate |= ledbit; + } else { + vhid->ledstate &= ~ledbit; + } + kbd_put_ledstate(vhid->ledstate); + break; + default: + fprintf(stderr, "%s: unknown type %d\n", __func__, + le16_to_cpu(event->type)); + break; + } +} + +static Property virtio_input_hid_properties[] = { + DEFINE_PROP_STRING("display", VirtIOInputHID, display), + DEFINE_PROP_UINT32("head", VirtIOInputHID, head, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_input_hid_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtIOInputClass *vic = VIRTIO_INPUT_CLASS(klass); + + device_class_set_props(dc, virtio_input_hid_properties); + vic->realize = virtio_input_hid_realize; + vic->unrealize = virtio_input_hid_unrealize; + vic->change_active = virtio_input_hid_change_active; + vic->handle_status = virtio_input_hid_handle_status; +} + +static const TypeInfo virtio_input_hid_info = { + .name = TYPE_VIRTIO_INPUT_HID, + .parent = TYPE_VIRTIO_INPUT, + .instance_size = sizeof(VirtIOInputHID), + .class_init = virtio_input_hid_class_init, + .abstract = true, +}; + +/* ----------------------------------------------------------------- */ + +static QemuInputHandler virtio_keyboard_handler = { + .name = VIRTIO_ID_NAME_KEYBOARD, + .mask = INPUT_EVENT_MASK_KEY, + .event = virtio_input_handle_event, + .sync = virtio_input_handle_sync, +}; + +static struct virtio_input_config virtio_keyboard_config[] = { + { + .select = VIRTIO_INPUT_CFG_ID_NAME, + .size = sizeof(VIRTIO_ID_NAME_KEYBOARD), + .u.string = VIRTIO_ID_NAME_KEYBOARD, + },{ + .select = VIRTIO_INPUT_CFG_ID_DEVIDS, + .size = sizeof(struct virtio_input_devids), + .u.ids = { + .bustype = const_le16(BUS_VIRTUAL), + .vendor = const_le16(0x0627), /* same we use for usb hid devices */ + .product = const_le16(0x0001), + .version = const_le16(0x0001), + }, + },{ + .select = VIRTIO_INPUT_CFG_EV_BITS, + .subsel = EV_REP, + .size = 1, + },{ + .select = VIRTIO_INPUT_CFG_EV_BITS, + .subsel = EV_LED, + .size = 1, + .u.bitmap = { + (1 << LED_NUML) | (1 << LED_CAPSL) | (1 << LED_SCROLLL), + }, + }, + { /* end of list */ }, +}; + +static void virtio_keyboard_init(Object *obj) +{ + VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj); + VirtIOInput *vinput = VIRTIO_INPUT(obj); + + vhid->handler = &virtio_keyboard_handler; + virtio_input_init_config(vinput, virtio_keyboard_config); + virtio_input_key_config(vinput, qemu_input_map_qcode_to_linux, + qemu_input_map_qcode_to_linux_len); +} + +static const TypeInfo virtio_keyboard_info = { + .name = TYPE_VIRTIO_KEYBOARD, + .parent = TYPE_VIRTIO_INPUT_HID, + .instance_size = sizeof(VirtIOInputHID), + .instance_init = virtio_keyboard_init, +}; + +/* ----------------------------------------------------------------- */ + +static QemuInputHandler virtio_mouse_handler = { + .name = VIRTIO_ID_NAME_MOUSE, + .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL, + .event = virtio_input_handle_event, + .sync = virtio_input_handle_sync, +}; + +static struct virtio_input_config virtio_mouse_config_v1[] = { + { + .select = VIRTIO_INPUT_CFG_ID_NAME, + .size = sizeof(VIRTIO_ID_NAME_MOUSE), + .u.string = VIRTIO_ID_NAME_MOUSE, + },{ + .select = VIRTIO_INPUT_CFG_ID_DEVIDS, + .size = sizeof(struct virtio_input_devids), + .u.ids = { + .bustype = const_le16(BUS_VIRTUAL), + .vendor = const_le16(0x0627), /* same we use for usb hid devices */ + .product = const_le16(0x0002), + .version = const_le16(0x0001), + }, + },{ + .select = VIRTIO_INPUT_CFG_EV_BITS, + .subsel = EV_REL, + .size = 1, + .u.bitmap = { + (1 << REL_X) | (1 << REL_Y), + }, + }, + { /* end of list */ }, +}; + +static struct virtio_input_config virtio_mouse_config_v2[] = { + { + .select = VIRTIO_INPUT_CFG_ID_NAME, + .size = sizeof(VIRTIO_ID_NAME_MOUSE), + .u.string = VIRTIO_ID_NAME_MOUSE, + },{ + .select = VIRTIO_INPUT_CFG_ID_DEVIDS, + .size = sizeof(struct virtio_input_devids), + .u.ids = { + .bustype = const_le16(BUS_VIRTUAL), + .vendor = const_le16(0x0627), /* same we use for usb hid devices */ + .product = const_le16(0x0002), + .version = const_le16(0x0002), + }, + },{ + .select = VIRTIO_INPUT_CFG_EV_BITS, + .subsel = EV_REL, + .size = 2, + .u.bitmap = { + (1 << REL_X) | (1 << REL_Y), + (1 << (REL_WHEEL - 8)) + }, + }, + { /* end of list */ }, +}; + +static Property virtio_mouse_properties[] = { + DEFINE_PROP_BOOL("wheel-axis", VirtIOInputHID, wheel_axis, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_mouse_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, virtio_mouse_properties); +} + +static void virtio_mouse_init(Object *obj) +{ + VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj); + VirtIOInput *vinput = VIRTIO_INPUT(obj); + + vhid->handler = &virtio_mouse_handler; + virtio_input_init_config(vinput, vhid->wheel_axis + ? virtio_mouse_config_v2 + : virtio_mouse_config_v1); + virtio_input_key_config(vinput, keymap_button, + ARRAY_SIZE(keymap_button)); +} + +static const TypeInfo virtio_mouse_info = { + .name = TYPE_VIRTIO_MOUSE, + .parent = TYPE_VIRTIO_INPUT_HID, + .instance_size = sizeof(VirtIOInputHID), + .instance_init = virtio_mouse_init, + .class_init = virtio_mouse_class_init, +}; + +/* ----------------------------------------------------------------- */ + +static QemuInputHandler virtio_tablet_handler = { + .name = VIRTIO_ID_NAME_TABLET, + .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_ABS, + .event = virtio_input_handle_event, + .sync = virtio_input_handle_sync, +}; + +static struct virtio_input_config virtio_tablet_config_v1[] = { + { + .select = VIRTIO_INPUT_CFG_ID_NAME, + .size = sizeof(VIRTIO_ID_NAME_TABLET), + .u.string = VIRTIO_ID_NAME_TABLET, + },{ + .select = VIRTIO_INPUT_CFG_ID_DEVIDS, + .size = sizeof(struct virtio_input_devids), + .u.ids = { + .bustype = const_le16(BUS_VIRTUAL), + .vendor = const_le16(0x0627), /* same we use for usb hid devices */ + .product = const_le16(0x0003), + .version = const_le16(0x0001), + }, + },{ + .select = VIRTIO_INPUT_CFG_EV_BITS, + .subsel = EV_ABS, + .size = 1, + .u.bitmap = { + (1 << ABS_X) | (1 << ABS_Y), + }, + },{ + .select = VIRTIO_INPUT_CFG_ABS_INFO, + .subsel = ABS_X, + .size = sizeof(virtio_input_absinfo), + .u.abs.min = const_le32(INPUT_EVENT_ABS_MIN), + .u.abs.max = const_le32(INPUT_EVENT_ABS_MAX), + },{ + .select = VIRTIO_INPUT_CFG_ABS_INFO, + .subsel = ABS_Y, + .size = sizeof(virtio_input_absinfo), + .u.abs.min = const_le32(INPUT_EVENT_ABS_MIN), + .u.abs.max = const_le32(INPUT_EVENT_ABS_MAX), + }, + { /* end of list */ }, +}; + +static struct virtio_input_config virtio_tablet_config_v2[] = { + { + .select = VIRTIO_INPUT_CFG_ID_NAME, + .size = sizeof(VIRTIO_ID_NAME_TABLET), + .u.string = VIRTIO_ID_NAME_TABLET, + },{ + .select = VIRTIO_INPUT_CFG_ID_DEVIDS, + .size = sizeof(struct virtio_input_devids), + .u.ids = { + .bustype = const_le16(BUS_VIRTUAL), + .vendor = const_le16(0x0627), /* same we use for usb hid devices */ + .product = const_le16(0x0003), + .version = const_le16(0x0002), + }, + },{ + .select = VIRTIO_INPUT_CFG_EV_BITS, + .subsel = EV_ABS, + .size = 1, + .u.bitmap = { + (1 << ABS_X) | (1 << ABS_Y), + }, + },{ + .select = VIRTIO_INPUT_CFG_EV_BITS, + .subsel = EV_REL, + .size = 2, + .u.bitmap = { + 0, + (1 << (REL_WHEEL - 8)) + }, + },{ + .select = VIRTIO_INPUT_CFG_ABS_INFO, + .subsel = ABS_X, + .size = sizeof(virtio_input_absinfo), + .u.abs.min = const_le32(INPUT_EVENT_ABS_MIN), + .u.abs.max = const_le32(INPUT_EVENT_ABS_MAX), + },{ + .select = VIRTIO_INPUT_CFG_ABS_INFO, + .subsel = ABS_Y, + .size = sizeof(virtio_input_absinfo), + .u.abs.min = const_le32(INPUT_EVENT_ABS_MIN), + .u.abs.max = const_le32(INPUT_EVENT_ABS_MAX), + }, + { /* end of list */ }, +}; + +static Property virtio_tablet_properties[] = { + DEFINE_PROP_BOOL("wheel-axis", VirtIOInputHID, wheel_axis, true), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_tablet_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + + device_class_set_props(dc, virtio_tablet_properties); +} + +static void virtio_tablet_init(Object *obj) +{ + VirtIOInputHID *vhid = VIRTIO_INPUT_HID(obj); + VirtIOInput *vinput = VIRTIO_INPUT(obj); + + vhid->handler = &virtio_tablet_handler; + virtio_input_init_config(vinput, vhid->wheel_axis + ? virtio_tablet_config_v2 + : virtio_tablet_config_v1); + virtio_input_key_config(vinput, keymap_button, + ARRAY_SIZE(keymap_button)); +} + +static const TypeInfo virtio_tablet_info = { + .name = TYPE_VIRTIO_TABLET, + .parent = TYPE_VIRTIO_INPUT_HID, + .instance_size = sizeof(VirtIOInputHID), + .instance_init = virtio_tablet_init, + .class_init = virtio_tablet_class_init, +}; + +/* ----------------------------------------------------------------- */ + +static void virtio_register_types(void) +{ + type_register_static(&virtio_input_hid_info); + type_register_static(&virtio_keyboard_info); + type_register_static(&virtio_mouse_info); + type_register_static(&virtio_tablet_info); +} + +type_init(virtio_register_types) diff --git a/hw/input/virtio-input-host.c b/hw/input/virtio-input-host.c new file mode 100644 index 000000000..137efba57 --- /dev/null +++ b/hw/input/virtio-input-host.c @@ -0,0 +1,260 @@ +/* + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/module.h" +#include "qemu/sockets.h" + +#include "hw/virtio/virtio.h" +#include "hw/qdev-properties.h" +#include "hw/virtio/virtio-input.h" + +#include <sys/ioctl.h> +#include "standard-headers/linux/input.h" + +/* ----------------------------------------------------------------- */ + +static struct virtio_input_config virtio_input_host_config[] = { + { /* empty list */ }, +}; + +static void virtio_input_host_event(void *opaque) +{ + VirtIOInputHost *vih = opaque; + VirtIOInput *vinput = VIRTIO_INPUT(vih); + struct virtio_input_event virtio; + struct input_event evdev; + int rc; + + for (;;) { + rc = read(vih->fd, &evdev, sizeof(evdev)); + if (rc != sizeof(evdev)) { + break; + } + + virtio.type = cpu_to_le16(evdev.type); + virtio.code = cpu_to_le16(evdev.code); + virtio.value = cpu_to_le32(evdev.value); + virtio_input_send(vinput, &virtio); + } +} + +static void virtio_input_bits_config(VirtIOInputHost *vih, + int type, int count) +{ + virtio_input_config bits; + int rc, i, size = 0; + + memset(&bits, 0, sizeof(bits)); + rc = ioctl(vih->fd, EVIOCGBIT(type, count/8), bits.u.bitmap); + if (rc < 0) { + return; + } + + for (i = 0; i < count/8; i++) { + if (bits.u.bitmap[i]) { + size = i+1; + } + } + if (size == 0) { + return; + } + + bits.select = VIRTIO_INPUT_CFG_EV_BITS; + bits.subsel = type; + bits.size = size; + virtio_input_add_config(VIRTIO_INPUT(vih), &bits); +} + +static void virtio_input_abs_config(VirtIOInputHost *vih, int axis) +{ + virtio_input_config config; + struct input_absinfo absinfo; + int rc; + + rc = ioctl(vih->fd, EVIOCGABS(axis), &absinfo); + if (rc < 0) { + return; + } + + memset(&config, 0, sizeof(config)); + config.select = VIRTIO_INPUT_CFG_ABS_INFO; + config.subsel = axis; + config.size = sizeof(virtio_input_absinfo); + + config.u.abs.min = cpu_to_le32(absinfo.minimum); + config.u.abs.max = cpu_to_le32(absinfo.maximum); + config.u.abs.fuzz = cpu_to_le32(absinfo.fuzz); + config.u.abs.flat = cpu_to_le32(absinfo.flat); + config.u.abs.res = cpu_to_le32(absinfo.resolution); + + virtio_input_add_config(VIRTIO_INPUT(vih), &config); +} + +static void virtio_input_host_realize(DeviceState *dev, Error **errp) +{ + VirtIOInputHost *vih = VIRTIO_INPUT_HOST(dev); + VirtIOInput *vinput = VIRTIO_INPUT(dev); + virtio_input_config id, *abs; + struct input_id ids; + int rc, ver, i, axis; + uint8_t byte; + + if (!vih->evdev) { + error_setg(errp, "evdev property is required"); + return; + } + + vih->fd = open(vih->evdev, O_RDWR); + if (vih->fd < 0) { + error_setg_file_open(errp, errno, vih->evdev); + return; + } + qemu_set_nonblock(vih->fd); + + rc = ioctl(vih->fd, EVIOCGVERSION, &ver); + if (rc < 0) { + error_setg(errp, "%s: is not an evdev device", vih->evdev); + goto err_close; + } + + rc = ioctl(vih->fd, EVIOCGRAB, 1); + if (rc < 0) { + error_setg_errno(errp, errno, "%s: failed to get exclusive access", + vih->evdev); + goto err_close; + } + + memset(&id, 0, sizeof(id)); + ioctl(vih->fd, EVIOCGNAME(sizeof(id.u.string)-1), id.u.string); + id.select = VIRTIO_INPUT_CFG_ID_NAME; + id.size = strlen(id.u.string); + virtio_input_add_config(vinput, &id); + + if (ioctl(vih->fd, EVIOCGID, &ids) == 0) { + memset(&id, 0, sizeof(id)); + id.select = VIRTIO_INPUT_CFG_ID_DEVIDS; + id.size = sizeof(struct virtio_input_devids); + id.u.ids.bustype = cpu_to_le16(ids.bustype); + id.u.ids.vendor = cpu_to_le16(ids.vendor); + id.u.ids.product = cpu_to_le16(ids.product); + id.u.ids.version = cpu_to_le16(ids.version); + virtio_input_add_config(vinput, &id); + } + + virtio_input_bits_config(vih, EV_KEY, KEY_CNT); + virtio_input_bits_config(vih, EV_REL, REL_CNT); + virtio_input_bits_config(vih, EV_ABS, ABS_CNT); + virtio_input_bits_config(vih, EV_MSC, MSC_CNT); + virtio_input_bits_config(vih, EV_SW, SW_CNT); + virtio_input_bits_config(vih, EV_LED, LED_CNT); + + abs = virtio_input_find_config(VIRTIO_INPUT(vih), + VIRTIO_INPUT_CFG_EV_BITS, EV_ABS); + if (abs) { + for (i = 0; i < abs->size; i++) { + byte = abs->u.bitmap[i]; + axis = 8 * i; + while (byte) { + if (byte & 1) { + virtio_input_abs_config(vih, axis); + } + axis++; + byte >>= 1; + } + } + } + + qemu_set_fd_handler(vih->fd, virtio_input_host_event, NULL, vih); + return; + +err_close: + close(vih->fd); + vih->fd = -1; + return; +} + +static void virtio_input_host_unrealize(DeviceState *dev) +{ + VirtIOInputHost *vih = VIRTIO_INPUT_HOST(dev); + + if (vih->fd > 0) { + qemu_set_fd_handler(vih->fd, NULL, NULL, NULL); + close(vih->fd); + } +} + +static void virtio_input_host_handle_status(VirtIOInput *vinput, + virtio_input_event *event) +{ + VirtIOInputHost *vih = VIRTIO_INPUT_HOST(vinput); + struct input_event evdev; + struct timeval tval; + int rc; + + if (gettimeofday(&tval, NULL)) { + perror("virtio_input_host_handle_status: gettimeofday"); + return; + } + + evdev.input_event_sec = tval.tv_sec; + evdev.input_event_usec = tval.tv_usec; + evdev.type = le16_to_cpu(event->type); + evdev.code = le16_to_cpu(event->code); + evdev.value = le32_to_cpu(event->value); + + rc = write(vih->fd, &evdev, sizeof(evdev)); + if (rc == -1) { + perror("virtio_input_host_handle_status: write"); + } +} + +static const VMStateDescription vmstate_virtio_input_host = { + .name = "virtio-input-host", + .unmigratable = 1, +}; + +static Property virtio_input_host_properties[] = { + DEFINE_PROP_STRING("evdev", VirtIOInputHost, evdev), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_input_host_class_init(ObjectClass *klass, void *data) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_CLASS(klass); + DeviceClass *dc = DEVICE_CLASS(klass); + + dc->vmsd = &vmstate_virtio_input_host; + device_class_set_props(dc, virtio_input_host_properties); + vic->realize = virtio_input_host_realize; + vic->unrealize = virtio_input_host_unrealize; + vic->handle_status = virtio_input_host_handle_status; +} + +static void virtio_input_host_init(Object *obj) +{ + VirtIOInput *vinput = VIRTIO_INPUT(obj); + + virtio_input_init_config(vinput, virtio_input_host_config); +} + +static const TypeInfo virtio_input_host_info = { + .name = TYPE_VIRTIO_INPUT_HOST, + .parent = TYPE_VIRTIO_INPUT, + .instance_size = sizeof(VirtIOInputHost), + .instance_init = virtio_input_host_init, + .class_init = virtio_input_host_class_init, +}; + +/* ----------------------------------------------------------------- */ + +static void virtio_register_types(void) +{ + type_register_static(&virtio_input_host_info); +} + +type_init(virtio_register_types) diff --git a/hw/input/virtio-input.c b/hw/input/virtio-input.c new file mode 100644 index 000000000..54bcb46c7 --- /dev/null +++ b/hw/input/virtio-input.c @@ -0,0 +1,343 @@ +/* + * This work is licensed under the terms of the GNU GPL, version 2 or + * (at your option) any later version. See the COPYING file in the + * top-level directory. + */ + +#include "qemu/osdep.h" +#include "qapi/error.h" +#include "qemu/iov.h" +#include "qemu/module.h" +#include "trace.h" + +#include "hw/virtio/virtio.h" +#include "hw/qdev-properties.h" +#include "hw/virtio/virtio-input.h" + +#include "standard-headers/linux/input.h" + +#define VIRTIO_INPUT_VM_VERSION 1 + +/* ----------------------------------------------------------------- */ + +void virtio_input_send(VirtIOInput *vinput, virtio_input_event *event) +{ + VirtQueueElement *elem; + int i, len; + + if (!vinput->active) { + return; + } + + /* queue up events ... */ + if (vinput->qindex == vinput->qsize) { + vinput->qsize++; + vinput->queue = g_realloc(vinput->queue, vinput->qsize * + sizeof(vinput->queue[0])); + } + vinput->queue[vinput->qindex++].event = *event; + + /* ... until we see a report sync ... */ + if (event->type != cpu_to_le16(EV_SYN) || + event->code != cpu_to_le16(SYN_REPORT)) { + return; + } + + /* ... then check available space ... */ + for (i = 0; i < vinput->qindex; i++) { + elem = virtqueue_pop(vinput->evt, sizeof(VirtQueueElement)); + if (!elem) { + while (--i >= 0) { + virtqueue_unpop(vinput->evt, vinput->queue[i].elem, 0); + } + vinput->qindex = 0; + trace_virtio_input_queue_full(); + return; + } + vinput->queue[i].elem = elem; + } + + /* ... and finally pass them to the guest */ + for (i = 0; i < vinput->qindex; i++) { + elem = vinput->queue[i].elem; + len = iov_from_buf(elem->in_sg, elem->in_num, + 0, &vinput->queue[i].event, sizeof(virtio_input_event)); + virtqueue_push(vinput->evt, elem, len); + g_free(elem); + } + virtio_notify(VIRTIO_DEVICE(vinput), vinput->evt); + vinput->qindex = 0; +} + +static void virtio_input_handle_evt(VirtIODevice *vdev, VirtQueue *vq) +{ + /* nothing */ +} + +static void virtio_input_handle_sts(VirtIODevice *vdev, VirtQueue *vq) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + virtio_input_event event; + VirtQueueElement *elem; + int len; + + for (;;) { + elem = virtqueue_pop(vinput->sts, sizeof(VirtQueueElement)); + if (!elem) { + break; + } + + memset(&event, 0, sizeof(event)); + len = iov_to_buf(elem->out_sg, elem->out_num, + 0, &event, sizeof(event)); + if (vic->handle_status) { + vic->handle_status(vinput, &event); + } + virtqueue_push(vinput->sts, elem, len); + g_free(elem); + } + virtio_notify(vdev, vinput->sts); +} + +virtio_input_config *virtio_input_find_config(VirtIOInput *vinput, + uint8_t select, + uint8_t subsel) +{ + VirtIOInputConfig *cfg; + + QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) { + if (select == cfg->config.select && + subsel == cfg->config.subsel) { + return &cfg->config; + } + } + return NULL; +} + +void virtio_input_add_config(VirtIOInput *vinput, + virtio_input_config *config) +{ + VirtIOInputConfig *cfg; + + if (virtio_input_find_config(vinput, config->select, config->subsel)) { + /* should not happen */ + fprintf(stderr, "%s: duplicate config: %d/%d\n", + __func__, config->select, config->subsel); + abort(); + } + + cfg = g_new0(VirtIOInputConfig, 1); + cfg->config = *config; + QTAILQ_INSERT_TAIL(&vinput->cfg_list, cfg, node); +} + +void virtio_input_init_config(VirtIOInput *vinput, + virtio_input_config *config) +{ + int i = 0; + + QTAILQ_INIT(&vinput->cfg_list); + while (config[i].select) { + virtio_input_add_config(vinput, config + i); + i++; + } +} + +void virtio_input_idstr_config(VirtIOInput *vinput, + uint8_t select, const char *string) +{ + virtio_input_config id; + + if (!string) { + return; + } + memset(&id, 0, sizeof(id)); + id.select = select; + id.size = snprintf(id.u.string, sizeof(id.u.string), "%s", string); + virtio_input_add_config(vinput, &id); +} + +static void virtio_input_get_config(VirtIODevice *vdev, uint8_t *config_data) +{ + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + virtio_input_config *config; + + config = virtio_input_find_config(vinput, vinput->cfg_select, + vinput->cfg_subsel); + if (config) { + memcpy(config_data, config, vinput->cfg_size); + } else { + memset(config_data, 0, vinput->cfg_size); + } +} + +static void virtio_input_set_config(VirtIODevice *vdev, + const uint8_t *config_data) +{ + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + virtio_input_config *config = (virtio_input_config *)config_data; + + vinput->cfg_select = config->select; + vinput->cfg_subsel = config->subsel; + virtio_notify_config(vdev); +} + +static uint64_t virtio_input_get_features(VirtIODevice *vdev, uint64_t f, + Error **errp) +{ + return f; +} + +static void virtio_input_set_status(VirtIODevice *vdev, uint8_t val) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + + if (val & VIRTIO_CONFIG_S_DRIVER_OK) { + if (!vinput->active) { + vinput->active = true; + if (vic->change_active) { + vic->change_active(vinput); + } + } + } +} + +static void virtio_input_reset(VirtIODevice *vdev) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vdev); + VirtIOInput *vinput = VIRTIO_INPUT(vdev); + + if (vinput->active) { + vinput->active = false; + if (vic->change_active) { + vic->change_active(vinput); + } + } +} + +static int virtio_input_post_load(void *opaque, int version_id) +{ + VirtIOInput *vinput = opaque; + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(vinput); + VirtIODevice *vdev = VIRTIO_DEVICE(vinput); + + vinput->active = vdev->status & VIRTIO_CONFIG_S_DRIVER_OK; + if (vic->change_active) { + vic->change_active(vinput); + } + return 0; +} + +static void virtio_input_device_realize(DeviceState *dev, Error **errp) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev); + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VirtIOInput *vinput = VIRTIO_INPUT(dev); + VirtIOInputConfig *cfg; + Error *local_err = NULL; + + if (vic->realize) { + vic->realize(dev, &local_err); + if (local_err) { + error_propagate(errp, local_err); + return; + } + } + + virtio_input_idstr_config(vinput, VIRTIO_INPUT_CFG_ID_SERIAL, + vinput->serial); + + QTAILQ_FOREACH(cfg, &vinput->cfg_list, node) { + if (vinput->cfg_size < cfg->config.size) { + vinput->cfg_size = cfg->config.size; + } + } + vinput->cfg_size += 8; + assert(vinput->cfg_size <= sizeof(virtio_input_config)); + + virtio_init(vdev, "virtio-input", VIRTIO_ID_INPUT, + vinput->cfg_size); + vinput->evt = virtio_add_queue(vdev, 64, virtio_input_handle_evt); + vinput->sts = virtio_add_queue(vdev, 64, virtio_input_handle_sts); +} + +static void virtio_input_finalize(Object *obj) +{ + VirtIOInput *vinput = VIRTIO_INPUT(obj); + VirtIOInputConfig *cfg, *next; + + QTAILQ_FOREACH_SAFE(cfg, &vinput->cfg_list, node, next) { + QTAILQ_REMOVE(&vinput->cfg_list, cfg, node); + g_free(cfg); + } + + g_free(vinput->queue); +} + +static void virtio_input_device_unrealize(DeviceState *dev) +{ + VirtIOInputClass *vic = VIRTIO_INPUT_GET_CLASS(dev); + VirtIODevice *vdev = VIRTIO_DEVICE(dev); + VirtIOInput *vinput = VIRTIO_INPUT(dev); + + if (vic->unrealize) { + vic->unrealize(dev); + } + virtio_delete_queue(vinput->evt); + virtio_delete_queue(vinput->sts); + virtio_cleanup(vdev); +} + +static const VMStateDescription vmstate_virtio_input = { + .name = "virtio-input", + .minimum_version_id = VIRTIO_INPUT_VM_VERSION, + .version_id = VIRTIO_INPUT_VM_VERSION, + .fields = (VMStateField[]) { + VMSTATE_VIRTIO_DEVICE, + VMSTATE_END_OF_LIST() + }, + .post_load = virtio_input_post_load, +}; + +static Property virtio_input_properties[] = { + DEFINE_PROP_STRING("serial", VirtIOInput, serial), + DEFINE_PROP_END_OF_LIST(), +}; + +static void virtio_input_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + VirtioDeviceClass *vdc = VIRTIO_DEVICE_CLASS(klass); + + device_class_set_props(dc, virtio_input_properties); + dc->vmsd = &vmstate_virtio_input; + set_bit(DEVICE_CATEGORY_INPUT, dc->categories); + vdc->realize = virtio_input_device_realize; + vdc->unrealize = virtio_input_device_unrealize; + vdc->get_config = virtio_input_get_config; + vdc->set_config = virtio_input_set_config; + vdc->get_features = virtio_input_get_features; + vdc->set_status = virtio_input_set_status; + vdc->reset = virtio_input_reset; +} + +static const TypeInfo virtio_input_info = { + .name = TYPE_VIRTIO_INPUT, + .parent = TYPE_VIRTIO_DEVICE, + .instance_size = sizeof(VirtIOInput), + .class_size = sizeof(VirtIOInputClass), + .class_init = virtio_input_class_init, + .abstract = true, + .instance_finalize = virtio_input_finalize, +}; + +/* ----------------------------------------------------------------- */ + +static void virtio_register_types(void) +{ + type_register_static(&virtio_input_info); +} + +type_init(virtio_register_types) |