chardev/msmouse.c | 17 +++++++++++++++++ 1 file changed, 17 insertions(+)
From: Trieu Huynh <vikingtc4@gmail.com>
MouseChardev holds state that must survive migration and savevm/loadvm:
tiocm - serial line control (RTS/DTR). When zero the mouse is
considered powered-off and all input events are silently
dropped. Losing this field after migration leaves the
mouse in a dead state: the guest driver must toggle
RTS/DTR again to re-detect the mouse, which most drivers
(Linux sermouse, DOS ctmouse) only do at initialisation.
axis[] - accumulated X/Y movement not yet encoded into a packet.
btns[] - current button state.
btnc[] - per-button change flags used for middle-button tracking.
outbuf - Fifo8 of encoded mouse bytes waiting to be delivered to
the serial port receive FIFO.
Add a VMStateDescription covering all five fields, registering it in
msmouse_chr_open and unregistering it in char_msmouse_finalize. This
follows the same pattern used by ui/vdagent.c
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1234
Signed-off-by: Trieu Huynh <vikingtc4@gmail.com>
---
Steps to reproduce:
* 1. Starting source QEMU under GDB
gdb -q ./qemu-system-x86_64
* Inside GDB:
(gdb) set args -chardev msmouse,id=mouse0 \
-device isa-serial,chardev=mouse0 \
- <other_options>
(gdb) break msmouse_input_sync
(gdb) run
* 2. Starting destination QEMU under GDB
gdb -q ./qemu-system-x86_64
* Inside GDB:
(gdb) set args -chardev msmouse,id=mouse0 \
-S -incoming tcp:0:4444
-device isa-serial,chardev=mouse0 \
- <other_options>
(gdb) break msmouse_chr_ioctl
(gdb) run
<waiting for migration>
* 3. Send mouse events to source
{"execute":"qmp_capabilities"}
* Power on the mouse:
{"execute":"human-monitor-command","arguments":{"command-line":"o 0x2fc 0x03"}}
* Inject a movement event with buttons:
{"execute":"input-send-event","arguments":{"events":[{"type":"rel","data":{"axis":"x","value":30}},{"type":"rel","data":{"axis":"y","value":-10}},{"type":"btn","data":{"button":"left","down":true}},{"type":"btn","data":{"button":"middle","down":true}}]}}
Thread 1 "qemu-system-x86" hit Breakpoint 1, msmouse_input_sync (dev=0x555557648a00)
at ../chardev/msmouse.c:171
171 MouseChardev *mouse = MOUSE_CHARDEV(dev);
(gdb) next
172 Chardev *chr = CHARDEV(dev);
(gdb) next
175 if (!MSMOUSE_PWR(mouse->tiocm)) {
(gdb) print mouse->tiocm
$17 = 6
(gdb) print mouse->axis[1]
$18 = -10
(gdb) print mouse->axis[0]
$19 = 30
(gdb) print mouse->btns[0]
$20 = true
(gdb) print mouse->btns[2]
$21 = false
(gdb) print mouse->btnc[0]
$22 = true
(gdb) print mouse->btnc[2]
$23 = false
(gdb) print mouse->outbuf
$24 = {data = 0x5555578bbdf0 "M3\b\001$1-5\020\020\020\021<<-/53%<<1%-5", capacity = 64,
head = 1, num = 55}
(gdb) continue
* 4. Start migration until completed
{"execute":"migrate","arguments":{"uri":"tcp:127.0.0.1:4444"}}
* 5. Verify mouse data on dest
* As-is:
Thread 1 "qemu-system-x86" hit Breakpoint 1, msmouse_chr_ioctl (chr=0x555557648ad0,
cmd=1, arg=0x7ffff415ba80) at ../chardev/msmouse.c:182
182 {
(gdb) next
183 MouseChardev *mouse = MOUSE_CHARDEV(chr);
(gdb) next
184 int c, i, j;
(gdb) print ((MouseChardev *)chr)->tiocm
$1 = 0
(gdb) print ((MouseChardev *)chr)->axis[0]
$2 = 0
(gdb) print ((MouseChardev *)chr)->axis[1]
$3 = 0
(gdb) print ((MouseChardev *)chr)->btns[0]
$4 = false
(gdb) print ((MouseChardev *)chr)->btns[2]
$5 = false
(gdb) print ((MouseChardev *)chr)->btnc[0]
$6 = false
(gdb) print ((MouseChardev *)chr)->btnc[2]
$7 = false
(gdb) print ((MouseChardev *)chr)->outbuf
$8 = {data = 0x5555578bbf60 "\273xUU\005", capacity = 64, head = 0, num = 0}
(gdb)
* To-be:
Thread 1 "qemu-system-x86" hit Breakpoint 1, msmouse_chr_ioctl (chr=0x555557648ad0,
cmd=1, arg=0x7ffff415ba80) at ../chardev/msmouse.c:197
197 {
(gdb) next
198 MouseChardev *mouse = MOUSE_CHARDEV(chr);
(gdb) next
199 int c, i, j;
(gdb) print ((MouseChardev *)chr)->tiocm
$1 = 6
(gdb) print ((MouseChardev *)chr)->axis[0]
$2 = 0
(gdb) print ((MouseChardev *)chr)->axis[1]
$3 = 0
(gdb) print ((MouseChardev *)chr)->btns[0]
$4 = true
(gdb) print ((MouseChardev *)chr)->btns[2]
$5 = false
(gdb) print ((MouseChardev *)chr)->btnc[0]
$6 = true
(gdb) print ((MouseChardev *)chr)->btnc[2]
$7 = false
(gdb) print ((MouseChardev *)chr)->outbuf
$8 = {data = 0x5555578bbf60 "M3\b\001$1-5\020\020\020\021<<-/53%<<1%-5", capacity = 64,
head = 1, num = 59}
chardev/msmouse.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/chardev/msmouse.c b/chardev/msmouse.c
index 365f04546e..e991135be9 100644
--- a/chardev/msmouse.c
+++ b/chardev/msmouse.c
@@ -30,6 +30,7 @@
#include "ui/console.h"
#include "ui/input.h"
#include "qom/object.h"
+#include "migration/vmstate.h"
#define MSMOUSE_LO6(n) ((n) & 0x3f)
#define MSMOUSE_HI2(n) (((n) & 0xc0) >> 6)
@@ -70,6 +71,20 @@ typedef struct MouseChardev MouseChardev;
DECLARE_INSTANCE_CHECKER(MouseChardev, MOUSE_CHARDEV,
TYPE_CHARDEV_MSMOUSE)
+static const VMStateDescription vmstate_msmouse = {
+ .name = "msmouse",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .fields = (const VMStateField[]) {
+ VMSTATE_INT32(tiocm, MouseChardev),
+ VMSTATE_INT32_ARRAY(axis, MouseChardev, INPUT_AXIS__MAX),
+ VMSTATE_BOOL_ARRAY(btns, MouseChardev, INPUT_BUTTON__MAX),
+ VMSTATE_BOOL_ARRAY(btnc, MouseChardev, INPUT_BUTTON__MAX),
+ VMSTATE_FIFO8(outbuf, MouseChardev),
+ VMSTATE_END_OF_LIST()
+ },
+};
+
static void msmouse_chr_accept_input(Chardev *chr)
{
MouseChardev *mouse = MOUSE_CHARDEV(chr);
@@ -247,6 +262,7 @@ static void char_msmouse_finalize(Object *obj)
{
MouseChardev *mouse = MOUSE_CHARDEV(obj);
+ vmstate_unregister(NULL, &vmstate_msmouse, mouse);
if (mouse->hs) {
qemu_input_handler_unregister(mouse->hs);
}
@@ -263,6 +279,7 @@ static bool msmouse_chr_open(Chardev *chr,
&msmouse_handler);
mouse->tiocm = 0;
fifo8_create(&mouse->outbuf, MSMOUSE_BUF_SZ);
+ vmstate_register_any(NULL, &vmstate_msmouse, mouse);
/* Never send CHR_EVENT_OPENED */
return true;
--
2.43.0
On Sat, 18 Apr 2026 at 12:53, Trieu Huynh <vikingtc4@gmail.com> wrote: > > From: Trieu Huynh <vikingtc4@gmail.com> > > MouseChardev holds state that must survive migration and savevm/loadvm: > > tiocm - serial line control (RTS/DTR). When zero the mouse is > considered powered-off and all input events are silently > dropped. Losing this field after migration leaves the > mouse in a dead state: the guest driver must toggle > RTS/DTR again to re-detect the mouse, which most drivers > (Linux sermouse, DOS ctmouse) only do at initialisation. > > axis[] - accumulated X/Y movement not yet encoded into a packet. > > btns[] - current button state. > > btnc[] - per-button change flags used for middle-button tracking. > > outbuf - Fifo8 of encoded mouse bytes waiting to be delivered to > the serial port receive FIFO. > > Add a VMStateDescription covering all five fields, registering it in > msmouse_chr_open and unregistering it in char_msmouse_finalize. This > follows the same pattern used by ui/vdagent.c > > Resolves: https://gitlab.com/qemu-project/qemu/-/issues/1234 > > Signed-off-by: Trieu Huynh <vikingtc4@gmail.com> > @@ -263,6 +279,7 @@ static bool msmouse_chr_open(Chardev *chr, > &msmouse_handler); > mouse->tiocm = 0; > fifo8_create(&mouse->outbuf, MSMOUSE_BUF_SZ); > + vmstate_register_any(NULL, &vmstate_msmouse, mouse); Adding a new use of vmstate_register_any() is a bit unfortunate -- this is supposed to be a legacy function. More generally, it seems odd that the chardev has any state that needs to be migrated at all. The chardev should have state corresponding to the host's situation, which is all fresh for the migration destination. State corresponding to things the guest has or has not done ought to be in a device model somewhere, surely? Put another way, why is this msmouse code in chardev/ and a subtype of TYPE_CHARDEV, and not a device in hw/input that's a subtype of TYPE_DEVICE? I guess that the idea is we want to be able to plug it into any UART device, but that seems to have some issues when it comes to migration. wctablet is probably in the same boat. -- PMM
© 2016 - 2026 Red Hat, Inc.