Patchwork [RFC/PATCH,1/2] s390: Add bootmap parsing to ipl device

login
register
mail settings
Submitter Christian Borntraeger
Date Dec. 7, 2012, 3:14 p.m.
Message ID <1354893290-53019-2-git-send-email-borntraeger@de.ibm.com>
Download mbox | patch
Permalink /patch/204530/
State New
Headers show

Comments

Christian Borntraeger - Dec. 7, 2012, 3:14 p.m.
The zipl bios code only works for specially prepared disks. This adds
parsing of the on disk bootmap of disks that are zipled with a zipl
under LPAR/zVM. Since all bootmaps are pretty similar the code is
written in a way to not only fcp bootmaps (which are architectured and
 also parsed by the firmware on real boxes) but also
- dasd bootmaps for eckd and fba, with new and old versions of zipl
- any kind of block size 512,1024,2048,4096
- bootmaps created with the SLES11 zipl (those understood by the "bios")

To make the behaviour consistent with old code, we have two ipl devices.
A default one (no_user) that just boots the kernel or the bios and one
that can be specified by the user with -device s390-ipl that also allows
to trigger the bootmap parsing.

Signed-off-by: Christian Borntraeger <borntraeger@de.ibm.com>
---
 hw/s390x/Makefile.objs |    2 +-
 hw/s390x/ipl-disk.c    |  499 ++++++++++++++++++++++++++++++++++++++++++++++++
 hw/s390x/ipl-disk.h    |  104 ++++++++++
 hw/s390x/ipl.c         |   51 ++++-
 4 files changed, 654 insertions(+), 2 deletions(-)
 create mode 100644 hw/s390x/ipl-disk.c
 create mode 100644 hw/s390x/ipl-disk.h

Patch

diff --git a/hw/s390x/Makefile.objs b/hw/s390x/Makefile.objs
index 4a5a5d8..a4a7c5a 100644
--- a/hw/s390x/Makefile.objs
+++ b/hw/s390x/Makefile.objs
@@ -4,4 +4,4 @@  obj-y := $(addprefix ../,$(obj-y))
 obj-y += sclp.o
 obj-y += event-facility.o
 obj-y += sclpquiesce.o sclpconsole.o
