@samitouri / QOSamiQemu / commits / ef64d2758e

tests/qtest/ide-test: cover the CHS translation across migration

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>

Denis V. Lunev committed Aug 4, 2026 at 18:34 UTC ef64d2758e439871500c726c0d536b05d19c2532
1 file changed +231
tests/qtest/ide-test.c
+231
@@ -96,6 +96,7 @@ enum {
96 enum {
97 CMD_DSM = 0x06,
98 CMD_READ = 0x20, /* READ SECTOR(S) */
99 + CMD_WRITE = 0x30, /* WRITE SECTOR(S) */
100 CMD_DIAGNOSE = 0x90,
101 CMD_INIT_DP = 0x91, /* INITIALIZE DEVICE PARAMETERS */
102 CMD_READ_DMA = 0xc8,
@@ -1255,6 +1256,233 @@ static void test_specify_zero_sectors(void)
1256 free_pci_device(dev);
1257 }
1258
1259 +/* Addressed by LBA, so no translation can influence where it lands */
1260 +static void ide_write_marker(QTestState *qts, QPCIDevice *dev, QPCIBar ide_bar,
1261 + uint32_t lba, const char *marker)
1262 +{
1263 + uint16_t buf[256];
1264 + uint8_t data;
1265 + int i;
1266 +
1267 + memset(buf, 0, sizeof(buf));
1268 + memcpy(buf, marker, strlen(marker));
1269 +
1270 + qpci_io_writeb(dev, ide_bar, reg_nsectors, 1);
1271 + qpci_io_writeb(dev, ide_bar, reg_lba_low, lba & 0xff);
1272 + qpci_io_writeb(dev, ide_bar, reg_lba_middle, (lba >> 8) & 0xff);
1273 + qpci_io_writeb(dev, ide_bar, reg_lba_high, (lba >> 16) & 0xff);
1274 + qpci_io_writeb(dev, ide_bar, reg_device, LBA | ((lba >> 24) & 0xf));
1275 + qpci_io_writeb(dev, ide_bar, reg_command, CMD_WRITE);
1276 +
1277 + data = ide_wait_clear(qts, BSY);
1278 + assert_bit_set(data, DRQ);
1279 + for (i = 0; i < 256; i++) {
1280 + qpci_io_writew(dev, ide_bar, reg_data, buf[i]);
1281 + }
1282 + data = ide_wait_clear(qts, BSY);
1283 + assert_bit_clear(data, ERR | DF | DRQ);
1284 +
1285 + qpci_io_writeb(dev, ide_bar, reg_command, CMD_FLUSH_CACHE);
1286 + data = ide_wait_clear(qts, BSY);
1287 + assert_bit_clear(data, ERR | DF);
1288 +}
1289 +
1290 +/* The marker read back names the sector the translation selected */
1291 +static void ide_read_chs_marker(QTestState *qts, QPCIDevice *dev,
1292 + QPCIBar ide_bar, uint8_t cyl_lo, uint8_t head,
1293 + uint8_t sector, char out[9])
1294 +{
1295 + uint16_t buf[256];
1296 + uint8_t data;
1297 + int i;
1298 +
1299 + qpci_io_writeb(dev, ide_bar, reg_nsectors, 1);
1300 + qpci_io_writeb(dev, ide_bar, reg_lba_low, sector);
1301 + qpci_io_writeb(dev, ide_bar, reg_lba_middle, cyl_lo);
1302 + qpci_io_writeb(dev, ide_bar, reg_lba_high, 0);
1303 + qpci_io_writeb(dev, ide_bar, reg_device, head & 0xf);
1304 + qpci_io_writeb(dev, ide_bar, reg_command, CMD_READ);
1305 +
1306 + data = ide_wait_clear(qts, BSY);
1307 + assert_bit_set(data, DRQ);
1308 + assert_bit_clear(data, ERR | DF);
1309 + for (i = 0; i < 256; i++) {
1310 + buf[i] = qpci_io_readw(dev, ide_bar, reg_data);
1311 + }
1312 + data = ide_wait_clear(qts, BSY);
1313 + assert_bit_clear(data, ERR | DF | DRQ);
1314 +
1315 + memcpy(out, buf, 8);
1316 + out[8] = '\0';
1317 +}
1318 +
1319 +static void ide_set_translation(QPCIDevice *dev, QPCIBar ide_bar,
1320 + uint8_t heads, uint8_t sectors)
1321 +{
1322 + qpci_io_writeb(dev, ide_bar, reg_nsectors, sectors);
1323 + qpci_io_writeb(dev, ide_bar, reg_device, heads - 1);
1324 + qpci_io_writeb(dev, ide_bar, reg_command, CMD_INIT_DP);
1325 + assert_bit_clear(qpci_io_readb(dev, ide_bar, reg_status), ERR);
1326 +}
1327 +
1328 +/* CHS 0/1/1 is LBA 32 under 8/32, and LBA 63 under the drive's own 16/63 */
1329 +#define CHS_MARKER_CUSTOM "CUSTOM__"
1330 +#define CHS_MARKER_DEFAULT "DEFAULT_"
1331 +
1332 +static void ide_prepare_markers(QTestState *qts, QPCIDevice *dev,
1333 + QPCIBar ide_bar)
1334 +{
1335 + ide_write_marker(qts, dev, ide_bar, 32, CHS_MARKER_CUSTOM);
1336 + ide_write_marker(qts, dev, ide_bar, 63, CHS_MARKER_DEFAULT);
1337 +}
1338 +
1339 +static void ide_hmp_quiet(QTestState *qts, const char *command)
1340 +{
1341 + g_autofree char *out = qtest_hmp(qts, "%s", command);
1342 +
1343 + g_assert_cmpstr(out, ==, "");
1344 +}
1345 +
1346 +static char *ide_migration_status(QTestState *qts)
1347 +{
1348 + QDict *ret;
1349 + char *status;
1350 +
1351 + ret = qtest_qmp_assert_success_ref(qts, "{ 'execute': 'query-migrate' }");
1352 + g_assert(qdict_haskey(ret, "status"));
1353 + status = g_strdup(qdict_get_str(ret, "status"));
1354 + qobject_unref(ret);
1355 +
1356 + return status;
1357 +}
1358 +
1359 +/* Waiting for the other side's event would hang if it refuses the stream */
1360 +static void ide_migration_wait(QTestState *qts, const char *expected)
1361 +{
1362 + while (true) {
1363 + g_autofree char *status = ide_migration_status(qts);
1364 +
1365 + if (g_str_equal(status, expected)) {
1366 + return;
1367 + }
1368 + if (!g_str_equal(status, "setup") && !g_str_equal(status, "active") &&
1369 + !g_str_equal(status, "device")) {
1370 + fprintf(stderr, "Migration status is %s, expected %s\n",
1371 + status, expected);
1372 + g_assert_not_reached();
1373 + }
1374 + g_usleep(5000);
1375 + }
1376 +}
1377 +
1378 +static void ide_migrate(QTestState *src, QTestState *dst, const char *uri)
1379 +{
1380 + qtest_qmp_assert_success(src, "{ 'execute': 'migrate',"
1381 + " 'arguments': { 'uri': %s } }", uri);
1382 + qtest_qmp_eventwait(src, "STOP");
1383 + ide_migration_wait(src, "completed");
1384 + qtest_qmp_eventwait(dst, "RESUME");
1385 +}
1386 +
1387 +/* A translation the guest selected has to survive migration */
1388 +static void test_migrate_chs_translation(void)
1389 +{
1390 + QTestState *src, *dst;
1391 + QPCIDevice *dev;
1392 + QPCIBar bmdma_bar, ide_bar;
1393 + g_autofree char *mig_path = NULL;
1394 + g_autofree char *uri = NULL;
1395 + g_autofree char *dst_args = NULL;
1396 + char marker[9];
1397 + int fd;
1398 +
1399 + fd = g_file_open_tmp("qtest-ide-migration.XXXXXX", &mig_path, NULL);
1400 + g_assert(fd >= 0);
1401 + close(fd);
1402 + uri = g_strdup_printf("unix:%s", mig_path);
1403 +
1404 + src = ide_test_start(
1405 + "-blockdev driver=file,node-name=hda,filename=%s,locking=off "
1406 + "-device ide-hd,drive=hda,bus=ide.0,unit=0 ",
1407 + tmp_path[0]);
1408 + dev = get_pci_device(src, &bmdma_bar, &ide_bar);
1409 +
1410 + ide_prepare_markers(src, dev, ide_bar);
1411 + ide_set_translation(dev, ide_bar, 8, 32);
1412 + ide_read_chs_marker(src, dev, ide_bar, 0, 1, 1, marker);
1413 + g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM);
1414 +
1415 + dst_args = g_strdup_printf(
1416 + "-machine pc "
1417 + "-blockdev driver=file,node-name=hda,filename=%s,locking=off "
1418 + "-device ide-hd,drive=hda,bus=ide.0,unit=0 -incoming %s",
1419 + tmp_path[0], uri);
1420 + dst = qtest_init(dst_args);
1421 +
1422 + ide_migrate(src, dst, uri);
1423 +
1424 + /* Talk to the destination instead of the source */
1425 + qpci_free_pc(pcibus);
1426 + pcibus = NULL;
1427 + free_pci_device(dev);
1428 + dev = get_pci_device(dst, &bmdma_bar, &ide_bar);
1429 +
1430 + ide_read_chs_marker(dst, dev, ide_bar, 0, 1, 1, marker);
1431 + g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM);
1432 +
1433 + free_pci_device(dev);
1434 + qtest_quit(dst);
1435 + ide_test_quit(src);
1436 + unlink(mig_path);
1437 +}
1438 +
1439 +/* A translation selected after the snapshot must not outlive loading it */
1440 +static void test_migrate_chs_snapshot(void)
1441 +{
1442 + QTestState *qts;
1443 + QPCIDevice *dev;
1444 + QPCIBar bmdma_bar, ide_bar;
1445 + g_autofree char *img = NULL;
1446 + char marker[9];
1447 + int fd;
1448 +
1449 + if (!have_qemu_img()) {
1450 + g_test_skip("QTEST_QEMU_IMG not set, snapshots need a qcow2 image");
1451 + return;
1452 + }
1453 +
1454 + fd = g_file_open_tmp("qtest-ide-snapshot.XXXXXX", &img, NULL);
1455 + g_assert(fd >= 0);
1456 + close(fd);
1457 + g_assert(mkimg(img, "qcow2", TEST_IMAGE_SIZE / (1024 * 1024)));
1458 +
1459 + qts = ide_test_start(
1460 + "-blockdev driver=qcow2,node-name=hda,file.driver=file,"
1461 + "file.filename=%s "
1462 + "-device ide-hd,drive=hda,bus=ide.0,unit=0 ", img);
1463 + dev = get_pci_device(qts, &bmdma_bar, &ide_bar);
1464 +
1465 + ide_prepare_markers(qts, dev, ide_bar);
1466 +
1467 + /* Snapshot taken while the default translation is in effect */
1468 + ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
1469 + g_assert_cmpstr(marker, ==, CHS_MARKER_DEFAULT);
1470 + ide_hmp_quiet(qts, "savevm s0");
1471 +
1472 + ide_set_translation(dev, ide_bar, 8, 32);
1473 + ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
1474 + g_assert_cmpstr(marker, ==, CHS_MARKER_CUSTOM);
1475 +
1476 + ide_hmp_quiet(qts, "loadvm s0");
1477 +
1478 + ide_read_chs_marker(qts, dev, ide_bar, 0, 1, 1, marker);
1479 + g_assert_cmpstr(marker, ==, CHS_MARKER_DEFAULT);
1480 +
1481 + free_pci_device(dev);
1482 + ide_test_quit(qts);
1483 + unlink(img);
1484 +}
1485 +
1486 static void test_cdrom_pio(void)
1487 {
1488 cdrom_read_impl(1, CDROM_PIO);
@@ -1327,6 +1555,9 @@ int main(int argc, char **argv)
1555
1556 qtest_add_func("/ide/read_native", test_specify);
1557 qtest_add_func("/ide/specify/zero_sectors", test_specify_zero_sectors);
1558 + qtest_add_func("/ide/migration/chs_translation",
1559 + test_migrate_chs_translation);
1560 + qtest_add_func("/ide/migration/chs_snapshot", test_migrate_chs_snapshot);
1561
1562 qtest_add_func("/ide/identify", test_identify);
1563