aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Maydell <peter.maydell@linaro.org>2014-06-29 11:43:30 +0100
committerPeter Maydell <peter.maydell@linaro.org>2014-06-29 11:43:31 +0100
commitde6793e8c2a4d34e28e5ea385276249fc98109ec (patch)
treed89981b7528e18523fc2617e3e7c77852742dd06
parent1045fc0439f02dc12298045ecb768c52d796dd99 (diff)
parent77416f4075a673a27cfe5a7a34e93c0fa9810e35 (diff)
Merge remote-tracking branch 'remotes/cohuck/tags/s390x-20140627' into staging
A series of patches to the s390-ccw bios: - code cleanup - improved error reporting - most important, support to ipl (boot) from ECKD DASD (CDL, LDL or CMS formatted) # gpg: Signature made Fri 27 Jun 2014 12:03:30 BST using RSA key ID C6F02FAF # gpg: Can't check signature: public key not found * remotes/cohuck/tags/s390x-20140627: pc-bios/s390-ccw: update binary pc-bios/s390-ccw: IPL from LDL/CMS-formatted ECKD DASD pc-bios/s390-ccw: IPL from CDL-formatted ECKD DASD pc-bios/s390-ccw: factor out ipl code pc-bios/s390-ccw: Add fill_hex_val func to provide better msgs pc-bios/s390-ccw: Unify error handling pc-bios/s390-ccw: add some utility code pc-bios/s390-ccw: handle different sector sizes pc-bios/s390-ccw: cleanup and enhance bootmap defintions pc-bios/s390-ccw: make checkpatch happy Signed-off-by: Peter Maydell <peter.maydell@linaro.org>
-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 differ
diff --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 */