Commit ba4a996631 for qemu.org

commit ba4a996631cd3882246e0892e805d398a63e9b09
Author: Denis V. Lunev <den@openvz.org>
Date:   Tue Aug 4 18:34:44 2026 +0200

    hw/ide: migrate the logical CHS translation

    INITIALIZE DEVICE PARAMETERS lets a guest replace the logical CHS
    translation used to turn the CHS registers into an LBA, but s->heads and
    s->sectors were in no VMStateDescription. The destination rebuilt them
    from the drive configuration, so a guest that had selected one of its own
    kept addressing the disk in it while the device translated with the
    default, landing on sectors nobody asked for.

    Add a subsection for it, sent only when the guest replaced the default, so
    that migration to an older QEMU keeps working for every other guest.
    s->cylinders is left out, as no command changes it.

    Validate what is loaded in the existing post_load: ide_get_sector()
    multiplies by these fields and ide_set_sector() divides by them.

    Cc: John Snow <jsnow@redhat.com>
    Cc: Peter Maydell <peter.maydell@linaro.org>
    Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
    Fixes: 176e4961bb33 ("hw/ide/core.c: Implement ATA INITIALIZE_DEVICE_PARAMETERS command")
    Signed-off-by: Denis V. Lunev <den@openvz.org>

diff --git a/hw/ide/core.c b/hw/ide/core.c
index 28219f5ef8..b074325afa 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -2879,6 +2879,13 @@ static int ide_drive_post_load(void *opaque, int version_id)
 {
     IDEState *s = opaque;

+    /* Only a disk has a translation; an empty slot and ATAPI keep these zero */
+    if (s->blk && s->drive_kind != IDE_CD &&
+        (s->heads < 1 || s->heads > 16 ||
+         s->sectors < 1 || s->sectors > 255)) {
+        return -EINVAL;
+    }
+
     if (s->blk && s->identify_set) {
         blk_set_enable_write_cache(s->blk, !!(s->identify_data[85] & (1 << 5)));
     }
@@ -2962,6 +2969,25 @@ static const VMStateDescription vmstate_ide_atapi_gesn_state = {
     }
 };

+static bool ide_chs_translation_needed(void *opaque)
+{
+    IDEState *s = opaque;
+
+    return s->heads != s->drive_heads || s->sectors != s->drive_sectors;
+}
+
+static const VMStateDescription vmstate_ide_drive_chs_translation = {
+    .name = "ide_drive/chs_translation",
+    .version_id = 1,
+    .minimum_version_id = 1,
+    .needed = ide_chs_translation_needed,
+    .fields = (const VMStateField[]) {
+        VMSTATE_INT32(heads, IDEState),
+        VMSTATE_INT32(sectors, IDEState),
+        VMSTATE_END_OF_LIST()
+    }
+};
+
 static const VMStateDescription vmstate_ide_tray_state = {
     .name = "ide_drive/tray_state",
     .version_id = 1,
@@ -3025,6 +3051,7 @@ const VMStateDescription vmstate_ide_drive = {
     },
     .subsections = (const VMStateDescription * const []) {
         &vmstate_ide_drive_pio_state,
+        &vmstate_ide_drive_chs_translation,
         &vmstate_ide_tray_state,
         &vmstate_ide_atapi_gesn_state,
         NULL