From patchwork Tue Jan 14 22:05:40 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Romain Naour X-Patchwork-Id: 310886 Return-Path: X-Original-To: incoming@patchwork.ozlabs.org Delivered-To: patchwork-incoming@bilbo.ozlabs.org Received: from lists.gnu.org (lists.gnu.org [IPv6:2001:4830:134:3::11]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by ozlabs.org (Postfix) with ESMTPS id 137AB2C0092 for ; Wed, 15 Jan 2014 09:11:03 +1100 (EST) Received: from localhost ([::1]:50925 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1W3CCK-00060J-B2 for incoming@patchwork.ozlabs.org; Tue, 14 Jan 2014 17:11:00 -0500 Received: from eggs.gnu.org ([2001:4830:134:3::10]:38014) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1W3CBu-000608-4D for qemu-devel@nongnu.org; Tue, 14 Jan 2014 17:10:40 -0500 Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1W3CBo-0003iq-2N for qemu-devel@nongnu.org; Tue, 14 Jan 2014 17:10:33 -0500 Received: from smtp6-g21.free.fr ([2a01:e0c:1:1599::15]:55982) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1W3CBn-0003iQ-6W for qemu-devel@nongnu.org; Tue, 14 Jan 2014 17:10:27 -0500 Received: from localhost.localdomain (unknown [81.57.22.125]) by smtp6-g21.free.fr (Postfix) with ESMTP id B1A4082293; Tue, 14 Jan 2014 23:10:21 +0100 (CET) From: Romain Naour To: qemu-devel@nongnu.org Date: Tue, 14 Jan 2014 23:05:40 +0100 Message-Id: <1389737140-6722-1-git-send-email-romain.naour@openwide.fr> X-Mailer: git-send-email 1.8.4.2 X-detected-operating-system: by eggs.gnu.org: Error: Malformed IPv6 address (bad octet value). X-Received-From: 2a01:e0c:1:1599::15 Cc: Romain Naour Subject: [Qemu-devel] [PATCH v2 1/1] genius: add genius serial mouse emulation X-BeenThere: qemu-devel@nongnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org Sender: qemu-devel-bounces+incoming=patchwork.ozlabs.org@nongnu.org This patch adds the emulation for a serial Genius mouse using Mouse Systems protocol (5bytes). This protocol is compatible with most 3-button serial mouse. Signed-off-by: Romain Naour --- Changes v1 -> v2: Fixes documentation (Paolo Bonzini) Fixes typos backends/Makefile.objs | 2 +- backends/gnmouse.c | 339 +++++++++++++++++++++++++++++++++++++++++++++++++ include/sysemu/char.h | 3 + qapi-schema.json | 1 + qemu-char.c | 4 + qemu-options.hx | 14 +- 6 files changed, 360 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..9581419 --- /dev/null +++ b/backends/gnmouse.c @@ -0,0 +1,339 @@ +/* + * 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 +#include +#include + +#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 gnmouse_save: + * 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 gnmouse_save { + 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; +} gnmouse_save; + + +/* 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 send to the emulated mouse. + * + * Return: lengh of the buffer + * + * @s: address of the CharDriverState used by the mouse + * @buf: buffer to write + * @len: lengh 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: this function close the mouse port. + * It stop and free the QEMU's timer and free gnmouse_save struct. + * + * Return: void + * + * @chr: address of the CharDriverState used by the mouse + */ +static void gnmouse_chr_close(struct CharDriverState *chr) +{ + /* stop and free the QEMU's timer */ + timer_del(((gnmouse_save *)chr->opaque)->transmit_timer); + timer_free(((gnmouse_save *)chr->opaque)->transmit_timer); + /* free gnmouse_save struct */ + g_free(chr->opaque); + g_free(chr); +} + +/** + * 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 send. + * + * Returns void + * + * @opaque: address of the CharDriverState used by the mouse + */ +static void gnmouse_handler(void *opaque) +{ + CharDriverState *chr = (CharDriverState *)opaque; + gnmouse_save *save = (gnmouse_save *)chr->opaque; + unsigned char *data = save->data; + int dx_tmp, dy_tmp; +/* + * 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: + if (save->dx && save->dy) { + if (save->dx >= 128) { + DPRINTF("overflow dx= %d\n", save->dx); + save->dx -= 128; + dx_tmp = 128; + } else if (save->dx <= -127) { + DPRINTF("overflow dx= %d\n", save->dx); + save->dx += 127; + dx_tmp = -127; + } else { + dx_tmp = save->dx; + save->dx = 0; + } + + if (save->dy >= 128) { + DPRINTF("overflow dy= %d\n", save->dy); + save->dy -= 128; + dy_tmp = 128; + } else if (save->dy <= -127) { + DPRINTF("overflow dy= %d\n", save->dy); + save->dy += 127; + dy_tmp = -127; + } else { + dy_tmp = save->dy; + save->dy = 0; + } + + DPRINTF("dx= %d\n", save->dx); + DPRINTF("dy= %d\n", save->dy); + + data[3] = dx_tmp; + data[4] = -(dy_tmp); + } + break; + + case STOP: + if (!(save->dx && save->dy)) { + /* no more data */ + DPRINTF("no more data\n"); + return; + } else { + /* data saved */ + DPRINTF("data saved\n"); + save->index = START; + } + /* No break, pass-through START */ + + case START: + /* New serial frame */ + /* Buttons */ + data[0] = save->button; + save->index = CHAR_1; + /* No break, pass-through CHAR_1 */ + + case CHAR_1: + /* avoid overflow on dx or dy */ + if (save->dx >= 128) { + DPRINTF("overflow dx= %d\n", save->dx); + save->dx -= 128; + dx_tmp = 128; + } else if (save->dx <= -127) { + DPRINTF("overflow dx= %d\n", save->dx); + save->dx += 127; + dx_tmp = -127; + } else{ + dx_tmp = save->dx; + save->dx = 0; + } + + if (save->dy >= 128) { + DPRINTF("overflow dy= %d\n", save->dy); + save->dy -= 128; + dy_tmp = 128; + } else if (save->dy <= -127) { + DPRINTF("overflow dy= %d\n", save->dy); + save->dy += 127; + dy_tmp = -127; + } else{ + dy_tmp = save->dy; + save->dy = 0; + } + + DPRINTF("dx= %d\n", save->dx); + DPRINTF("dy= %d\n", save->dy); + + /* Movement deltas */ + data[1] = dx_tmp; + data[2] = -(dy_tmp); + data[3] = 0; + data[4] = 0; + + case CHAR_2: + case CHAR_3: + case CHAR_5: + break; + default: + return; + } + + /* reload timer */ + timer_mod(save->transmit_timer, + qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + save->transmit_time); + DPRINTF("mod_timer: %d\n", save->index); + /* write data on serial port */ + qemu_chr_be_write(chr, &(data[save->index - 1]), 1); + DPRINTF("write :%x\n", data[save->index - 1]); + /* next state */ + save->index += 1; +} + +/** + * gnmouse_event: event handler called by SDL functions + * on each mouse movement or button press. + * + * Return void + * + * @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; + gnmouse_save *save = (gnmouse_save *)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 */ + 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((void *) chr); + } +} + +/** + * qemu_chr_open_gnmouse: Init function for Genius mouse + * allocate a gnmouse_save structure to save data used by gnmouse emulation. + * allocate a new CharDriverState. + * create a new QEMU's timer with gnmouse_handler() as timeout handler. + * calculate the transmit_time for 1200 bauds transmission. + * + * Return address of the initialized CharDriverState + * + * @opts: argument not used + */ +CharDriverState *qemu_chr_open_gnmouse(void) +{ + CharDriverState *chr; + gnmouse_save *save; + + DPRINTF("qemu_chr_open_gnmouse\n"); + + /* allocate CharDriverState and gnmouse_save */ + chr = g_malloc0(sizeof(CharDriverState)); + save = g_malloc0(sizeof(gnmouse_save)); + + chr->chr_write = gnmouse_chr_write; + chr->chr_close = gnmouse_chr_close; + chr->explicit_be_open = true; + + /* create a new QEMU's timer with gnmouse_handler() as timeout handler. */ + save->transmit_timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + (QEMUTimerCB *) gnmouse_handler, chr); + /* calculate the transmit_time for 1200 bauds transmission */ + save->transmit_time = (get_ticks_per_sec() / 1200) * 10; /* 1200 bauds */ + + DPRINTF("transmit_time = %lld\n", save->transmit_time); + + /* init state machine */ + save->index = STOP; + + /* keep address of gnmouse_save */ + 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..17cb8aa 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}, +@option{gnmouse}, @option{vc}, @option{ringbuf}, @option{file}, @@ -1927,8 +1929,13 @@ If neither is specified the device may use either protocol. @item -chardev msmouse ,id=@var{id} -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. + +@item -chardev gnmouse ,id=@var{id} + +Forward events from QEMU's emulated mouse to the guest using the Genius +(Mouse Systems) protocol. @option{gnmouse} does not take any options. @item -chardev vc ,id=@var{id} [[,width=@var{width}] [,height=@var{height}]] [[,cols=@var{cols}] [,rows=@var{rows}]] @@ -2514,6 +2521,9 @@ or fake device. @item msmouse Three button serial mouse. Configure the guest to use Microsoft protocol. + +@item gnmouse +Three button serial mouse. Configure the guest to use Genius (Mouse Systems) protocol. @end table ETEXI