* Don't stop at the first unbootable device, continue scanning

* Fix corner cases in booting from ECKD
 * s390x-ccw bios cleanup part 2​
 -----BEGIN PGP SIGNATURE-----
 
 iQJFBAABCAAvFiEEJ7iIR+7gJQEY8+q5LtnXdP5wLbUFAl98tMoRHHRodXRoQHJl
 ZGhhdC5jb20ACgkQLtnXdP5wLbWioA//bp1fp++Apn8w+Bbw3YY30wIG6/OLgjlw
 k//MtdcNasDswrWEES77lyR9ud22B/MSKqdfFpnTI1vQ8+e6BuH82iu3d7yNS0cd
 8tgb8jr1dO4gMjExJZ6ada5gY/ZkNWyM7gJooqVqTd+lEnIeo8yMAIPHE/3OyfeA
 xIIA0+owppxEQc3UuE3hS7Cu0ph03HDugWDEqMA6LcD2E8Yl2bP3Sh5uztVdjqmI
 CH1nuB62WR0kNheU+9woKPDBZP/NPwFvLRnJmNZptKensPotiwX1QjK9aqE15iU5
 Mr28qVxubRIMxfmlzJ+3NImTNjdJnOgKFxDwP/hCjJ5lBSHgM/69+nZcDt49Cg4k
 52LH9y+9RsDvOz7jNle0xn2V+T48ykkzLKvAHT/2wzSqE8eccpKilqP8ZoFPq11c
 WcyP68C6ag28AK72JRdNN43aM0clTeUVdUMgy4nqu6HzBDDHKhwTyj6W1zbs+aJC
 bBtITOxZyjrqPi156pJQCKWYrQQyq2SZV7BBAKSq56nkR+wkj4lxSbor2Am8h3Xk
 2QQBXtMgsmwcQTOfGlKer9KXWh7bEINh3iGtJ6QDL0KJtUFn2Uyi2U7uiSq4SlGS
 QH0AIxTKkYZ8VlRNgCCTf4XXJM0LGjX3XKIDePAnY5aA5F5bYdd7vFfCBChUWMML
 ARN7JMSXL70=
 =T7wc
 -----END PGP SIGNATURE-----

Merge remote-tracking branch 'remotes/huth-gitlab/tags/pull-request-2020-10-06' into staging

* Don't stop at the first unbootable device, continue scanning
* Fix corner cases in booting from ECKD
* s390x-ccw bios cleanup part 2​

# gpg: Signature made Tue 06 Oct 2020 19:17:46 BST
# gpg:                using RSA key 27B88847EEE0250118F3EAB92ED9D774FE702DB5
# gpg:                issuer "thuth@redhat.com"
# gpg: Good signature from "Thomas Huth <th.huth@gmx.de>" [full]
# gpg:                 aka "Thomas Huth <thuth@redhat.com>" [full]
# gpg:                 aka "Thomas Huth <huth@tuxfamily.org>" [full]
# gpg:                 aka "Thomas Huth <th.huth@posteo.de>" [unknown]
# Primary key fingerprint: 27B8 8847 EEE0 2501 18F3  EAB9 2ED9 D774 FE70 2DB5

* remotes/huth-gitlab/tags/pull-request-2020-10-06:
  tests/qtest/cdrom: Add more s390x-related boot tests
  pc-bios/s390: Update the s390-ccw bios binaries
  pc-bios: s390x: Go into disabled wait when encountering a PGM exception
  pc-bios: s390x: Use reset PSW if avaliable
  pc-bios: s390x: Save PSW rework
  pc-bios: s390x: Fix bootmap.c zipl component entry data handling
  pc-bios/s390-ccw: break loop if a null block number is reached
  pc-bios/s390-ccw: fix off-by-one error
  pc-bios/s390-ccw/main: Remove superfluous call to enable_subchannel()
  pc-bios/s390-ccw: Allow booting in case the first virtio-blk disk is bad
  pc-bios/s390-ccw: Scan through all devices if no boot device specified
  pc-bios/s390-ccw: Do not bail out early if not finding a SCSI disk
  pc-bios/s390-ccw: Move the inner logic of find_subch() to a separate function
  pc-bios/s390-ccw: Introduce ENODEV define and remove guards of others
  pc-bios/s390-ccw: Move ipl-related code from main() into a separate function
  pc-bios/s390-ccw/Makefile: Compile with -std=gnu99, -fwrapv and -fno-common

Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
This commit is contained in:
Peter Maydell 2020-10-06 21:13:34 +01:00
commit 6eeea6725a
13 changed files with 224 additions and 129 deletions

