summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--pc-bios/s390-ccw.imgbin9432 -> 17624 bytes
-rw-r--r--pc-bios/s390-ccw/bootmap.c445
-rw-r--r--pc-bios/s390-ccw/bootmap.h344
-rw-r--r--pc-bios/s390-ccw/main.c13
-rw-r--r--pc-bios/s390-ccw/s390-ccw.h38
-rw-r--r--pc-bios/s390-ccw/sclp-ascii.c4
-rw-r--r--pc-bios/s390-ccw/virtio.c122
-rw-r--r--pc-bios/s390-ccw/virtio.h50
8 files changed, 837 insertions, 179 deletions
diff --git a/pc-bios/s390-ccw.img b/pc-bios/s390-ccw.img
index 1c7f7640fc..603e19e003 100644
--- a/pc-bios/s390-ccw.img
+++ b/pc-bios/s390-ccw.img
Binary files differdiff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c
index 5ee3fcb38f..fa54abb4d3 100644
--- a/pc-bios/s390-ccw/bootmap.c
+++ b/pc-bios/s390-ccw/bootmap.c
@@ -9,8 +9,12 @@
  */
 
 #include "s390-ccw.h"
+#include "bootmap.h"
+#include "virtio.h"
 
-// #define DEBUG_FALLBACK
+#ifdef DEBUG
+/* #define DEBUG_FALLBACK */
+#endif
 
 #ifdef DEBUG_FALLBACK
 #define dputs(txt) \
@@ -20,43 +24,8 @@
     do { } while (0)
 #endif
 
-struct scsi_blockptr {
-    uint64_t blockno;
-    uint16_t size;
-    uint16_t blockct;
-    uint8_t reserved[4];
-} __attribute__ ((packed));
-
-struct component_entry {
-    struct scsi_blockptr data;
-    uint8_t pad[7];
-    uint8_t component_type;
-    uint64_t load_address;
-} __attribute((packed));
-
-struct component_header {
-    uint8_t magic[4];
-    uint8_t type;
-    uint8_t reserved[27];
-} __attribute((packed));
-
-struct mbr {
-    uint8_t magic[4];
-    uint32_t version_id;
-    uint8_t reserved[8];
-    struct scsi_blockptr blockptr;
-} __attribute__ ((packed));
-
-#define ZIPL_MAGIC			"zIPL"
-
-#define ZIPL_COMP_HEADER_IPL		0x00
-#define ZIPL_COMP_HEADER_DUMP		0x01
-
-#define ZIPL_COMP_ENTRY_LOAD		0x02
-#define ZIPL_COMP_ENTRY_EXEC		0x01
-
 /* Scratch space */
-static uint8_t sec[SECTOR_SIZE] __attribute__((__aligned__(SECTOR_SIZE)));
+static uint8_t sec[MAX_SECTOR_SIZE*4] __attribute__((__aligned__(PAGE_SIZE)));
 
 typedef struct ResetInfo {
     uint32_t ipl_mask;
@@ -104,43 +73,243 @@ static void jump_to_IPL_code(uint64_t address)
     virtio_panic("\n! IPL returns !\n");
 }
 
