@@ -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
new file mode 100644
@@ -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);
+}
+
new file mode 100644
@@ -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
@@ -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)
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