* [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects
@ 2026-08-20 10:08 Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 01/17] hw/ide: reject an unsupported CHS translation Denis V. Lunev
` (17 more replies)
0 siblings, 18 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
INITIALIZE DEVICE PARAMETERS lets a guest replace the logical CHS
translation that turns the CHS registers into an LBA. cmd_specify() has
implemented it since 176e4961bb33, released in v7.2, and the geometry it
stores is mishandled four ways.
It is not validated: zero sectors per logical track kills QEMU with SIGFPE
in ide_set_sector(), which is issue 2399, and since handle_cmd() takes the
count from a 16 bit field of the register FIS, an AHCI guest can drive
ide_get_sector() into an int overflow as well. It is not reported:
IDENTIFY DEVICE is built once and cached, so both the words describing the
default geometry and the words describing the current one go wrong. It
does not travel, leaving a migrated guest addressing the disk in a
translation the destination does not have. And it does not revert on a
hardware reset, which ATA-5 9.1 requires, so it outlives the reset of the
machine it was selected on.
The two subsections patches 10 and 11 add are the only change to the
migration stream, and both are sent only when the guest replaced the
default geometry. An unaffected guest migrates to an older QEMU as before;
an affected one now fails the load on the unknown subsection instead of
silently addressing the wrong sectors.
Tested with ide-test (25 cases), ahci-test (74), a full make check, a
clang build, and real guests on both controllers: FreeDOS reading a FAT16
disk through INT 13h CHS, Red Hat Linux 9 whose 2.4 IDE driver prints the
geometry it read, and Ubuntu 26.04 over libata, each with a CD and a disk,
plus a machine reset in the middle.
v1:
https://lore.kernel.org/qemu-devel/20260817205242.1199851-1-den@openvz.org/
Changes in v2
-------------
- patch 1, patch 5: Cc qemu-stable. (Philippe)
- patch 6: new, the "XXX: retired, remove ?" on IDENTIFY DEVICE words 4,
5 and 20 is answered rather than carried. ATA-5 3.2.3.6 keeps the
ATA-1 meaning of a retired word that a device still fills in, so the
comments name that meaning; word 21 is a buffer size, not a cache
size. (Philippe)
- patch 7: new, splits the extraction of ide_identify_chs() out of what
is now patch 8, leaving that one with the two call sites that keep the
words in sync. (Philippe)
Signed-off-by: Denis V. Lunev <den@openvz.org>
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Denis V. Lunev (17):
hw/ide: reject an unsupported CHS translation
tests/qtest/ide-test: cover a CHS translation with zero sectors
tests/qtest/libqos/ahci: allow a count and an expected error
tests/qtest/ahci: cover the sector count of INITIALIZE DEVICE
PARAMETERS
hw/ide: report the default CHS translation in IDENTIFY DEVICE
hw/ide: name the retired IDENTIFY DEVICE words the device fills in
hw/ide: factor out the IDENTIFY DEVICE current geometry words
hw/ide: keep the IDENTIFY DEVICE current geometry in sync
hw/ide: restore the power-on device state before loading
hw/ide: migrate the logical CHS translation
hw/ide: migrate the power-on defaults revert flag
tests/qtest/ide-test: cover the CHS translation across migration
tests/qtest/ide-test: cover a rejected CHS translation in the stream
tests/qtest/ide-test: cover the IDENTIFY DEVICE geometry words
hw/ide: revert the CHS translation on a hardware reset
tests/qtest/ide-test: cover the CHS translation across resets
hw/ide: drop a redundant interrupt from INITIALIZE DEVICE PARAMETERS
hw/ide/ahci.c | 12 +-
hw/ide/cmd646.c | 2 +-
hw/ide/core.c | 147 ++++++++---
hw/ide/ide-internal.h | 7 +-
hw/ide/isa.c | 2 +-
hw/ide/macio.c | 2 +-
hw/ide/mmio.c | 2 +-
hw/ide/piix.c | 2 +-
hw/ide/sii3112.c | 6 +-
hw/ide/via.c | 2 +-
tests/qtest/ahci-test.c | 40 +++
tests/qtest/ide-test.c | 499 ++++++++++++++++++++++++++++++++++++++
tests/qtest/libqos/ahci.c | 14 ++
tests/qtest/libqos/ahci.h | 6 +
14 files changed, 690 insertions(+), 53 deletions(-)
base-commit: ae4f3443209ab154b48b706a146e5f557ab147cb
--
2.53.0
^ permalink raw reply [flat|nested] 21+ messages in thread
* [PATCH v2 01/17] hw/ide: reject an unsupported CHS translation
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 02/17] tests/qtest/ide-test: cover a CHS translation with zero sectors Denis V. Lunev
` (16 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé, qemu-stable, Zheyu Ma
From: Denis V. Lunev <den@openvz.org>
ide_set_sector() divides by (s->heads * s->sectors) when the drive is
addressed in CHS mode. Both come from the guest via INITIALIZE DEVICE
PARAMETERS, and cmd_specify() stored them without any check, so a guest
asking for zero sectors per logical track killed QEMU with SIGFPE on the
completion of the first CHS read or write. s->heads is safe, as the
command passes a heads-1 value.
The count has an upper bound as well. The legacy sector count register is
eight bits wide, but handle_cmd() takes the count from a 16 bit field of
the register FIS, so an AHCI guest can ask for up to 65535 sectors per
track, and the CHS branch of ide_get_sector() then overflows the int it
multiplies cylinder, heads and sectors in.
ATA-5 6.2 numbers CHS sectors from one and ATA-2 D.2.8 limits IDENTIFY
DEVICE word 56 to 1 through 255, so neither end is a translation a device
may accept. ATA-5 8.16.6 requires an unsupported one to be reported as an
aborted command: do that, leave the translation in effect alone, and
refuse the value rather than checking it at every use.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Cc: qemu-stable@nongnu.org
Fixes: 176e4961bb33 ("hw/ide/core.c: Implement ATA INITIALIZE_DEVICE_PARAMETERS command")
Reported-by: Zheyu Ma <zheyuma97@gmail.com>
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/2399
Signed-off-by: Denis V. Lunev <den@openvz.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
---
hw/ide/core.c | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index fb9bf11b45..747fa71677 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -1655,14 +1655,21 @@ static bool cmd_check_power_mode(IDEState *s, uint8_t cmd)
/* INITIALIZE DEVICE PARAMETERS */
static bool cmd_specify(IDEState *s, uint8_t cmd)
{
- if (s->blk && s->drive_kind != IDE_CD) {
- s->heads = (s->select & (ATA_DEV_HS)) + 1;
- s->sectors = s->nsector;
- ide_bus_set_irq(s->bus);
- } else {
+ if (!s->blk || s->drive_kind == IDE_CD) {
+ ide_abort_command(s);
+ return true;
+ }
+
+ /* ATA-2 D.2.8 limits IDENTIFY DEVICE word 56, and the count, to 1..255 */
+ if (s->nsector == 0 || s->nsector > 255) {
ide_abort_command(s);
+ return true;
}
+ s->heads = (s->select & (ATA_DEV_HS)) + 1;
+ s->sectors = s->nsector;
+ ide_bus_set_irq(s->bus);
+
return true;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 02/17] tests/qtest/ide-test: cover a CHS translation with zero sectors
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 01/17] hw/ide: reject an unsupported CHS translation Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 03/17] tests/qtest/libqos/ahci: allow a count and an expected error Denis V. Lunev
` (15 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
Ask for zero sectors per logical track via INITIALIZE DEVICE PARAMETERS
and check that the command is aborted, that IDENTIFY DEVICE still reports
the translation that was in effect before, and that a CHS read then
completes normally rather than killing QEMU with SIGFPE.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qtest/ide-test.c | 62 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 62 insertions(+)
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
index b36b0f8875..99aa5e039c 100644
--- a/tests/qtest/ide-test.c
+++ b/tests/qtest/ide-test.c
@@ -95,6 +95,7 @@ enum {
enum {
CMD_DSM = 0x06,
+ CMD_READ = 0x20, /* READ SECTOR(S) */
CMD_DIAGNOSE = 0x90,
CMD_INIT_DP = 0x91, /* INITIALIZE DEVICE PARAMETERS */
CMD_READ_DMA = 0xc8,
@@ -1194,6 +1195,66 @@ static void cdrom_read_impl(int nblocks, unsigned flags)
free_pci_device(dev);
}
+/* Zero sectors per track has to abort (ATA-5 8.16.6), not divide by zero */
+static void test_specify_zero_sectors(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint16_t buf[256];
+ uint8_t data;
+ int i;
+
+ qts = ide_test_start(
+ "-blockdev driver=file,node-name=hda,filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 ",
+ tmp_path[0]);
+
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ qpci_io_writeb(dev, ide_bar, reg_nsectors, 0);
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_INIT_DP);
+
+ assert_bit_set(qpci_io_readb(dev, ide_bar, reg_status), ERR);
+ assert_bit_set(qpci_io_readb(dev, ide_bar, reg_error), ABRT);
+
+ /* The refused request has to leave the default translation in effect */
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_IDENTIFY);
+ for (i = 0; i < 256; i++) {
+ buf[i] = qpci_io_readw(dev, ide_bar, reg_data);
+ }
+ g_assert_cmpint(buf[55], ==, 16);
+ g_assert_cmpint(buf[56], ==, 63);
+
+ /* READ SECTOR(S) of CHS 0/0/1, which used to crash QEMU */
+ qpci_io_writeb(dev, ide_bar, reg_nsectors, 1);
+ qpci_io_writeb(dev, ide_bar, reg_lba_low, 1);
+ qpci_io_writeb(dev, ide_bar, reg_lba_middle, 0);
+ qpci_io_writeb(dev, ide_bar, reg_lba_high, 0);
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_READ);
+
+ data = ide_wait_clear(qts, BSY);
+ assert_bit_set(data, DRQ);
+ assert_bit_clear(data, ERR | DF);
+ for (i = 0; i < 256; i++) {
+ buf[i] = qpci_io_readw(dev, ide_bar, reg_data);
+ }
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), ERR | DF | DRQ);
+
+ /* A supported translation is still accepted */
+ qpci_io_writeb(dev, ide_bar, reg_nsectors, 32);
+ qpci_io_writeb(dev, ide_bar, reg_device, 7);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_INIT_DP);
+
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), ERR);
+
+ ide_test_quit(qts);
+ free_pci_device(dev);
+}
+
static void test_cdrom_pio(void)
{
cdrom_read_impl(1, CDROM_PIO);
@@ -1265,6 +1326,7 @@ int main(int argc, char **argv)
g_test_init(&argc, &argv, NULL);
qtest_add_func("/ide/read_native", test_specify);
+ qtest_add_func("/ide/specify/zero_sectors", test_specify_zero_sectors);
qtest_add_func("/ide/identify", test_identify);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 03/17] tests/qtest/libqos/ahci: allow a count and an expected error
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 01/17] hw/ide: reject an unsupported CHS translation Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 02/17] tests/qtest/ide-test: cover a CHS translation with zero sectors Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 04/17] tests/qtest/ahci: cover the sector count of INITIALIZE DEVICE PARAMETERS Denis V. Lunev
` (14 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
A command that transfers no data can still take an argument in the count
register of the register FIS, and a test may well expect such a command
to be aborted. AHCICommand is private to the library, so add two
setters: ahci_command_set_count() writes the count of a non-data
command, and ahci_command_expect_error() records the error register bits
the command is expected to complete with, which is what
ahci_atapi_test_ready() does inline for a sense key today.
INITIALIZE DEVICE PARAMETERS is the first user of both, so describe it
in the command properties table as well.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qtest/libqos/ahci.c | 14 ++++++++++++++
tests/qtest/libqos/ahci.h | 6 ++++++
2 files changed, 20 insertions(+)
diff --git a/tests/qtest/libqos/ahci.c b/tests/qtest/libqos/ahci.c
index e3019a1eca..50b63239c2 100644
--- a/tests/qtest/libqos/ahci.c
+++ b/tests/qtest/libqos/ahci.c
@@ -74,6 +74,7 @@ AHCICommandProp ahci_command_properties[] = {
{ .cmd = CMD_READ_MAX, .lba28 = true },
{ .cmd = CMD_READ_MAX_EXT, .lba48 = true },
{ .cmd = CMD_FLUSH_CACHE, .data = false },
+ { .cmd = CMD_INIT_DP, .data = false },
{ .cmd = CMD_PACKET, .data = true, .size = 16,
.atapi = true, .pio = true },
{ .cmd = CMD_PACKET_ID, .data = true, .pio = true,
@@ -1180,6 +1181,19 @@ void ahci_command_set_prd_size(AHCICommand *cmd, unsigned prd_size)
ahci_command_set_sizes(cmd, cmd->xbytes, prd_size);
}
+/* For a no-data command, whose count carries an argument of its own */
+void ahci_command_set_count(AHCICommand *cmd, uint16_t count)
+{
+ g_assert(!cmd->props->data);
+ cmd->fis.count = count;
+}
+
+void ahci_command_expect_error(AHCICommand *cmd, uint8_t err)
+{
+ cmd->interrupts |= AHCI_PX_IS_TFES;
+ cmd->errors |= err;
+}
+
void ahci_command_adjust(AHCICommand *cmd, uint64_t offset, uint64_t buffer,
uint64_t xbytes, unsigned prd_size)
{
diff --git a/tests/qtest/libqos/ahci.h b/tests/qtest/libqos/ahci.h
index 2a48a7523d..6d861c79ee 100644
--- a/tests/qtest/libqos/ahci.h
+++ b/tests/qtest/libqos/ahci.h
@@ -278,6 +278,7 @@ enum {
CMD_READ_MAX = 0xF8,
CMD_READ_MAX_EXT = 0x27,
CMD_FLUSH_CACHE = 0xE7,
+ CMD_INIT_DP = 0x91, /* INITIALIZE DEVICE PARAMETERS */
CMD_IDENTIFY = 0xEC,
CMD_PACKET = 0xA0,
CMD_PACKET_ID = 0xA1,
@@ -324,6 +325,9 @@ enum {
#define ATA_DEVICE_DRIVE 0x10
#define ATA_DEVICE_HEAD 0x0F
+/* ATA error register bits */
+#define ATA_ERR_ABRT 0x04
+
/*** Structures ***/
typedef struct AHCIPortQState {
@@ -638,6 +642,8 @@ void ahci_command_set_size(AHCICommand *cmd, uint64_t xbytes);
void ahci_command_set_prd_size(AHCICommand *cmd, unsigned prd_size);
void ahci_command_set_sizes(AHCICommand *cmd, uint64_t xbytes,
unsigned prd_size);
+void ahci_command_set_count(AHCICommand *cmd, uint16_t count);
+void ahci_command_expect_error(AHCICommand *cmd, uint8_t err);
void ahci_command_set_acmd(AHCICommand *cmd, void *acmd);
void ahci_command_enable_atapi_dma(AHCICommand *cmd);
void ahci_command_adjust(AHCICommand *cmd, uint64_t lba_sect, uint64_t gbuffer,
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 04/17] tests/qtest/ahci: cover the sector count of INITIALIZE DEVICE PARAMETERS
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (2 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 03/17] tests/qtest/libqos/ahci: allow a count and an expected error Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 05/17] hw/ide: report the default CHS translation in IDENTIFY DEVICE Denis V. Lunev
` (13 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
The sector count register of a legacy port is eight bits wide, so
ide-test can only reach the lower end of the range the command has to
refuse. A register FIS carries a 16 bit count, which leaves AHCI as the
only way to ask for a translation of 256 sectors per logical track or
more.
Ask for 0, 256 and 65535 sectors and expect each to be aborted, then ask
for 32 and expect it to be accepted, so that the check cannot pass by
refusing everything.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qtest/ahci-test.c | 40 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 40 insertions(+)
diff --git a/tests/qtest/ahci-test.c b/tests/qtest/ahci-test.c
index 71d23fe56a..30d7005626 100644
--- a/tests/qtest/ahci-test.c
+++ b/tests/qtest/ahci-test.c
@@ -905,6 +905,30 @@ static void ahci_test_flush(AHCIQState *ahci)
ahci_test_nondata(ahci, CMD_FLUSH_CACHE);
}
+static void ahci_test_specify(AHCIQState *ahci, uint16_t sectors,
+ bool supported)
+{
+ AHCICommand *cmd;
+ uint8_t port;
+
+ port = ahci_port_select(ahci);
+ ahci_port_clear(ahci, port);
+
+ cmd = ahci_command_create(CMD_INIT_DP);
+ ahci_command_set_count(cmd, sectors);
+ if (!supported) {
+ ahci_command_expect_error(cmd, ATA_ERR_ABRT);
+ }
+ ahci_command_commit(ahci, cmd, port);
+ ahci_command_issue(ahci, cmd);
+ if (!supported) {
+ ASSERT_BIT_SET(ahci_px_rreg(ahci, port, AHCI_PX_TFD),
+ AHCI_PX_TFD_STS_ERR);
+ }
+ ahci_command_verify(ahci, cmd);
+ ahci_command_free(cmd);
+}
+
static void ahci_test_max(AHCIQState *ahci)
{
RegD2HFIS *d2h = g_malloc0(0x20);
@@ -1012,6 +1036,21 @@ static void test_identify(void)
ahci_shutdown(ahci);
}
+static void test_specify(void)
+{
+ AHCIQState *ahci;
+
+ ahci = ahci_boot_and_enable(NULL);
+
+ /* A register FIS carries 16 bits of count, the legacy ports only eight */
+ ahci_test_specify(ahci, 0, false);
+ ahci_test_specify(ahci, 256, false);
+ ahci_test_specify(ahci, 0xffff, false);
+ ahci_test_specify(ahci, 32, true);
+
+ ahci_shutdown(ahci);
+}
+
/**
* Fragmented DMA test: Perform a standard 4K DMA read/write
* test, but make sure the physical regions are fragmented to
@@ -2220,6 +2259,7 @@ int main(int argc, char **argv)
qtest_add_func("/ahci/migrate/dma/halted", test_migrate_halted_dma);
qtest_add_func("/ahci/max", test_max);
+ qtest_add_func("/ahci/specify", test_specify);
qtest_add_func("/ahci/reset/simple", test_reset);
qtest_add_func("/ahci/reset/pending_callback", test_reset_pending_callback);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 05/17] hw/ide: report the default CHS translation in IDENTIFY DEVICE
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (3 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 04/17] tests/qtest/ahci: cover the sector count of INITIALIZE DEVICE PARAMETERS Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 06/17] hw/ide: name the retired IDENTIFY DEVICE words the device fills in Denis V. Lunev
` (12 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé, qemu-stable
From: Denis V. Lunev <den@openvz.org>
IDENTIFY DEVICE words 1, 3 and 6 describe the default CHS translation,
and ATA-5 8.16.8 requires INITIALIZE DEVICE PARAMETERS to leave them
alone; the translation in effect is described by words 54 to 56 instead.
Words 3 and 6 were filled from s->heads and s->sectors, which the command
replaces, so a guest that selected a translation of its own was told that
its choice was what the drive came with, and could no longer find out the
default. Word 1 is already right, as no command changes s->cylinders.
Report s->drive_heads and s->drive_sectors, which ide_init_drive() keeps
for exactly this, along with the retired word 4 derived from them. The
CompactFlash data labels those words as the default geometry too, and
INITIALIZE DEVICE PARAMETERS is accepted for CFA drives, so fix both.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Cc: qemu-stable@nongnu.org
Fixes: 176e4961bb33 ("hw/ide/core.c: Implement ATA INITIALIZE_DEVICE_PARAMETERS command")
Signed-off-by: Denis V. Lunev <den@openvz.org>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
---
hw/ide/core.c | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 747fa71677..befbab9486 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -138,11 +138,12 @@ static void ide_identify(IDEState *s)
memset(p, 0, sizeof(s->identify_data));
put_le16(p + 0, 0x0040);
+ /* Words 1, 3 and 6 describe the default translation (ATA-5 8.16.8) */
put_le16(p + 1, s->cylinders);
- put_le16(p + 3, s->heads);
- put_le16(p + 4, 512 * s->sectors); /* XXX: retired, remove ? */
+ put_le16(p + 3, s->drive_heads);
+ put_le16(p + 4, 512 * s->drive_sectors); /* XXX: retired, remove ? */
put_le16(p + 5, 512); /* XXX: retired, remove ? */
- put_le16(p + 6, s->sectors);
+ put_le16(p + 6, s->drive_sectors);
padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
put_le16(p + 20, 3); /* XXX: retired, remove ? */
put_le16(p + 21, 512); /* cache size in sectors */
@@ -332,8 +333,8 @@ static void ide_cfata_identify(IDEState *s)
put_le16(p + 0, 0x848a); /* CF Storage Card signature */
put_le16(p + 1, s->cylinders); /* Default cylinders */
- put_le16(p + 3, s->heads); /* Default heads */
- put_le16(p + 6, s->sectors); /* Default sectors per track */
+ put_le16(p + 3, s->drive_heads); /* Default heads */
+ put_le16(p + 6, s->drive_sectors); /* Default sectors per track */
/* *(p + 7) := nb_sectors >> 16 -- see ide_cfata_identify_size */
/* *(p + 8) := nb_sectors -- see ide_cfata_identify_size */
padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 06/17] hw/ide: name the retired IDENTIFY DEVICE words the device fills in
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (4 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 05/17] hw/ide: report the default CHS translation in IDENTIFY DEVICE Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 07/17] hw/ide: factor out the IDENTIFY DEVICE current geometry words Denis V. Lunev
` (11 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
Words 4, 5 and 20 have carried an "XXX: retired, remove ?" since the
device was written, and word 21 is labelled a cache size when it is a
buffer size. ATA-4 8.12.13 retired words 4 and 5, 8.12.17 retired words
20 and 21, and ATA-5 keeps all four that way.
Retired is not a reason to drop them. ATA-5 3.2.3.6 says a retired word
that is still used shall have "the meaning or functionality as described
in previous standards", and that is what these carry: ATA-1 9.9.3, 9.9.4
and 9.9.7 define the unformatted bytes per track and per sector and the
buffer type, and the ATA-1 IDENTIFY table gives word 21 as the buffer
size in 512 byte increments. Software old enough to read them gets what
it expects, so answer the question rather than leave it open.
Word 22 is obsolete rather than retired (ATA-4 8.12.18) and already
carries its ATA-1 9.9.8 name, so leave it alone.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
hw/ide/core.c | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index befbab9486..292908fb1c 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -141,12 +141,12 @@ static void ide_identify(IDEState *s)
/* Words 1, 3 and 6 describe the default translation (ATA-5 8.16.8) */
put_le16(p + 1, s->cylinders);
put_le16(p + 3, s->drive_heads);
- put_le16(p + 4, 512 * s->drive_sectors); /* XXX: retired, remove ? */
- put_le16(p + 5, 512); /* XXX: retired, remove ? */
+ put_le16(p + 4, 512 * s->drive_sectors); /* ATA-1 unformatted bytes/trk */
+ put_le16(p + 5, 512); /* ATA-1 unformatted bytes per sector */
put_le16(p + 6, s->drive_sectors);
padstr((char *)(p + 10), s->drive_serial_str, 20); /* serial number */
- put_le16(p + 20, 3); /* XXX: retired, remove ? */
- put_le16(p + 21, 512); /* cache size in sectors */
+ put_le16(p + 20, 3); /* ATA-1 buffer type: dual ported, read caching */
+ put_le16(p + 21, 512); /* ATA-1 buffer size in 512 byte increments */
put_le16(p + 22, 4); /* ecc bytes */
padstr((char *)(p + 23), s->version, 8); /* firmware version */
padstr((char *)(p + 27), s->drive_model_str, 40); /* model */
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 07/17] hw/ide: factor out the IDENTIFY DEVICE current geometry words
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (5 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 06/17] hw/ide: name the retired IDENTIFY DEVICE words the device fills in Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 11:07 ` Philippe Mathieu-Daudé
2026-08-20 10:08 ` [PATCH v2 08/17] hw/ide: keep the IDENTIFY DEVICE current geometry in sync Denis V. Lunev
` (10 subsequent siblings)
17 siblings, 1 reply; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
Words 54 to 58 of IDENTIFY DEVICE describe the CHS translation in effect
and the capacity it addresses. Both ide_identify() and
ide_cfata_identify() fill them the same way while building their cached
data.
Move them into ide_identify_chs(), so that the next change can refresh
them in place once the translation changes, the way ide_identify_size()
does for the capacity words.
No functional change.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
hw/ide/core.c | 29 ++++++++++++++---------------
1 file changed, 14 insertions(+), 15 deletions(-)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 292908fb1c..e5fd570575 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -110,6 +110,18 @@ static void put_le16(uint16_t *p, unsigned int v)
*p = cpu_to_le16(v);
}
+static void ide_identify_chs(IDEState *s)
+{
+ uint16_t *p = (uint16_t *)s->identify_data;
+ unsigned int cur_sec = s->cylinders * s->heads * s->sectors;
+
+ put_le16(p + 54, s->cylinders);
+ put_le16(p + 55, s->heads);
+ put_le16(p + 56, s->sectors);
+ put_le16(p + 57, cur_sec);
+ put_le16(p + 58, cur_sec >> 16);
+}
+
static void ide_identify_size(IDEState *s)
{
uint16_t *p = (uint16_t *)s->identify_data;
@@ -128,7 +140,6 @@ static void ide_identify_size(IDEState *s)
static void ide_identify(IDEState *s)
{
uint16_t *p;
- unsigned int oldsize;
IDEDevice *dev = s->unit ? s->bus->slave : s->bus->master;
p = (uint16_t *)s->identify_data;
@@ -158,12 +169,7 @@ static void ide_identify(IDEState *s)
put_le16(p + 51, 0x200); /* PIO transfer cycle */
put_le16(p + 52, 0x200); /* DMA transfer cycle */
put_le16(p + 53, 1 | (1 << 1) | (1 << 2)); /* words 54-58,64-70,88 are valid */
- put_le16(p + 54, s->cylinders);
- put_le16(p + 55, s->heads);
- put_le16(p + 56, s->sectors);
- oldsize = s->cylinders * s->heads * s->sectors;
- put_le16(p + 57, oldsize);
- put_le16(p + 58, oldsize >> 16);
+ ide_identify_chs(s);
if (s->mult_sectors)
put_le16(p + 59, 0x100 | s->mult_sectors);
/* *(p + 60) := nb_sectors -- see ide_identify_size */
@@ -321,7 +327,6 @@ static void ide_cfata_identify_size(IDEState *s)
static void ide_cfata_identify(IDEState *s)
{
uint16_t *p;
- uint32_t cur_sec;
p = (uint16_t *)s->identify_data;
if (s->identify_set) {
@@ -329,8 +334,6 @@ static void ide_cfata_identify(IDEState *s)
}
memset(p, 0, sizeof(s->identify_data));
- cur_sec = s->cylinders * s->heads * s->sectors;
-
put_le16(p + 0, 0x848a); /* CF Storage Card signature */
put_le16(p + 1, s->cylinders); /* Default cylinders */
put_le16(p + 3, s->drive_heads); /* Default heads */
@@ -350,11 +353,7 @@ static void ide_cfata_identify(IDEState *s)
put_le16(p + 51, 0x0002); /* PIO cycle timing mode */
put_le16(p + 52, 0x0001); /* DMA cycle timing mode */
put_le16(p + 53, 0x0003); /* Translation params valid */
- put_le16(p + 54, s->cylinders); /* Current cylinders */
- put_le16(p + 55, s->heads); /* Current heads */
- put_le16(p + 56, s->sectors); /* Current sectors */
- put_le16(p + 57, cur_sec); /* Current capacity */
- put_le16(p + 58, cur_sec >> 16); /* Current capacity */
+ ide_identify_chs(s); /* Current C/H/S and capacity */
if (s->mult_sectors) /* Multiple sector setting */
put_le16(p + 59, 0x100 | s->mult_sectors);
/* *(p + 60) := nb_sectors -- see ide_cfata_identify_size */
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 08/17] hw/ide: keep the IDENTIFY DEVICE current geometry in sync
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (6 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 07/17] hw/ide: factor out the IDENTIFY DEVICE current geometry words Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 09/17] hw/ide: restore the power-on device state before loading Denis V. Lunev
` (9 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
Bit 0 of IDENTIFY DEVICE word 53 says that words 54 to 58 describe the CHS
translation in effect, and ATA-5 8.16.8 has INITIALIZE DEVICE PARAMETERS
set words 55 and 56 to the heads and sectors per track it was given. The
data is built once and then cached, so those words kept describing
whatever was in effect when a guest first asked for IDENTIFY DEVICE: the
device reported one geometry while addressing the medium with another, and
nothing reported an error. The revert SET FEATURES 0xCC asks for on the
next reset left the same disagreement.
Do not drop the cached data on a change, as parts of it are guest state
rather than a description of the drive: SET FEATURES records the write
cache setting in word 85, which ide_drive_post_load() reads back after
migration. Refresh the affected words in place instead, the way
ide_identify_size() does for the capacity words.
An ATAPI device has no translation but does take SET FEATURES 0xCC, so
leave its IDENTIFY PACKET DEVICE data alone, where those words differ.
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>
---
hw/ide/core.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index e5fd570575..048655b2d0 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -1360,6 +1360,10 @@ static void ide_reset(IDEState *s)
s->reset_reverts = false;
s->heads = s->drive_heads;
s->sectors = s->drive_sectors;
+ /* An ATAPI device takes SET FEATURES 0xCC but has no translation */
+ if (s->identify_set && s->drive_kind != IDE_CD) {
+ ide_identify_chs(s);
+ }
}
if (s->drive_kind == IDE_CFATA)
s->mult_sectors = 0;
@@ -1668,6 +1672,9 @@ static bool cmd_specify(IDEState *s, uint8_t cmd)
s->heads = (s->select & (ATA_DEV_HS)) + 1;
s->sectors = s->nsector;
+ if (s->identify_set) {
+ ide_identify_chs(s);
+ }
ide_bus_set_irq(s->bus);
return true;
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 09/17] hw/ide: restore the power-on device state before loading
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (7 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 08/17] hw/ide: keep the IDENTIFY DEVICE current geometry in sync Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 10/17] hw/ide: migrate the logical CHS translation Denis V. Lunev
` (8 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
Loading a snapshot reuses the IDEState of the machine it is loaded into:
load_snapshot() resets the machine and then feeds the stream into the
existing devices. The reset does not help, as ide_reset() restores the
logical CHS translation only when the guest asked for power-on defaults to
be reverted with SET FEATURES 0xCC.
A guest that replaced the translation with INITIALIZE DEVICE PARAMETERS
therefore keeps it across the load of a snapshot taken before it did,
while the restored guest expects the geometry of that moment. Every CHS
access then lands on a sector other than the one asked for, with no error
reported. s->reset_reverts survives a load the same way.
Add a pre_load restoring the defaults, which
docs/devel/migration/main.rst recommends for state a stream need not
carry, and which the following subsections rely on. The
RESET_TYPE_SNAPSHOT_LOAD marking that reset would be another way to
recognise the case, but no IDE controller can see it while they all use
device_class_set_legacy_reset().
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>
---
hw/ide/core.c | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 048655b2d0..28219f5ef8 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -2863,6 +2863,18 @@ static int transfer_end_table_idx(EndTransferFunc *fn)
return -1;
}
+static int ide_drive_pre_load(void *opaque)
+{
+ IDEState *s = opaque;
+
+ /* The subsections below are sent only where the guest replaced these */
+ s->heads = s->drive_heads;
+ s->sectors = s->drive_sectors;
+ s->reset_reverts = false;
+
+ return 0;
+}
+
static int ide_drive_post_load(void *opaque, int version_id)
{
IDEState *s = opaque;
@@ -2986,6 +2998,7 @@ const VMStateDescription vmstate_ide_drive = {
.name = "ide_drive",
.version_id = 3,
.minimum_version_id = 0,
+ .pre_load = ide_drive_pre_load,
.post_load = ide_drive_post_load,
.fields = (const VMStateField[]) {
VMSTATE_INT32(mult_sectors, IDEState),
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 10/17] hw/ide: migrate the logical CHS translation
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (8 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 09/17] hw/ide: restore the power-on device state before loading Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 11/17] hw/ide: migrate the power-on defaults revert flag Denis V. Lunev
` (7 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
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>
---
hw/ide/core.c | 27 +++++++++++++++++++++++++++
1 file changed, 27 insertions(+)
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
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 11/17] hw/ide: migrate the power-on defaults revert flag
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (9 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 10/17] hw/ide: migrate the logical CHS translation Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 12/17] tests/qtest/ide-test: cover the CHS translation across migration Denis V. Lunev
` (6 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
SET FEATURES 0xCC asks for the next reset to revert to the power-on
defaults, and 0x66 cancels that; ide_reset() restores the default CHS
translation only when the flag is set. It was in no VMStateDescription, so
it always arrived cleared.
That was invisible while the destination had the default translation
anyway. Now that the translation is migrated, the flag decides how long it
stays in effect: without it, a reset after the migration reverts the
geometry on the source and keeps it on the destination.
Send it only alongside a translation the guest replaced. On the default
geometry it reverts to what is already in effect, so such a guest need not
lose its migration to an older QEMU over a subsection that changes
nothing.
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>
---
hw/ide/core.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index b074325afa..5a5f072630 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -2988,6 +2988,25 @@ static const VMStateDescription vmstate_ide_drive_chs_translation = {
}
};
+static bool ide_reset_reverts_needed(void *opaque)
+{
+ IDEState *s = opaque;
+
+ return s->reset_reverts && ide_chs_translation_needed(opaque);
+}
+
+/* The flag decides nothing on the default geometry, so it travels with one */
+static const VMStateDescription vmstate_ide_drive_reset_reverts = {
+ .name = "ide_drive/reset_reverts",
+ .version_id = 1,
+ .minimum_version_id = 1,
+ .needed = ide_reset_reverts_needed,
+ .fields = (const VMStateField[]) {
+ VMSTATE_BOOL(reset_reverts, IDEState),
+ VMSTATE_END_OF_LIST()
+ }
+};
+
static const VMStateDescription vmstate_ide_tray_state = {
.name = "ide_drive/tray_state",
.version_id = 1,
@@ -3052,6 +3071,7 @@ const VMStateDescription vmstate_ide_drive = {
.subsections = (const VMStateDescription * const []) {
&vmstate_ide_drive_pio_state,
&vmstate_ide_drive_chs_translation,
+ &vmstate_ide_drive_reset_reverts,
&vmstate_ide_tray_state,
&vmstate_ide_atapi_gesn_state,
NULL
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 12/17] tests/qtest/ide-test: cover the CHS translation across migration
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (10 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 11/17] hw/ide: migrate the power-on defaults revert flag Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 13/17] tests/qtest/ide-test: cover a rejected CHS translation in the stream Denis V. Lunev
` (5 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
Both defects here are silent: the guest addresses the disk in the
translation it selected while the device translates with another, so reads
and writes land on a sector nobody asked for. Put a marker in each of the
two candidate sectors and name the one the translation picked. CHS 0/1/1
is LBA 32 under 8 heads and 32 sectors per track and LBA 63 under the
16/63 the test drive is configured with; both markers are written by LBA,
which no translation can influence.
A translation the guest selected has to survive migration, and one it
selected after a snapshot was taken must not outlive loading that snapshot
back. The second needs a qcow2 image, so it is skipped without qemu-img.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qtest/ide-test.c | 231 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 231 insertions(+)
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
index 99aa5e039c..19e9734d3c 100644
--- a/tests/qtest/ide-test.c
+++ b/tests/qtest/ide-test.c
@@ -96,6 +96,7 @@ enum {
enum {
CMD_DSM = 0x06,
CMD_READ = 0x20, /* READ SECTOR(S) */
+ CMD_WRITE = 0x30, /* WRITE SECTOR(S) */
CMD_DIAGNOSE = 0x90,
CMD_INIT_DP = 0x91, /* INITIALIZE DEVICE PARAMETERS */
CMD_READ_DMA = 0xc8,
@@ -1255,6 +1256,233 @@ static void test_specify_zero_sectors(void)
free_pci_device(dev);
}
+/* Addressed by LBA, so no translation can influence where it lands */
+static void ide_write_marker(QTestState *qts, QPCIDevice *dev, QPCIBar ide_bar,
+ uint32_t lba, const char *marker)
+{
+ uint16_t buf[256];
+ uint8_t data;
+ int i;
+
+ memset(buf, 0, sizeof(buf));
+ memcpy(buf, marker, strlen(marker));
+
+ qpci_io_writeb(dev, ide_bar, reg_nsectors, 1);
+ qpci_io_writeb(dev, ide_bar, reg_lba_low, lba & 0xff);
+ qpci_io_writeb(dev, ide_bar, reg_lba_middle, (lba >> 8) & 0xff);
+ qpci_io_writeb(dev, ide_bar, reg_lba_high, (lba >> 16) & 0xff);
+ qpci_io_writeb(dev, ide_bar, reg_device, LBA | ((lba >> 24) & 0xf));
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_WRITE);
+
+ data = ide_wait_clear(qts, BSY);
+ assert_bit_set(data, DRQ);
+ for (i = 0; i < 256; i++) {
+ qpci_io_writew(dev, ide_bar, reg_data, buf[i]);
+ }
+ data = ide_wait_clear(qts, BSY);
+ assert_bit_clear(data, ERR | DF | DRQ);
+
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_FLUSH_CACHE);
+ data = ide_wait_clear(qts, BSY);
+ assert_bit_clear(data, ERR | DF);
+}
+
+/* The marker read back names the sector the translation selected */
+static void ide_read_chs_marker(QTestState *qts, QPCIDevice *dev,
+ QPCIBar ide_bar, uint8_t cyl_lo, uint8_t head,
+ uint8_t sector, char out[9])
+{
+ uint16_t buf[256];
+ uint8_t data;
+ int i;
+
+ qpci_io_writeb(dev, ide_bar, reg_nsectors, 1);
+ qpci_io_writeb(dev, ide_bar, reg_lba_low, sector);
+ qpci_io_writeb(dev, ide_bar, reg_lba_middle, cyl_lo);
+ qpci_io_writeb(dev, ide_bar, reg_lba_high, 0);
+ qpci_io_writeb(dev, ide_bar, reg_device, head & 0xf);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_READ);
+
+ data = ide_wait_clear(qts, BSY);
+ assert_bit_set(data, DRQ);
+ assert_bit_clear(data, ERR | DF);
+ for (i = 0; i < 256; i++) {
+ buf[i] = qpci_io_readw(dev, ide_bar, reg_data);
+ }
+ data = ide_wait_clear(qts, BSY);
+ assert_bit_clear(data, ERR | DF | DRQ);
+
+ memcpy(out, buf, 8);
+ out[8] = '\0';
+}
+
+static void ide_set_translation(QPCIDevice *dev, QPCIBar ide_bar,
+ uint8_t heads, uint8_t sectors)
+{
+ qpci_io_writeb(dev, ide_bar, reg_nsectors, sectors);
+ qpci_io_writeb(dev, ide_bar, reg_device, heads - 1);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_INIT_DP);
+ assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), ERR);
+}
+
+/* CHS 0/1/1 is LBA 32 under 8/32, and LBA 63 under the drive's own 16/63 */
+#define CHS_MARKER_CUSTOM "CUSTOM__"
+#define CHS_MARKER_DEFAULT "DEFAULT_"
+
+static void ide_prepare_markers(QTestState *qts, QPCIDevice *dev,
+ QPCIBar ide_bar)
+{
+ ide_write_marker(qts, dev, ide_bar, 32, CHS_MARKER_CUSTOM);
+ ide_write_marker(qts, dev, ide_bar, 63, CHS_MARKER_DEFAULT);
+}
+
+static void ide_hmp_quiet(QTestState *qts, const char *command)
+{
+ g_autofree char *out = qtest_hmp(qts, "%s", command);
+
+ g_assert_cmpstr(out, ==, "");
+}
+
+static char *ide_migration_status(QTestState *qts)
+{
+ QDict *ret;
+ char *status;
+
+ ret = qtest_qmp_assert_success_ref(qts, "{ 'execute': 'query-migrate' }");
+ g_assert(qdict_haskey(ret, "status"));
+ status = g_strdup(qdict_get_str(ret, "status"));
+ qobject_unref(ret);
+
+ return status;
+}
+
+/* Waiting for the other side's event would hang if it refuses the stream */
+static void ide_migration_wait(QTestState *qts, const char *expected)
+{
+ while (true) {
+ g_autofree char *status = ide_migration_status(qts);
+
+ if (g_str_equal(status, expected)) {
+ return;
+ }
+ if (!g_str_equal(status, "setup") && !g_str_equal(status, "active") &&
+ !g_str_equal(status, "device")) {
+ fprintf(stderr, "Migration status is %s, expected %s\n",
+ status, expected);
+ g_assert_not_reached();
+ }
+ g_usleep(5000);
+ }
+}
+
+static void ide_migrate(QTestState *src, QTestState *dst, const char *uri)
+{
+ qtest_qmp_assert_success(src, "{ 'execute': 'migrate',"
+ " 'arguments': { 'uri': %s } }", uri);
+ qtest_qmp_eventwait(src, "STOP");
+ ide_migration_wait(src, "completed");
+ qtest_qmp_eventwait(dst, "RESUME");
+}
+
+/* A translation the guest selected has to survive migration */
+static void test_migrate_chs_translation(void)
+{
+ QTestState *src, *dst;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ g_autofree char *mig_path = NULL;
+ g_autofree char *uri = NULL;
+ g_autofree char *dst_args = NULL;
+ char marker[9];
+ int fd;
+
+ fd = g_file_open_tmp("qtest-ide-migration.XXXXXX", &mig_path, NULL);
+ g_assert(fd >= 0);
+ close(fd);
+ uri = g_strdup_printf("unix:%s", mig_path);
+
+ src = ide_test_start(
+ "-blockdev driver=file,node-name=hda,filename=%s,locking=off "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 ",
+ tmp_path[0]);
+ dev = get_pci_device(src, &bmdma_bar, &ide_bar);
+
+ ide_prepare_markers(src, dev, ide_bar);
+ ide_set_translation(dev, ide_bar, 8, 32);
+ ide_read_chs_marker(src, dev, ide_bar, 0, 1, 1, marker);
+ g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM);
+
+ dst_args = g_strdup_printf(
+ "-machine pc "
+ "-blockdev driver=file,node-name=hda,filename=%s,locking=off "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 -incoming %s",
+ tmp_path[0], uri);
+ dst = qtest_init(dst_args);
+
+ ide_migrate(src, dst, uri);
+
+ /* Talk to the destination instead of the source */
+ qpci_free_pc(pcibus);
+ pcibus = NULL;
+ free_pci_device(dev);
+ dev = get_pci_device(dst, &bmdma_bar, &ide_bar);
+
+ ide_read_chs_marker(dst, dev, ide_bar, 0, 1, 1, marker);
+ g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM);
+
+ free_pci_device(dev);
+ qtest_quit(dst);
+ ide_test_quit(src);
+ unlink(mig_path);
+}
+
+/* A translation selected after the snapshot must not outlive loading it */
+static void test_migrate_chs_snapshot(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ g_autofree char *img = NULL;
+ char marker[9];
+ int fd;
+
+ if (!have_qemu_img()) {
+ g_test_skip("QTEST_QEMU_IMG not set, snapshots need a qcow2 image");
+ return;
+ }
+
+ fd = g_file_open_tmp("qtest-ide-snapshot.XXXXXX", &img, NULL);
+ g_assert(fd >= 0);
+ close(fd);
+ g_assert(mkimg(img, "qcow2", TEST_IMAGE_SIZE / (1024 * 1024)));
+
+ qts = ide_test_start(
+ "-blockdev driver=qcow2,node-name=hda,file.driver=file,"
+ "file.filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 ", img);
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ ide_prepare_markers(qts, dev, ide_bar);
+
+ /* Snapshot taken while the default translation is in effect */
+ ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
+ g_assert_cmpstr(marker, ==, CHS_MARKER_DEFAULT);
+ ide_hmp_quiet(qts, "savevm s0");
+
+ ide_set_translation(dev, ide_bar, 8, 32);
+ ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
+ g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM);
+
+ ide_hmp_quiet(qts, "loadvm s0");
+
+ ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
+ g_assert_cmpstr(marker, ==, CHS_MARKER_DEFAULT);
+
+ free_pci_device(dev);
+ ide_test_quit(qts);
+ unlink(img);
+}
+
static void test_cdrom_pio(void)
{
cdrom_read_impl(1, CDROM_PIO);
@@ -1327,6 +1555,9 @@ int main(int argc, char **argv)
qtest_add_func("/ide/read_native", test_specify);
qtest_add_func("/ide/specify/zero_sectors", test_specify_zero_sectors);
+ qtest_add_func("/ide/migration/chs_translation",
+ test_migrate_chs_translation);
+ qtest_add_func("/ide/migration/chs_snapshot", test_migrate_chs_snapshot);
qtest_add_func("/ide/identify", test_identify);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 13/17] tests/qtest/ide-test: cover a rejected CHS translation in the stream
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (11 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 12/17] tests/qtest/ide-test: cover the CHS translation across migration Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 14/17] tests/qtest/ide-test: cover the IDENTIFY DEVICE geometry words Denis V. Lunev
` (4 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
ide_drive_post_load() refuses a logical CHS translation that no command
could have selected, as the fields are a divisor in ide_set_sector() and a
factor in ide_get_sector(). Nothing exercised that, a fixed QEMU having no
way to produce such a stream.
Migrate a guest that selected a translation to a file, replace the number
of sectors per logical track in the subsection with a zero, and let a
destination read the result back. The load has to fail rather than take
the value, so the destination is asked not to exit on a failed incoming
migration and its migration status is what the test looks at.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qtest/ide-test.c | 81 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 81 insertions(+)
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
index 19e9734d3c..4fa58ac525 100644
--- a/tests/qtest/ide-test.c
+++ b/tests/qtest/ide-test.c
@@ -1483,6 +1483,86 @@ static void test_migrate_chs_snapshot(void)
unlink(img);
}
+/* A migration stream holds NUL bytes, so this cannot be a string search */
+static char *ide_stream_find(char *stream, gsize len, const char *name)
+{
+ gsize name_len = strlen(name);
+ gsize i;
+
+ if (len < name_len) {
+ return NULL;
+ }
+ for (i = 0; i <= len - name_len; i++) {
+ if (memcmp(stream + i, name, name_len) == 0) {
+ return stream + i;
+ }
+ }
+
+ return NULL;
+}
+
+/* A translation no command could have selected has to be refused on load */
+static void test_migrate_chs_rejected(void)
+{
+ const char *name = "ide_drive/chs_translation";
+ QTestState *src, *dst;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ g_autofree char *path = NULL;
+ g_autofree char *uri = NULL;
+ g_autofree char *dst_args = NULL;
+ g_autofree char *stream = NULL;
+ char *subsection;
+ gsize len;
+ int fd;
+
+ fd = g_file_open_tmp("qtest-ide-stream.XXXXXX", &path, NULL);
+ g_assert(fd >= 0);
+ close(fd);
+ uri = g_strdup_printf("file:%s", path);
+
+ src = ide_test_start(
+ "-blockdev driver=file,node-name=hda,filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 ",
+ tmp_path[0]);
+ dev = get_pci_device(src, &bmdma_bar, &ide_bar);
+
+ ide_set_translation(dev, ide_bar, 8, 32);
+ qtest_qmp_assert_success(src, "{ 'execute': 'migrate',"
+ " 'arguments': { 'uri': %s } }", uri);
+ qtest_qmp_eventwait(src, "STOP");
+ ide_migration_wait(src, "completed");
+ free_pci_device(dev);
+ ide_test_quit(src);
+
+ /*
+ * Behind the name come version, heads and sectors, each big endian 32 bit.
+ * The name recurs in the description at the end of the stream, so the
+ * first match is the one carrying data.
+ */
+ g_assert(g_file_get_contents(path, &stream, &len, NULL));
+ subsection = ide_stream_find(stream, len, name);
+ g_assert(subsection);
+ g_assert_cmpint(subsection - stream + strlen(name) + 12, <=, len);
+ memset(subsection + strlen(name) + 8, 0, 4);
+ g_assert(g_file_set_contents(path, stream, len, NULL));
+
+ dst_args = g_strdup_printf(
+ "-machine pc "
+ "-blockdev driver=file,node-name=hda,filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 -incoming defer",
+ tmp_path[0]);
+ dst = qtest_init(dst_args);
+
+ qtest_qmp_assert_success(dst, "{ 'execute': 'migrate-incoming',"
+ " 'arguments': { 'uri': %s,"
+ " 'exit-on-error': false } }", uri);
+ ide_migration_wait(dst, "failed");
+
+ qtest_quit(dst);
+ unlink(path);
+}
+
static void test_cdrom_pio(void)
{
cdrom_read_impl(1, CDROM_PIO);
@@ -1558,6 +1638,7 @@ int main(int argc, char **argv)
qtest_add_func("/ide/migration/chs_translation",
test_migrate_chs_translation);
qtest_add_func("/ide/migration/chs_snapshot", test_migrate_chs_snapshot);
+ qtest_add_func("/ide/migration/chs_rejected", test_migrate_chs_rejected);
qtest_add_func("/ide/identify", test_identify);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 14/17] tests/qtest/ide-test: cover the IDENTIFY DEVICE geometry words
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (12 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 13/17] tests/qtest/ide-test: cover a rejected CHS translation in the stream Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 15/17] hw/ide: revert the CHS translation on a hardware reset Denis V. Lunev
` (3 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
INITIALIZE DEVICE PARAMETERS has to leave the geometry the drive came with
in words 3 and 6 and describe the translation it selected in words 54 to
58, and the data is cached, so which of the two a guest is told depends on
when it first asked. Cover both orders, as each alone leaves half of it
untested: one test has the data built while the default is in effect and
then replaces the translation, which the cached copy has to follow, the
other replaces it before the first IDENTIFY DEVICE, where the words
describing the default have to keep doing so.
Factor the reading of the data out of test_specify_zero_sectors() for the
three of them to share.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qtest/ide-test.c | 89 +++++++++++++++++++++++++++++++++++++++---
1 file changed, 84 insertions(+), 5 deletions(-)
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
index 4fa58ac525..e7a56e0710 100644
--- a/tests/qtest/ide-test.c
+++ b/tests/qtest/ide-test.c
@@ -1196,6 +1196,18 @@ static void cdrom_read_impl(int nblocks, unsigned flags)
free_pci_device(dev);
}
+static void ide_identify_words(QPCIDevice *dev, QPCIBar ide_bar,
+ uint16_t buf[256])
+{
+ int i;
+
+ qpci_io_writeb(dev, ide_bar, reg_device, 0);
+ qpci_io_writeb(dev, ide_bar, reg_command, CMD_IDENTIFY);
+ for (i = 0; i < 256; i++) {
+ buf[i] = qpci_io_readw(dev, ide_bar, reg_data);
+ }
+}
+
/* Zero sectors per track has to abort (ATA-5 8.16.6), not divide by zero */
static void test_specify_zero_sectors(void)
{
@@ -1221,11 +1233,7 @@ static void test_specify_zero_sectors(void)
assert_bit_set(qpci_io_readb(dev, ide_bar, reg_error), ABRT);
/* The refused request has to leave the default translation in effect */
- qpci_io_writeb(dev, ide_bar, reg_device, 0);
- qpci_io_writeb(dev, ide_bar, reg_command, CMD_IDENTIFY);
- for (i = 0; i < 256; i++) {
- buf[i] = qpci_io_readw(dev, ide_bar, reg_data);
- }
+ ide_identify_words(dev, ide_bar, buf);
g_assert_cmpint(buf[55], ==, 16);
g_assert_cmpint(buf[56], ==, 63);
@@ -1563,6 +1571,74 @@ static void test_migrate_chs_rejected(void)
unlink(path);
}
+/* Words 54 to 58 follow the translation even when the data was cached first */
+static void test_specify_identify(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint16_t buf[256];
+ unsigned int cyls;
+
+ qts = ide_test_start(
+ "-blockdev driver=file,node-name=hda,filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 ",
+ tmp_path[0]);
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* Have the data built while the default translation is still in effect */
+ ide_identify_words(dev, ide_bar, buf);
+ cyls = buf[1];
+ g_assert_cmpint(buf[3], ==, 16);
+ g_assert_cmpint(buf[6], ==, 63);
+ g_assert_cmpint(buf[53] & 1, ==, 1);
+ g_assert_cmpint(buf[55], ==, 16);
+ g_assert_cmpint(buf[56], ==, 63);
+ g_assert_cmpint(buf[57] | (buf[58] << 16), ==, cyls * 16 * 63);
+
+ ide_set_translation(dev, ide_bar, 8, 32);
+
+ ide_identify_words(dev, ide_bar, buf);
+ g_assert_cmpint(buf[1], ==, cyls);
+ g_assert_cmpint(buf[3], ==, 16);
+ g_assert_cmpint(buf[4], ==, 512 * 63);
+ g_assert_cmpint(buf[6], ==, 63);
+ g_assert_cmpint(buf[55], ==, 8);
+ g_assert_cmpint(buf[56], ==, 32);
+ g_assert_cmpint(buf[57] | (buf[58] << 16), ==, cyls * 8 * 32);
+
+ free_pci_device(dev);
+ ide_test_quit(qts);
+}
+
+/* Words 3 and 6 keep the drive's own geometry even if built after a change */
+static void test_specify_identify_default(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar;
+ uint16_t buf[256];
+
+ qts = ide_test_start(
+ "-blockdev driver=file,node-name=hda,filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 ",
+ tmp_path[0]);
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+
+ /* No IDENTIFY DEVICE before this one, so nothing was cached yet */
+ ide_set_translation(dev, ide_bar, 8, 32);
+ ide_identify_words(dev, ide_bar, buf);
+ g_assert_cmpint(buf[3], ==, 16);
+ g_assert_cmpint(buf[4], ==, 512 * 63);
+ g_assert_cmpint(buf[6], ==, 63);
+ g_assert_cmpint(buf[55], ==, 8);
+ g_assert_cmpint(buf[56], ==, 32);
+ g_assert_cmpint(buf[57] | (buf[58] << 16), ==, buf[1] * 8 * 32);
+
+ free_pci_device(dev);
+ ide_test_quit(qts);
+}
+
static void test_cdrom_pio(void)
{
cdrom_read_impl(1, CDROM_PIO);
@@ -1635,6 +1711,9 @@ int main(int argc, char **argv)
qtest_add_func("/ide/read_native", test_specify);
qtest_add_func("/ide/specify/zero_sectors", test_specify_zero_sectors);
+ qtest_add_func("/ide/specify/identify", test_specify_identify);
+ qtest_add_func("/ide/specify/identify_default",
+ test_specify_identify_default);
qtest_add_func("/ide/migration/chs_translation",
test_migrate_chs_translation);
qtest_add_func("/ide/migration/chs_snapshot", test_migrate_chs_snapshot);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 15/17] hw/ide: revert the CHS translation on a hardware reset
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (13 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 14/17] tests/qtest/ide-test: cover the IDENTIFY DEVICE geometry words Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 16/17] tests/qtest/ide-test: cover the CHS translation across resets Denis V. Lunev
` (2 subsequent siblings)
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
A power on or hardware reset returns the device parameters to their
power-on defaults (ATA-5 9.1). A software reset keeps them unless the
guest asked with SET FEATURES 0xCC for the next reset to revert (ATA-5 9.2
and 8.16.6). ide_reset() applied the second rule to every reset, so a
translation a guest selected outlived the reset of the machine it selected
it on, and the guest that came up next addressed the disk through a
geometry it never asked for.
Neither ide_reset() nor, for AHCI, ide_bus_reset() could tell the two
apart: a guest clearing SRST in the second host to device FIS of the
software reset protocol lands in the same ahci_reset_port() as a COMRESET
or a reset of the host adapter. Pass the kind down from the callers, which
do know.
ide_drive_pre_load() stays necessary: it restores the same fields, but a
vmstate cannot depend on its device having been reset first.
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>
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
---
hw/ide/ahci.c | 12 ++++++------
hw/ide/cmd646.c | 2 +-
hw/ide/core.c | 18 +++++++++---------
hw/ide/ide-internal.h | 7 ++++++-
hw/ide/isa.c | 2 +-
hw/ide/macio.c | 2 +-
hw/ide/mmio.c | 2 +-
hw/ide/piix.c | 2 +-
hw/ide/sii3112.c | 6 +++---
hw/ide/via.c | 2 +-
10 files changed, 30 insertions(+), 25 deletions(-)
diff --git a/hw/ide/ahci.c b/hw/ide/ahci.c
index 749f0efa1d..49f3047e6f 100644
--- a/hw/ide/ahci.c
+++ b/hw/ide/ahci.c
@@ -37,7 +37,7 @@
static void check_cmd(AHCIState *s, int port);
static void handle_cmd(AHCIState *s, int port, uint8_t slot);
-static void ahci_reset_port(AHCIState *s, int port);
+static void ahci_reset_port(AHCIState *s, int port, IDEResetKind kind);
static bool ahci_write_fis_d2h(AHCIDevice *ad, bool d2h_fis_i);
static void ahci_clear_cmd_issue(AHCIDevice *ad, uint8_t slot);
static void ahci_init_d2h(AHCIDevice *ad);
@@ -334,7 +334,7 @@ static void ahci_port_write(AHCIState *s, int port, int offset, uint32_t val)
case AHCI_PORT_REG_SCR_CTL:
if (((pr->scr_ctl & AHCI_SCR_SCTL_DET) == 1) &&
((val & AHCI_SCR_SCTL_DET) == 0)) {
- ahci_reset_port(s, port);
+ ahci_reset_port(s, port, IDE_RESET_HARDWARE);
}
pr->scr_ctl = val;
break;
@@ -619,7 +619,7 @@ static void ahci_set_signature(AHCIDevice *ad, uint32_t sig)
s->lcyl, s->hcyl, sig);
}
-static void ahci_reset_port(AHCIState *s, int port)
+static void ahci_reset_port(AHCIState *s, int port, IDEResetKind kind)
{
AHCIDevice *d = &s->dev[port];
AHCIPortRegs *pr = &d->port_regs;
@@ -628,7 +628,7 @@ static void ahci_reset_port(AHCIState *s, int port)
trace_ahci_reset_port(s, port);
- ide_bus_reset(&d->port);
+ ide_bus_reset(&d->port, kind);
ide_state->ncq_queues = AHCI_MAX_CMDS;
pr->scr_stat = 0;
@@ -1244,7 +1244,7 @@ static void handle_reg_h2d_fis(AHCIState *s, int port,
* COMRESET or by setting and clearing the SRST bit. Therefore,
* the logic for this is found in ahci_init_d2h() and not here.
*/
- ahci_reset_port(s, port);
+ ahci_reset_port(s, port, IDE_RESET_SOFTWARE);
}
break;
}
@@ -1650,7 +1650,7 @@ void ahci_reset(AHCIState *s)
pr->irq_mask = 0;
pr->scr_ctl = 0;
pr->cmd = PORT_CMD_SPIN_UP | PORT_CMD_POWER_ON;
- ahci_reset_port(s, i);
+ ahci_reset_port(s, i, IDE_RESET_HARDWARE);
}
}
diff --git a/hw/ide/cmd646.c b/hw/ide/cmd646.c
index d44a90a162..79ab65d64a 100644
--- a/hw/ide/cmd646.c
+++ b/hw/ide/cmd646.c
@@ -214,7 +214,7 @@ static void cmd646_reset(DeviceState *dev)
unsigned int i;
for (i = 0; i < 2; i++) {
- ide_bus_reset(&d->bus[i]);
+ ide_bus_reset(&d->bus[i], IDE_RESET_HARDWARE);
}
}
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 5a5f072630..3716cf1b5d 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -1347,7 +1347,7 @@ void ide_ioport_write(void *opaque, uint32_t addr, uint32_t val)
}
}
-static void ide_reset(IDEState *s)
+static void ide_reset(IDEState *s, IDEResetKind kind)
{
trace_ide_reset(s);
@@ -1356,7 +1356,7 @@ static void ide_reset(IDEState *s)
s->pio_aiocb = NULL;
}
- if (s->reset_reverts) {
+ if (kind == IDE_RESET_HARDWARE || s->reset_reverts) {
s->reset_reverts = false;
s->heads = s->drive_heads;
s->sectors = s->drive_sectors;
@@ -1424,7 +1424,7 @@ static bool cmd_device_reset(IDEState *s, uint8_t cmd)
ide_cancel_dma_sync(s);
/* Reset any PIO commands, reset signature, etc */
- ide_reset(s);
+ ide_reset(s, IDE_RESET_SOFTWARE);
/* RESET: ATA8-ACS3 7.10.4 "Normal Outputs";
* ATA8-ACS3 Table 184 "Device Signatures for Normal Output" */
@@ -2348,7 +2348,7 @@ static void ide_perform_srst(IDEState *s)
ide_cancel_dma_sync(s);
/* Cancel PIO callback, reset registers/signature, etc */
- ide_reset(s);
+ ide_reset(s, IDE_RESET_SOFTWARE);
/* perform diagnostic */
cmd_exec_dev_diagnostic(s, WIN_DIAGNOSE);
@@ -2553,7 +2553,7 @@ static void ide_dummy_transfer_stop(IDEState *s)
s->io_buffer[3] = 0xff;
}
-void ide_bus_reset(IDEBus *bus)
+void ide_bus_reset(IDEBus *bus, IDEResetKind kind)
{
/* pending async DMA - needs the IDEState before it is reset */
if (bus->dma->aiocb) {
@@ -2564,8 +2564,8 @@ void ide_bus_reset(IDEBus *bus)
bus->unit = 0;
bus->cmd = 0;
- ide_reset(&bus->ifs[0]);
- ide_reset(&bus->ifs[1]);
+ ide_reset(&bus->ifs[0], kind);
+ ide_reset(&bus->ifs[1], kind);
ide_clear_hob(bus);
/* reset dma provider too */
@@ -2679,7 +2679,7 @@ int ide_init_drive(IDEState *s, IDEDevice *dev, IDEDriveKind kind, Error **errp)
pstrcpy(s->version, sizeof(s->version), QEMU_HW_VERSION);
}
- ide_reset(s);
+ ide_reset(s, IDE_RESET_HARDWARE);
blk_iostatus_enable(s->blk);
return 0;
}
@@ -2816,7 +2816,7 @@ void ide_bus_init_output_irq(IDEBus *bus, qemu_irq irq_out)
for(i = 0; i < 2; i++) {
ide_init1(bus, i);
- ide_reset(&bus->ifs[i]);
+ ide_reset(&bus->ifs[i], IDE_RESET_HARDWARE);
}
bus->irq = irq_out;
bus->dma = &ide_dma_nop;
diff --git a/hw/ide/ide-internal.h b/hw/ide/ide-internal.h
index 281d07c9d5..094772209d 100644
--- a/hw/ide/ide-internal.h
+++ b/hw/ide/ide-internal.h
@@ -393,7 +393,12 @@ extern const VMStateDescription vmstate_ide_drive;
#define VMSTATE_IDE_DRIVE(_field, _state) \
VMSTATE_STRUCT(_field, _state, 1, vmstate_ide_drive, IDEState)
-void ide_bus_reset(IDEBus *bus);
+typedef enum {
+ IDE_RESET_HARDWARE, /* power on, hardware reset or COMRESET, ATA-5 9.1 */
+ IDE_RESET_SOFTWARE, /* SRST or DEVICE RESET, ATA-5 9.2 */
+} IDEResetKind;
+
+void ide_bus_reset(IDEBus *bus, IDEResetKind kind);
int64_t ide_get_sector(IDEState *s);
void ide_set_sector(IDEState *s, int64_t sector_num);
diff --git a/hw/ide/isa.c b/hw/ide/isa.c
index c97b7a1ff4..30f02867b1 100644
--- a/hw/ide/isa.c
+++ b/hw/ide/isa.c
@@ -51,7 +51,7 @@ static void isa_ide_reset(DeviceState *d)
{
ISAIDEState *s = ISA_IDE(d);
- ide_bus_reset(&s->bus);
+ ide_bus_reset(&s->bus, IDE_RESET_HARDWARE);
}
static const VMStateDescription vmstate_ide_isa = {
diff --git a/hw/ide/macio.c b/hw/ide/macio.c
index a7ed41fa26..40fb4f3b4f 100644
--- a/hw/ide/macio.c
+++ b/hw/ide/macio.c
@@ -368,7 +368,7 @@ static void macio_ide_reset(DeviceState *dev)
{
MACIOIDEState *d = MACIO_IDE(dev);
- ide_bus_reset(&d->bus);
+ ide_bus_reset(&d->bus, IDE_RESET_HARDWARE);
}
static int ide_nop_int(const IDEDMA *dma, bool is_write)
diff --git a/hw/ide/mmio.c b/hw/ide/mmio.c
index 0de904ac56..00819d559d 100644
--- a/hw/ide/mmio.c
+++ b/hw/ide/mmio.c
@@ -55,7 +55,7 @@ static void mmio_ide_reset(DeviceState *dev)
{
MMIOIDEState *s = MMIO_IDE(dev);
- ide_bus_reset(&s->bus);
+ ide_bus_reset(&s->bus, IDE_RESET_HARDWARE);
}
static uint64_t mmio_ide_read(void *opaque, hwaddr addr,
diff --git a/hw/ide/piix.c b/hw/ide/piix.c
index a0f2709c69..a8472f3e58 100644
--- a/hw/ide/piix.c
+++ b/hw/ide/piix.c
@@ -111,7 +111,7 @@ static void piix_ide_reset(DeviceState *dev)
int i;
for (i = 0; i < 2; i++) {
- ide_bus_reset(&d->bus[i]);
+ ide_bus_reset(&d->bus[i], IDE_RESET_HARDWARE);
}
/* PCI command register default value (0000h) per [1, p.48]. */
diff --git a/hw/ide/sii3112.c b/hw/ide/sii3112.c
index 9b28c691fd..03313eb271 100644
--- a/hw/ide/sii3112.c
+++ b/hw/ide/sii3112.c
@@ -185,7 +185,7 @@ static void sii3112_reg_write(void *opaque, hwaddr addr,
case 0x100:
d->regs[0].scontrol = val & 0xfff;
if (val & 1) {
- ide_bus_reset(&d->i.bus[0]);
+ ide_bus_reset(&d->i.bus[0], IDE_RESET_HARDWARE);
}
break;
case 0x148:
@@ -194,7 +194,7 @@ static void sii3112_reg_write(void *opaque, hwaddr addr,
case 0x180:
d->regs[1].scontrol = val & 0xfff;
if (val & 1) {
- ide_bus_reset(&d->i.bus[1]);
+ ide_bus_reset(&d->i.bus[1], IDE_RESET_HARDWARE);
}
break;
case 0x1c8:
@@ -243,7 +243,7 @@ static void sii3112_reset(DeviceState *dev)
for (i = 0; i < 2; i++) {
s->regs[i].confstat = 0x6515 << 16;
- ide_bus_reset(&s->i.bus[i]);
+ ide_bus_reset(&s->i.bus[i], IDE_RESET_HARDWARE);
}
}
diff --git a/hw/ide/via.c b/hw/ide/via.c
index 3a77d744ca..6c0610ff41 100644
--- a/hw/ide/via.c
+++ b/hw/ide/via.c
@@ -127,7 +127,7 @@ static void via_ide_reset(DeviceState *dev)
int i;
for (i = 0; i < ARRAY_SIZE(d->bus); i++) {
- ide_bus_reset(&d->bus[i]);
+ ide_bus_reset(&d->bus[i], IDE_RESET_HARDWARE);
}
pci_config_set_prog_interface(pci_conf, 0x8a); /* legacy mode */
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 16/17] tests/qtest/ide-test: cover the CHS translation across resets
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (14 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 15/17] hw/ide: revert the CHS translation on a hardware reset Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 17/17] hw/ide: drop a redundant interrupt from INITIALIZE DEVICE PARAMETERS Denis V. Lunev
2026-08-20 13:09 ` [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
A translation the guest selected has to survive a software reset and not a
hardware one, and the two arrive at the same ide_reset(), so a fix for
either direction can break the other. Select a translation, put the drive
through a software reset and then through a machine reset, and name the
sector each translation picks along the way.
The marker read says which translation the device is addressing the disk
with, while IDENTIFY DEVICE words 55 and 56 say which one it reports. The
machine reset leaves the PCI command register cleared, so the device has
to be enabled again before the ports answer.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
tests/qtest/ide-test.c | 46 ++++++++++++++++++++++++++++++++++++++++++
1 file changed, 46 insertions(+)
diff --git a/tests/qtest/ide-test.c b/tests/qtest/ide-test.c
index e7a56e0710..f14a0851f0 100644
--- a/tests/qtest/ide-test.c
+++ b/tests/qtest/ide-test.c
@@ -1639,6 +1639,51 @@ static void test_specify_identify_default(void)
ide_test_quit(qts);
}
+/* A hardware reset reverts the translation (ATA-5 9.1), SRST does not (9.2) */
+static void test_specify_reset(void)
+{
+ QTestState *qts;
+ QPCIDevice *dev;
+ QPCIBar bmdma_bar, ide_bar, ide_bar2;
+ uint16_t buf[256];
+ char marker[9];
+
+ qts = ide_test_start(
+ "-blockdev driver=file,node-name=hda,filename=%s "
+ "-device ide-hd,drive=hda,bus=ide.0,unit=0 ",
+ tmp_path[0]);
+ dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
+ ide_bar2 = qpci_legacy_iomap(dev, IDE_BASE2);
+
+ ide_prepare_markers(qts, dev, ide_bar);
+ ide_set_translation(dev, ide_bar, 8, 32);
+ ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
+ g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM);
+
+ qpci_io_writeb(dev, ide_bar2, 0, IDE_CTRL_RESET);
+ qpci_io_writeb(dev, ide_bar2, 0, 0);
+ ide_wait_clear(qts, BSY);
+
+ ide_identify_words(dev, ide_bar, buf);
+ g_assert_cmpint(buf[55], ==, 8);
+ g_assert_cmpint(buf[56], ==, 32);
+ ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
+ g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM);
+
+ qtest_qmp_assert_success(qts, "{ 'execute': 'system_reset' }");
+ qtest_qmp_eventwait(qts, "RESET");
+ qpci_device_enable(dev);
+
+ ide_identify_words(dev, ide_bar, buf);
+ g_assert_cmpint(buf[55], ==, 16);
+ g_assert_cmpint(buf[56], ==, 63);
+ ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
+ g_assert_cmpstr(marker, ==, CHS_MARKER_DEFAULT);
+
+ free_pci_device(dev);
+ ide_test_quit(qts);
+}
+
static void test_cdrom_pio(void)
{
cdrom_read_impl(1, CDROM_PIO);
@@ -1714,6 +1759,7 @@ int main(int argc, char **argv)
qtest_add_func("/ide/specify/identify", test_specify_identify);
qtest_add_func("/ide/specify/identify_default",
test_specify_identify_default);
+ qtest_add_func("/ide/specify/reset", test_specify_reset);
qtest_add_func("/ide/migration/chs_translation",
test_migrate_chs_translation);
qtest_add_func("/ide/migration/chs_snapshot", test_migrate_chs_snapshot);
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* [PATCH v2 17/17] hw/ide: drop a redundant interrupt from INITIALIZE DEVICE PARAMETERS
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (15 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 16/17] tests/qtest/ide-test: cover the CHS translation across resets Denis V. Lunev
@ 2026-08-20 10:08 ` Denis V. Lunev
2026-08-20 11:08 ` Philippe Mathieu-Daudé
2026-08-20 13:09 ` [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
17 siblings, 1 reply; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 10:08 UTC (permalink / raw)
To: qemu-devel
Cc: qemu-block, Denis V. Lunev, John Snow, Peter Maydell,
Philippe Mathieu-Daudé
From: Denis V. Lunev <den@openvz.org>
ide_bus_exec_cmd() raises the interrupt for every command handler that
reports the command complete, which cmd_specify() does, so the request it
raised itself was the first of two. The one from ide_bus_exec_cmd() is the
one that belongs there, being raised after BSY is cleared and after
ide_cmd_done() has let the bus master post its own completion.
Cc: John Snow <jsnow@redhat.com>
Cc: Peter Maydell <peter.maydell@linaro.org>
Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
Signed-off-by: Denis V. Lunev <den@openvz.org>
---
hw/ide/core.c | 1 -
1 file changed, 1 deletion(-)
diff --git a/hw/ide/core.c b/hw/ide/core.c
index 3716cf1b5d..8190549ee8 100644
--- a/hw/ide/core.c
+++ b/hw/ide/core.c
@@ -1675,7 +1675,6 @@ static bool cmd_specify(IDEState *s, uint8_t cmd)
if (s->identify_set) {
ide_identify_chs(s);
}
- ide_bus_set_irq(s->bus);
return true;
}
--
2.53.0
^ permalink raw reply related [flat|nested] 21+ messages in thread
* Re: [PATCH v2 07/17] hw/ide: factor out the IDENTIFY DEVICE current geometry words
2026-08-20 10:08 ` [PATCH v2 07/17] hw/ide: factor out the IDENTIFY DEVICE current geometry words Denis V. Lunev
@ 2026-08-20 11:07 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 21+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-08-20 11:07 UTC (permalink / raw)
To: Denis V. Lunev, qemu-devel; +Cc: qemu-block, John Snow, Peter Maydell
On 20/8/26 12:08, Denis V. Lunev wrote:
> From: Denis V. Lunev <den@openvz.org>
>
> Words 54 to 58 of IDENTIFY DEVICE describe the CHS translation in effect
> and the capacity it addresses. Both ide_identify() and
> ide_cfata_identify() fill them the same way while building their cached
> data.
>
> Move them into ide_identify_chs(), so that the next change can refresh
> them in place once the translation changes, the way ide_identify_size()
> does for the capacity words.
>
> No functional change.
>
> Cc: John Snow <jsnow@redhat.com>
> Cc: Peter Maydell <peter.maydell@linaro.org>
> Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> ---
> hw/ide/core.c | 29 ++++++++++++++---------------
> 1 file changed, 14 insertions(+), 15 deletions(-)
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 17/17] hw/ide: drop a redundant interrupt from INITIALIZE DEVICE PARAMETERS
2026-08-20 10:08 ` [PATCH v2 17/17] hw/ide: drop a redundant interrupt from INITIALIZE DEVICE PARAMETERS Denis V. Lunev
@ 2026-08-20 11:08 ` Philippe Mathieu-Daudé
0 siblings, 0 replies; 21+ messages in thread
From: Philippe Mathieu-Daudé @ 2026-08-20 11:08 UTC (permalink / raw)
To: Denis V. Lunev, qemu-devel; +Cc: qemu-block, John Snow, Peter Maydell
On 20/8/26 12:08, Denis V. Lunev wrote:
> From: Denis V. Lunev <den@openvz.org>
>
> ide_bus_exec_cmd() raises the interrupt for every command handler that
> reports the command complete, which cmd_specify() does, so the request it
> raised itself was the first of two. The one from ide_bus_exec_cmd() is the
> one that belongs there, being raised after BSY is cleared and after
> ide_cmd_done() has let the bus master post its own completion.
>
> Cc: John Snow <jsnow@redhat.com>
> Cc: Peter Maydell <peter.maydell@linaro.org>
> Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> ---
> hw/ide/core.c | 1 -
> 1 file changed, 1 deletion(-)
Good catch.
Reviewed-by: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
^ permalink raw reply [flat|nested] 21+ messages in thread
* Re: [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
` (16 preceding siblings ...)
2026-08-20 10:08 ` [PATCH v2 17/17] hw/ide: drop a redundant interrupt from INITIALIZE DEVICE PARAMETERS Denis V. Lunev
@ 2026-08-20 13:09 ` Denis V. Lunev
17 siblings, 0 replies; 21+ messages in thread
From: Denis V. Lunev @ 2026-08-20 13:09 UTC (permalink / raw)
To: Denis V. Lunev, qemu-devel
Cc: qemu-block, John Snow, Peter Maydell, Philippe Mathieu-Daudé
On 8/20/26 12:08, Denis V. Lunev wrote:
> This email originated from an IP that might not be authorized by the domain it was sent from.
> Do not click links or open attachments unless it is an email you expected to receive.
> INITIALIZE DEVICE PARAMETERS lets a guest replace the logical CHS
> translation that turns the CHS registers into an LBA. cmd_specify() has
> implemented it since 176e4961bb33, released in v7.2, and the geometry it
> stores is mishandled four ways.
>
> It is not validated: zero sectors per logical track kills QEMU with SIGFPE
> in ide_set_sector(), which is issue 2399, and since handle_cmd() takes the
> count from a 16 bit field of the register FIS, an AHCI guest can drive
> ide_get_sector() into an int overflow as well. It is not reported:
> IDENTIFY DEVICE is built once and cached, so both the words describing the
> default geometry and the words describing the current one go wrong. It
> does not travel, leaving a migrated guest addressing the disk in a
> translation the destination does not have. And it does not revert on a
> hardware reset, which ATA-5 9.1 requires, so it outlives the reset of the
> machine it was selected on.
>
> The two subsections patches 10 and 11 add are the only change to the
> migration stream, and both are sent only when the guest replaced the
> default geometry. An unaffected guest migrates to an older QEMU as before;
> an affected one now fails the load on the unknown subsection instead of
> silently addressing the wrong sectors.
>
> Tested with ide-test (25 cases), ahci-test (74), a full make check, a
> clang build, and real guests on both controllers: FreeDOS reading a FAT16
> disk through INT 13h CHS, Red Hat Linux 9 whose 2.4 IDE driver prints the
> geometry it read, and Ubuntu 26.04 over libata, each with a CD and a disk,
> plus a machine reset in the middle.
>
> v1:
> https://lore.kernel.org/qemu-devel/20260817205242.1199851-1-den@openvz.org/
>
> Changes in v2
> -------------
>
> - patch 1, patch 5: Cc qemu-stable. (Philippe)
> - patch 6: new, the "XXX: retired, remove ?" on IDENTIFY DEVICE words 4,
> 5 and 20 is answered rather than carried. ATA-5 3.2.3.6 keeps the
> ATA-1 meaning of a retired word that a device still fills in, so the
> comments name that meaning; word 21 is a buffer size, not a cache
> size. (Philippe)
> - patch 7: new, splits the extraction of ide_identify_chs() out of what
> is now patch 8, leaving that one with the two call sites that keep the
> words in sync. (Philippe)
>
> Signed-off-by: Denis V. Lunev <den@openvz.org>
> Cc: John Snow <jsnow@redhat.com>
> Cc: Peter Maydell <peter.maydell@linaro.org>
> Cc: Philippe Mathieu-Daudé <philmd@oss.qualcomm.com>
>
> Denis V. Lunev (17):
> hw/ide: reject an unsupported CHS translation
> tests/qtest/ide-test: cover a CHS translation with zero sectors
> tests/qtest/libqos/ahci: allow a count and an expected error
> tests/qtest/ahci: cover the sector count of INITIALIZE DEVICE
> PARAMETERS
> hw/ide: report the default CHS translation in IDENTIFY DEVICE
> hw/ide: name the retired IDENTIFY DEVICE words the device fills in
> hw/ide: factor out the IDENTIFY DEVICE current geometry words
> hw/ide: keep the IDENTIFY DEVICE current geometry in sync
> hw/ide: restore the power-on device state before loading
> hw/ide: migrate the logical CHS translation
> hw/ide: migrate the power-on defaults revert flag
> tests/qtest/ide-test: cover the CHS translation across migration
> tests/qtest/ide-test: cover a rejected CHS translation in the stream
> tests/qtest/ide-test: cover the IDENTIFY DEVICE geometry words
> hw/ide: revert the CHS translation on a hardware reset
> tests/qtest/ide-test: cover the CHS translation across resets
> hw/ide: drop a redundant interrupt from INITIALIZE DEVICE PARAMETERS
>
> hw/ide/ahci.c | 12 +-
> hw/ide/cmd646.c | 2 +-
> hw/ide/core.c | 147 ++++++++---
> hw/ide/ide-internal.h | 7 +-
> hw/ide/isa.c | 2 +-
> hw/ide/macio.c | 2 +-
> hw/ide/mmio.c | 2 +-
> hw/ide/piix.c | 2 +-
> hw/ide/sii3112.c | 6 +-
> hw/ide/via.c | 2 +-
> tests/qtest/ahci-test.c | 40 +++
> tests/qtest/ide-test.c | 499 ++++++++++++++++++++++++++++++++++++++
> tests/qtest/libqos/ahci.c | 14 ++
> tests/qtest/libqos/ahci.h | 6 +
> 14 files changed, 690 insertions(+), 53 deletions(-)
>
>
> base-commit: ae4f3443209ab154b48b706a146e5f557ab147cb
queued
^ permalink raw reply [flat|nested] 21+ messages in thread
end of thread, other threads:[~2026-08-20 13:10 UTC | newest]
Thread overview: 21+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-08-20 10:08 [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 01/17] hw/ide: reject an unsupported CHS translation Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 02/17] tests/qtest/ide-test: cover a CHS translation with zero sectors Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 03/17] tests/qtest/libqos/ahci: allow a count and an expected error Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 04/17] tests/qtest/ahci: cover the sector count of INITIALIZE DEVICE PARAMETERS Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 05/17] hw/ide: report the default CHS translation in IDENTIFY DEVICE Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 06/17] hw/ide: name the retired IDENTIFY DEVICE words the device fills in Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 07/17] hw/ide: factor out the IDENTIFY DEVICE current geometry words Denis V. Lunev
2026-08-20 11:07 ` Philippe Mathieu-Daudé
2026-08-20 10:08 ` [PATCH v2 08/17] hw/ide: keep the IDENTIFY DEVICE current geometry in sync Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 09/17] hw/ide: restore the power-on device state before loading Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 10/17] hw/ide: migrate the logical CHS translation Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 11/17] hw/ide: migrate the power-on defaults revert flag Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 12/17] tests/qtest/ide-test: cover the CHS translation across migration Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 13/17] tests/qtest/ide-test: cover a rejected CHS translation in the stream Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 14/17] tests/qtest/ide-test: cover the IDENTIFY DEVICE geometry words Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 15/17] hw/ide: revert the CHS translation on a hardware reset Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 16/17] tests/qtest/ide-test: cover the CHS translation across resets Denis V. Lunev
2026-08-20 10:08 ` [PATCH v2 17/17] hw/ide: drop a redundant interrupt from INITIALIZE DEVICE PARAMETERS Denis V. Lunev
2026-08-20 11:08 ` Philippe Mathieu-Daudé
2026-08-20 13:09 ` [PATCH v2 00/17] hw/ide: fix the logical CHS translation a guest selects Denis V. Lunev
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.