-obj-y += ipl.o
+obj-y += ipl.o ipl-disk.o
diff --git a/hw/s390x/ipl-disk.c b/hw/s390x/ipl-disk.c
new file mode 100644
index 0000000..1aab32b
--- /dev/null
+++ b/hw/s390x/ipl-disk.c
@@ -0,0 +1,499 @@ 
+/*
+ * disk ipl support
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ *  Christian Borntraeger <borntraeger@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version.  See the COPYING file in the top-level directory.
+ *
+ */
+
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <linux/fs.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <sysemu.h>
+#include "hw/loader.h"
+#include "hw/s390-virtio-bus.h"
+#include "hw/sysbus.h"
+#include "hw/s390x/ipl.h"
+#include "hw/s390x/ipl-disk.h"
+
+
+typedef struct {
+    BlockDriverState *bs;
+    uint64_t (*blockno)(BlockPtr *blockptr);
+    uint64_t (*offset)(BlockPtr *blockptr);
+    uint64_t (*size)(BlockPtr *blockptr);
+    bool (*empty)(BlockPtr *blockptr);
+    BlockPtr *(*element)(BlockPtr *blockptr, int num);
+    uint32_t (*entries)(void);
+    uint32_t loadparm;
+    uint8_t heads;
+    uint8_t secs;
+    uint16_t blk_size;
+} Loader;
+
+/*
+ * We have one structure that is setup with the right callbacks for the
+ * detected type of boot loader
+ */
+static Loader loader;
+
+/* here are the FCP Callbacks */
+static uint64_t getblockno_fcp(BlockPtr *entry)
+{
+    return be64_to_cpu(entry->u.fcp.blockno);
+}
+
+static uint64_t getoffset_fcp(BlockPtr *entry)
+{
+    return getblockno_fcp(entry) * be16_to_cpu(entry->u.fcp.size);
+}
+
+static uint64_t getsize_fcp(BlockPtr *entry)
+{
+    return loader.blk_size * (be16_to_cpu(entry->u.fcp.blockct) + 1);
+}
+
+static bool getempty_fcp(BlockPtr *entry)
+{
+    return getblockno_fcp(entry) == 0UL;
+}
+
+static BlockPtr *getelement_fcp(BlockPtr *blockptr, int num)
+{
+     FCPBlockPtr *fcp = (FCPBlockPtr *) blockptr;
+
+     return (BlockPtr *) &fcp[num];
+}
+
+static uint32_t entries_fcp(void)
+{
+    return loader.blk_size / sizeof(FCPBlockPtr);
+};
+
+/* and here the callbacks for the new and old eckd map */
+static uint64_t getblockno_eckd(BlockPtr *entry)
+{
+    return 1UL * loader.secs * loader.heads * entry->u.eckd.cyls +
+           1UL * loader.secs * entry->u.eckd.heads +
+           1UL * entry->u.eckd.secs - 1UL;
+}
+
+static uint64_t getoffset_eckd(BlockPtr *entry)
+{
+    return getblockno_eckd(entry) * entry->u.eckd.block_size;
+}
+
+static uint64_t getsize_eckd(BlockPtr *entry)
+{
+    return loader.blk_size * (entry->u.eckd.count + 1);
+}
+
+static bool getempty_eckd(BlockPtr *entry)
+{
+    return getblockno_eckd(entry) == -1UL;
+}
+
+static BlockPtr *getelement_eckd(BlockPtr *blockptr, int num)
+{
+     ECKDBlockPtr *eckd = (ECKDBlockPtr *) blockptr;
+
+     return (BlockPtr *) &eckd[num];
+}
+
+static BlockPtr *getelement_neckd(BlockPtr *blockptr, int num)
+{
+     NECKDBlockPtr *neckd = (NECKDBlockPtr *) blockptr;
+
+     return (BlockPtr *) &neckd[num];
+}
+
+static uint32_t entries_eckd(void)
+{
+    return loader.blk_size / sizeof(ECKDBlockPtr);
+};
+
+static uint32_t entries_neckd(void)
+{
+    return loader.blk_size / sizeof(NECKDBlockPtr);
+};
+
+static bool magic_ok(void *tmp)
+{
+    return memcmp(tmp, "zIPL", 4) == 0 ? true : false;
+}
+
+static bool blk_size_ok(uint32_t blk_size)
+{
+    return blk_size == 512 || blk_size == 1024 || blk_size == 2048 ||
+           blk_size == 4096;
+}
+
+static uint64_t parse_segment_elements(BlockPtr *bptr,
+                                       uint64_t *address,
+                                       Loader *loader)
+{
+    unsigned d;
+    int len;
+    BlockPtr *block_entry;
+
+    for (d = 0; d < loader->entries() - 1; d++) {
+        if (*address > ram_size) {
+            error_report("s390-ipl: bootmap points to illegal address");
+            exit(1);
+        }
+        if (loader->empty(loader->element(bptr, d))) {
+            return 0;
+        }
+        len = bdrv_pread(loader->bs,
+                         loader->offset(loader->element(bptr, d)),
+                         (void *) (*address + qemu_get_ram_ptr(0)),
+                         loader->size(loader->element(bptr, d)));
+        if (len != loader->size(loader->element(bptr, d))) {
+            error_report("s390-ipl: error while parsing bootmap");
+            exit(1);
+        }
+        *address += len;
+    }
+
+    block_entry = loader->element(bptr, loader->entries() - 1);
+
+    return loader->blockno(block_entry);
+}
+
+static void parse_segment_table(uint64_t blockno, uint64_t address,
+                                Loader *loader)
+{
+    BlockPtr entries[loader->entries() + 1];
+
+    do {
+        bdrv_pread(loader->bs, blockno * loader->blk_size, entries,
+                   sizeof(entries));
+        blockno = parse_segment_elements(entries, &address, loader);
+    } while (blockno);
+}
+
+static uint64_t parse_program(BlockPtr *blockptr, Loader *loader)
+{
+    int ret;
+    uint64_t offset = loader->offset(blockptr);
+    ComponentHeader header;
+    ComponentEntry entry;
+
+    ret = bdrv_pread(loader->bs, offset, &header, sizeof(header));
+    if (ret != sizeof(header)) {
+        return -1;
+    }
+    if (!magic_ok(&header.magic)) {
+        return -1;
+    }
+
+    if (header.type != component_header_ipl) {
+        error_report("s390-ipl: no IPL header on bootdevice\n");
+        exit(1);
+    }
+
+    offset += sizeof(header);
+    ret = bdrv_pread(loader->bs, offset, &entry, sizeof(entry));
+    if (ret != sizeof(header)) {
+        return -1;
+    }
+
+    while (entry.component_type == component_load) {
+        parse_segment_table(loader->blockno(&entry.blkptr),
+                            entry.address.load_address, loader);
+        offset += sizeof(entry);
+        ret = bdrv_pread(loader->bs, offset, &entry, sizeof(entry));
+        if (ret != sizeof(header)) {
+            return -1;
+        }
+    }
+    if (entry.component_type == component_execute) {
+        return entry.address.load_address;
+    } else {
+        error_report("s390-ipl: no IPL address on bootmap");
+        exit(1);
+    }
+}
+
+static uint64_t parse_program_table(BlockPtr *blockptr,
+                                    Loader *loader)
+{
+    uint32_t n;
+    BlockPtr *block_entry;
+    BlockPtr entries[loader->entries()];
+
+    if (bdrv_pread(loader->bs, loader->offset(blockptr),
+                   entries, loader->blk_size) != loader->blk_size) {
+        return -1;
+    }
+
+    /* entry 0, holds the magic */
+    if (!magic_ok(&entries[0])) {
+        return -1;
+    }
+
+    /* Get the number of entries */
+    for (n = 1; n < loader->entries(); n++) {
+        if (loader->empty(loader->element(entries, n))) {
+            break;
+        }
+    }
+
+    /*
+     * on disk: 0 = magic, 1 = default, 2..n = entries
+     * on HMC: 0 = default, 1..m = entries
+     */
+    if (loader->loadparm >= n - 1) {
+        error_report("s390-ipl: Loadparm entry %d does not exist",
+                     loader->loadparm);
+        exit(1);
+    }
+
+    block_entry = loader->element(entries, loader->loadparm + 1);
+
+    return parse_program(block_entry, loader);
+}
+
+static uint64_t load_scsi_disk(BlockConf conf)
+{
+    FCPMbr fmbr;
+
+    bdrv_pread(conf.bs, 0, &fmbr, sizeof(fmbr));
+    if (magic_ok(&fmbr.magic) &&
+        blk_size_ok(be16_to_cpu(fmbr.blockptr.u.fcp.size))) {
+        loader.blk_size = be16_to_cpu(fmbr.blockptr.u.fcp.size);
+        return parse_program_table(&fmbr.blockptr, &loader);
+    }
+    return -1;
+}
+
+static uint64_t load_new_ldl_disk(BlockConf conf)
+{
+    NewECKDMbr nembr;
+
+    bdrv_pread(conf.bs, 112, &nembr, sizeof(nembr));
+    if (magic_ok(&nembr.magic) &&
+        blk_size_ok(be16_to_cpu(nembr.blockptr.u.neckd.block_size)) &&
+        nembr.dev_type == DEV_TYPE_ECKD) {
+        loader.blk_size = be16_to_cpu(nembr.blockptr.u.neckd.block_size);
+        return parse_program_table(&nembr.blockptr, &loader);
+    }
+    return -1;
+}
+
+static uint64_t parse_cdl(BlockConf conf, uint16_t blk_size)
+{
+    NewECKDMbr nembr;
+
+    loader.blk_size = blk_size;
+
+    /* new dasd bootmap for CDL*/
+    bdrv_pread(conf.bs, blk_size + 92, &nembr, sizeof(nembr));
+    if (magic_ok(&nembr.magic) &&
+        blk_size_ok(be16_to_cpu(nembr.blockptr.u.neckd.block_size)) &&
+        nembr.dev_type == DEV_TYPE_ECKD) {
+        return parse_program_table(&nembr.blockptr, &loader);
+    }
+
+    return -1;
+}
+
+static uint64_t load_new_cdl_disk(BlockConf conf)
+{
+    uint64_t address;
+
+    address = parse_cdl(conf, 4096);
+    if (address != -1) {
+        return address;
+    }
+    address = parse_cdl(conf, 2048);
+    if (address != -1) {
+        return address;
+    }
+    address = parse_cdl(conf, 1024);
+    if (address != -1) {
+        return address;
+    }
+    return parse_cdl(conf, 512);
+}
+
+static uint64_t parse_classic(BlockConf conf, uint16_t blk_size)
+{
+    int ret = -1;
+    ECKDMbr embr;
+
+    loader.blk_size = blk_size;
+
+    /* unfortunately there is no magic available */
+
+    /* classic cdl dasd bootmap */
+    bdrv_pread(conf.bs, blk_size + 4, &embr, sizeof(embr));
+    if (blk_size_ok(be16_to_cpu(embr.blockptr.block_size))) {
+        ret = parse_program_table((BlockPtr *) &embr.blockptr, &loader);
+    }
+
+    if (ret == -1) {
+        /* last chance, classic ldl dasd bootmap */
+        bdrv_pread(conf.bs, blk_size, &embr, sizeof(embr));
+        if (blk_size_ok(be16_to_cpu(embr.blockptr.block_size))) {
+            ret = parse_program_table((BlockPtr *) &embr.blockptr, &loader);
+        }
+    }
+    return ret;
+}
+
+static uint64_t load_classic_cdl_or_ldl_disk(BlockConf conf)
+{
+    uint64_t address;
+
+    address = parse_classic(conf, 4096);
+    if (address != -1) {
+        return address;
+    }
+    address = parse_classic(conf, 2048);
+    if (address != -1) {
+        return address;
+    }
+    address = parse_classic(conf, 1024);
+    if (address != -1) {
+        return address;
+    }
+    return parse_classic(conf, 512);
+}
+
+/*
+ * looks at the program tables written by the boot loader to load
+ * everything which is specified in the bootmap
+ */
+static unsigned long load_from_disk(BlockConf conf, uint32_t loadparm)
+{
+    uint64_t address;
+
+    loader.bs       = conf.bs;
+    loader.loadparm = loadparm;
+
+    /* try SCSI first */
+    loader.blockno  = getblockno_fcp;
+    loader.offset   = getoffset_fcp;
+    loader.size     = getsize_fcp;
+    loader.empty    = getempty_fcp;
+    loader.element  = getelement_fcp;
+    loader.entries  = entries_fcp;
+
+    address = load_scsi_disk(conf);
+    if (address != -1) {
+        return address & 0x7fffffff;
+    }
+
+    /* lets try several ECKD boot-loader types */
+    loader.heads    = conf.heads;
+    loader.secs     = conf.secs;
+    loader.blockno  = getblockno_eckd;
+    loader.offset   = getoffset_eckd;
+    loader.size     = getsize_eckd;
+    loader.empty    = getempty_eckd;
+    loader.element  = getelement_neckd;
+    loader.entries  = entries_neckd;
+
+    /* try new DASD LDL format */
+    address = load_new_ldl_disk(conf);
+    if (address != -1) {
+        return address & 0x7fffffff;
+    }
+
+    /* try new DASD CDL format with various block sizes */
+    address = load_new_cdl_disk(conf);
+    if (address != -1) {
+        return address & 0x7fffffff;
+    }
+
+    loader.element  = getelement_eckd;
+    loader.entries  = entries_eckd;
+
+    /* try classic CDL and LDL formats with various block sizes */
+    address = load_classic_cdl_or_ldl_disk(conf);
+    if (address != -1) {
+        return address & 0x7fffffff;
+    }
+    return -1;
+}
+
+static VirtIOBlkConf *getVirtIOBlkConf(DeviceState *dev, const char *id)
+{
+    VirtIOBlkConf *blk;
+
+    if (strcmp(dev->parent_bus->name, "s390-virtio") == 0) {
+        VirtIOS390Device *s390dev;
+        s390dev = DO_UPCAST(VirtIOS390Device, qdev, dev);
+        blk = &s390dev->blk;
+    } else {
+        error_report("s390-ipl: device '%s' is not a virtio device",
+                     id ? id : "0");
+        exit(1);
+    }
+
+    if (!blk->conf.bs) {
+        error_report("s390-ipl: device '%s' is not a block device",
+                     id ? id : "0");
+        exit(1);
+    }
+    return blk;
+}
+
+void s390_ipl_disk(const char *id, uint32_t loadparm)
+{
+    uint64_t addr = -1UL;
+    DeviceState *dev;
+    VirtIOBlkConf *blk;
+    DriveInfo *drive;
+
+    /* If no disk is specified, use the first one */
+    if (!id) {
+        /*
+         * libvirt and friends use if=none to create the device itself,
+         * standard command line without an if= will result in virtio.
+         * Lets search both types for a device
+         */
+        drive = drive_get_by_index(IF_NONE, 0);
+        if (!drive) {
+            drive = drive_get_by_index(IF_VIRTIO, 0);
+        }
+        if (drive) {
+            dev = bdrv_get_attached_dev(drive->bdrv);
+            if (!dev) {
+                error_report("s390-ipl: First drive has no attached device");
+                exit(1);
+            }
+        } else {
+            error_report("s390-ipl: No bootable disk found");
+            exit(1);
+        }
+    } else {
+        dev = qdev_find_recursive(sysbus_get_default(), id);
+        if (!dev) {
+            error_report("s390-ipl: Unable to find device '%s'", id);
+            exit(1);
+        }
+    }
+
+    blk = getVirtIOBlkConf(dev, id); /* or fail if no block device */
+
+    addr = load_from_disk(blk->conf, loadparm);
+    if (addr == -1)  {
+        error_report("s390-ipl: %s id '%s' does not contain a valid bootmap",
+                     qdev_fw_name(dev), id ? id : "0");
+        exit(1);
+    }
+
+    s390_ipl_cpu(addr);
+}
+
diff --git a/hw/s390x/ipl-disk.h b/hw/s390x/ipl-disk.h
new file mode 100644
index 0000000..9cab0cb
--- /dev/null
+++ b/hw/s390x/ipl-disk.h
@@ -0,0 +1,104 @@ 
+/*
+ * ipl support
+ *
+ * Copyright IBM, Corp. 2012
+ *
+ * Authors:
+ *  Christian Borntraeger <borntraeger@de.ibm.com>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at your
+ * option) any later version.  See the COPYING file in the top-level directory.
+ *
+ */
+
+
+#ifndef S390_IPL_DISK_H
+#define S390_IPL_DISK_H
+
+#include "blockdev.h"
+#include "block_int.h"
+#include "cpu.h"
+#include "hw/s390x/ipl.h"
+
+typedef struct {
+    uint64_t blockno;
+    uint16_t size;
+    uint16_t blockct;
+    uint8_t reserved[4];
+} QEMU_PACKED FCPBlockPtr;
+
+typedef struct {
+    uint16_t cyls;
+    uint16_t heads;
+    uint8_t secs;
+    uint16_t block_size;
+    uint8_t count;
+    uint8_t reserved[8];
+} QEMU_PACKED NECKDBlockPtr;
+
+typedef struct {
+    uint16_t cyls;
+    uint16_t heads;
+    uint8_t secs;
+    uint16_t block_size;
+    uint8_t count;
+} QEMU_PACKED ECKDBlockPtr;
+
+
+typedef struct {
+    union {
+        NECKDBlockPtr neckd;
+        ECKDBlockPtr eckd;
+        FCPBlockPtr  fcp;
+    } u;
+} QEMU_PACKED BlockPtr;
+
+typedef struct {
+    BlockPtr blkptr;
+    uint8_t pad[7];
+    uint8_t component_type;
+    union {
+        uint64_t load_address;
+        uint64_t load_psw;
+    } address;
+} QEMU_PACKED ComponentEntry;
+
+typedef struct {
+    uint8_t magic[4];
+    uint8_t type;
+    uint8_t reserved[27];
+} QEMU_PACKED ComponentHeader;
+
+typedef struct {
+    ECKDBlockPtr blockptr;
+} QEMU_PACKED ECKDMbr;
+
+#define DEV_TYPE_ECKD    0x00
+#define DEV_TYPE_FBA     0x01
+
+typedef struct {
+    char    magic[4];
+    uint8_t version;
+    uint8_t bp_type;
+    uint8_t dev_type;
+    uint8_t flags;
+    BlockPtr blockptr;
+    uint8_t reserved[8];
+} QEMU_PACKED NewECKDMbr;
+
+typedef struct {
+    char magic[4];
+    uint32_t version_id;
+    uint8_t reserved[8];
+    BlockPtr blockptr;
+} QEMU_PACKED FCPMbr;
+
+#define component_execute 0x01
+#define component_load    0x02
+
+#define component_header_ipl 0x00
+
+/* IPLs the given device id */
+void s390_ipl_disk(const char *id, uint32_t loadparm);
+
+#endif //S390_IPL_DISK_H
diff --git a/hw/s390x/ipl.c b/hw/s390x/ipl.c
index 86249cd..c2ef8b6 100644
--- a/hw/s390x/ipl.c
+++ b/hw/s390x/ipl.c
@@ -16,6 +16,7 @@ 
 #include "hw/loader.h"
 #include "hw/sysbus.h"
 #include "hw/s390x/ipl.h"
