[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
[Prev in Thread] |
Current Thread |
[Next in Thread] |
- [Qemu-devel] [PATCH v3 1/1] genius: add genius serial mouse emulation,
Romain Naour <=