| /* | 
 |  * 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); |