-/* Check for ZIPL magic. Returns 0 if not matched. */
-static int zipl_magic(uint8_t *ptr)
+/***********************************************************************
+ * IPL an ECKD DASD (CDL or LDL/CMS format)
+ */
+
+static unsigned char _bprs[8*1024]; /* guessed "max" ECKD sector size */
+const int max_bprs_entries = sizeof(_bprs) / sizeof(ExtEckdBlockPtr);
+
+static inline void verify_boot_info(BootInfo *bip)
+{
+    IPL_assert(magic_match(bip->magic, ZIPL_MAGIC), "No zIPL magic");
+    IPL_assert(bip->version == BOOT_INFO_VERSION, "Wrong zIPL version");
+    IPL_assert(bip->bp_type == BOOT_INFO_BP_TYPE_IPL, "DASD is not for IPL");
+    IPL_assert(bip->dev_type == BOOT_INFO_DEV_TYPE_ECKD, "DASD is not ECKD");
+    IPL_assert(bip->flags == BOOT_INFO_FLAGS_ARCH, "Not for this arch");
+    IPL_assert(block_size_ok(bip->bp.ipl.bm_ptr.eckd.bptr.size),
+               "Bad block size in zIPL section of the 1st record.");
+}
+
+static bool eckd_valid_address(BootMapPointer *p)
 {
-    uint32_t *p = (void*)ptr;
-    uint32_t *z = (void*)ZIPL_MAGIC;
+    const uint64_t cylinder = p->eckd.cylinder
+                            + ((p->eckd.head & 0xfff0) << 12);
+    const uint64_t head = p->eckd.head & 0x000f;
+
+    if (head >= virtio_get_heads()
+        ||  p->eckd.sector > virtio_get_sectors()
+        ||  p->eckd.sector <= 0) {
+        return false;
+    }
 
-    if (*p != *z) {
-        debug_print_int("invalid magic", *p);
-        virtio_panic("invalid magic");
+    if (!virtio_guessed_disk_nature() && cylinder >= virtio_get_cylinders()) {
+        return false;
     }
 
-    return 1;
+    return true;
 }
 
-#define FREE_SPACE_FILLER '\xAA'
+static block_number_t eckd_block_num(BootMapPointer *p)
+{
+    const uint64_t sectors = virtio_get_sectors();
+    const uint64_t heads = virtio_get_heads();
+    const uint64_t cylinder = p->eckd.cylinder
+                            + ((p->eckd.head & 0xfff0) << 12);
+    const uint64_t head = p->eckd.head & 0x000f;
+    const block_number_t block = sectors * heads * cylinder
+                               + sectors * head
+                               + p->eckd.sector
+                               - 1; /* block nr starts with zero */
+    return block;
+}
 
-static inline bool unused_space(const void *p, unsigned int size)
+static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
 {
-    int i;
-    const unsigned char *m = p;
+    block_number_t block_nr;
+    int j, rc;
+    BootMapPointer *bprs = (void *)_bprs;
+    bool more_data;
+
+    memset(_bprs, FREE_SPACE_FILLER, sizeof(_bprs));
+    read_block(blk, bprs, "BPRS read failed");
+
+    do {
+        more_data = false;
+        for (j = 0;; j++) {
+            block_nr = eckd_block_num((void *)&(bprs[j].xeckd));
+            if (is_null_block_number(block_nr)) { /* end of chunk */
+                break;
+            }
+
+            /* we need the updated blockno for the next indirect entry
+             * in the chain, but don't want to advance address
+             */
+            if (j == (max_bprs_entries - 1)) {
+                break;
+            }
+
+            IPL_assert(block_size_ok(bprs[j].xeckd.bptr.size),
+                       "bad chunk block size");
+            IPL_assert(eckd_valid_address(&bprs[j]), "bad chunk ECKD addr");
+
+            if ((bprs[j].xeckd.bptr.count == 0) && unused_space(&(bprs[j+1]),
+                sizeof(EckdBlockPtr))) {
+                /* This is a "continue" pointer.
+                 * This ptr should be the last one in the current
+                 * script section.
+                 * I.e. the next ptr must point to the unused memory area
+                 */
+                memset(_bprs, FREE_SPACE_FILLER, sizeof(_bprs));
+                read_block(block_nr, bprs, "BPRS continuation read failed");
+                more_data = true;
+                break;
+            }
 
-    for (i = 0; i < size; i++) {
-        if (m[i] != FREE_SPACE_FILLER) {
-            return false;
+            /* Load (count+1) blocks of code at (block_nr)
+             * to memory (address).
+             */
+            rc = virtio_read_many(block_nr, (void *)(*address),
+                                  bprs[j].xeckd.bptr.count+1);
+            IPL_assert(rc == 0, "code chunk read failed");
+
+            *address += (bprs[j].xeckd.bptr.count+1) * virtio_get_block_size();
         }
+    } while (more_data);
+    return block_nr;
+}
+
+static void run_eckd_boot_script(block_number_t mbr_block_nr)
+{
+    int i;
+    block_number_t block_nr;
+    uint64_t address;
+    ScsiMbr *scsi_mbr = (void *)sec;
+    BootMapScript *bms = (void *)sec;
+
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    read_block(mbr_block_nr, sec, "Cannot read MBR");
+
+    block_nr = eckd_block_num((void *)&(scsi_mbr->blockptr));
+
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    read_block(block_nr, sec, "Cannot read Boot Map Script");
+
+    for (i = 0; bms->entry[i].type == BOOT_SCRIPT_LOAD; i++) {
+        address = bms->entry[i].address.load_address;
+        block_nr = eckd_block_num(&(bms->entry[i].blkptr));
+
+        do {
+            block_nr = load_eckd_segments(block_nr, &address);
+        } while (block_nr != -1);
+    }
+
+    IPL_assert(bms->entry[i].type == BOOT_SCRIPT_EXEC,
+               "Unknown script entry type");
+    jump_to_IPL_code(bms->entry[i].address.load_address); /* no return */
+}
+
+static void ipl_eckd_cdl(void)
+{
+    XEckdMbr *mbr;
+    Ipl2 *ipl2 = (void *)sec;
+    IplVolumeLabel *vlbl = (void *)sec;
+    block_number_t block_nr;
+
+    /* we have just read the block #0 and recognized it as "IPL1" */
+    sclp_print("CDL\n");
+
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    read_block(1, ipl2, "Cannot read IPL2 record at block 1");
+    IPL_assert(magic_match(ipl2, IPL2_MAGIC), "No IPL2 record");
+
+    mbr = &ipl2->u.x.mbr;
+    IPL_assert(magic_match(mbr, ZIPL_MAGIC), "No zIPL section in IPL2 record.");
+    IPL_assert(block_size_ok(mbr->blockptr.xeckd.bptr.size),
+               "Bad block size in zIPL section of IPL2 record.");
+    IPL_assert(mbr->dev_type == DEV_TYPE_ECKD,
+               "Non-ECKD device type in zIPL section of IPL2 record.");
+
+    /* save pointer to Boot Script */
+    block_nr = eckd_block_num((void *)&(mbr->blockptr));
+
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    read_block(2, vlbl, "Cannot read Volume Label at block 2");
+    IPL_assert(magic_match(vlbl->key, VOL1_MAGIC),
+               "Invalid magic of volume label block");
+    IPL_assert(magic_match(vlbl->f.key, VOL1_MAGIC),
+               "Invalid magic of volser block");
+    print_volser(vlbl->f.volser);
+
+    run_eckd_boot_script(block_nr);
+    /* no return */
+}
+
+static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
+{
+    LDL_VTOC *vlbl = (void *)sec; /* already read, 3rd block */
+    char msg[4] = { '?', '.', '\n', '\0' };
+    block_number_t block_nr;
+    BootInfo *bip;
+
+    sclp_print((mode == ECKD_CMS) ? "CMS" : "LDL");
+    sclp_print(" version ");
+    switch (vlbl->LDL_version) {
+    case LDL1_VERSION:
+        msg[0] = '1';
+        break;
+    case LDL2_VERSION:
+        msg[0] = '2';
+        break;
+    default:
+        msg[0] = vlbl->LDL_version;
+        msg[0] &= 0x0f; /* convert EBCDIC   */
+        msg[0] |= 0x30; /* to ASCII (digit) */
+        msg[1] = '?';
+        break;
+    }
+    sclp_print(msg);
+    print_volser(vlbl->volser);
+
+    /* DO NOT read BootMap pointer (only one, xECKD) at block #2 */
+
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    read_block(0, sec, "Cannot read block 0");
+    bip = (void *)(sec + 0x70); /* "boot info" is "eckd mbr" for LDL */
+    verify_boot_info(bip);
+
+    block_nr = eckd_block_num((void *)&(bip->bp.ipl.bm_ptr.eckd.bptr));
+    run_eckd_boot_script(block_nr);
+    /* no return */
+}
+
+static void ipl_eckd(ECKD_IPL_mode_t mode)
+{
+    switch (mode) {
+    case ECKD_CDL:
+        ipl_eckd_cdl(); /* no return */
+    case ECKD_CMS:
+    case ECKD_LDL:
+        ipl_eckd_ldl(mode); /* no return */
+    default:
+        virtio_panic("\n! Unknown ECKD IPL mode !\n");
     }
-    return true;
 }
 
-static int zipl_load_segment(struct component_entry *entry)
+/***********************************************************************
+ * IPL a SCSI disk
+ */
+
+static void zipl_load_segment(ComponentEntry *entry)
 {
-    const int max_entries = (SECTOR_SIZE / sizeof(struct scsi_blockptr));
-    struct scsi_blockptr *bprs = (void*)sec;
+    const int max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr));
+    ScsiBlockPtr *bprs = (void *)sec;
     const int bprs_size = sizeof(sec);
-    uint64_t blockno;
-    long address;
+    block_number_t blockno;
+    uint64_t address;
     int i;
+    char err_msg[] = "zIPL failed to read BPRS at 0xZZZZZZZZZZZZZZZZ";
+    char *blk_no = &err_msg[30]; /* where to print blockno in (those ZZs) */
 
     blockno = entry->data.blockno;
     address = entry->load_address;
@@ -150,25 +319,25 @@ static int zipl_load_segment(struct component_entry *entry)
 
     do {
         memset(bprs, FREE_SPACE_FILLER, bprs_size);
-        if (virtio_read(blockno, (uint8_t *)bprs)) {
-            debug_print_int("failed reading bprs at", blockno);
-            goto fail;
-        }
+        fill_hex_val(blk_no, &blockno, sizeof(blockno));
+        read_block(blockno, bprs, err_msg);
 
         for (i = 0;; i++) {
-            u64 *cur_desc = (void*)&bprs[i];
+            uint64_t *cur_desc = (void *)&bprs[i];
 
             blockno = bprs[i].blockno;
-            if (!blockno)
+            if (!blockno) {
                 break;
+            }
 
             /* we need the updated blockno for the next indirect entry in the
                chain, but don't want to advance address */
-            if (i == (max_entries - 1))
+            if (i == (max_entries - 1)) {
                 break;
+            }
 
             if (bprs[i].blockct == 0 && unused_space(&bprs[i + 1],
-                sizeof(struct scsi_blockptr))) {
+                sizeof(ScsiBlockPtr))) {
                 /* This is a "continue" pointer.
                  * This ptr is the last one in the current script section.
                  * I.e. the next ptr must point to the unused memory area.
@@ -178,102 +347,66 @@ static int zipl_load_segment(struct component_entry *entry)
                 break;
             }
             address = virtio_load_direct(cur_desc[0], cur_desc[1], 0,
-                                         (void*)address);
-            if (address == -1)
-                goto fail;
+                                         (void *)address);
+            IPL_assert(address != -1, "zIPL load segment failed");
         }
     } while (blockno);
-
-    return 0;
-
-fail:
-    sclp_print("failed loading segment\n");
-    return -1;
 }
 
 /* Run a zipl program */
-static int zipl_run(struct scsi_blockptr *pte)
+static void zipl_run(ScsiBlockPtr *pte)
 {
-    struct component_header *header;
-    struct component_entry *entry;
-    uint8_t tmp_sec[SECTOR_SIZE];
+    ComponentHeader *header;
+    ComponentEntry *entry;
+    uint8_t tmp_sec[MAX_SECTOR_SIZE];
 
-    virtio_read(pte->blockno, tmp_sec);
-    header = (struct component_header *)tmp_sec;
+    read_block(pte->blockno, tmp_sec, "Cannot read header");
+    header = (ComponentHeader *)tmp_sec;
 
-    if (!zipl_magic(tmp_sec)) {
-        goto fail;
-    }
-
-    if (header->type != ZIPL_COMP_HEADER_IPL) {
-        goto fail;
-    }
+    IPL_assert(magic_match(tmp_sec, ZIPL_MAGIC), "No zIPL magic");
+    IPL_assert(header->type == ZIPL_COMP_HEADER_IPL, "Bad header type");
 
     dputs("start loading images\n");
 
     /* Load image(s) into RAM */
-    entry = (struct component_entry *)(&header[1]);
+    entry = (ComponentEntry *)(&header[1]);
     while (entry->component_type == ZIPL_COMP_ENTRY_LOAD) {
-        if (zipl_load_segment(entry) < 0) {
-            goto fail;
-        }
+        zipl_load_segment(entry);
 
         entry++;
 
-        if ((uint8_t*)(&entry[1]) > (tmp_sec + SECTOR_SIZE)) {
-            goto fail;
-        }
+        IPL_assert((uint8_t *)(&entry[1]) <= (tmp_sec + MAX_SECTOR_SIZE),
+                   "Wrong entry value");
     }
 
-    if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) {
-        goto fail;
-    }
+    IPL_assert(entry->component_type == ZIPL_COMP_ENTRY_EXEC, "No EXEC entry");
 
     /* should not return */
     jump_to_IPL_code(entry->load_address);
-
-    return 0;
-
-fail:
-    sclp_print("failed running zipl\n");
-    return -1;
 }
 
-int zipl_load(void)
+static void ipl_scsi(void)
 {
-    struct mbr *mbr = (void*)sec;
+    ScsiMbr *mbr = (void *)sec;
     uint8_t *ns, *ns_end;
     int program_table_entries = 0;
-    int pte_len = sizeof(struct scsi_blockptr);
-    struct scsi_blockptr *prog_table_entry;
-    const char *error = "";
-
-    /* Grab the MBR */
-    virtio_read(0, (void*)mbr);
-
-    dputs("checking magic\n");
+    const int pte_len = sizeof(ScsiBlockPtr);
+    ScsiBlockPtr *prog_table_entry;
 
-    if (!zipl_magic(mbr->magic)) {
-        error = "zipl_magic 1";
-        goto fail;
-    }
+    /* The 0-th block (MBR) was already read into sec[] */
 
+    sclp_print("Using SCSI scheme.\n");
     debug_print_int("program table", mbr->blockptr.blockno);
 
     /* Parse the program table */
-    if (virtio_read(mbr->blockptr.blockno, sec)) {
-        error = "virtio_read";
-        goto fail;
-    }
+    read_block(mbr->blockptr.blockno, sec,
+               "Error reading Program Table");
 
-    if (!zipl_magic(sec)) {
-        error = "zipl_magic 2";
-        goto fail;
-    }
+    IPL_assert(magic_match(sec, ZIPL_MAGIC), "No zIPL magic");
 
-    ns_end = sec + SECTOR_SIZE;
+    ns_end = sec + virtio_get_block_size();
     for (ns = (sec + pte_len); (ns + pte_len) < ns_end; ns++) {
-        prog_table_entry = (struct scsi_blockptr *)ns;
+        prog_table_entry = (ScsiBlockPtr *)ns;
         if (!prog_table_entry->blockno) {
             break;
         }
@@ -283,19 +416,55 @@ int zipl_load(void)
 
     debug_print_int("program table entries", program_table_entries);
 
-    if (!program_table_entries) {
-        goto fail;
-    }
+    IPL_assert(program_table_entries != 0, "Empty Program Table");
 
     /* Run the default entry */
 
-    prog_table_entry = (struct scsi_blockptr *)(sec + pte_len);
+    prog_table_entry = (ScsiBlockPtr *)(sec + pte_len);
 
-    return zipl_run(prog_table_entry);
+    zipl_run(prog_table_entry); /* no return */
+}
 
-fail:
-    sclp_print("failed loading zipl: ");
-    sclp_print(error);
-    sclp_print("\n");
-    return -1;
+/***********************************************************************
+ * IPL starts here
+ */
+
+void zipl_load(void)
+{
+    ScsiMbr *mbr = (void *)sec;
+    LDL_VTOC *vlbl = (void *)sec;
+
+    /* Grab the MBR */
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    read_block(0, mbr, "Cannot read block 0");
+
+    dputs("checking magic\n");
+
+    if (magic_match(mbr->magic, ZIPL_MAGIC)) {
+        ipl_scsi(); /* no return */
+    }
+
+    /* We have failed to follow the SCSI scheme, so */
+    sclp_print("Using ECKD scheme.\n");
+    if (virtio_guessed_disk_nature()) {
+        sclp_print("Using guessed DASD geometry.\n");
+        virtio_assume_eckd();
+    }
+
+    if (magic_match(mbr->magic, IPL1_MAGIC)) {
+        ipl_eckd(ECKD_CDL); /* no return */
+    }
+
+    /* LDL/CMS? */
+    memset(sec, FREE_SPACE_FILLER, sizeof(sec));
+    read_block(2, vlbl, "Cannot read block 2");
+
+    if (magic_match(vlbl->magic, CMS1_MAGIC)) {
+        ipl_eckd(ECKD_CMS); /* no return */
+    }
+    if (magic_match(vlbl->magic, LNX1_MAGIC)) {
+        ipl_eckd(ECKD_LDL); /* no return */
+    }
+
+    virtio_panic("\n* invalid MBR magic *\n");
 }
diff --git a/pc-bios/s390-ccw/bootmap.h b/pc-bios/s390-ccw/bootmap.h
new file mode 100644
index 0000000000..30ef22fe61
--- /dev/null
+++ b/pc-bios/s390-ccw/bootmap.h
@@ -0,0 +1,344 @@
+/*
+ * QEMU S390 bootmap interpreter -- declarations
+ *
+ * Copyright 2014 IBM Corp.
+ * Author(s): Eugene (jno) Dvurechenski <jno@linux.vnet.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 _PC_BIOS_S390_CCW_BOOTMAP_H
+#define _PC_BIOS_S390_CCW_BOOTMAP_H
+
+#include "s390-ccw.h"
+#include "virtio.h"
+
+typedef uint64_t block_number_t;
+#define NULL_BLOCK_NR 0xffffffffffffffff
+
+#define FREE_SPACE_FILLER '\xAA'
+
+typedef struct ScsiBlockPtr {
+    uint64_t blockno;
+    uint16_t size;
+    uint16_t blockct;
+    uint8_t reserved[4];
+} __attribute__ ((packed)) ScsiBlockPtr;
+
+typedef struct FbaBlockPtr {
+    uint32_t blockno;
+    uint16_t size;
+    uint16_t blockct;
+} __attribute__ ((packed)) FbaBlockPtr;
+
+typedef struct EckdBlockPtr {
+    uint16_t cylinder; /* cylinder/head/sector is an address of the block */
+    uint16_t head;
+    uint8_t sector;
+    uint16_t size;
+    uint8_t count; /* (size_in_blocks-1);
+                    * it's 0 for TablePtr, ScriptPtr, and SectionPtr */
+} __attribute__ ((packed)) EckdBlockPtr;
+
+typedef struct ExtEckdBlockPtr {
+    EckdBlockPtr bptr;
+    uint8_t reserved[8];
+} __attribute__ ((packed)) ExtEckdBlockPtr;
+
+typedef union BootMapPointer {
+    ScsiBlockPtr scsi;
+    FbaBlockPtr fba;
+    EckdBlockPtr eckd;
+    ExtEckdBlockPtr xeckd;
+} __attribute__ ((packed)) BootMapPointer;
+
+typedef struct ComponentEntry {
+    ScsiBlockPtr data;
+    uint8_t pad[7];
+    uint8_t component_type;
+    uint64_t load_address;
+} __attribute((packed)) ComponentEntry;
+
+typedef struct ComponentHeader {
+    uint8_t magic[4];   /* == "zIPL"                    */
+    uint8_t type;       /* == ZIPL_COMP_HEADER_*        */
+    uint8_t reserved[27];
+} __attribute((packed)) ComponentHeader;
+
+typedef struct ScsiMbr {
+    uint8_t magic[4];
+    uint32_t version_id;
+    uint8_t reserved[8];
+    ScsiBlockPtr blockptr;
+} __attribute__ ((packed)) ScsiMbr;
+
+#define ZIPL_MAGIC              "zIPL"
+#define IPL1_MAGIC "\xc9\xd7\xd3\xf1" /* == "IPL1" in EBCDIC */
+#define IPL2_MAGIC "\xc9\xd7\xd3\xf2" /* == "IPL2" in EBCDIC */
+#define VOL1_MAGIC "\xe5\xd6\xd3\xf1" /* == "VOL1" in EBCDIC */
+#define LNX1_MAGIC "\xd3\xd5\xe7\xf1" /* == "LNX1" in EBCDIC */
+#define CMS1_MAGIC "\xc3\xd4\xe2\xf1" /* == "CMS1" in EBCDIC */
+
+#define LDL1_VERSION '\x40' /* == ' ' in EBCDIC */
+#define LDL2_VERSION '\xf2' /* == '2' in EBCDIC */
+
+#define ZIPL_COMP_HEADER_IPL    0x00
+#define ZIPL_COMP_HEADER_DUMP   0x01
+
+#define ZIPL_COMP_ENTRY_LOAD    0x02
+#define ZIPL_COMP_ENTRY_EXEC    0x01
+
+typedef struct XEckdMbr {
+    uint8_t magic[4];   /* == "xIPL"        */
+    uint8_t version;
+    uint8_t bp_type;
+    uint8_t dev_type;   /* == DEV_TYPE_*    */
+#define DEV_TYPE_ECKD 0x00
+#define DEV_TYPE_FBA  0x01
+    uint8_t flags;
+    BootMapPointer blockptr;
+    uint8_t reserved[8];
+} __attribute__ ((packed)) XEckdMbr; /* see also BootInfo */
+
+typedef struct BootMapScriptEntry {
+    BootMapPointer blkptr;
+    uint8_t pad[7];
+    uint8_t type;   /* == BOOT_SCRIPT_* */
+#define BOOT_SCRIPT_EXEC 0x01
+#define BOOT_SCRIPT_LOAD 0x02
+    union {
+        uint64_t load_address;
+        uint64_t load_psw;
+    } address;
+} __attribute__ ((packed)) BootMapScriptEntry;
+
+typedef struct BootMapScriptHeader {
+    uint32_t magic;
+    uint8_t type;
+#define BOOT_SCRIPT_HDR_IPL 0x00
+    uint8_t reserved[27];
+} __attribute__ ((packed)) BootMapScriptHeader;
+
+typedef struct BootMapScript {
+    BootMapScriptHeader header;
+    BootMapScriptEntry  entry[0];
+} __attribute__ ((packed)) BootMapScript;
+
+/*
+ * These aren't real VTOCs, but referred to this way in some docs.
+ * They are "volume labels" actually.
+ *
+ * Some structures looks similar to described above, but left
+ * separate as there is no indication that they are the same.
+ * So, the value definitions are left separate too.
+ */
+typedef struct LDL_VTOC {       /* @ rec.3 cyl.0 trk.0 for ECKD */
+    char magic[4];              /* "LNX1", EBCDIC               */
+    char volser[6];             /* volser, EBCDIC               */
+    uint8_t reserved[69];       /* reserved, 0x40               */
+    uint8_t LDL_version;        /* 0x40 or 0xF2                 */
+    uint64_t formatted_blocks;  /* if LDL_version >= 0xF2       */
+} __attribute__ ((packed)) LDL_VTOC;
+
+typedef struct format_date {
+    uint8_t YY;
+    uint8_t MM;
+    uint8_t DD;
+    uint8_t hh;
+    uint8_t mm;
+    uint8_t ss;
+} __attribute__ ((packed)) format_date_t;
+
+typedef struct CMS_VTOC {       /* @ rec.3 cyl.0 trk.0 for ECKD */
+                                /* @ blk.1 (zero based) for FBA */
+    char magic[4];              /* 'CMS1', EBCDIC               */
+    char volser[6];             /* volser, EBCDIC               */
+    uint16_t version;           /* = 0                          */
+    uint32_t block_size;        /* = 512, 1024, 2048, or 4096   */
+    uint32_t disk_origin;       /* = 4 or 5                     */
+    uint32_t blocks;            /* Number of usable cyls/blocks */
+    uint32_t formatted;         /* Max number of fmtd cyls/blks */
+    uint32_t CMS_blocks;        /* disk size in CMS blocks      */
+    uint32_t CMS_used;          /* Number of CMS blocks in use  */
+    uint32_t FST_size;          /* = 64, bytes                  */
+    uint32_t FST_per_CMS_blk;   /*                              */
+    format_date_t format_date;  /* YYMMDDhhmmss as 6 bytes      */
+    uint8_t reserved1[2];       /* = 0                          */
+    uint32_t offset;            /* disk offset when reserved    */
+    uint32_t next_hole;         /* block nr                     */
+    uint32_t HBLK_hole_offset;  /* >> HBLK data of next hole    */
+    uint32_t alloc_map_usr_off; /* >> user part of Alloc map    */
+    uint8_t reserved2[4];       /* = 0                          */
+    char shared_seg_name[8];    /*                              */
+} __attribute__ ((packed)) CMS_VTOC;
+
+/* from zipl/include/boot.h */
+typedef struct BootInfoBpIpl {
+    union {
+        ExtEckdBlockPtr eckd;
+        ScsiBlockPtr linr;
+    } bm_ptr;
+    uint8_t unused[16];
+} __attribute__ ((packed)) BootInfoBpIpl;
+
+typedef struct EckdDumpParam {
+    uint32_t        start_blk;
+    uint32_t        end_blk;
+    uint16_t        blocksize;
+    uint8_t         num_heads;
+    uint8_t         bpt;
+    char            reserved[4];
+} __attribute((packed, may_alias)) EckdDumpParam;
+
+typedef struct FbaDumpParam {
+    uint64_t        start_blk;
+    uint64_t        blockct;
+} __attribute((packed)) FbaDumpParam;
+
+typedef struct BootInfoBpDump {
+    union {
+        EckdDumpParam eckd;
+        FbaDumpParam fba;
+    } param;
+    uint8_t         unused[16];
+} __attribute__ ((packed)) BootInfoBpDump;
+
+typedef struct BootInfo {          /* @ 0x70, record #0    */
+    unsigned char magic[4]; /* = 'zIPL', ASCII      */
+    uint8_t version;        /* = 1                  */
+#define BOOT_INFO_VERSION               1
+    uint8_t bp_type;        /* = 0                  */
+#define BOOT_INFO_BP_TYPE_IPL           0x00
+#define BOOT_INFO_BP_TYPE_DUMP          0x01
+    uint8_t dev_type;       /* = 0                  */
+#define BOOT_INFO_DEV_TYPE_ECKD         0x00
+#define BOOT_INFO_DEV_TYPE_FBA          0x01
+    uint8_t flags;          /* = 1                  */
+#ifdef __s390x__
+#define BOOT_INFO_FLAGS_ARCH            0x01
+#else
+#define BOOT_INFO_FLAGS_ARCH            0x00
+#endif
+    union {
+        BootInfoBpDump dump;
+        BootInfoBpIpl ipl;
+    } bp;
+} __attribute__ ((packed)) BootInfo; /* see also XEckdMbr   */
+
+typedef struct Ipl1 {
+    unsigned char key[4]; /* == "IPL1" */
+    unsigned char data[24];
+} __attribute__((packed)) Ipl1;
+
+typedef struct Ipl2 {
+    unsigned char key[4]; /* == "IPL2" */
+    union {
+        unsigned char data[144];
+        struct {
+            unsigned char reserved1[92-4];
+            XEckdMbr mbr;
+            unsigned char reserved2[144-(92-4)-sizeof(XEckdMbr)];
+        } x;
+    } u;
+} __attribute__((packed)) Ipl2;
+
+typedef struct IplVolumeLabel {
+    unsigned char key[4]; /* == "VOL1" */
+    union {
+        unsigned char data[80];
+        struct {
+            unsigned char key[4]; /* == "VOL1" */
+            unsigned char volser[6];
+            unsigned char reserved[6];
+        } f;
+    };
+} __attribute__((packed)) IplVolumeLabel;
+
+typedef enum {
+    ECKD_NO_IPL,
+    ECKD_CDL,
+    ECKD_CMS,
+    ECKD_LDL,
+} ECKD_IPL_mode_t;
+
+/* utility code below */
+
+static inline void IPL_assert(bool term, const char *message)
+{
+    if (!term) {
+        sclp_print("\n! ");
+        sclp_print(message);
+        virtio_panic(" !\n"); /* no return */
+    }
+}
+
+static const unsigned char ebc2asc[256] =
+      /* 0123456789abcdef0123456789abcdef */
+        "................................" /* 1F */
+        "................................" /* 3F */
+        " ...........<(+|&.........!$*);." /* 5F first.chr.here.is.real.space */
+        "-/.........,%_>?.........`:#@'=\""/* 7F */
+        ".abcdefghi.......jklmnopqr......" /* 9F */
+        "..stuvwxyz......................" /* BF */
+        ".ABCDEFGHI.......JKLMNOPQR......" /* DF */
+        "..STUVWXYZ......0123456789......";/* FF */
+
+static inline void ebcdic_to_ascii(const char *src,
+                                   char *dst,
+                                   unsigned int size)
+{
+    unsigned int i;
+    for (i = 0; i < size; i++) {
+        unsigned c = src[i];
+        dst[i] = ebc2asc[c];
+    }
+}
+
+static inline void print_volser(const void *volser)
+{
+    char ascii[8];
+
+    ebcdic_to_ascii((char *)volser, ascii, 6);
+    ascii[6] = '\0';
+    sclp_print("VOLSER=[");
+    sclp_print(ascii);
+    sclp_print("]\n");
+}
+
+static inline bool unused_space(const void *p, size_t size)
+{
+    size_t i;
+    const unsigned char *m = p;
+
+    for (i = 0; i < size; i++) {
+        if (m[i] != FREE_SPACE_FILLER) {
+            return false;
+        }
+    }
+    return true;
+}
+
+static inline bool is_null_block_number(block_number_t x)
+{
+    return x == NULL_BLOCK_NR;
+}
+
+static inline void read_block(block_number_t blockno,
+                              void *buffer,
+                              const char *errmsg)
+{
+    IPL_assert(virtio_read(blockno, buffer) == 0, errmsg);
+}
+
+static inline bool block_size_ok(uint32_t block_size)
+{
+    return block_size == virtio_get_block_size();
+}
+
+static inline bool magic_match(const void *data, const void *magic)
+{
+    return *((uint32_t *)data) == *((uint32_t *)magic);
+}
+
+#endif /* _PC_BIOS_S390_CCW_BOOTMAP_H */
diff --git a/pc-bios/s390-ccw/main.c b/pc-bios/s390-ccw/main.c
index 5c33766533..dbfb40e685 100644
--- a/pc-bios/s390-ccw/main.c
+++ b/pc-bios/s390-ccw/main.c
@@ -9,6 +9,7 @@
  */
 
 #include "s390-ccw.h"
+#include "virtio.h"
 
 char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
 uint64_t boot_value;
@@ -64,6 +65,10 @@ static void virtio_setup(uint64_t dev_info)
     }
 
     virtio_setup_block(blk_schid);
+
+    if (!virtio_ipl_disk_is_valid()) {
+        virtio_panic("No valid hard disk detected.\n");
+    }
 }
 
 int main(void)
@@ -72,8 +77,8 @@ int main(void)
     debug_print_int("boot reg[7] ", boot_value);
     virtio_setup(boot_value);
 
-    if (zipl_load() < 0)
-        sclp_print("Failed to load OS from hard disk\n");
-    disabled_wait();
-    while (1) { }
+    zipl_load(); /* no return */
+
+    virtio_panic("Failed to load OS from hard disk\n");
+    return 0; /* make compiler happy */
 }
