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 <borntrae...@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 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 <borntrae...@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 <borntrae...@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) -- 1.7.10.1