Commit 9214d03444 for qemu.org

commit 9214d034440a96fb51a8d3e827f110ae5b694935
Author: Doru Blânzeanu <dblanzeanu@linux.microsoft.com>
Date:   Wed Jul 8 01:16:45 2026 +0300

    target/i386/mshv: fix pio handlers clobbering device-modified registers

    When a device handler (e.g. vmport) calls cpu_synchronize_state() during
    I/O port dispatch, it sets cpu->accel->dirty = true and may modify
    registers directly in env. The old PIO code ignored this: it
    unconditionally wrote the stale info->rax from the VM-exit intercept
    message back to the hypervisor and then cleared dirty, discarding any
    register changes made by the device.

    Bifurcate both handlers on cpu->accel->dirty:

    handle_pio_non_str:
    - dirty path: update env->eip directly. For reads (IN), merge the I/O
      result into env->regs[R_EAX] (which may have been modified by the
      device) rather than info->rax. For writes (OUT), leave RAX untouched.
      Flush all registers via mshv_store_regs() and clear dirty.
    - non-dirty path: write RIP and RAX via set_x64_registers hypercall as
      before.

    handle_pio_str:
    - dirty path: update env->eip and the appropriate index register
      (RSI for OUTS, RDI for INS) directly. Flush via mshv_store_regs()
      and clear dirty.
    - non-dirty path: write the index register and RIP via
      set_x64_registers. Drop the RAX assignment that was here before;
      string I/O does not modify RAX, and set_x64_registers is hardcoded
      to write only 2 registers so the third slot was silently ignored
      anyway.

    Remove the unconditional "cpu->accel->dirty = false" at the end of both
    handlers. In the non-dirty fast path it was redundant (already false).
    In the dirty path it was actively harmful: it told the vcpu run loop
    that env was clean when it was not, losing the device's modifications.

    Signed-off-by: Doru Blânzeanu <dblanzeanu@linux.microsoft.com>
    Reviewed-by: Magnus Kulke <magnuskulke@linux.microsoft.com>
    Signed-off-by: Magnus Kulke <magnuskulke@linux.microsoft.com>

diff --git a/target/i386/mshv/mshv-cpu.c b/target/i386/mshv/mshv-cpu.c
index 5a34357693..4f113da02d 100644
--- a/target/i386/mshv/mshv-cpu.c
+++ b/target/i386/mshv/mshv-cpu.c
@@ -1718,10 +1718,12 @@ static int handle_pio_non_str(CPUState *cpu,
     uint32_t val, eax;
     const uint32_t eax_mask =  0xffffffffu >> (32 - len * 8);
     size_t insn_len;
-    uint64_t rip, rax;
+    uint64_t rip;
     uint32_t reg_names[2];
     uint64_t reg_values[2];
     uint16_t port = info->port_number;
+    X86CPU *x86_cpu = X86_CPU(cpu);
+    CPUX86State *env = &x86_cpu->env;

     if (access_type == HV_X64_INTERCEPT_ACCESS_TYPE_WRITE) {
         union {
@@ -1752,21 +1754,36 @@ static int handle_pio_non_str(CPUState *cpu,

     /* Advance RIP and update RAX */
     rip = info->header.rip + insn_len;
-    rax = info->rax;

-    reg_names[0] = HV_X64_REGISTER_RIP;
-    reg_values[0] = rip;
-    reg_names[1] = HV_X64_REGISTER_RAX;
-    reg_values[1] = rax;
+    if (cpu->vcpu_dirty) {
+        env->eip = rip;
+        if (access_type != HV_X64_INTERCEPT_ACCESS_TYPE_WRITE) {
+            /*
+             * For reads, merge the I/O result into the current RAX.
+             * Use env->regs[R_EAX] as the base since a device handler
+             * (e.g. vmport) may have called cpu_synchronize_state()
+             * and modified registers.
+             */
+            eax = (((uint32_t)env->regs[R_EAX]) & ~eax_mask)
+                  | (val & eax_mask);
+            env->regs[R_EAX] = (uint64_t)eax;
+        }
+        /* Sync modified standard registers back and clear dirty. */
+        store_regs(cpu);
+        cpu->vcpu_dirty = false;
+    } else {
+        reg_names[0] = HV_X64_REGISTER_RIP;
+        reg_values[0] = rip;
+        reg_names[1] = HV_X64_REGISTER_RAX;
+        reg_values[1] = info->rax;

-    ret = set_x64_registers(cpu, reg_names, reg_values);
-    if (ret < 0) {
-        error_report("Failed to set x64 registers");
-        return -1;
+        ret = set_x64_registers(cpu, reg_names, reg_values);
+        if (ret < 0) {
+            error_report("Failed to set x64 registers");
+            return -1;
+        }
     }

-    cpu->vcpu_dirty = false;
-
     return 0;
 }

@@ -1915,6 +1932,7 @@ static int handle_pio_str(CPUState *cpu, hv_x64_io_port_intercept_message *info)
     bool repop = info->access_info.rep_prefix == 1;
     size_t repeat = repop ? info->rcx : 1;
     size_t insn_len = info->header.instruction_length;
+    uint64_t rip;
     bool direction_flag;
     uint32_t reg_names[3];
     uint64_t reg_values[3];
@@ -1944,18 +1962,28 @@ static int handle_pio_str(CPUState *cpu, hv_x64_io_port_intercept_message *info)
         reg_values[0] = info->rdi;
     }

-    reg_names[1] = HV_X64_REGISTER_RIP;
-    reg_values[1] = info->header.rip + insn_len;
-    reg_names[2] = HV_X64_REGISTER_RAX;
-    reg_values[2] = info->rax;
+    rip = info->header.rip + insn_len;

-    ret = set_x64_registers(cpu, reg_names, reg_values);
-    if (ret < 0) {
-        error_report("Failed to set RIP and RAX registers");
-        return -1;
-    }
+    if (cpu->vcpu_dirty) {
+        env->eip = rip;
+        if (access_type == HV_X64_INTERCEPT_ACCESS_TYPE_WRITE) {
+            env->regs[R_ESI] = info->rsi;
+        } else {
+            env->regs[R_EDI] = info->rdi;
+        }
+        /* Sync modified standard registers back and clear dirty. */
+        store_regs(cpu);
+        cpu->vcpu_dirty = false;
+    } else {
+        reg_names[1] = HV_X64_REGISTER_RIP;
+        reg_values[1] = rip;

-    cpu->vcpu_dirty = false;
+        ret = set_x64_registers(cpu, reg_names, reg_values);
+        if (ret < 0) {
+            error_report("Failed to set x64 registers");
+            return -1;
+        }
+    }

     return 0;
 }