diff --git a/pc-bios/s390-ccw/s390-ccw.h b/pc-bios/s390-ccw/s390-ccw.h
index 5e871ac84c..959aed0d0b 100644
--- a/pc-bios/s390-ccw/s390-ccw.h
+++ b/pc-bios/s390-ccw/s390-ccw.h
@@ -34,10 +34,10 @@ typedef unsigned long long __u64;
 #define PAGE_SIZE 4096
 
 #ifndef EIO
-#define EIO	1
+#define EIO     1
 #endif
 #ifndef EBUSY
-#define EBUSY	2
+#define EBUSY   2
 #endif
 #ifndef NULL
 #define NULL    0
@@ -57,14 +57,14 @@ void sclp_setup(void);
 
 /* virtio.c */
 unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
-				 ulong subchan_id, void *load_addr);
+                                 ulong subchan_id, void *load_addr);
 bool virtio_is_blk(struct subchannel_id schid);
 void virtio_setup_block(struct subchannel_id schid);
 int virtio_read(ulong sector, void *load_addr);
 int enable_mss_facility(void);
 
 /* bootmap.c */
-int zipl_load(void);
+void zipl_load(void);
 
 static inline void *memset(void *s, int c, size_t n)
 {
@@ -86,15 +86,21 @@ static inline void fill_hex(char *out, unsigned char val)
     out[1] = hex[val & 0xf];
 }
 
