qemu-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[Qemu-devel] [PATCH 1/1] s390: IPL device for s390


From: Christian Borntraeger
Subject: [Qemu-devel] [PATCH 1/1] s390: IPL device for s390
Date: Fri, 4 May 2012 15:44:00 +0200

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 <address@hidden>
---
 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 <address@hidden>
+ *
+ * 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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]