+#include "hw/s390x/ipl-disk.h"
 
 void s390_ipl_cpu(uint64_t pswaddr)
 {
@@ -25,17 +26,36 @@  void s390_ipl_cpu(uint64_t pswaddr)
     s390_add_running_cpu(env);
 }
 
+typedef struct {
+    SysBusDevice busdev;
+    uint32_t loadparm;
+    uint8_t use_bios;
+    char *iplid;
+} S390IPLState;
+
 static int s390_ipl_default_init(SysBusDevice *dev)
 {
     return 0;
 }
 
+static int s390_ipl_user_init(SysBusDevice *dev)
+{
+    dev->qdev.id = strdup("s390-ipl");
+    return 0;
+}
+
 static Property s390_ipl_properties[] = {
+        DEFINE_PROP_UINT32("loadparm", S390IPLState, loadparm, 0),
+        DEFINE_PROP_STRING("iplid", S390IPLState, iplid),
+        DEFINE_PROP_UINT8("use_bios", S390IPLState, use_bios, 0),
         DEFINE_PROP_END_OF_LIST(),
 };
 
 static void s390_ipl_reset(DeviceState *dev)
 {
+    S390IPLState *iplstate;
+    DeviceState *idev;
+
     if (rom_ptr(KERN_IMAGE_START)) {
         /*
          * we can not rely on the ELF entry point, since up to 3.2 this
@@ -44,7 +64,18 @@  static void s390_ipl_reset(DeviceState *dev)
          */
         return s390_ipl_cpu(KERN_IMAGE_START);
     }
-    return s390_ipl_cpu(ZIPL_IMAGE_START);
+
+    idev = qdev_find_recursive(sysbus_get_default(), "s390-ipl");
+    if (idev) {
+        iplstate = container_of(idev, S390IPLState, busdev.qdev);
+    } else {
+        iplstate = container_of(dev, S390IPLState, busdev.qdev);
+    }
+    if (iplstate->use_bios) {
+        return s390_ipl_cpu(ZIPL_IMAGE_START);
+    } else {
+        return s390_ipl_disk(iplstate->iplid, iplstate->loadparm);
+    }
 }
 
 static void s390_ipl_default_class_init(ObjectClass *klass, void *data)
@@ -58,15 +89,33 @@  static void s390_ipl_default_class_init(ObjectClass *klass, void *data)
     dc->no_user = 1;
 }
 
+static void s390_ipl_class_init(ObjectClass *klass, void *data)
+{
+    DeviceClass *dc = DEVICE_CLASS(klass);
+    SysBusDeviceClass *k = SYS_BUS_DEVICE_CLASS(klass);
+
+    k->init = s390_ipl_user_init;
+    dc->props = s390_ipl_properties;
+}
+
 static TypeInfo s390_ipl_default_info = {
     .class_init = s390_ipl_default_class_init,
     .parent = TYPE_SYS_BUS_DEVICE,
     .name  = "s390-ipl-default",
+    .instance_size  = sizeof(S390IPLState),
+};
+
+static TypeInfo s390_ipl_info = {
+    .class_init = s390_ipl_class_init,
+    .parent = TYPE_SYS_BUS_DEVICE,
+    .name  = "s390-ipl",
+    .instance_size  = sizeof(S390IPLState),
 };
 
 static void s390_register_ipl(void)
 {
     type_register_static(&s390_ipl_default_info);
+    type_register_static(&s390_ipl_info);
 }
 
 type_init(s390_register_ipl)