-static inline void print_int(const char *desc, u64 addr)
+static inline void fill_hex_val(char *out, void *ptr, unsigned size)
 {
-    unsigned char *addr_c = (unsigned char*)&addr;
-    char out[] = ": 0xffffffffffffffff\n";
+    unsigned char *value = ptr;
     unsigned int i;
 
-    for (i = 0; i < sizeof(addr); i++) {
-        fill_hex(&out[4 + (i*2)], addr_c[i]);
+    for (i = 0; i < size; i++) {
+        fill_hex(&out[i*2], value[i]);
     }
+}
+
+static inline void print_int(const char *desc, u64 addr)
+{
+    char out[] = ": 0xffffffffffffffff\n";
+
+    fill_hex_val(&out[4], &addr, sizeof(addr));
 
     sclp_print(desc);
     sclp_print(out);
@@ -118,18 +124,18 @@ static inline void debug_print_addr(const char *desc, void *p)
  *           Hypercall functions               *
  ***********************************************/
 
-#define KVM_S390_VIRTIO_NOTIFY		0
-#define KVM_S390_VIRTIO_RESET		1
-#define KVM_S390_VIRTIO_SET_STATUS	2
+#define KVM_S390_VIRTIO_NOTIFY          0
+#define KVM_S390_VIRTIO_RESET           1
+#define KVM_S390_VIRTIO_SET_STATUS      2
 #define KVM_S390_VIRTIO_CCW_NOTIFY      3
 
 static inline void yield(void)
 {
-	asm volatile ("diag 0,0,0x44"
-                      : :
-		      : "memory", "cc");
+    asm volatile ("diag 0,0,0x44"
+                  : :
+                  : "memory", "cc");
 }
 
-#define SECTOR_SIZE 512
+#define MAX_SECTOR_SIZE 4096
 
 #endif /* S390_CCW_H */
diff --git a/pc-bios/s390-ccw/sclp-ascii.c b/pc-bios/s390-ccw/sclp-ascii.c
index 1c93937104..761fb44ff5 100644
--- a/pc-bios/s390-ccw/sclp-ascii.c
+++ b/pc-bios/s390-ccw/sclp-ascii.c
@@ -33,7 +33,7 @@ static int sclp_service_call(unsigned int command, void *sccb)
 
 static void sclp_set_write_mask(void)
 {
-    WriteEventMask *sccb = (void*)_sccb;
+    WriteEventMask *sccb = (void *)_sccb;
 
     sccb->h.length = sizeof(WriteEventMask);
     sccb->mask_length = sizeof(unsigned int);
@@ -68,7 +68,7 @@ static void _memcpy(char *dest, const char *src, int len)
 void sclp_print(const char *str)
 {
     int len = _strlen(str);
-    WriteEventData *sccb = (void*)_sccb;
+    WriteEventData *sccb = (void *)_sccb;
 
     sccb->h.length = sizeof(WriteEventData) + len;
     sccb->h.function_code = SCLP_FC_NORMAL_WRITE;
diff --git a/pc-bios/s390-ccw/virtio.c b/pc-bios/s390-ccw/virtio.c
index bbb3c4f36d..31b23b086c 100644
--- a/pc-bios/s390-ccw/virtio.c
+++ b/pc-bios/s390-ccw/virtio.c
@@ -18,22 +18,22 @@ static char chsc_page[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
 static long kvm_hypercall(unsigned long nr, unsigned long param1,
                           unsigned long param2)
 {
-	register ulong r_nr asm("1") = nr;
-	register ulong r_param1 asm("2") = param1;
-	register ulong r_param2 asm("3") = param2;
-	register long retval asm("2");
+    register ulong r_nr asm("1") = nr;
+    register ulong r_param1 asm("2") = param1;
+    register ulong r_param2 asm("3") = param2;
+    register long retval asm("2");
 
-	asm volatile ("diag 2,4,0x500"
-		      : "=d" (retval)
-		      : "d" (r_nr), "0" (r_param1), "r"(r_param2)
-		      : "memory", "cc");
+    asm volatile ("diag 2,4,0x500"
+                  : "=d" (retval)
+                  : "d" (r_nr), "0" (r_param1), "r"(r_param2)
+                  : "memory", "cc");
 
-	return retval;
+    return retval;
 }
 
 static void virtio_notify(struct subchannel_id schid)
 {
-    kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32*)&schid, 0);
+    kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid, 0);
 }
 
 /***********************************************
@@ -202,7 +202,7 @@ static int vring_wait_reply(struct vring *vr, int timeout)
  *               Virtio block                  *
  ***********************************************/
 
-static int virtio_read_many(ulong sector, void *load_addr, int sec_num)
+int virtio_read_many(ulong sector, void *load_addr, int sec_num)
 {
     struct virtio_blk_outhdr out_hdr;
     u8 status;
@@ -211,12 +211,12 @@ static int virtio_read_many(ulong sector, void *load_addr, int sec_num)
     /* Tell the host we want to read */
     out_hdr.type = VIRTIO_BLK_T_IN;
     out_hdr.ioprio = 99;
-    out_hdr.sector = sector;
+    out_hdr.sector = virtio_sector_adjust(sector);
 
     vring_send_buf(&block, &out_hdr, sizeof(out_hdr), VRING_DESC_F_NEXT);
 
     /* This is where we want to receive data */
-    vring_send_buf(&block, load_addr, SECTOR_SIZE * sec_num,
+    vring_send_buf(&block, load_addr, virtio_get_block_size() * sec_num,
                    VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN |
                    VRING_DESC_F_NEXT);
 
@@ -236,7 +236,7 @@ static int virtio_read_many(ulong sector, void *load_addr, int sec_num)
 }
 
 unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
-				 ulong subchan_id, void *load_addr)
+                                 ulong subchan_id, void *load_addr)
 {
     u8 status;
     int sec = rec_list1;
@@ -244,16 +244,16 @@ unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
     int sec_len = rec_list2 >> 48;
     ulong addr = (ulong)load_addr;
 
-    if (sec_len != SECTOR_SIZE) {
+    if (sec_len != virtio_get_block_size()) {
         return -1;
     }
 
     sclp_print(".");
-    status = virtio_read_many(sec, (void*)addr, sec_num);
+    status = virtio_read_many(sec, (void *)addr, sec_num);
     if (status) {
         virtio_panic("I/O Error");
     }
-    addr += sec_num * SECTOR_SIZE;
+    addr += sec_num * virtio_get_block_size();
 
     return addr;
 }
@@ -263,18 +263,98 @@ int virtio_read(ulong sector, void *load_addr)
     return virtio_read_many(sector, load_addr, 1);
 }
 
+static VirtioBlkConfig blk_cfg = {};
+static bool guessed_disk_nature;
+
+bool virtio_guessed_disk_nature(void)
+{
+    return guessed_disk_nature;
+}
+
+void virtio_assume_scsi(void)
+{
+    guessed_disk_nature = true;
+    blk_cfg.blk_size = 512;
+}
+
+void virtio_assume_eckd(void)
+{
+    guessed_disk_nature = true;
+    blk_cfg.blk_size = 4096;
+
+    /* this must be here to calculate code segment position */
+    blk_cfg.geometry.heads = 15;
+    blk_cfg.geometry.sectors = 12;
+}
+
+bool virtio_disk_is_scsi(void)
+{
+    if (guessed_disk_nature) {
+        return (blk_cfg.blk_size  == 512);
+    }
+    return (blk_cfg.geometry.heads == 255)
+        && (blk_cfg.geometry.sectors == 63)
+        && (blk_cfg.blk_size  == 512);
+}
+
+bool virtio_disk_is_eckd(void)
+{
+    if (guessed_disk_nature) {
+        return (blk_cfg.blk_size  == 4096);
+    }
+    return (blk_cfg.geometry.heads == 15)
+        && (blk_cfg.geometry.sectors == 12)
+        && (blk_cfg.blk_size  == 4096);
+}
+
+bool virtio_ipl_disk_is_valid(void)
+{
+    return blk_cfg.blk_size && (virtio_disk_is_scsi() || virtio_disk_is_eckd());
+}
+
+int virtio_get_block_size(void)
+{
+    return blk_cfg.blk_size;
+}
+
+uint16_t virtio_get_cylinders(void)
+{
+    return blk_cfg.geometry.cylinders;
+}
+
+uint8_t virtio_get_heads(void)
+{
+    return blk_cfg.geometry.heads;
+}
+
+uint8_t virtio_get_sectors(void)
+{
+    return blk_cfg.geometry.sectors;
+}
+
 void virtio_setup_block(struct subchannel_id schid)
 {
     struct vq_info_block info;
     struct vq_config_block config = {};
 
+    blk_cfg.blk_size = 0; /* mark "illegal" - setup started... */
+
     virtio_reset(schid);
 
+    /*
+     * Skipping CCW_CMD_READ_FEAT. We're not doing anything fancy, and
+     * we'll just stop dead anyway if anything does not work like we
+     * expect it.
+     */
+
     config.index = 0;
     if (run_ccw(schid, CCW_CMD_READ_VQ_CONF, &config, sizeof(config))) {
+        virtio_panic("Could not get block device VQ configuration\n");
+    }
+    if (run_ccw(schid, CCW_CMD_READ_CONF, &blk_cfg, sizeof(blk_cfg))) {
         virtio_panic("Could not get block device configuration\n");
     }
-    vring_init(&block, config.num, (void*)(100 * 1024 * 1024),
+    vring_init(&block, config.num, (void *)(100 * 1024 * 1024),
                KVM_S390_VIRTIO_RING_ALIGN);
 
     info.queue = (100ULL * 1024ULL* 1024ULL);
@@ -286,6 +366,12 @@ void virtio_setup_block(struct subchannel_id schid)
     if (!run_ccw(schid, CCW_CMD_SET_VQ, &info, sizeof(info))) {
         virtio_set_status(schid, VIRTIO_CONFIG_S_DRIVER_OK);
     }
+
+    if (!virtio_ipl_disk_is_valid()) {
+        /* make sure all getters but blocksize return 0 for invalid IPL disk */
+        memset(&blk_cfg, 0, sizeof(blk_cfg));
+        virtio_assume_scsi();
+    }
 }
 
 bool virtio_is_blk(struct subchannel_id schid)
diff --git a/pc-bios/s390-ccw/virtio.h b/pc-bios/s390-ccw/virtio.h
index 772a63f152..f1fb1b08fa 100644
--- a/pc-bios/s390-ccw/virtio.h
+++ b/pc-bios/s390-ccw/virtio.h
@@ -66,7 +66,7 @@ struct virtio_dev {
     char *config;
 };
 
-#define KVM_S390_VIRTIO_RING_ALIGN	4096
+#define KVM_S390_VIRTIO_RING_ALIGN  4096
 
 #define VRING_USED_F_NO_NOTIFY  1
 
@@ -161,4 +161,52 @@ struct virtio_blk_outhdr {
         u64 sector;
 };
 
+typedef struct VirtioBlkConfig {
+    u64 capacity; /* in 512-byte sectors */
+    u32 size_max; /* max segment size (if VIRTIO_BLK_F_SIZE_MAX) */
+    u32 seg_max;  /* max number of segments (if VIRTIO_BLK_F_SEG_MAX) */
+
+    struct virtio_blk_geometry {
+        u16 cylinders;
+        u8 heads;
+        u8 sectors;
+    } geometry; /* (if VIRTIO_BLK_F_GEOMETRY) */
+
+    u32 blk_size; /* block size of device (if VIRTIO_BLK_F_BLK_SIZE) */
+
+    /* the next 4 entries are guarded by VIRTIO_BLK_F_TOPOLOGY  */
+    u8 physical_block_exp; /* exponent for physical block per logical block */
+    u8 alignment_offset;   /* alignment offset in logical blocks */
+    u16 min_io_size;       /* min I/O size without performance penalty
+                              in logical blocks */
+    u32 opt_io_size;       /* optimal sustained I/O size in logical blocks */
+
+    u8 wce; /* writeback mode (if VIRTIO_BLK_F_CONFIG_WCE) */
+} __attribute__((packed)) VirtioBlkConfig;
+
+bool virtio_guessed_disk_nature(void);
+void virtio_assume_scsi(void);
+void virtio_assume_eckd(void);
+
+extern bool virtio_disk_is_scsi(void);
+extern bool virtio_disk_is_eckd(void);
+extern bool virtio_ipl_disk_is_valid(void);
+extern int virtio_get_block_size(void);
+extern uint16_t virtio_get_cylinders(void);
+extern uint8_t virtio_get_heads(void);
+extern uint8_t virtio_get_sectors(void);
+extern int virtio_read_many(ulong sector, void *load_addr, int sec_num);
+
+#define VIRTIO_SECTOR_SIZE 512
+
+static inline ulong virtio_eckd_sector_adjust(ulong sector)
+{
+     return sector * (virtio_get_block_size() / VIRTIO_SECTOR_SIZE);
+}
+
+static inline ulong virtio_sector_adjust(ulong sector)
+{
+    return virtio_disk_is_eckd() ? virtio_eckd_sector_adjust(sector) : sector;
+}
+
 #endif /* VIRTIO_H */