qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH v3 1/1] genius: add genius serial mouse emulation


From: Romain Naour
Subject: [Qemu-devel] [PATCH v3 1/1] genius: add genius serial mouse emulation
Date: Sat, 18 Jan 2014 22:43:06 +0100

This patch adds the emulation for a serial Genius mouse using
Mouse Systems protocol (5bytes).
This protocol is compatible with most 3-button serial mice.

Signed-off-by: Romain Naour <address@hidden>
---
Changes v2 -> v3:
Following Peter Maydell's feedback:
 Fixes typos
 Duplicated code factored into a common utility function.
 Documentation: Prefer msmouse if available.
Fixes build warning when debug is set

Changes v1 -> v2:
 Fixes documentation (Paolo Bonzini)
 Fixes typos

 backends/Makefile.objs |   2 +-
 backends/gnmouse.c     | 313 +++++++++++++++++++++++++++++++++++++++++++++++++
 include/sysemu/char.h  |   3 +
 qapi-schema.json       |   1 +
 qemu-char.c            |   4 +
 qemu-options.hx        |  17 ++-
 6 files changed, 337 insertions(+), 3 deletions(-)
 create mode 100644 backends/gnmouse.c

diff --git a/backends/Makefile.objs b/backends/Makefile.objs
index 42557d5..e4b072c 100644
--- a/backends/Makefile.objs
+++ b/backends/Makefile.objs
@@ -1,7 +1,7 @@
 common-obj-y += rng.o rng-egd.o
 common-obj-$(CONFIG_POSIX) += rng-random.o
 
-common-obj-y += msmouse.o
+common-obj-y += msmouse.o gnmouse.o
 common-obj-$(CONFIG_BRLAPI) += baum.o
 $(obj)/baum.o: QEMU_CFLAGS += $(SDL_CFLAGS) 
 
