| /* |
| * QEMU Microsoft serial mouse emulation |
| * |
| * Copyright (c) 2008 Lubomir Rintel |
| * |
| * 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/module.h" |
| #include "qemu/fifo8.h" |
| #include "chardev/char.h" |
| #include "chardev/char-serial.h" |
| #include "ui/console.h" |
| #include "ui/input.h" |
| #include "qom/object.h" |
| |
| #define MSMOUSE_LO6(n) ((n) & 0x3f) |
| #define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6) |
| #define MSMOUSE_PWR(cm) (cm & (CHR_TIOCM_RTS | CHR_TIOCM_DTR)) |
| |
| /* Serial PnP for 6 bit devices/mice sends all ASCII chars - 0x20 */ |
| #define M(c) (c - 0x20) |
| /* Serial fifo size. */ |
| #define MSMOUSE_BUF_SZ 64 |
| |
| /* Mouse ID: Send "M3" cause we behave like a 3 button logitech mouse. */ |
| const uint8_t mouse_id[] = {'M', '3'}; |
| /* |
| * PnP start "(", PnP version (1.0), vendor ID, product ID, '\\', |
| * serial ID (omitted), '\\', MS class name, '\\', driver ID (omitted), '\\', |
| * product description, checksum, ")" |
| * Missing parts are inserted later. |
| */ |
| const uint8_t pnp_data[] = {M('('), 1, '$', M('Q'), M('M'), M('U'), |
| M('0'), M('0'), M('0'), M('1'), |
| M('\\'), M('\\'), |
| M('M'), M('O'), M('U'), M('S'), M('E'), |
| M('\\'), M('\\')}; |
| |
| struct MouseChardev { |
| Chardev parent; |
| |
| QemuInputHandlerState *hs; |
| int tiocm; |
| int axis[INPUT_AXIS__MAX]; |
| bool btns[INPUT_BUTTON__MAX]; |
| bool btnc[INPUT_BUTTON__MAX]; |
| Fifo8 outbuf; |
| }; |
| typedef struct MouseChardev MouseChardev; |
| |
| #define TYPE_CHARDEV_MSMOUSE "chardev-msmouse" |
| DECLARE_INSTANCE_CHECKER(MouseChardev, MOUSE_CHARDEV, |
| TYPE_CHARDEV_MSMOUSE) |
| |
| static void msmouse_chr_accept_input(Chardev *chr) |
| { |
| MouseChardev *mouse = MOUSE_CHARDEV(chr); |
| uint32_t len, avail; |
| |
| len = qemu_chr_be_can_write(chr); |
| avail = fifo8_num_used(&mouse->outbuf); |
| while (len > 0 && avail > 0) { |
| const uint8_t *buf; |
| uint32_t size; |
| |
| buf = fifo8_pop_buf(&mouse->outbuf, MIN(len, avail), &size); |
| qemu_chr_be_write(chr, buf, size); |
| len = qemu_chr_be_can_write(chr); |
| avail -= size; |
| } |
| } |
| |
| static void msmouse_queue_event(MouseChardev *mouse) |
| { |
| unsigned char bytes[4] = { 0x40, 0x00, 0x00, 0x00 }; |
| int dx, dy, count = 3; |
| |
| dx = mouse->axis[INPUT_AXIS_X]; |
| mouse->axis[INPUT_AXIS_X] = 0; |
| |
| dy = mouse->axis[INPUT_AXIS_Y]; |
| mouse->axis[INPUT_AXIS_Y] = 0; |
| |
| /* Movement deltas */ |
| bytes[0] |= (MSMOUSE_HI2(dy) << 2) | MSMOUSE_HI2(dx); |
| bytes[1] |= MSMOUSE_LO6(dx); |
| bytes[2] |= MSMOUSE_LO6(dy); |
| |
| /* Buttons */ |
| bytes[0] |= (mouse->btns[INPUT_BUTTON_LEFT] ? 0x20 : 0x00); |
| bytes[0] |= (mouse->btns[INPUT_BUTTON_RIGHT] ? 0x10 : 0x00); |
| if (mouse->btns[INPUT_BUTTON_MIDDLE] || |
| mouse->btnc[INPUT_BUTTON_MIDDLE]) { |
| bytes[3] |= (mouse->btns[INPUT_BUTTON_MIDDLE] ? 0x20 : 0x00); |
| mouse->btnc[INPUT_BUTTON_MIDDLE] = false; |
| count++; |
| } |
| |
| if (fifo8_num_free(&mouse->outbuf) >= count) { |
| fifo8_push_all(&mouse->outbuf, bytes, count); |
| } else { |
| /* queue full -> drop event */ |
| } |
| } |
| |
| static void msmouse_input_event(DeviceState *dev, QemuConsole *src, |
| InputEvent *evt) |
| { |
| MouseChardev *mouse = MOUSE_CHARDEV(dev); |
| InputMoveEvent *move; |
| InputBtnEvent *btn; |
| |
| /* Ignore events if serial mouse powered down. */ |
| if (!MSMOUSE_PWR(mouse->tiocm)) { |
| return; |
| } |
| |
| switch (evt->type) { |
| case INPUT_EVENT_KIND_REL: |
| move = evt->u.rel.data; |
| mouse->axis[move->axis] += move->value; |
| break; |
| |
| case INPUT_EVENT_KIND_BTN: |
| btn = evt->u.btn.data; |
| mouse->btns[btn->button] = btn->down; |
| mouse->btnc[btn->button] = true; |
| break; |
| |
| default: |
| /* keep gcc happy */ |
| break; |
| } |
| } |
| |
| static void msmouse_input_sync(DeviceState *dev) |
| { |
| MouseChardev *mouse = MOUSE_CHARDEV(dev); |
| Chardev *chr = CHARDEV(dev); |
| |
| /* Ignore events if serial mouse powered down. */ |
| if (!MSMOUSE_PWR(mouse->tiocm)) { |
| return; |
| } |
| |
| msmouse_queue_event(mouse); |
| msmouse_chr_accept_input(chr); |
| } |
| |
| static int msmouse_chr_write(struct Chardev *s, const uint8_t *buf, int len) |
| { |
| /* Ignore writes to mouse port */ |
| return len; |
| } |
| |
| static QemuInputHandler msmouse_handler = { |
| .name = "QEMU Microsoft Mouse", |
| .mask = INPUT_EVENT_MASK_BTN | INPUT_EVENT_MASK_REL, |
| .event = msmouse_input_event, |
| .sync = msmouse_input_sync, |
| }; |
| |
| static int msmouse_ioctl(Chardev *chr, int cmd, void *arg) |
| { |
| MouseChardev *mouse = MOUSE_CHARDEV(chr); |
| int c, i, j; |
| uint8_t bytes[MSMOUSE_BUF_SZ / 2]; |
| int *targ = (int *)arg; |
| const uint8_t hexchr[16] = {M('0'), M('1'), M('2'), M('3'), M('4'), M('5'), |
| M('6'), M('7'), M('8'), M('9'), M('A'), M('B'), |
| M('C'), M('D'), M('E'), M('F')}; |
| |
| switch (cmd) { |
| case CHR_IOCTL_SERIAL_SET_TIOCM: |
| c = mouse->tiocm; |
| mouse->tiocm = *(int *)arg; |
| if (MSMOUSE_PWR(mouse->tiocm)) { |
| if (!MSMOUSE_PWR(c)) { |
| /* |
| * Power on after reset: Send ID and PnP data |
| * No need to check fifo space as it is empty at this point. |
| */ |
| fifo8_push_all(&mouse->outbuf, mouse_id, sizeof(mouse_id)); |
| /* Add PnP data: */ |
| fifo8_push_all(&mouse->outbuf, pnp_data, sizeof(pnp_data)); |
| /* |
| * Add device description from qemu handler name. |
| * Make sure this all fits into the queue beforehand! |
| */ |
| c = M(')'); |
| for (i = 0; msmouse_handler.name[i]; i++) { |
| bytes[i] = M(msmouse_handler.name[i]); |
| c += bytes[i]; |
| } |
| /* Calc more of checksum */ |
| for (j = 0; j < sizeof(pnp_data); j++) { |
| c += pnp_data[j]; |
| } |
| c &= 0xff; |
| bytes[i++] = hexchr[c >> 4]; |
| bytes[i++] = hexchr[c & 0x0f]; |
| bytes[i++] = M(')'); |
| fifo8_push_all(&mouse->outbuf, bytes, i); |
| /* Start sending data to serial. */ |
| msmouse_chr_accept_input(chr); |
| } |
| break; |
| } |
| /* |
| * Reset mouse buffers on power down. |
| * Mouse won't send anything without power. |
| */ |
| fifo8_reset(&mouse->outbuf); |
| memset(mouse->axis, 0, sizeof(mouse->axis)); |
| memset(mouse->btns, false, sizeof(mouse->btns)); |
| memset(mouse->btnc, false, sizeof(mouse->btns)); |
| break; |
| case CHR_IOCTL_SERIAL_GET_TIOCM: |
| /* Remember line control status. */ |
| *targ = mouse->tiocm; |
| break; |
| default: |
| return -ENOTSUP; |
| } |
| return 0; |
| } |
| |
| static void char_msmouse_finalize(Object *obj) |
| { |
| MouseChardev *mouse = MOUSE_CHARDEV(obj); |
| |
| if (mouse->hs) { |
| qemu_input_handler_unregister(mouse->hs); |
| } |
| fifo8_destroy(&mouse->outbuf); |
| } |
| |
| static void msmouse_chr_open(Chardev *chr, |
| ChardevBackend *backend, |
| bool *be_opened, |
| Error **errp) |
| { |
| MouseChardev *mouse = MOUSE_CHARDEV(chr); |
| |
| *be_opened = false; |
| mouse->hs = qemu_input_handler_register((DeviceState *)mouse, |
| &msmouse_handler); |
| mouse->tiocm = 0; |
| fifo8_create(&mouse->outbuf, MSMOUSE_BUF_SZ); |
| } |
| |
| static void char_msmouse_class_init(ObjectClass *oc, void *data) |
| { |
| ChardevClass *cc = CHARDEV_CLASS(oc); |
| |
| cc->open = msmouse_chr_open; |
| cc->chr_write = msmouse_chr_write; |
| cc->chr_accept_input = msmouse_chr_accept_input; |
| cc->chr_ioctl = msmouse_ioctl; |
| } |
| |
| static const TypeInfo char_msmouse_type_info = { |
| .name = TYPE_CHARDEV_MSMOUSE, |
| .parent = TYPE_CHARDEV, |
| .instance_size = sizeof(MouseChardev), |
| .instance_finalize = char_msmouse_finalize, |
| .class_init = char_msmouse_class_init, |
| }; |
| |
| static void register_types(void) |
| { |
| type_register_static(&char_msmouse_type_info); |
| } |
| |
| type_init(register_types); |