An IPL (booting) on s390 of SCSI disks is done by a firmware component. Lets implement this scheme as an qemu device that also allows to configure the IPL like the HMC. We have a parameter iplid that refers to a disk device and a load parm that specifies the entry on the disk to be ipled. We also provide a default device if no -device s390-ipl statement is given.
Signed-off-by: Christian Borntraeger <borntrae...@de.ibm.com> --- Makefile.target | 2 +- hw/s390-loader.c | 463 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ hw/s390-loader.h | 81 ++++++++++ hw/s390-virtio.c | 36 +---- vl.c | 7 + 5 files changed, 554 insertions(+), 35 deletions(-) create mode 100644 hw/s390-loader.c create mode 100644 hw/s390-loader.h diff --git a/Makefile.target b/Makefile.target index 1582904..7b8cd84 100644 --- a/Makefile.target +++ b/Makefile.target @@ -374,7 +374,7 @@ obj-sh4-y += ide/mmio.o obj-m68k-y = an5206.o mcf5206.o mcf_uart.o mcf_intc.o mcf5208.o mcf_fec.o obj-m68k-y += m68k-semi.o dummy_m68k.o -obj-s390x-y = s390-virtio-bus.o s390-virtio.o +obj-s390x-y = s390-virtio-bus.o s390-virtio.o s390-loader.o obj-alpha-y = mc146818rtc.o obj-alpha-y += alpha_pci.o alpha_dp264.o alpha_typhoon.o diff --git a/hw/s390-loader.c b/hw/s390-loader.c new file mode 100644 index 0000000..2d63ecf --- /dev/null +++ b/hw/s390-loader.c @@ -0,0 +1,463 @@ +/* + * bootloader support + * Copyright IBM Corp. 2007,2012 + * Author: Christian Borntraeger <borntrae...@de.ibm.com> + * + * This file is licensed under the terms of the GNU General Public License(GPL) + */ + +#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 <stdlib.h> +#include <string.h> +#include <unistd.h> +#include "cpu.h" +#include "hw/loader.h" +#include "hw/s390-loader.h" +#include "hw/s390-virtio-bus.h" +#include "hw/sysbus.h" + +#define KERN_IMAGE_START 0x010000UL + +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 int magic_ok(void *tmp) +{ + return memcmp(tmp, "zIPL", 4) == 0 ? 1 : 0; +} + +static uint64_t parse_segment_elements(BlockPtr *bprs, + uint64_t *address, + Loader *loader) +{ + unsigned d; + int len; + + 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(bprs, d))) { + return 0; + } + len = bdrv_pread(loader->bs, + loader->offset(loader->element(bprs, d)), + (void *) (*address + qemu_get_ram_ptr(0)), + loader->size(loader->element(bprs, d))); + if (len != loader->size(loader->element(bprs, d))) { + error_report("s390-ipl: error while parsing bootmap\n"); + exit(1); + } + *address += len; + } + return loader->blockno(loader->element(bprs, loader->entries() - 1)); +} + +static void parse_segment_table(uint64_t blockno, uint64_t address, + Loader *loader) +{ + BlockPtr bprs[loader->entries() + 1]; + + do { + bdrv_pread(loader->bs, blockno * loader->blk_size, bprs, sizeof(bprs)); + blockno = parse_segment_elements(bprs, &address, loader); + } while (blockno); +} + +static uint64_t parse_program(BlockPtr *blockptr, Loader *loader) +{ + ComponentHeader header; + ComponentEntry entry; + uint64_t offset = loader->offset(blockptr); + int ret; + + 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\n"); + exit(1); + } +} + +static uint64_t parse_program_table(BlockPtr *blockptr, + Loader *loader) +{ + BlockPtr entries[loader->entries()]; + uint32_t n; + + 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 exists", + loader->loadparm); + exit(1); + } + + return parse_program(loader->element(entries, loader->loadparm + 1), + loader); +} + +static uint64_t parse_mbr(BlockDriverState *bs, uint8_t loadparm, + uint16_t blk_size) +{ + FCPMbr fmbr; + NewECKDMbr nembr; + ECKDMbr embr; + + int ret; + + loader.bs = bs; + loader.blk_size = be16_to_cpu(fmbr.blockptr.u.fcp.size); + loader.loadparm = loadparm; + loader.blk_size = blk_size; + + + /* is this a scsi bootmap ? */ + bdrv_pread(bs, 0, &fmbr, sizeof(fmbr)); + if (magic_ok(&fmbr.magic)) { + loader.blockno = getblockno_fcp; + loader.offset = getoffset_fcp; + loader.size = getsize_fcp; + loader.empty = getempty_fcp; + loader.element = getelement_fcp; + loader.entries = entries_fcp; + fprintf(stderr, "s390-ipl: detected FCP bootmap\n"); + return parse_program_table(&fmbr.blockptr, &loader); + } + + /* lets try several ECKD bootloader types */ + loader.heads = bs->heads; + loader.secs = bs->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; + + /* new dasd bootmap for CDL*/ + bdrv_pread(bs, blk_size + 92, &nembr, sizeof(nembr)); + if (magic_ok(&nembr.magic)) { + if (nembr.dev_type != DEV_TYPE_ECKD) { + return -1; + } + fprintf(stderr, "s390-ipl: detected new CDL bootmap\n"); + return parse_program_table(&nembr.blockptr, &loader); + } + /* new dasd bootmap for LDL */ + bdrv_pread(bs, 112, &nembr, sizeof(nembr)); + if (magic_ok(&nembr.magic)) { + if (nembr.dev_type != DEV_TYPE_ECKD) { + return -1; + } + fprintf(stderr, "s390-ipl: detected new LDL bootmap\n"); + return parse_program_table(&nembr.blockptr, &loader); + } + + /* classic cdl dasd bootmap, unfortunately there is no magic available */ + loader.element = getelement_eckd; + loader.entries = entries_eckd; + + bdrv_pread(bs, blk_size + 4, &embr, sizeof(embr)); + fprintf(stderr, "s390-ipl: trying old CDL bootmap\n"); + ret = parse_program_table((BlockPtr *) &embr.blockptr, &loader); + if (ret == -1) { + /* last chance, classic ldl dasd bootmap */ + bdrv_pread(bs, blk_size, &embr, sizeof(embr)); + fprintf(stderr, "s390-ipl: CDL failed. trying old LDL bootmap\n"); + ret = parse_program_table((BlockPtr *) &embr.blockptr, &loader); + } + return ret; +} + +/* + * 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(BlockDriverState *bs, uint32_t loadparm, + uint32_t blk_size) +{ + uint64_t address; + + address = parse_mbr(bs, loadparm, blk_size); + if (address == -1) { + return -1; + } + return address & 0x7fffffff; +} + +static void s390_ipl_disk(const char *id, uint32_t loadparm) +{ + uint64_t addr = -1UL; + DeviceState *dev; + VirtIOS390Device *vdev; + DriveInfo *drive; + CPUS390XState *env; + + /* 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"); + return; + } + } else { + error_report("s390-ipl: No bootable disk found"); + return; + } + } else { + dev = qdev_find_recursive(sysbus_get_default(), id); + if (!dev) { + error_report("s390-ipl: Unable to find '%s'", id); + return; + } + } + + vdev = DO_UPCAST(VirtIOS390Device, qdev, dev); + if (!vdev->block.bs) { + error_report("s390-ipl: '%s' is not a block device", id); + return; + } + + addr = load_from_disk(vdev->block.bs, loadparm, + vdev->block.logical_block_size); + if (addr == -1) { + error_report("s390-ipl: %s id \"%s\" does not contain a valid bootmap", + qdev_fw_name(dev), id ? id : ""); + return; + } + + env = qemu_get_cpu(0); + env->psw.addr = addr; + env->psw.mask = 0x0000000180000000ULL; + s390_add_running_cpu(env); +} + +static void s390_ipl_kernel(void) +{ + CPUS390XState *env; + env = qemu_get_cpu(0); + /* + * we can not rely on the ELF entry point, since up to 3.2 this + * value was 0x800 (the SALIPL loader) and it wont work. For + * all (Linux) cases 0x10000 (KERN_IMAGE_START) should be fine. + */ + env->psw.addr = KERN_IMAGE_START; + env->psw.mask = 0x0000000180000000ULL; + s390_add_running_cpu(env); +} + +typedef struct { + SysBusDevice busdev; + uint32_t loadparm; + char *iplid; +} S390IPLState; + +static int s390_ipl_init(SysBusDevice *dev) +{ + return 0; +} + +static Property s390_ipl_properties[] = { + DEFINE_PROP_UINT32("loadparm", S390IPLState, loadparm, 0), + DEFINE_PROP_STRING("iplid", S390IPLState, iplid), + DEFINE_PROP_END_OF_LIST(), +}; + +static void s390_ipl_reset(DeviceState *s390ipl) +{ + S390IPLState *iplstate = container_of(s390ipl, S390IPLState, busdev.qdev); + + /* if the user provides a kernel, we dont load from disk */ + if (rom_ptr(KERN_IMAGE_START)) { + s390_ipl_kernel(); + } else { + s390_ipl_disk(iplstate->iplid, iplstate->loadparm); + } +} + +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_init; + dc->props = s390_ipl_properties; + dc->reset = s390_ipl_reset; +} + +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_info); +} + +type_init(s390_register_ipl) diff --git a/hw/s390-loader.h b/hw/s390-loader.h new file mode 100644 index 0000000..347c07f --- /dev/null +++ b/hw/s390-loader.h @@ -0,0 +1,81 @@ +#include "blockdev.h" +#include "block_int.h" +#include "cpu.h" + +typedef struct { + uint64_t blockno; + uint16_t size; + uint16_t blockct; + uint8_t reserved[4]; +} __attribute__ ((packed)) FCPBlockPtr; + +typedef struct { + uint16_t cyls; + uint16_t heads; + uint8_t secs; + uint16_t block_size; + uint8_t count; + uint8_t reserved[8]; +} __attribute__ ((packed)) NECKDBlockPtr; + +typedef struct { + uint16_t cyls; + uint16_t heads; + uint8_t secs; + uint16_t block_size; + uint8_t count; +} __attribute__ ((packed)) ECKDBlockPtr; + + +typedef struct { + union { + NECKDBlockPtr neckd; + ECKDBlockPtr eckd; + FCPBlockPtr fcp; + } u; +} __attribute__ ((packed)) BlockPtr; + +typedef struct { + BlockPtr blkptr; + uint8_t pad[7]; + uint8_t component_type; + union { + uint64_t load_address; + uint64_t load_psw; + } address; +} __attribute((packed)) ComponentEntry; + +typedef struct { + uint8_t magic[4]; + uint8_t type; + uint8_t reserved[27]; +} __attribute((packed)) ComponentHeader; + +typedef struct { + ECKDBlockPtr blockptr; +} __attribute__ ((packed)) ECKDMbr; + +typedef struct { + char magic[4]; + uint8_t version; + uint8_t bp_type; +#define DEV_TYPE_ECKD 0x00 +#define DEV_TYPE_FBA 0x01 + uint8_t dev_type; + uint8_t flags; + BlockPtr blockptr; + uint8_t reserved[8]; +} __attribute__ ((packed)) NewECKDMbr; + +typedef struct { + char magic[4]; + uint32_t version_id; + uint8_t reserved[8]; + BlockPtr blockptr; +} __attribute__ ((packed)) FCPMbr; + +#define component_execute 0x01 +#define component_load 0x02 + +#define component_header_ipl 0x00 +#define component_header_dump 0x01 diff --git a/hw/s390-virtio.c b/hw/s390-virtio.c index 2c5820f..d68e25f 100644 --- a/hw/s390-virtio.c +++ b/hw/s390-virtio.c @@ -20,6 +20,7 @@ #include "hw.h" #include "block.h" #include "blockdev.h" +#include "block_int.h" #include "sysemu.h" #include "net.h" #include "boards.h" @@ -33,6 +34,7 @@ #include "hw/s390-virtio-bus.h" +#include "hw/s390-loader.h" //#define DEBUG_S390 #ifdef DEBUG_S390 @@ -221,11 +223,7 @@ static void s390_init(ram_addr_t my_ram_size, tmp_env->storage_keys = storage_keys; } - /* One CPU has to run */ - s390_add_running_cpu(env); - if (kernel_filename) { - kernel_size = load_elf(kernel_filename, NULL, NULL, NULL, NULL, NULL, 1, ELF_MACHINE, 0); if (kernel_size == -1UL) { @@ -236,36 +234,6 @@ static void s390_init(ram_addr_t my_ram_size, kernel_filename); exit(1); } - /* - * we can not rely on the ELF entry point, since up to 3.2 this - * value was 0x800 (the SALIPL loader) and it wont work. For - * all (Linux) cases 0x10000 (KERN_IMAGE_START) should be fine. - */ - env->psw.addr = KERN_IMAGE_START; - env->psw.mask = 0x0000000180000000ULL; - } else { - ram_addr_t bios_size = 0; - char *bios_filename; - - /* Load zipl bootloader */ - if (bios_name == NULL) { - bios_name = ZIPL_FILENAME; - } - - bios_filename = qemu_find_file(QEMU_FILE_TYPE_BIOS, bios_name); - bios_size = load_image_targphys(bios_filename, ZIPL_LOAD_ADDR, 4096); - g_free(bios_filename); - - if ((long)bios_size < 0) { - hw_error("could not load bootloader '%s'\n", bios_name); - } - - if (bios_size > 4096) { - hw_error("stage1 bootloader is > 4k\n"); - } - - env->psw.addr = ZIPL_START; - env->psw.mask = 0x0000000180000000ULL; } if (initrd_filename) { diff --git a/vl.c b/vl.c index e724084..f8341c9 100644 --- a/vl.c +++ b/vl.c @@ -273,6 +273,7 @@ static int default_monitor = 1; static int default_floppy = 1; static int default_cdrom = 1; static int default_sdcard = 1; +static int default_loader = 1; static struct { const char *driver; @@ -288,6 +289,7 @@ static struct { { .driver = "virtio-serial-pci", .flag = &default_virtcon }, { .driver = "virtio-serial-s390", .flag = &default_virtcon }, { .driver = "virtio-serial", .flag = &default_virtcon }, + { .driver = "s390-ipl", .flag = &default_loader }, }; static void res_free(void) @@ -3114,6 +3116,7 @@ int main(int argc, char **argv, char **envp) default_floppy = 0; default_cdrom = 0; default_sdcard = 0; + default_loader = 0; vga_model = "none"; break; case QEMU_OPTION_xen_domid: @@ -3268,6 +3271,10 @@ int main(int argc, char **argv, char **envp) qemu_opts_foreach(qemu_find_opts("device"), default_driver_check, NULL, 0); qemu_opts_foreach(qemu_find_opts("global"), default_driver_check, NULL, 0); + if (default_loader && strcmp(machine->alias, "s390") == 0) { + qdev_init_nofail(qdev_create(NULL, "s390-ipl")); + } + if (machine->no_serial) { default_serial = 0; } -- 1.7.9.6