diff --git a/backends/gnmouse.c b/backends/gnmouse.c
new file mode 100644
index 0000000..e127b8b
--- /dev/null
+++ b/backends/gnmouse.c
@@ -0,0 +1,313 @@
+/*
+ * QEMU Genius GM-6 serial mouse emulation
+ *
+ * Adapted from msmouse
+ *
+ * Copyright (c) 2014 Romain Naour
+ *
+ * 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 <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+
+#include "qemu-common.h"
+#include "sysemu/char.h"
+#include "ui/console.h"
+#include "qemu/timer.h"
+
+/* #define DEBUG_GENIUS_MOUSE */
+
+#ifdef DEBUG_GENIUS_MOUSE
+#define DPRINTF(fmt, ...) \
+do { fprintf(stderr, "gnmouse: " fmt , ## __VA_ARGS__); } while (0)
+#else
+#define DPRINTF(fmt, ...) \
+do {} while (0)
+#endif
+
+/*
+ * struct GnMouseSave:
+ * This structure is used to save private info for Genius mouse.
+ *
+ * dx: deltas on x-axis saved since last frame send to emulated system.
+ * dy: deltas on y-axis saved since last frame send to emulated system.
+ * transmit_timer: QEMU's timer
+ * transmit_time: reload value for transmit_timer
+ * data: frame to be sent
+ * index: used to save current state of the state machine. see type states 
below
+ */
+typedef struct GnMouseSave {
+    int dx;
+    int dy;
+    int button;
+    struct QEMUTimer *transmit_timer; /* QEMU timer */
+    uint64_t transmit_time;           /* time to transmit a char in ticks */
+    unsigned char data[5];
+    int index;
+} GnMouseSave;
+
+
+/* states */
+typedef enum {
+    START,  /* 0 */
+    CHAR_1, /* 1 : bp */
+    CHAR_2, /* 2 : Dx */
+    CHAR_3, /* 3 : Dy */
+    CHAR_4, /* 4 : Dx */
+    CHAR_5, /* 5 : Dy */
+    STOP    /* 6 */
+} States;
+
+/**
+ * gnmouse_chr_write: this function is used when QEMU
+ * try to write something to mouse port.
+ * Nothing is sent to the emulated mouse.
+ *
+ * Return: length of the buffer
+ *
+ * @s: address of the CharDriverState used by the mouse
+ * @buf: buffer to write
+ * @len: length of the buffer to write
+ */
+static int gnmouse_chr_write(struct CharDriverState *s, const uint8_t *buf,
+                             int len)
+{
+    /* Ignore writes to mouse port */
+    return len;
+}
+
+/**
+ * gnmouse_chr_close: his function closes the mouse port.
+ * stops and frees the QEMU timer and frees the GnMouseSave struct.
+ *
+ * Return: void
+ *
+ * @chr: address of the CharDriverState used by the mouse
+ */
+static void gnmouse_chr_close(struct CharDriverState *chr)
+{
+    GnMouseSave *save = (GnMouseSave *)chr->opaque;
+
+    timer_del(save->transmit_timer);
+    timer_free(save->transmit_timer);
+    g_free(chr->opaque);
+    g_free(chr);
+}
+
+/**
+ * gnmouse_return_accumulated_delta: this function checks and returns
+ * the accumulated value of delta that is passed as argument.
+ *
+ * Return: delta between -127 and 128
+ *
+ * @delta: address where delta is stored
+ */
+static int gnmouse_return_accumulated_delta(int *delta)
+{
+    int ret;
+
+    /* avoid overflow on dx or dy */
+    if (*delta >= 128) {
+        DPRINTF("overflow delta= %d\n", *delta);
+        *delta -= 128;
+        return 128;
+    } else if (*delta <= -127) {
+        DPRINTF("overflow delta= %d\n", *delta);
+        *delta += 127;
+        return -127;
+    } else {
+        ret = *delta;
+        *delta = 0;
+        return ret;
+    }
+}
+
+/**
+ * gnmouse_handler: send a byte on serial port to the guest system
+ * This handler is called on each timer timeout or directly by gnmouse_event()
+ * when no transmission is underway.
+ * It use a state machine in order to know which byte of the frame must be 
sent.
+ *
+ * Returns void
+ *
+ * @opaque: address of the CharDriverState used by the mouse
+ */
+static void gnmouse_handler(void *opaque)
+{
+    CharDriverState *chr = (CharDriverState *)opaque;
+    GnMouseSave *save = (GnMouseSave *)chr->opaque;
+    unsigned char *data = save->data;
+
+    /*
+     * Byte 0:  1,  0,  0,  0,  0,  L,  M,  R
+     * Byte 1: X7, X6, X5, X4, X3, X2, X1, X0
+     * Byte 2: Y7, Y6, Y5, Y4, Y3, Y2, Y1, Y0
+     * Byte 3: X7, X6, X5, X4, X3, X2, X1, X0
+     * Byte 4: Y7, Y6, Y5, Y4, Y3, Y2, Y1, Y0
+     */
+    switch (save->index) {
+    case CHAR_4:
+        /*
+         * Send as much of our accumulated delta as we can fit into
+         * the packet format. Anything remaining will get sent in a
+         * subsequent packet.
+         */
+        data[3] = gnmouse_return_accumulated_delta(&save->dx);
+        data[4] = -gnmouse_return_accumulated_delta(&save->dy);
+
+        DPRINTF("3: dx= %d\n", data[3]);
+        DPRINTF("4: dy= %d\n", data[4]);
+
+        break;
+
+    case STOP:
+        if (!(save->dx && save->dy)) {
+            DPRINTF("no more data\n");
+            return;
+        } else {
+            DPRINTF("data saved\n");
+            save->index = START;
+        }
+        /* fall through */
+
+    case START:
+        /* New serial frame */
+        /* Buttons */
+        data[0] = save->button;
+        DPRINTF("0: bp= %d\n", data[0]);
+        save->index = CHAR_1;
+        /* fall through */
+
+    case CHAR_1:
+        data[1] = gnmouse_return_accumulated_delta(&save->dx);
+        data[2] = -gnmouse_return_accumulated_delta(&save->dy);
+
+        DPRINTF("1: dx= %d\n", data[3]);
+        DPRINTF("2: dy= %d\n", data[4]);
+
+        /* clear bytes 4 and 5 */
+        data[3] = 0;
+        data[4] = 0;
+
+    case CHAR_2:
+    case CHAR_3:
+    case CHAR_5:
+        break;
+    default:
+        return;
+    }
+
+    timer_mod(save->transmit_timer,
+              qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + save->transmit_time);
+    DPRINTF("mod_timer: %d\n", save->index);
+
+    qemu_chr_be_write(chr, &(data[save->index - 1]), 1);
+    DPRINTF("write :%x\n", data[save->index - 1]);
+
+    /* next state */
+    save->index++;
+}
+
+/**
+ * gnmouse_event: event handler called by the UI layer.
+ *
+ * @opaque: address of the CharDriverState used by the mouse
+ * @dx: deltas on the x-axis since last event
+ * @dy: deltas on the y-axis since last event
+ * @dz: deltas on the z-axis since last event (not used)
+ * @button_state: status of mouse button
+ */
+static void gnmouse_event(void *opaque,
+                          int dx, int dy, int dz, int buttons_state)
+{
+    CharDriverState *chr = (CharDriverState *)opaque;
+    GnMouseSave *save = (GnMouseSave *)chr->opaque;
+    char bp = 0x80;
+
+    /* save deltas */
+    save->dx += dx;
+    save->dy += dy;
+
+    DPRINTF("dx= %d; dy= %d; buttons=%x\n", dx, dy, buttons_state);
+
+    /* Buttons:
+     * Bit0 to bit2 give 0 when the buttons were pressed,
+     * otherwise they're 1.
+     */
+    bp |= (buttons_state & 0x01 ? 0x00 : 0x04); /* bp1 = L */
+    bp |= (buttons_state & 0x02 ? 0x00 : 0x01); /* bp2 = R */
+    bp |= (buttons_state & 0x04 ? 0x00 : 0x02); /* bp4 = M */
+
+    save->button = bp;
+    if (save->index == STOP) {
+        /* no transmission is underway, start a new transmission */
+        save->index = START;
+        gnmouse_handler(chr);
+    }
+}
+
+/**
+ * qemu_chr_open_gnmouse: Init function for Genius mouse.
+ *
+ * Return address of the initialized CharDriverState
+ *
+ * @opts: argument not used
+ */
+CharDriverState *qemu_chr_open_gnmouse(void)
+{
+    CharDriverState *chr;
+    GnMouseSave *save;
+
+    DPRINTF("qemu_chr_open_gnmouse\n");
+
+    chr = g_malloc0(sizeof(CharDriverState));
+    save = g_malloc0(sizeof(GnMouseSave));
+
+    chr->chr_write = gnmouse_chr_write;
+    chr->chr_close = gnmouse_chr_close;
+    chr->explicit_be_open = true;
+
+    /*
+     * Using a timer is needed to avoids saturating the receive buffer.
+     * Usually, this buffer is limited to 16 bytes (16550A device).
+     * If this is not done then a frame is sent on each GUI event (delta = 1)
+     */
+    save->transmit_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL,
+                                        (QEMUTimerCB *) gnmouse_handler, chr);
+
+    save->transmit_time = (get_ticks_per_sec() / 1200) * 10; /* 1200 bauds */
+
+    DPRINTF("transmit_time = %" PRIu64 "\n", save->transmit_time);
+
+    save->index = STOP;
+    chr->opaque = save;
+
+    qemu_add_mouse_event_handler(gnmouse_event, chr, 0,
+                                 "QEMU Genius GM-6 Mouse");
+
+    return chr;
+}
+
+static void register_types(void)
+{
+    register_char_driver_qapi("gnmouse", CHARDEV_BACKEND_KIND_GNMOUSE, NULL);
+}
+
+type_init(register_types);
diff --git a/include/sysemu/char.h b/include/sysemu/char.h
index b81a6ff..f769e3b 100644
--- a/include/sysemu/char.h
+++ b/include/sysemu/char.h
@@ -306,6 +306,9 @@ CharDriverState *qemu_char_get_next_serial(void);
 /* msmouse */
 CharDriverState *qemu_chr_open_msmouse(void);
 
+/* gnmouse */
+CharDriverState *qemu_chr_open_gnmouse(void);
+
 /* baum.c */
 CharDriverState *chr_baum_init(void);
 
diff --git a/qapi-schema.json b/qapi-schema.json
index c3c939c..5a5db7f 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -3617,6 +3617,7 @@
                                        'null'   : 'ChardevDummy',
                                        'mux'    : 'ChardevMux',
                                        'msmouse': 'ChardevDummy',
+                                       'gnmouse': 'ChardevDummy',
                                        'braille': 'ChardevDummy',
                                        'stdio'  : 'ChardevStdio',
                                        'console': 'ChardevDummy',
diff --git a/qemu-char.c b/qemu-char.c
index 418dc69..9766815 100644
--- a/qemu-char.c
+++ b/qemu-char.c
@@ -2961,6 +2961,7 @@ QemuOpts *qemu_chr_parse_compat(const char *label, const 
char *filename)
     if (strcmp(filename, "null")    == 0 ||
         strcmp(filename, "pty")     == 0 ||
         strcmp(filename, "msmouse") == 0 ||
+        strcmp(filename, "gnmouse") == 0 ||
         strcmp(filename, "braille") == 0 ||
         strcmp(filename, "stdio")   == 0) {
         qemu_opt_set(opts, "backend", filename);
@@ -3732,6 +3733,9 @@ ChardevReturn *qmp_chardev_add(const char *id, 
ChardevBackend *backend,
     case CHARDEV_BACKEND_KIND_MSMOUSE:
         chr = qemu_chr_open_msmouse();
         break;
+    case CHARDEV_BACKEND_KIND_GNMOUSE:
+        chr = qemu_chr_open_gnmouse();
+        break;
 #ifdef CONFIG_BRLAPI
     case CHARDEV_BACKEND_KIND_BRAILLE:
         chr = chr_baum_init();
diff --git a/qemu-options.hx b/qemu-options.hx
index bcfe9ea..4f64d7d 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -1792,6 +1792,7 @@ DEF("chardev", HAS_ARG, QEMU_OPTION_chardev,
     "-chardev udp,id=id[,host=host],port=port[,localaddr=localaddr]\n"
     "         [,localport=localport][,ipv4][,ipv6][,mux=on|off]\n"
     "-chardev msmouse,id=id[,mux=on|off]\n"
+    "-chardev gnmouse,id=id[,mux=on|off]\n"
     "-chardev 
vc,id=id[[,width=width][,height=height]][[,cols=cols][,rows=rows]]\n"
     "         [,mux=on|off]\n"
     "-chardev ringbuf,id=id[,size=size]\n"
@@ -1831,6 +1832,7 @@ Backend is one of:
 @option{socket},
 @option{udp},
 @option{msmouse},
address@hidden,
 @option{vc},
 @option{ringbuf},
 @option{file},
@@ -1927,8 +1929,16 @@ If neither is specified the device may use either 
protocol.
 
 @item -chardev msmouse ,address@hidden
 
-Forward QEMU's emulated msmouse events to the guest. @option{msmouse} does not
-take any options.
+Forward events from QEMU's emulated mouse to the guest using the
+Microsoft protocol. @option{msmouse} does not take any options.
+
address@hidden -chardev gnmouse ,address@hidden
+
+Forward events from QEMU's emulated mouse to the guest using the Genius
+(Mouse Systems) protocol. Use msmouse unless your guest OS only
+supports the Mouse Systems protocol.
+
address@hidden does not take any options.
 
 @item -chardev vc ,address@hidden [[,address@hidden [,address@hidden 
[[,address@hidden [,address@hidden
 
@@ -2514,6 +2524,9 @@ or fake device.
 
 @item msmouse
 Three button serial mouse. Configure the guest to use Microsoft protocol.
+
address@hidden gnmouse
+Three button serial mouse. Configure the guest to use Mouse Systems protocol.
 @end table
 ETEXI
 
-- 
1.8.4.2




reply via email to

[Prev in Thread] Current Thread [Next in Thread]