Binary file not shown.

View File

@ -30,10 +30,11 @@ OBJECTS = start.o main.o bootmap.o jump2ipl.o sclp.o menu.o \
virtio.o virtio-scsi.o virtio-blkdev.o libc.o cio.o dasd-ipl.o
QEMU_CFLAGS := -Wall $(filter -W%, $(QEMU_CFLAGS))
QEMU_CFLAGS += -ffreestanding -fno-delete-null-pointer-checks -msoft-float
QEMU_CFLAGS += -march=z900 -fPIE -fno-strict-aliasing
QEMU_CFLAGS += -fno-asynchronous-unwind-tables
QEMU_CFLAGS += -ffreestanding -fno-delete-null-pointer-checks -fno-common -fPIE
QEMU_CFLAGS += -fwrapv -fno-strict-aliasing -fno-asynchronous-unwind-tables
QEMU_CFLAGS += $(call cc-option, $(QEMU_CFLAGS), -fno-stack-protector)
QEMU_CFLAGS += -msoft-float -march=z900
QEMU_CFLAGS += -std=gnu99
LDFLAGS += -Wl,-pie -nostdlib
build-all: s390-ccw.img s390-netboot.img

View File

@ -10,6 +10,7 @@
#include "libc.h"
#include "s390-ccw.h"
#include "s390-arch.h"
#include "bootmap.h"
#include "virtio.h"
#include "bswap.h"
@ -163,7 +164,7 @@ static bool find_zipl_boot_menu_banner(int *offset)
int i;
/* Menu banner starts with "zIPL" */
for (i = 0; i < virtio_get_block_size() - 4; i++) {
for (i = 0; i <= virtio_get_block_size() - 4; i++) {
if (magic_match(s2_cur_blk + i, ZIPL_MAGIC_EBCDIC)) {
*offset = i;
return true;
@ -192,7 +193,7 @@ static int eckd_get_boot_menu_index(block_number_t s1b_block_nr)
for (i = 0; i < STAGE2_BLK_CNT_MAX; i++) {
cur_block_nr = eckd_block_num(&s1b->seek[i].chs);
if (!cur_block_nr) {
if (!cur_block_nr || is_null_block_number(cur_block_nr)) {
break;
}
@ -272,7 +273,8 @@ static void run_eckd_boot_script(block_number_t bmt_block_nr,
IPL_assert(bms->entry[i].type == BOOT_SCRIPT_EXEC,
"Unknown script entry type");
jump_to_IPL_code(bms->entry[i].address.load_address); /* no return */
write_reset_psw(bms->entry[i].address.load_address); /* no return */
jump_to_IPL_code(0); /* no return */
}
static void ipl_eckd_cdl(void)
@ -289,11 +291,18 @@ static void ipl_eckd_cdl(void)
read_block(1, ipl2, "Cannot read IPL2 record at block 1");
mbr = &ipl2->mbr;
IPL_assert(magic_match(mbr, ZIPL_MAGIC), "No zIPL section in IPL2 record.");
IPL_assert(block_size_ok(mbr->blockptr.xeckd.bptr.size),
"Bad block size in zIPL section of IPL2 record.");
IPL_assert(mbr->dev_type == DEV_TYPE_ECKD,
"Non-ECKD device type in zIPL section of IPL2 record.");
if (!magic_match(mbr, ZIPL_MAGIC)) {
sclp_print("No zIPL section in IPL2 record.\n");
return;
}
if (!block_size_ok(mbr->blockptr.xeckd.bptr.size)) {
sclp_print("Bad block size in zIPL section of IPL2 record.\n");
return;
}
if (!mbr->dev_type == DEV_TYPE_ECKD) {
sclp_print("Non-ECKD device type in zIPL section of IPL2 record.\n");
return;
}
/* save pointer to Boot Map Table */
bmt_block_nr = eckd_block_num(&mbr->blockptr.xeckd.bptr.chs);
@ -303,10 +312,14 @@ static void ipl_eckd_cdl(void)
memset(sec, FREE_SPACE_FILLER, sizeof(sec));
read_block(2, vlbl, "Cannot read Volume Label at block 2");
IPL_assert(magic_match(vlbl->key, VOL1_MAGIC),
"Invalid magic of volume label block");
IPL_assert(magic_match(vlbl->f.key, VOL1_MAGIC),
"Invalid magic of volser block");
if (!magic_match(vlbl->key, VOL1_MAGIC)) {
sclp_print("Invalid magic of volume label block.\n");
return;
}
if (!magic_match(vlbl->f.key, VOL1_MAGIC)) {
sclp_print("Invalid magic of volser block.\n");
return;
}
print_volser(vlbl->f.volser);
run_eckd_boot_script(bmt_block_nr, s1b_block_nr);
@ -398,7 +411,8 @@ static void ipl_eckd(void)
read_block(0, mbr, "Cannot read block 0 on DASD");
if (magic_match(mbr->magic, IPL1_MAGIC)) {
ipl_eckd_cdl(); /* no return */
ipl_eckd_cdl(); /* only returns in case of error */
return;
}
/* LDL/CMS? */
@ -436,7 +450,7 @@ static void zipl_load_segment(ComponentEntry *entry)
char *blk_no = &err_msg[30]; /* where to print blockno in (those ZZs) */
blockno = entry->data.blockno;
address = entry->load_address;
address = entry->compdat.load_addr;
debug_print_int("loading segment at block", blockno);
debug_print_int("addr", address);
@ -514,7 +528,8 @@ static void zipl_run(ScsiBlockPtr *pte)
IPL_assert(entry->component_type == ZIPL_COMP_ENTRY_EXEC, "No EXEC entry");
/* should not return */
jump_to_IPL_code(entry->load_address);
write_reset_psw(entry->compdat.load_psw);
jump_to_IPL_code(0);
}
static void ipl_scsi(void)
@ -825,5 +840,5 @@ void zipl_load(void)
panic("\n! Unknown IPL device type !\n");
}
panic("\n* this can never happen *\n");
sclp_print("zIPL load failed.\n");
}

View File

@ -64,11 +64,16 @@ typedef struct BootMapTable {
BootMapPointer entry[];
} __attribute__ ((packed)) BootMapTable;
typedef union ComponentEntryData {
uint64_t load_psw;
uint64_t load_addr;
} ComponentEntryData;
typedef struct ComponentEntry {
ScsiBlockPtr data;
uint8_t pad[7];
uint8_t component_type;
uint64_t load_address;
ComponentEntryData compdat;
} __attribute((packed)) ComponentEntry;
typedef struct ComponentHeader {

View File

@ -12,21 +12,24 @@
#define KERN_IMAGE_START 0x010000UL
#define RESET_PSW_MASK (PSW_MASK_SHORTPSW | PSW_MASK_64)
#define RESET_PSW ((uint64_t)&jump_to_IPL_addr | RESET_PSW_MASK)
typedef struct ResetInfo {
uint64_t ipl_psw;
uint32_t ipl_continue;
} ResetInfo;
static uint64_t *reset_psw = 0, save_psw, ipl_continue;
static ResetInfo save;
static void jump_to_IPL_2(void)
void write_reset_psw(uint64_t psw)
{
ResetInfo *current = 0;
*reset_psw = psw;
}
void (*ipl)(void) = (void *) (uint64_t) current->ipl_continue;
*current = save;
ipl(); /* should not return */
static void jump_to_IPL_addr(void)
{
__attribute__((noreturn)) void (*ipl)(void) = (void *)ipl_continue;
/* Restore reset PSW */
write_reset_psw(save_psw);
ipl();
/* should not return */
}
void jump_to_IPL_code(uint64_t address)
@ -46,15 +49,12 @@ void jump_to_IPL_code(uint64_t address)
* content of non-BIOS memory after we loaded the guest, so we
* save the original content and restore it in jump_to_IPL_2.
*/
ResetInfo *current = 0;
save = *current;
current->ipl_psw = (uint64_t) &jump_to_IPL_2;
current->ipl_psw |= RESET_PSW_MASK;
current->ipl_continue = address & PSW_MASK_SHORT_ADDR;
debug_print_int("set IPL addr to", current->ipl_continue);
if (address) {
save_psw = *reset_psw;
write_reset_psw(RESET_PSW);
ipl_continue = address;
}
debug_print_int("set IPL addr to", address ?: *reset_psw & PSW_MASK_SHORT_ADDR);
/* Ensure the guest output starts fresh */
sclp_print("\n");
@ -84,7 +84,12 @@ void jump_to_low_kernel(void)
/* Trying to get PSW at zero address */
if (*((uint64_t *)0) & RESET_PSW_MASK) {
jump_to_IPL_code((*((uint64_t *)0)) & PSW_MASK_SHORT_ADDR);
/*
* Surely nobody will try running directly from lowcore, so
* let's use 0 as an indication that we want to load the reset
* psw at 0x0 and not jump to the entry.
*/
jump_to_IPL_code(0);
}
/* No other option left, so use the Linux kernel start address */

View File

@ -51,6 +51,60 @@ unsigned int get_loadparm_index(void)
return atoui(loadparm_str);
}
static int is_dev_possibly_bootable(int dev_no, int sch_no)
{
bool is_virtio;
Schib schib;
int r;
blk_schid.sch_no = sch_no;
r = stsch_err(blk_schid, &schib);
if (r == 3 || r == -EIO) {
return -ENODEV;
}
if (!schib.pmcw.dnv) {
return false;
}
enable_subchannel(blk_schid);
cutype = cu_type(blk_schid);
/*
* Note: we always have to run virtio_is_supported() here to make
* sure that the vdev.senseid data gets pre-initialized correctly
*/
is_virtio = virtio_is_supported(blk_schid);
/* No specific devno given, just return whether the device is possibly bootable */
if (dev_no < 0) {
switch (cutype) {
case CU_TYPE_VIRTIO:
if (is_virtio) {
/*
* Skip net devices since no IPLB is created and therefore
* no network bootloader has been loaded
*/
if (virtio_get_device_type() != VIRTIO_ID_NET) {
return true;
}
}
return false;
case CU_TYPE_DASD_3990:
case CU_TYPE_DASD_2107:
return true;
default:
return false;
}
}
/* Caller asked for a specific devno */
if (schib.pmcw.dev == dev_no) {
return true;
}
return false;
}
/*
* Find the subchannel connected to the given device (dev_no) and fill in the
* subchannel information block (schib) with the connected subchannel's info.
@ -62,53 +116,14 @@ unsigned int get_loadparm_index(void)
*/
static bool find_subch(int dev_no)
{
Schib schib;
int i, r;
bool is_virtio;
for (i = 0; i < 0x10000; i++) {
blk_schid.sch_no = i;
r = stsch_err(blk_schid, &schib);
if ((r == 3) || (r == -EIO)) {
r = is_dev_possibly_bootable(dev_no, i);
if (r < 0) {
break;
}
if (!schib.pmcw.dnv) {
continue;
}
enable_subchannel(blk_schid);
cutype = cu_type(blk_schid);
/*
* Note: we always have to run virtio_is_supported() here to make
* sure that the vdev.senseid data gets pre-initialized correctly
*/
is_virtio = virtio_is_supported(blk_schid);
/* No specific devno given, just return 1st possibly bootable device */
if (dev_no < 0) {
switch (cutype) {
case CU_TYPE_VIRTIO:
if (is_virtio) {
/*
* Skip net devices since no IPLB is created and therefore
* no network bootloader has been loaded
*/
if (virtio_get_device_type() != VIRTIO_ID_NET) {
return true;
}
}
continue;
case CU_TYPE_DASD_3990:
case CU_TYPE_DASD_2107:
return true;
default:
continue;
}
}
/* Caller asked for a specific devno */
if (schib.pmcw.dev == dev_no) {
if (r == true) {
return true;
}
}
@ -167,20 +182,8 @@ static void boot_setup(void)
static void find_boot_device(void)
{
VDev *vdev = virtio_get_device();
int ssid;
bool found;
if (!have_iplb) {
for (ssid = 0; ssid < 0x3; ssid++) {
blk_schid.ssid = ssid;
found = find_subch(-1);
if (found) {
return;
}
}
panic("Could not find a suitable boot device (none specified)\n");
}
switch (iplb.pbt) {
case S390_IPL_TYPE_CCW:
debug_print_int("device no. ", iplb.ccw.devno);
@ -203,7 +206,7 @@ static void find_boot_device(void)
IPL_assert(found, "Boot device not found\n");
}
static void virtio_setup(void)
static int virtio_setup(void)
{
VDev *vdev = virtio_get_device();
QemuIplParameters *early_qipl = (QemuIplParameters *)QIPL_ADDRESS;
@ -218,9 +221,56 @@ static void virtio_setup(void)
sclp_print("Network boot device detected\n");
vdev->netboot_start_addr = qipl.netboot_start_addr;
} else {
virtio_blk_setup_device(blk_schid);
int ret = virtio_blk_setup_device(blk_schid);
if (ret) {
return ret;
}
IPL_assert(virtio_ipl_disk_is_valid(), "No valid IPL device detected");
}
return 0;
}
static void ipl_boot_device(void)
{
switch (cutype) {
case CU_TYPE_DASD_3990:
case CU_TYPE_DASD_2107:
dasd_ipl(blk_schid, cutype); /* no return */
break;
case CU_TYPE_VIRTIO:
if (virtio_setup() == 0) {
zipl_load(); /* Only returns in case of errors */
}
break;
default:
print_int("Attempting to boot from unexpected device type", cutype);
panic("\nBoot failed.\n");
}
}
/*
* No boot device has been specified, so we have to scan through the
* channels to find one.
*/
static void probe_boot_device(void)
{
int ssid, sch_no, ret;
for (ssid = 0; ssid < 0x3; ssid++) {
blk_schid.ssid = ssid;
for (sch_no = 0; sch_no < 0x10000; sch_no++) {
ret = is_dev_possibly_bootable(-1, sch_no);
if (ret < 0) {
break;
}
if (ret == true) {
ipl_boot_device(); /* Only returns if unsuccessful */
}
}
}
sclp_print("Could not find a suitable boot device (none specified)\n");
}
int main(void)
@ -228,21 +278,11 @@ int main(void)
sclp_setup();
css_setup();
boot_setup();
find_boot_device();
enable_subchannel(blk_schid);
switch (cutype) {
case CU_TYPE_DASD_3990:
case CU_TYPE_DASD_2107:
dasd_ipl(blk_schid, cutype); /* no return */
break;
case CU_TYPE_VIRTIO:
virtio_setup();
zipl_load(); /* no return */
break;
default:
print_int("Attempting to boot from unexpected device type", cutype);
panic("");
if (have_iplb) {
find_boot_device();
ipl_boot_device();
} else {
probe_boot_device();
}
panic("Failed to load OS from hard disk\n");

View File

@ -27,12 +27,10 @@ typedef unsigned long long __u64;
#define false 0
#define PAGE_SIZE 4096
#ifndef EIO
#define EIO 1
#endif
#ifndef EBUSY
#define EBUSY 2
#endif
#define ENODEV 3
#ifndef NULL
#define NULL 0
#endif
@ -71,13 +69,14 @@ int sclp_read(char *str, size_t count);
unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
ulong subchan_id, void *load_addr);
bool virtio_is_supported(SubChannelId schid);
void virtio_blk_setup_device(SubChannelId schid);
int virtio_blk_setup_device(SubChannelId schid);
int virtio_read(ulong sector, void *load_addr);
/* bootmap.c */
void zipl_load(void);
/* jump2ipl.c */
void write_reset_psw(uint64_t psw);
void jump_to_IPL_code(uint64_t address);
void jump_to_low_kernel(void);

View File

@ -34,7 +34,10 @@ remainder:
larl %r2,memsetxc
ex %r3,0(%r2)
done:
j main /* And call C */
/* set up a pgm exception disabled wait psw */
larl %r2, disabled_wait_psw
mvc 0x01d0(16), 0(%r2)
j main /* And call C */
memsetxc:
xc 0(1,%r1),0(%r1)

View File

@ -263,9 +263,10 @@ uint64_t virtio_get_blocks(void)
return 0;
}
void virtio_blk_setup_device(SubChannelId schid)
int virtio_blk_setup_device(SubChannelId schid)
{
VDev *vdev = virtio_get_device();
int ret = 0;
vdev->schid = schid;
virtio_setup_ccw(vdev);
@ -288,9 +289,11 @@ void virtio_blk_setup_device(SubChannelId schid)
"Config: CDB size mismatch");
sclp_print("Using virtio-scsi.\n");
virtio_scsi_setup(vdev);
ret = virtio_scsi_setup(vdev);
break;
default:
panic("\n! No IPL device available !\n");
}
return ret;
}

View File

@ -194,7 +194,12 @@ static bool scsi_read_capacity(VDev *vdev,
/* virtio-scsi routines */
static void virtio_scsi_locate_device(VDev *vdev)
/*
* Tries to locate a SCSI device and and adds the information for the found
* device to the vdev->scsi_device structure.
* Returns 0 if SCSI device could be located, or a error code < 0 otherwise
*/
static int virtio_scsi_locate_device(VDev *vdev)
{
const uint16_t channel = 0; /* again, it's what QEMU does */
uint16_t target;
@ -220,7 +225,7 @@ static void virtio_scsi_locate_device(VDev *vdev)
IPL_check(sdev->channel == 0, "non-zero channel requested");
IPL_check(sdev->target <= vdev->config.scsi.max_target, "target# high");
IPL_check(sdev->lun <= vdev->config.scsi.max_lun, "LUN# high");
return;
return 0;
}
for (target = 0; target <= vdev->config.scsi.max_target; target++) {
@ -247,18 +252,20 @@ static void virtio_scsi_locate_device(VDev *vdev)
*/
sdev->lun = r->lun[0].v16[0]; /* it's returned this way */
debug_print_int("Have to use LUN", sdev->lun);
return; /* we have to use this device */
return 0; /* we have to use this device */
}
for (i = 0; i < luns; i++) {
if (r->lun[i].v64) {
/* Look for non-zero LUN - we have where to choose from */
sdev->lun = r->lun[i].v16[0];
debug_print_int("Will use LUN", sdev->lun);
return; /* we have found a device */
return 0; /* we have found a device */
}
}
}
panic("\n! Cannot locate virtio-scsi device !\n");
sclp_print("Warning: Could not locate a usable virtio-scsi device\n");
return -ENODEV;
}
int virtio_scsi_read_many(VDev *vdev,
@ -322,17 +329,20 @@ static void scsi_parse_capacity_report(void *data,
}
}
void virtio_scsi_setup(VDev *vdev)
int virtio_scsi_setup(VDev *vdev)
{
int retry_test_unit_ready = 3;
uint8_t data[256];
uint32_t data_size = sizeof(data);
ScsiInquiryEvpdPages *evpd = &scsi_inquiry_evpd_pages_response;
ScsiInquiryEvpdBl *evpd_bl = &scsi_inquiry_evpd_bl_response;
int i;
int i, ret;
vdev->scsi_device = &default_scsi_device;
virtio_scsi_locate_device(vdev);
ret = virtio_scsi_locate_device(vdev);
if (ret < 0) {
return ret;
}
/* We have to "ping" the device before it becomes readable */
while (!scsi_test_unit_ready(vdev)) {
@ -417,4 +427,6 @@ void virtio_scsi_setup(VDev *vdev)
}
scsi_parse_capacity_report(data, &vdev->scsi_last_block,
(uint32_t *) &vdev->scsi_block_size);
return 0;
}

View File

@ -67,7 +67,7 @@ static inline bool virtio_scsi_response_ok(const VirtioScsiCmdResp *r)
return r->response == VIRTIO_SCSI_S_OK && r->status == CDB_STATUS_GOOD;
}
void virtio_scsi_setup(VDev *vdev);
int virtio_scsi_setup(VDev *vdev);
int virtio_scsi_read_many(VDev *vdev,
ulong sector, void *load_addr, int sec_num);

Binary file not shown.

View File

@ -163,6 +163,18 @@ static void add_s390x_tests(void)
qtest_add_data_func("cdrom/boot/virtio-scsi",
"-device virtio-scsi -device scsi-cd,drive=cdr "
"-blockdev file,node-name=cdr,filename=", test_cdboot);
qtest_add_data_func("cdrom/boot/with-bootindex",
"-device virtio-serial -device virtio-scsi "
"-device virtio-blk,drive=d1 "
"-drive driver=null-co,read-zeroes=on,if=none,id=d1 "
"-device virtio-blk,drive=d2,bootindex=1 "
"-drive if=none,id=d2,media=cdrom,file=", test_cdboot);
qtest_add_data_func("cdrom/boot/without-bootindex",
"-device virtio-scsi -device virtio-serial "
"-device x-terminal3270 -device virtio-blk,drive=d1 "
"-drive driver=null-co,read-zeroes=on,if=none,id=d1 "
"-device virtio-blk,drive=d2 "
"-drive if=none,id=d2,media=cdrom,file=", test_cdboot);
}
int main(int argc, char **argv)