| /* | |
| * 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_bufptr(&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, | |
| QemuInputEvent *evt) | |
| { | |
| MouseChardev *mouse = MOUSE_CHARDEV(dev); | |
| /* Ignore events if serial mouse powered down. */ | |
| if (!MSMOUSE_PWR(mouse->tiocm)) { | |
| return; | |
| } | |
| switch (evt->type) { | |
| case INPUT_EVENT_KIND_REL: | |
| mouse->axis[evt->rel.axis] += evt->rel.value; | |
| break; | |
| case INPUT_EVENT_KIND_BTN: | |
| mouse->btns[evt->btn.button] = evt->btn.down; | |
| mouse->btnc[evt->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 const 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_chr_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 bool msmouse_chr_open(Chardev *chr, | |
| ChardevBackend *backend, | |
| Error **errp) | |
| { | |
| MouseChardev *mouse = MOUSE_CHARDEV(chr); | |
| mouse->hs = qemu_input_handler_register((DeviceState *)mouse, | |
| &msmouse_handler); | |
| mouse->tiocm = 0; | |
| fifo8_create(&mouse->outbuf, MSMOUSE_BUF_SZ); | |
| /* Never send CHR_EVENT_OPENED */ | |
| return true; | |
| } | |
| static void char_msmouse_class_init(ObjectClass *oc, const void *data) | |
| { | |
| ChardevClass *cc = CHARDEV_CLASS(oc); | |
| cc->chr_open = msmouse_chr_open; | |
| cc->chr_write = msmouse_chr_write; | |
| cc->chr_accept_input = msmouse_chr_accept_input; | |
| cc->chr_ioctl = msmouse_chr_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); |