summaryrefslogtreecommitdiffstats
path: root/src/pc-bios/s390-ccw
diff options
context:
space:
mode:
Diffstat (limited to 'src/pc-bios/s390-ccw')
-rw-r--r--src/pc-bios/s390-ccw/Makefile28
-rw-r--r--src/pc-bios/s390-ccw/bootmap.c701
-rw-r--r--src/pc-bios/s390-ccw/bootmap.h550
-rw-r--r--src/pc-bios/s390-ccw/cio.h342
-rw-r--r--src/pc-bios/s390-ccw/main.c116
-rw-r--r--src/pc-bios/s390-ccw/s390-ccw.h146
-rw-r--r--src/pc-bios/s390-ccw/sclp-ascii.c82
-rw-r--r--src/pc-bios/s390-ccw/sclp.h107
-rw-r--r--src/pc-bios/s390-ccw/start.S65
-rw-r--r--src/pc-bios/s390-ccw/virtio.c441
-rw-r--r--src/pc-bios/s390-ccw/virtio.h208
11 files changed, 2786 insertions, 0 deletions
diff --git a/src/pc-bios/s390-ccw/Makefile b/src/pc-bios/s390-ccw/Makefile
new file mode 100644
index 0000000..11c5dd4
--- /dev/null
+++ b/src/pc-bios/s390-ccw/Makefile
@@ -0,0 +1,28 @@
+all: build-all
+# Dummy command so that make thinks it has done something
+ @true
+
+include ../../config-host.mak
+include $(SRC_PATH)/rules.mak
+
+$(call set-vpath, $(SRC_PATH)/pc-bios/s390-ccw)
+
+.PHONY : all clean build-all
+
+OBJECTS = start.o main.o bootmap.o sclp-ascii.o virtio.o
+CFLAGS += -fPIE -fno-stack-protector -ffreestanding -march=z900
+CFLAGS += -fno-delete-null-pointer-checks -msoft-float
+LDFLAGS += -Wl,-pie -nostdlib
+
+build-all: s390-ccw.img
+
+s390-ccw.elf: $(OBJECTS)
+ $(call quiet-command,$(CC) $(LDFLAGS) -o $@ $(OBJECTS)," Building $(TARGET_DIR)$@")
+
+s390-ccw.img: s390-ccw.elf
+ $(call quiet-command,strip --strip-unneeded $< -o $@," Stripping $(TARGET_DIR)$@")
+
+$(OBJECTS): Makefile
+
+clean:
+ rm -f *.o *.d *.img *.elf *~
diff --git a/src/pc-bios/s390-ccw/bootmap.c b/src/pc-bios/s390-ccw/bootmap.c
new file mode 100644
index 0000000..415508b
--- /dev/null
+++ b/src/pc-bios/s390-ccw/bootmap.c
@@ -0,0 +1,701 @@
+/*
+ * QEMU S390 bootmap interpreter
+ *
+ * Copyright (c) 2009 Alexander Graf <agraf@suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "s390-ccw.h"
+#include "bootmap.h"
+#include "virtio.h"
+
+#ifdef DEBUG
+/* #define DEBUG_FALLBACK */
+#endif
+
+#ifdef DEBUG_FALLBACK
+#define dputs(txt) \
+ do { sclp_print("zipl: " txt); } while (0)
+#else
+#define dputs(fmt, ...) \
+ do { } while (0)
+#endif
+
+/* Scratch space */
+static uint8_t sec[MAX_SECTOR_SIZE*4] __attribute__((__aligned__(PAGE_SIZE)));
+
+typedef struct ResetInfo {
+ uint32_t ipl_mask;
+ uint32_t ipl_addr;
+ uint32_t ipl_continue;
+} ResetInfo;
+
+static ResetInfo save;
+
+static void jump_to_IPL_2(void)
+{
+ ResetInfo *current = 0;
+
+ void (*ipl)(void) = (void *) (uint64_t) current->ipl_continue;
+ *current = save;
+ ipl(); /* should not return */
+}
+
+static void jump_to_IPL_code(uint64_t address)
+{
+ /* store the subsystem information _after_ the bootmap was loaded */
+ write_subsystem_identification();
+ /*
+ * The IPL PSW is at address 0. We also must not overwrite the
+ * content of non-BIOS memory after we loaded the guest, so we
+ * save the original content and restore it in jump_to_IPL_2.
+ */
+ ResetInfo *current = 0;
+
+ save = *current;
+ current->ipl_addr = (uint32_t) (uint64_t) &jump_to_IPL_2;
+ current->ipl_continue = address & 0x7fffffff;
+
+ debug_print_int("set IPL addr to", current->ipl_continue);
+
+ /* Ensure the guest output starts fresh */
+ sclp_print("\n");
+
+ /*
+ * HACK ALERT.
+ * We use the load normal reset to keep r15 unchanged. jump_to_IPL_2
+ * can then use r15 as its stack pointer.
+ */
+ asm volatile("lghi 1,1\n\t"
+ "diag 1,1,0x308\n\t"
+ : : : "1", "memory");
+ virtio_panic("\n! IPL returns !\n");
+}
+
+/***********************************************************************
+ * IPL an ECKD DASD (CDL or LDL/CMS format)
+ */
+
+static unsigned char _bprs[8*1024]; /* guessed "max" ECKD sector size */
+static 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 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 bool eckd_valid_address(BootMapPointer *p)
+{
+ 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 (!virtio_guessed_disk_nature() &&
+ eckd_block_num(p) >= virtio_get_blocks()) {
+ return false;
+ }
+
+ return true;
+}
+
+static block_number_t load_eckd_segments(block_number_t blk, uint64_t *address)
+{
+ 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;
+ }
+
+ /* 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");
+
+ 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 print_eckd_ldl_msg(ECKD_IPL_mode_t mode)
+{
+ LDL_VTOC *vlbl = (void *)sec; /* already read, 3rd block */
+ char msg[4] = { '?', '.', '\n', '\0' };
+
+ 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);
+}
+
+static void ipl_eckd_ldl(ECKD_IPL_mode_t mode)
+{
+ block_number_t block_nr;
+ BootInfo *bip = (void *)(sec + 0x70); /* BootInfo is MBR for LDL */
+
+ if (mode != ECKD_LDL_UNLABELED) {
+ print_eckd_ldl_msg(mode);
+ }
+
+ /* 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 to grab boot info.");
+ if (mode == ECKD_LDL_UNLABELED) {
+ if (!magic_match(bip->magic, ZIPL_MAGIC)) {
+ return; /* not applicable layout */
+ }
+ sclp_print("unlabeled LDL.\n");
+ }
+ 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 print_eckd_msg(void)
+{
+ char msg[] = "Using ECKD scheme (block size *****), ";
+ char *p = &msg[34], *q = &msg[30];
+ int n = virtio_get_block_size();
+
+ /* Fill in the block size and show up the message */
+ if (n > 0 && n <= 99999) {
+ while (n) {
+ *p-- = '0' + (n % 10);
+ n /= 10;
+ }
+ while (p >= q) {
+ *p-- = ' ';
+ }
+ }
+ sclp_print(msg);
+}
+
+/***********************************************************************
+ * IPL a SCSI disk
+ */
+
+static void zipl_load_segment(ComponentEntry *entry)
+{
+ const int max_entries = (MAX_SECTOR_SIZE / sizeof(ScsiBlockPtr));
+ ScsiBlockPtr *bprs = (void *)sec;
+ const int bprs_size = sizeof(sec);
+ 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;
+
+ debug_print_int("loading segment at block", blockno);
+ debug_print_int("addr", address);
+
+ do {
+ memset(bprs, FREE_SPACE_FILLER, bprs_size);
+ fill_hex_val(blk_no, &blockno, sizeof(blockno));
+ read_block(blockno, bprs, err_msg);
+
+ for (i = 0;; i++) {
+ uint64_t *cur_desc = (void *)&bprs[i];
+
+ blockno = bprs[i].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)) {
+ break;
+ }
+
+ if (bprs[i].blockct == 0 && unused_space(&bprs[i + 1],
+ 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.
+ * The blockno is not zero, so the upper loop must continue
+ * reading next section of BPRS.
+ */
+ break;
+ }
+ address = virtio_load_direct(cur_desc[0], cur_desc[1], 0,
+ (void *)address);
+ IPL_assert(address != -1, "zIPL load segment failed");
+ }
+ } while (blockno);
+}
+
+/* Run a zipl program */
+static void zipl_run(ScsiBlockPtr *pte)
+{
+ ComponentHeader *header;
+ ComponentEntry *entry;
+ uint8_t tmp_sec[MAX_SECTOR_SIZE];
+
+ read_block(pte->blockno, tmp_sec, "Cannot read header");
+ header = (ComponentHeader *)tmp_sec;
+
+ 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 = (ComponentEntry *)(&header[1]);
+ while (entry->component_type == ZIPL_COMP_ENTRY_LOAD) {
+ zipl_load_segment(entry);
+
+ entry++;
+
+ IPL_assert((uint8_t *)(&entry[1]) <= (tmp_sec + MAX_SECTOR_SIZE),
+ "Wrong entry value");
+ }
+
+ IPL_assert(entry->component_type == ZIPL_COMP_ENTRY_EXEC, "No EXEC entry");
+
+ /* should not return */
+ jump_to_IPL_code(entry->load_address);
+}
+
+static void ipl_scsi(void)
+{
+ ScsiMbr *mbr = (void *)sec;
+ uint8_t *ns, *ns_end;
+ int program_table_entries = 0;
+ const int pte_len = sizeof(ScsiBlockPtr);
+ ScsiBlockPtr *prog_table_entry;
+
+ /* 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 */
+ read_block(mbr->blockptr.blockno, sec,
+ "Error reading Program Table");
+
+ IPL_assert(magic_match(sec, ZIPL_MAGIC), "No zIPL magic");
+
+ ns_end = sec + virtio_get_block_size();
+ for (ns = (sec + pte_len); (ns + pte_len) < ns_end; ns++) {
+ prog_table_entry = (ScsiBlockPtr *)ns;
+ if (!prog_table_entry->blockno) {
+ break;
+ }
+
+ program_table_entries++;
+ }
+
+ debug_print_int("program table entries", program_table_entries);
+
+ IPL_assert(program_table_entries != 0, "Empty Program Table");
+
+ /* Run the default entry */
+
+ prog_table_entry = (ScsiBlockPtr *)(sec + pte_len);
+
+ zipl_run(prog_table_entry); /* no return */
+}
+
+/***********************************************************************
+ * IPL El Torito ISO9660 image or DVD
+ */
+
+static bool is_iso_bc_entry_compatible(IsoBcSection *s)
+{
+ uint8_t *magic_sec = (uint8_t *)(sec + ISO_SECTOR_SIZE);
+
+ if (s->unused || !s->sector_count) {
+ return false;
+ }
+ read_iso_sector(bswap32(s->load_rba), magic_sec,
+ "Failed to read image sector 0");
+
+ /* Checking bytes 8 - 32 for S390 Linux magic */
+ return !_memcmp(magic_sec + 8, linux_s390_magic, 24);
+}
+
+/* Location of the current sector of the directory */
+static uint32_t sec_loc[ISO9660_MAX_DIR_DEPTH];
+/* Offset in the current sector of the directory */
+static uint32_t sec_offset[ISO9660_MAX_DIR_DEPTH];
+/* Remained directory space in bytes */
+static uint32_t dir_rem[ISO9660_MAX_DIR_DEPTH];
+
+static inline uint32_t iso_get_file_size(uint32_t load_rba)
+{
+ IsoVolDesc *vd = (IsoVolDesc *)sec;
+ IsoDirHdr *cur_record = &vd->vd.primary.rootdir;
+ uint8_t *temp = sec + ISO_SECTOR_SIZE;
+ int level = 0;
+
+ read_iso_sector(ISO_PRIMARY_VD_SECTOR, sec,
+ "Failed to read ISO primary descriptor");
+ sec_loc[0] = iso_733_to_u32(cur_record->ext_loc);
+ dir_rem[0] = 0;
+ sec_offset[0] = 0;
+
+ while (level >= 0) {
+ IPL_assert(sec_offset[level] <= ISO_SECTOR_SIZE,
+ "Directory tree structure violation");
+
+ cur_record = (IsoDirHdr *)(temp + sec_offset[level]);
+
+ if (sec_offset[level] == 0) {
+ read_iso_sector(sec_loc[level], temp,
+ "Failed to read ISO directory");
+ if (dir_rem[level] == 0) {
+ /* Skip self and parent records */
+ dir_rem[level] = iso_733_to_u32(cur_record->data_len) -
+ cur_record->dr_len;
+ sec_offset[level] += cur_record->dr_len;
+
+ cur_record = (IsoDirHdr *)(temp + sec_offset[level]);
+ dir_rem[level] -= cur_record->dr_len;
+ sec_offset[level] += cur_record->dr_len;
+ continue;
+ }
+ }
+
+ if (!cur_record->dr_len || sec_offset[level] == ISO_SECTOR_SIZE) {
+ /* Zero-padding and/or the end of current sector */
+ dir_rem[level] -= ISO_SECTOR_SIZE - sec_offset[level];
+ sec_offset[level] = 0;
+ sec_loc[level]++;
+ } else {
+ /* The directory record is valid */
+ if (load_rba == iso_733_to_u32(cur_record->ext_loc)) {
+ return iso_733_to_u32(cur_record->data_len);
+ }
+
+ dir_rem[level] -= cur_record->dr_len;
+ sec_offset[level] += cur_record->dr_len;
+
+ if (cur_record->file_flags & 0x2) {
+ /* Subdirectory */
+ if (level == ISO9660_MAX_DIR_DEPTH - 1) {
+ sclp_print("ISO-9660 directory depth limit exceeded\n");
+ } else {
+ level++;
+ sec_loc[level] = iso_733_to_u32(cur_record->ext_loc);
+ sec_offset[level] = 0;
+ dir_rem[level] = 0;
+ continue;
+ }
+ }
+ }
+
+ if (dir_rem[level] == 0) {
+ /* Nothing remaining */
+ level--;
+ read_iso_sector(sec_loc[level], temp,
+ "Failed to read ISO directory");
+ }
+ }
+
+ return 0;
+}
+
+static void load_iso_bc_entry(IsoBcSection *load)
+{
+ IsoBcSection s = *load;
+ /*
+ * According to spec, extent for each file
+ * is padded and ISO_SECTOR_SIZE bytes aligned
+ */
+ uint32_t blks_to_load = bswap16(s.sector_count) >> ET_SECTOR_SHIFT;
+ uint32_t real_size = iso_get_file_size(bswap32(s.load_rba));
+
+ if (real_size) {
+ /* Round up blocks to load */
+ blks_to_load = (real_size + ISO_SECTOR_SIZE - 1) / ISO_SECTOR_SIZE;
+ sclp_print("ISO boot image size verified\n");
+ } else {
+ sclp_print("ISO boot image size could not be verified\n");
+ }
+
+ read_iso_boot_image(bswap32(s.load_rba),
+ (void *)((uint64_t)bswap16(s.load_segment)),
+ blks_to_load);
+
+ /* Trying to get PSW at zero address */
+ if (*((uint64_t *)0) & IPL_PSW_MASK) {
+ jump_to_IPL_code((*((uint64_t *)0)) & 0x7fffffff);
+ }
+
+ /* Try default linux start address */
+ jump_to_IPL_code(KERN_IMAGE_START);
+}
+
+static uint32_t find_iso_bc(void)
+{
+ IsoVolDesc *vd = (IsoVolDesc *)sec;
+ uint32_t block_num = ISO_PRIMARY_VD_SECTOR;
+
+ if (virtio_read_many(block_num++, sec, 1)) {
+ /* If primary vd cannot be read, there is no boot catalog */
+ return 0;
+ }
+
+ while (is_iso_vd_valid(vd) && vd->type != VOL_DESC_TERMINATOR) {
+ if (vd->type == VOL_DESC_TYPE_BOOT) {
+ IsoVdElTorito *et = &vd->vd.boot;
+
+ if (!_memcmp(&et->el_torito[0], el_torito_magic, 32)) {
+ return bswap32(et->bc_offset);
+ }
+ }
+ read_iso_sector(block_num++, sec,
+ "Failed to read ISO volume descriptor");
+ }
+
+ return 0;
+}
+
+static IsoBcSection *find_iso_bc_entry(void)
+{
+ IsoBcEntry *e = (IsoBcEntry *)sec;
+ uint32_t offset = find_iso_bc();
+ int i;
+
+ if (!offset) {
+ return NULL;
+ }
+
+ read_iso_sector(offset, sec, "Failed to read El Torito boot catalog");
+
+ if (!is_iso_bc_valid(e)) {
+ /* The validation entry is mandatory */
+ virtio_panic("No valid boot catalog found!\n");
+ return NULL;
+ }
+
+ /*
+ * Each entry has 32 bytes size, so one sector cannot contain > 64 entries.
+ * We consider only boot catalogs with no more than 64 entries.
+ */
+ for (i = 1; i < ISO_BC_ENTRY_PER_SECTOR; i++) {
+ if (e[i].id == ISO_BC_BOOTABLE_SECTION) {
+ if (is_iso_bc_entry_compatible(&e[i].body.sect)) {
+ return &e[i].body.sect;
+ }
+ }
+ }
+
+ virtio_panic("No suitable boot entry found on ISO-9660 media!\n");
+
+ return NULL;
+}
+
+static void ipl_iso_el_torito(void)
+{
+ IsoBcSection *s = find_iso_bc_entry();
+
+ if (s) {
+ load_iso_bc_entry(s);
+ /* no return */
+ }
+}
+
+/***********************************************************************
+ * 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 */
+ }
+
+ /* Check if we can boot as ISO media */
+ if (virtio_guessed_disk_nature()) {
+ virtio_assume_iso9660();
+ }
+ ipl_iso_el_torito();
+
+ /* We have failed to follow the SCSI scheme, so */
+ if (virtio_guessed_disk_nature()) {
+ sclp_print("Using guessed DASD geometry.\n");
+ virtio_assume_eckd();
+ }
+ print_eckd_msg();
+ if (magic_match(mbr->magic, IPL1_MAGIC)) {
+ ipl_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_ldl(ECKD_CMS); /* no return */
+ }
+ if (magic_match(vlbl->magic, LNX1_MAGIC)) {
+ ipl_eckd_ldl(ECKD_LDL); /* no return */
+ }
+
+ ipl_eckd_ldl(ECKD_LDL_UNLABELED); /* it still may return */
+ /*
+ * Ok, it is not a LDL by any means.
+ * It still might be a CDL with zero record keys for IPL1 and IPL2
+ */
+ ipl_eckd_cdl();
+
+ virtio_panic("\n* this can never happen *\n");
+}
diff --git a/src/pc-bios/s390-ccw/bootmap.h b/src/pc-bios/s390-ccw/bootmap.h
new file mode 100644
index 0000000..f98765b
--- /dev/null
+++ b/src/pc-bios/s390-ccw/bootmap.h
@@ -0,0 +1,550 @@
+/*
+ * 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 0xffffffffffffffffULL
+
+#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_CMS,
+ ECKD_LDL,
+ ECKD_LDL_UNLABELED,
+} 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);
+}
+
+static inline int _memcmp(const void *s1, const void *s2, size_t n)
+{
+ int i;
+ const uint8_t *p1 = s1, *p2 = s2;
+
+ for (i = 0; i < n; i++) {
+ if (p1[i] != p2[i]) {
+ return p1[i] > p2[i] ? 1 : -1;
+ }
+ }
+
+ return 0;
+}
+
+/* from include/qemu/bswap.h */
+
+/* El Torito is always little-endian */
+static inline uint16_t bswap16(uint16_t x)
+{
+ return ((x & 0x00ff) << 8) | ((x & 0xff00) >> 8);
+}
+
+static inline uint32_t bswap32(uint32_t x)
+{
+ return ((x & 0x000000ffU) << 24) | ((x & 0x0000ff00U) << 8) |
+ ((x & 0x00ff0000U) >> 8) | ((x & 0xff000000U) >> 24);
+}
+
+static inline uint64_t bswap64(uint64_t x)
+{
+ return ((x & 0x00000000000000ffULL) << 56) |
+ ((x & 0x000000000000ff00ULL) << 40) |
+ ((x & 0x0000000000ff0000ULL) << 24) |
+ ((x & 0x00000000ff000000ULL) << 8) |
+ ((x & 0x000000ff00000000ULL) >> 8) |
+ ((x & 0x0000ff0000000000ULL) >> 24) |
+ ((x & 0x00ff000000000000ULL) >> 40) |
+ ((x & 0xff00000000000000ULL) >> 56);
+}
+
+static inline uint32_t iso_733_to_u32(uint64_t x)
+{
+ return (uint32_t)x;
+}
+
+#define ISO_SECTOR_SIZE 2048
+/* El Torito specifies boot image size in 512 byte blocks */
+#define ET_SECTOR_SHIFT 2
+#define KERN_IMAGE_START 0x010000UL
+#define PSW_MASK_64 0x0000000100000000ULL
+#define PSW_MASK_32 0x0000000080000000ULL
+#define IPL_PSW_MASK (PSW_MASK_32 | PSW_MASK_64)
+
+#define ISO_PRIMARY_VD_SECTOR 16
+
+static inline void read_iso_sector(uint32_t block_offset, void *buf,
+ const char *errmsg)
+{
+ IPL_assert(virtio_read_many(block_offset, buf, 1) == 0, errmsg);
+}
+
+static inline void read_iso_boot_image(uint32_t block_offset, void *load_addr,
+ uint32_t blks_to_load)
+{
+ IPL_assert(virtio_read_many(block_offset, load_addr, blks_to_load) == 0,
+ "Failed to read boot image!");
+}
+
+const uint8_t el_torito_magic[] = "EL TORITO SPECIFICATION"
+ "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";
+
+#define ISO9660_MAX_DIR_DEPTH 8
+
+typedef struct IsoDirHdr {
+ uint8_t dr_len;
+ uint8_t ear_len;
+ uint64_t ext_loc;
+ uint64_t data_len;
+ uint8_t recording_datetime[7];
+ uint8_t file_flags;
+ uint8_t file_unit_size;
+ uint8_t gap_size;
+ uint32_t vol_seqnum;
+ uint8_t fileid_len;
+} __attribute__((packed)) IsoDirHdr;
+
+typedef struct IsoVdElTorito {
+ uint8_t el_torito[32]; /* must contain el_torito_magic value */
+ uint8_t unused0[32];
+ uint32_t bc_offset;
+ uint8_t unused1[1974];
+} __attribute__((packed)) IsoVdElTorito;
+
+typedef struct IsoVdPrimary {
+ uint8_t unused1;
+ uint8_t sys_id[32];
+ uint8_t vol_id[32];
+ uint8_t unused2[8];
+ uint64_t vol_space_size;
+ uint8_t unused3[32];
+ uint32_t vol_set_size;
+ uint32_t vol_seqnum;
+ uint32_t log_block_size;
+ uint64_t path_table_size;
+ uint32_t l_path_table;
+ uint32_t opt_l_path_table;
+ uint32_t m_path_table;
+ uint32_t opt_m_path_table;
+ IsoDirHdr rootdir;
+ uint8_t root_null;
+ uint8_t reserved2[1858];
+} __attribute__((packed)) IsoVdPrimary;
+
+typedef struct IsoVolDesc {
+ uint8_t type;
+ uint8_t ident[5];
+ uint8_t version;
+ union {
+ IsoVdElTorito boot;
+ IsoVdPrimary primary;
+ } vd;
+} __attribute__((packed)) IsoVolDesc;
+
+const uint8_t vol_desc_magic[] = "CD001";
+#define VOL_DESC_TYPE_BOOT 0
+#define VOL_DESC_TYPE_PRIMARY 1
+#define VOL_DESC_TYPE_SUPPLEMENT 2
+#define VOL_DESC_TYPE_PARTITION 3
+#define VOL_DESC_TERMINATOR 255
+
+static inline bool is_iso_vd_valid(IsoVolDesc *vd)
+{
+ return !_memcmp(&vd->ident[0], vol_desc_magic, 5) &&
+ vd->version == 0x1 &&
+ vd->type <= VOL_DESC_TYPE_PARTITION;
+}
+
+typedef struct IsoBcValid {
+ uint8_t platform_id;
+ uint16_t reserved;
+ uint8_t id[24];
+ uint16_t checksum;
+ uint8_t key[2];
+} __attribute__((packed)) IsoBcValid;
+
+typedef struct IsoBcSection {
+ uint8_t boot_type;
+ uint16_t load_segment;
+ uint8_t sys_type;
+ uint8_t unused;
+ uint16_t sector_count;
+ uint32_t load_rba;
+ uint8_t selection[20];
+} __attribute__((packed)) IsoBcSection;
+
+typedef struct IsoBcHdr {
+ uint8_t platform_id;
+ uint16_t sect_num;
+ uint8_t id[28];
+} __attribute__((packed)) IsoBcHdr;
+
+/*
+ * Match two CCWs located after PSW and eight filler bytes.
+ * From libmagic and arch/s390/kernel/head.S.
+ */
+const uint8_t linux_s390_magic[] = "\x02\x00\x00\x18\x60\x00\x00\x50\x02\x00"
+ "\x00\x68\x60\x00\x00\x50\x40\x40\x40\x40"
+ "\x40\x40\x40\x40";
+
+typedef struct IsoBcEntry {
+ uint8_t id;
+ union {
+ IsoBcValid valid; /* id == 0x01 */
+ IsoBcSection sect; /* id == 0x88 || id == 0x0 */
+ IsoBcHdr hdr; /* id == 0x90 || id == 0x91 */
+ } body;
+} __attribute__((packed)) IsoBcEntry;
+
+#define ISO_BC_ENTRY_PER_SECTOR (ISO_SECTOR_SIZE / sizeof(IsoBcEntry))
+#define ISO_BC_HDR_VALIDATION 0x01
+#define ISO_BC_BOOTABLE_SECTION 0x88
+#define ISO_BC_MAGIC_55 0x55
+#define ISO_BC_MAGIC_AA 0xaa
+#define ISO_BC_PLATFORM_X86 0x0
+#define ISO_BC_PLATFORM_PPC 0x1
+#define ISO_BC_PLATFORM_MAC 0x2
+
+static inline bool is_iso_bc_valid(IsoBcEntry *e)
+{
+ IsoBcValid *v = &e->body.valid;
+
+ if (e->id != ISO_BC_HDR_VALIDATION) {
+ return false;
+ }
+
+ if (v->platform_id != ISO_BC_PLATFORM_X86 &&
+ v->platform_id != ISO_BC_PLATFORM_PPC &&
+ v->platform_id != ISO_BC_PLATFORM_MAC) {
+ return false;
+ }
+
+ return v->key[0] == ISO_BC_MAGIC_55 &&
+ v->key[1] == ISO_BC_MAGIC_AA &&
+ v->reserved == 0x0;
+}
+
+#endif /* _PC_BIOS_S390_CCW_BOOTMAP_H */
diff --git a/src/pc-bios/s390-ccw/cio.h b/src/pc-bios/s390-ccw/cio.h
new file mode 100644
index 0000000..f5b4549
--- /dev/null
+++ b/src/pc-bios/s390-ccw/cio.h
@@ -0,0 +1,342 @@
+/*
+ * Channel IO definitions
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ *
+ * Inspired by various s390 headers in Linux 3.9.
+ *
+ * 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 CIO_H
+#define CIO_H
+
+/*
+ * path management control word
+ */
+struct pmcw {
+ __u32 intparm; /* interruption parameter */
+ __u32 qf : 1; /* qdio facility */
+ __u32 w : 1;
+ __u32 isc : 3; /* interruption sublass */
+ __u32 res5 : 3; /* reserved zeros */
+ __u32 ena : 1; /* enabled */
+ __u32 lm : 2; /* limit mode */
+ __u32 mme : 2; /* measurement-mode enable */
+ __u32 mp : 1; /* multipath mode */
+ __u32 tf : 1; /* timing facility */
+ __u32 dnv : 1; /* device number valid */
+ __u32 dev : 16; /* device number */
+ __u8 lpm; /* logical path mask */
+ __u8 pnom; /* path not operational mask */
+ __u8 lpum; /* last path used mask */
+ __u8 pim; /* path installed mask */
+ __u16 mbi; /* measurement-block index */
+ __u8 pom; /* path operational mask */
+ __u8 pam; /* path available mask */
+ __u8 chpid[8]; /* CHPID 0-7 (if available) */
+ __u32 unused1 : 8; /* reserved zeros */
+ __u32 st : 3; /* subchannel type */
+ __u32 unused2 : 18; /* reserved zeros */
+ __u32 mbfc : 1; /* measurement block format control */
+ __u32 xmwme : 1; /* extended measurement word mode enable */
+ __u32 csense : 1; /* concurrent sense; can be enabled ...*/
+ /* ... per MSCH, however, if facility */
+ /* ... is not installed, this results */
+ /* ... in an operand exception. */
+} __attribute__ ((packed));
+
+/* Target SCHIB configuration. */
+struct schib_config {
+ __u64 mba;
+ __u32 intparm;
+ __u16 mbi;
+ __u32 isc:3;
+ __u32 ena:1;
+ __u32 mme:2;
+ __u32 mp:1;
+ __u32 csense:1;
+ __u32 mbfc:1;
+} __attribute__ ((packed));
+
+struct scsw {
+ __u16 flags;
+ __u16 ctrl;
+ __u32 cpa;
+ __u8 dstat;
+ __u8 cstat;
+ __u16 count;
+} __attribute__ ((packed));
+
+#define SCSW_FCTL_CLEAR_FUNC 0x1000
+#define SCSW_FCTL_HALT_FUNC 0x2000
+#define SCSW_FCTL_START_FUNC 0x4000
+
+/*
+ * subchannel information block
+ */
+struct schib {
+ struct pmcw pmcw; /* path management control word */
+ struct scsw scsw; /* subchannel status word */
+ __u64 mba; /* measurement block address */
+ __u8 mda[4]; /* model dependent area */
+} __attribute__ ((packed,aligned(4)));
+
+struct subchannel_id {
+ __u32 cssid : 8;
+ __u32 : 4;
+ __u32 m : 1;
+ __u32 ssid : 2;
+ __u32 one : 1;
+ __u32 sch_no : 16;
+} __attribute__ ((packed, aligned(4)));
+
+struct chsc_header {
+ __u16 length;
+ __u16 code;
+} __attribute__((packed));
+
+struct chsc_area_sda {
+ struct chsc_header request;
+ __u8 reserved1:4;
+ __u8 format:4;
+ __u8 reserved2;
+ __u16 operation_code;
+ __u32 reserved3;
+ __u32 reserved4;
+ __u32 operation_data_area[252];
+ struct chsc_header response;
+ __u32 reserved5:4;
+ __u32 format2:4;
+ __u32 reserved6:24;
+} __attribute__((packed));
+
+/*
+ * TPI info structure
+ */
+struct tpi_info {
+ struct subchannel_id schid;
+ __u32 intparm; /* interruption parameter */
+ __u32 adapter_IO : 1;
+ __u32 reserved2 : 1;
+ __u32 isc : 3;
+ __u32 reserved3 : 12;
+ __u32 int_type : 3;
+ __u32 reserved4 : 12;
+} __attribute__ ((packed));
+
+/* channel command word (type 1) */
+struct ccw1 {
+ __u8 cmd_code;
+ __u8 flags;
+ __u16 count;
+ __u32 cda;
+} __attribute__ ((packed));
+
+#define CCW_FLAG_DC 0x80
+#define CCW_FLAG_CC 0x40
+#define CCW_FLAG_SLI 0x20
+#define CCW_FLAG_SKIP 0x10
+#define CCW_FLAG_PCI 0x08
+#define CCW_FLAG_IDA 0x04
+#define CCW_FLAG_SUSPEND 0x02
+
+#define CCW_CMD_NOOP 0x03
+#define CCW_CMD_BASIC_SENSE 0x04
+#define CCW_CMD_TIC 0x08
+#define CCW_CMD_SENSE_ID 0xe4
+
+#define CCW_CMD_SET_VQ 0x13
+#define CCW_CMD_VDEV_RESET 0x33
+#define CCW_CMD_READ_FEAT 0x12
+#define CCW_CMD_WRITE_FEAT 0x11
+#define CCW_CMD_READ_CONF 0x22
+#define CCW_CMD_WRITE_CONF 0x21
+#define CCW_CMD_WRITE_STATUS 0x31
+#define CCW_CMD_SET_IND 0x43
+#define CCW_CMD_SET_CONF_IND 0x53
+#define CCW_CMD_READ_VQ_CONF 0x32
+
+/*
+ * Command-mode operation request block
+ */
+struct cmd_orb {
+ __u32 intparm; /* interruption parameter */
+ __u32 key:4; /* flags, like key, suspend control, etc. */
+ __u32 spnd:1; /* suspend control */
+ __u32 res1:1; /* reserved */
+ __u32 mod:1; /* modification control */
+ __u32 sync:1; /* synchronize control */
+ __u32 fmt:1; /* format control */
+ __u32 pfch:1; /* prefetch control */
+ __u32 isic:1; /* initial-status-interruption control */
+ __u32 alcc:1; /* address-limit-checking control */
+ __u32 ssic:1; /* suppress-suspended-interr. control */
+ __u32 res2:1; /* reserved */
+ __u32 c64:1; /* IDAW/QDIO 64 bit control */
+ __u32 i2k:1; /* IDAW 2/4kB block size control */
+ __u32 lpm:8; /* logical path mask */
+ __u32 ils:1; /* incorrect length */
+ __u32 zero:6; /* reserved zeros */
+ __u32 orbx:1; /* ORB extension control */
+ __u32 cpa; /* channel program address */
+} __attribute__ ((packed, aligned(4)));
+
+struct ciw {
+ __u8 type;
+ __u8 command;
+ __u16 count;
+};
+
+/*
+ * sense-id response buffer layout
+ */
+struct senseid {
+ /* common part */
+ __u8 reserved; /* always 0x'FF' */
+ __u16 cu_type; /* control unit type */
+ __u8 cu_model; /* control unit model */
+ __u16 dev_type; /* device type */
+ __u8 dev_model; /* device model */
+ __u8 unused; /* padding byte */
+ /* extended part */
+ struct ciw ciw[62];
+} __attribute__ ((packed, aligned(4)));
+
+/* interruption response block */
+struct irb {
+ struct scsw scsw;
+ __u32 esw[5];
+ __u32 ecw[8];
+ __u32 emw[8];
+} __attribute__ ((packed, aligned(4)));
+
+/*
+ * Some S390 specific IO instructions as inline
+ */
+
+static inline int stsch_err(struct subchannel_id schid, struct schib *addr)
+{
+ register struct subchannel_id reg1 asm ("1") = schid;
+ int ccode = -EIO;
+
+ asm volatile(
+ " stsch 0(%3)\n"
+ "0: ipm %0\n"
+ " srl %0,28\n"
+ "1:\n"
+ : "+d" (ccode), "=m" (*addr)
+ : "d" (reg1), "a" (addr)
+ : "cc");
+ return ccode;
+}
+
+static inline int msch(struct subchannel_id schid, struct schib *addr)
+{
+ register struct subchannel_id reg1 asm ("1") = schid;
+ int ccode;
+
+ asm volatile(
+ " msch 0(%2)\n"
+ " ipm %0\n"
+ " srl %0,28"
+ : "=d" (ccode)
+ : "d" (reg1), "a" (addr), "m" (*addr)
+ : "cc");
+ return ccode;
+}
+
+static inline int msch_err(struct subchannel_id schid, struct schib *addr)
+{
+ register struct subchannel_id reg1 asm ("1") = schid;
+ int ccode = -EIO;
+
+ asm volatile(
+ " msch 0(%2)\n"
+ "0: ipm %0\n"
+ " srl %0,28\n"
+ "1:\n"
+ : "+d" (ccode)
+ : "d" (reg1), "a" (addr), "m" (*addr)
+ : "cc");
+ return ccode;
+}
+
+static inline int tsch(struct subchannel_id schid, struct irb *addr)
+{
+ register struct subchannel_id reg1 asm ("1") = schid;
+ int ccode;
+
+ asm volatile(
+ " tsch 0(%3)\n"
+ " ipm %0\n"
+ " srl %0,28"
+ : "=d" (ccode), "=m" (*addr)
+ : "d" (reg1), "a" (addr)
+ : "cc");
+ return ccode;
+}
+
+static inline int ssch(struct subchannel_id schid, struct cmd_orb *addr)
+{
+ register struct subchannel_id reg1 asm("1") = schid;
+ int ccode = -EIO;
+
+ asm volatile(
+ " ssch 0(%2)\n"
+ "0: ipm %0\n"
+ " srl %0,28\n"
+ "1:\n"
+ : "+d" (ccode)
+ : "d" (reg1), "a" (addr), "m" (*addr)
+ : "cc", "memory");
+ return ccode;
+}
+
+static inline int csch(struct subchannel_id schid)
+{
+ register struct subchannel_id reg1 asm("1") = schid;
+ int ccode;
+
+ asm volatile(
+ " csch\n"
+ " ipm %0\n"
+ " srl %0,28"
+ : "=d" (ccode)
+ : "d" (reg1)
+ : "cc");
+ return ccode;
+}
+
+static inline int tpi(struct tpi_info *addr)
+{
+ int ccode;
+
+ asm volatile(
+ " tpi 0(%2)\n"
+ " ipm %0\n"
+ " srl %0,28"
+ : "=d" (ccode), "=m" (*addr)
+ : "a" (addr)
+ : "cc");
+ return ccode;
+}
+
+static inline int chsc(void *chsc_area)
+{
+ typedef struct { char _[4096]; } addr_type;
+ int cc;
+
+ asm volatile(
+ " .insn rre,0xb25f0000,%2,0\n"
+ " ipm %0\n"
+ " srl %0,28\n"
+ : "=d" (cc), "=m" (*(addr_type *) chsc_area)
+ : "d" (chsc_area), "m" (*(addr_type *) chsc_area)
+ : "cc");
+ return cc;
+}
+
+#endif /* CIO_H */
diff --git a/src/pc-bios/s390-ccw/main.c b/src/pc-bios/s390-ccw/main.c
new file mode 100644
index 0000000..d5fe4ce
--- /dev/null
+++ b/src/pc-bios/s390-ccw/main.c
@@ -0,0 +1,116 @@
+/*
+ * S390 virtio-ccw loading program
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "s390-ccw.h"
+#include "virtio.h"
+
+char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
+char ring_area[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
+uint64_t boot_value;
+static struct subchannel_id blk_schid = { .one = 1 };
+
+/*
+ * Priniciples of Operations (SA22-7832-09) chapter 17 requires that
+ * a subsystem-identification is at 184-187 and bytes 188-191 are zero
+ * after list-directed-IPL and ccw-IPL.
+ */
+void write_subsystem_identification(void)
+{
+ struct subchannel_id *schid = (struct subchannel_id *) 184;
+ uint32_t *zeroes = (uint32_t *) 188;
+
+ *schid = blk_schid;
+ *zeroes = 0;
+}
+
+
+void virtio_panic(const char *string)
+{
+ sclp_print(string);
+ disabled_wait();
+ while (1) { }
+}
+
+static bool find_dev(struct schib *schib, int dev_no)
+{
+ int i, r;
+
+ for (i = 0; i < 0x10000; i++) {
+ blk_schid.sch_no = i;
+ r = stsch_err(blk_schid, schib);
+ if ((r == 3) || (r == -EIO)) {
+ break;
+ }
+ if (!schib->pmcw.dnv) {
+ continue;
+ }
+ if (!virtio_is_blk(blk_schid)) {
+ continue;
+ }
+ if ((dev_no < 0) || (schib->pmcw.dev == dev_no)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static void virtio_setup(uint64_t dev_info)
+{
+ struct schib schib;
+ int ssid;
+ bool found = false;
+ uint16_t dev_no;
+
+ /*
+ * We unconditionally enable mss support. In every sane configuration,
+ * this will succeed; and even if it doesn't, stsch_err() can deal
+ * with the consequences.
+ */
+ enable_mss_facility();
+
+ if (dev_info != -1) {
+ dev_no = dev_info & 0xffff;
+ debug_print_int("device no. ", dev_no);
+ blk_schid.ssid = (dev_info >> 16) & 0x3;
+ debug_print_int("ssid ", blk_schid.ssid);
+ found = find_dev(&schib, dev_no);
+ } else {
+ for (ssid = 0; ssid < 0x3; ssid++) {
+ blk_schid.ssid = ssid;
+ found = find_dev(&schib, -1);
+ if (found) {
+ break;
+ }
+ }
+ }
+
+ if (!found) {
+ virtio_panic("No virtio-blk device found!\n");
+ }
+
+ virtio_setup_block(blk_schid);
+
+ if (!virtio_ipl_disk_is_valid()) {
+ virtio_panic("No valid hard disk detected.\n");
+ }
+}
+
+int main(void)
+{
+ sclp_setup();
+ debug_print_int("boot reg[7] ", boot_value);
+ virtio_setup(boot_value);
+
+ zipl_load(); /* no return */
+
+ virtio_panic("Failed to load OS from hard disk\n");
+ return 0; /* make compiler happy */
+}
diff --git a/src/pc-bios/s390-ccw/s390-ccw.h b/src/pc-bios/s390-ccw/s390-ccw.h
new file mode 100644
index 0000000..5484c2a
--- /dev/null
+++ b/src/pc-bios/s390-ccw/s390-ccw.h
@@ -0,0 +1,146 @@
+/*
+ * S390 CCW boot loader
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#ifndef S390_CCW_H
+#define S390_CCW_H
+
+/* #define DEBUG */
+
+typedef unsigned char u8;
+typedef unsigned short u16;
+typedef unsigned int u32;
+typedef unsigned long long u64;
+typedef unsigned long ulong;
+typedef long size_t;
+typedef int bool;
+typedef unsigned char uint8_t;
+typedef unsigned short uint16_t;
+typedef unsigned int uint32_t;
+typedef unsigned long long uint64_t;
+typedef unsigned char __u8;
+typedef unsigned short __u16;
+typedef unsigned int __u32;
+typedef unsigned long long __u64;
+
+#define true 1
+#define false 0
+#define PAGE_SIZE 4096
+
+#ifndef EIO
+#define EIO 1
+#endif
+#ifndef EBUSY
+#define EBUSY 2
+#endif
+#ifndef NULL
+#define NULL 0
+#endif
+
+#include "cio.h"
+
+/* start.s */
+void disabled_wait(void);
+void consume_sclp_int(void);
+
+/* main.c */
+void virtio_panic(const char *string);
+void write_subsystem_identification(void);
+extern char stack[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
+extern char ring_area[PAGE_SIZE * 8] __attribute__((__aligned__(PAGE_SIZE)));
+extern uint64_t boot_value;
+
+/* sclp-ascii.c */
+void sclp_print(const char *string);
+void sclp_setup(void);
+
+/* virtio.c */
+unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
+ 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 */
+void zipl_load(void);
+
+static inline void *memset(void *s, int c, size_t n)
+{
+ int i;
+ unsigned char *p = s;
+
+ for (i = 0; i < n; i++) {
+ p[i] = c;
+ }
+
+ return s;
+}
+
+static inline void fill_hex(char *out, unsigned char val)
+{
+ const char hex[] = "0123456789abcdef";
+
+ out[0] = hex[(val >> 4) & 0xf];
+ out[1] = hex[val & 0xf];
+}
+
+static inline void fill_hex_val(char *out, void *ptr, unsigned size)
+{
+ unsigned char *value = ptr;
+ unsigned int 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);
+}
+
+static inline void debug_print_int(const char *desc, u64 addr)
+{
+#ifdef DEBUG
+ print_int(desc, addr);
+#endif
+}
+
+static inline void debug_print_addr(const char *desc, void *p)
+{
+#ifdef DEBUG
+ debug_print_int(desc, (unsigned int)(unsigned long)p);
+#endif
+}
+
+/***********************************************
+ * 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_CCW_NOTIFY 3
+
+static inline void yield(void)
+{
+ asm volatile ("diag 0,0,0x44"
+ : :
+ : "memory", "cc");
+}
+
+#define MAX_SECTOR_SIZE 4096
+
+#endif /* S390_CCW_H */
diff --git a/src/pc-bios/s390-ccw/sclp-ascii.c b/src/pc-bios/s390-ccw/sclp-ascii.c
new file mode 100644
index 0000000..dc1c3e4
--- /dev/null
+++ b/src/pc-bios/s390-ccw/sclp-ascii.c
@@ -0,0 +1,82 @@
+/*
+ * SCLP ASCII access driver
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "s390-ccw.h"
+#include "sclp.h"
+
+static char _sccb[PAGE_SIZE] __attribute__((__aligned__(4096)));
+
+/* Perform service call. Return 0 on success, non-zero otherwise. */
+static int sclp_service_call(unsigned int command, void *sccb)
+{
+ int cc;
+
+ asm volatile(
+ " .insn rre,0xb2200000,%1,%2\n" /* servc %1,%2 */
+ " ipm %0\n"
+ " srl %0,28"
+ : "=&d" (cc) : "d" (command), "a" (__pa(sccb))
+ : "cc", "memory");
+ consume_sclp_int();
+ if (cc == 3)
+ return -EIO;
+ if (cc == 2)
+ return -EBUSY;
+ return 0;
+}
+
+static void sclp_set_write_mask(void)
+{
+ WriteEventMask *sccb = (void *)_sccb;
+
+ sccb->h.length = sizeof(WriteEventMask);
+ sccb->mask_length = sizeof(unsigned int);
+ sccb->receive_mask = SCLP_EVENT_MASK_MSG_ASCII;
+ sccb->cp_receive_mask = SCLP_EVENT_MASK_MSG_ASCII;
+ sccb->send_mask = SCLP_EVENT_MASK_MSG_ASCII;
+ sccb->cp_send_mask = SCLP_EVENT_MASK_MSG_ASCII;
+
+ sclp_service_call(SCLP_CMD_WRITE_EVENT_MASK, sccb);
+}
+
+void sclp_setup(void)
+{
+ sclp_set_write_mask();
+}
+
+static int _strlen(const char *str)
+{
+ int i;
+ for (i = 0; *str; i++)
+ str++;
+ return i;
+}
+
+static void _memcpy(char *dest, const char *src, int len)
+{
+ int i;
+ for (i = 0; i < len; i++)
+ dest[i] = src[i];
+}
+
+void sclp_print(const char *str)
+{
+ int len = _strlen(str);
+ WriteEventData *sccb = (void *)_sccb;
+
+ sccb->h.length = sizeof(WriteEventData) + len;
+ sccb->h.function_code = SCLP_FC_NORMAL_WRITE;
+ sccb->ebh.length = sizeof(EventBufferHeader) + len;
+ sccb->ebh.type = SCLP_EVENT_ASCII_CONSOLE_DATA;
+ sccb->ebh.flags = 0;
+ _memcpy(sccb->data, str, len);
+
+ sclp_service_call(SCLP_CMD_WRITE_EVENT_DATA, sccb);
+}
diff --git a/src/pc-bios/s390-ccw/sclp.h b/src/pc-bios/s390-ccw/sclp.h
new file mode 100644
index 0000000..3cbfb78
--- /dev/null
+++ b/src/pc-bios/s390-ccw/sclp.h
@@ -0,0 +1,107 @@
+/*
+ * SCLP ASCII access driver
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ *
+ * 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 SCLP_H
+#define SCLP_H
+
+/* SCLP command codes */
+#define SCLP_CMDW_READ_SCP_INFO 0x00020001
+#define SCLP_CMDW_READ_SCP_INFO_FORCED 0x00120001
+#define SCLP_CMD_READ_EVENT_DATA 0x00770005
+#define SCLP_CMD_WRITE_EVENT_DATA 0x00760005
+#define SCLP_CMD_READ_EVENT_DATA 0x00770005
+#define SCLP_CMD_WRITE_EVENT_DATA 0x00760005
+#define SCLP_CMD_WRITE_EVENT_MASK 0x00780005
+
+/* SCLP response codes */
+#define SCLP_RC_NORMAL_READ_COMPLETION 0x0010
+#define SCLP_RC_NORMAL_COMPLETION 0x0020
+#define SCLP_RC_INVALID_SCLP_COMMAND 0x01f0
+#define SCLP_RC_CONTAINED_EQUIPMENT_CHECK 0x0340
+#define SCLP_RC_INSUFFICIENT_SCCB_LENGTH 0x0300
+#define SCLP_RC_INVALID_FUNCTION 0x40f0
+#define SCLP_RC_NO_EVENT_BUFFERS_STORED 0x60f0
+#define SCLP_RC_INVALID_SELECTION_MASK 0x70f0
+#define SCLP_RC_INCONSISTENT_LENGTHS 0x72f0
+#define SCLP_RC_EVENT_BUFFER_SYNTAX_ERROR 0x73f0
+#define SCLP_RC_INVALID_MASK_LENGTH 0x74f0
+
+/* Service Call Control Block (SCCB) and its elements */
+
+#define SCCB_SIZE 4096
+
+#define SCLP_VARIABLE_LENGTH_RESPONSE 0x80
+#define SCLP_EVENT_BUFFER_ACCEPTED 0x80
+
+#define SCLP_FC_NORMAL_WRITE 0
+
+typedef struct SCCBHeader {
+ uint16_t length;
+ uint8_t function_code;
+ uint8_t control_mask[3];
+ uint16_t response_code;
+} __attribute__((packed)) SCCBHeader;
+
+#define SCCB_DATA_LEN (SCCB_SIZE - sizeof(SCCBHeader))
+
+typedef struct ReadInfo {
+ SCCBHeader h;
+ uint16_t rnmax;
+ uint8_t rnsize;
+} __attribute__((packed)) ReadInfo;
+
+typedef struct SCCB {
+ SCCBHeader h;
+ char data[SCCB_DATA_LEN];
+ } __attribute__((packed)) SCCB;
+
+/* SCLP event types */
+#define SCLP_EVENT_ASCII_CONSOLE_DATA 0x1a
+#define SCLP_EVENT_SIGNAL_QUIESCE 0x1d
+
+/* SCLP event masks */
+#define SCLP_EVENT_MASK_SIGNAL_QUIESCE 0x00000008
+#define SCLP_EVENT_MASK_MSG_ASCII 0x00000040
+
+#define SCLP_UNCONDITIONAL_READ 0x00
+#define SCLP_SELECTIVE_READ 0x01
+
+typedef struct WriteEventMask {
+ SCCBHeader h;
+ uint16_t _reserved;
+ uint16_t mask_length;
+ uint32_t cp_receive_mask;
+ uint32_t cp_send_mask;
+ uint32_t send_mask;
+ uint32_t receive_mask;
+} __attribute__((packed)) WriteEventMask;
+
+typedef struct EventBufferHeader {
+ uint16_t length;
+ uint8_t type;
+ uint8_t flags;
+ uint16_t _reserved;
+} __attribute__((packed)) EventBufferHeader;
+
+typedef struct WriteEventData {
+ SCCBHeader h;
+ EventBufferHeader ebh;
+ char data[0];
+} __attribute__((packed)) WriteEventData;
+
+typedef struct ReadEventData {
+ SCCBHeader h;
+ EventBufferHeader ebh;
+ uint32_t mask;
+} __attribute__((packed)) ReadEventData;
+
+#define __pa(x) (x)
+
+#endif /* SCLP_H */
diff --git a/src/pc-bios/s390-ccw/start.S b/src/pc-bios/s390-ccw/start.S
new file mode 100644
index 0000000..b6dd8c2
--- /dev/null
+++ b/src/pc-bios/s390-ccw/start.S
@@ -0,0 +1,65 @@
+/*
+ * First stage boot loader for virtio devices. The compiled output goes
+ * into the pc-bios directory of qemu.
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ * Copyright 2013 IBM Corp.
+ *
+ * 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.
+ */
+
+ .globl _start
+_start:
+
+larl %r15, stack + 0x8000 /* Set up stack */
+larl %r6, boot_value
+stg %r7, 0(%r6) /* save the boot_value before any function calls */
+j main /* And call C */
+
+/*
+ * void disabled_wait(void)
+ *
+ * stops the current guest cpu.
+ */
+ .globl disabled_wait
+disabled_wait:
+ larl %r1,disabled_wait_psw
+ lpswe 0(%r1)
+
+
+/*
+ * void consume_sclp_int(void)
+ *
+ * eats one sclp interrupt
+ */
+ .globl consume_sclp_int
+consume_sclp_int:
+ /* enable service interrupts in cr0 */
+ stctg 0,0,0(15)
+ oi 6(15), 0x2
+ lctlg 0,0,0(15)
+ /* prepare external call handler */
+ larl %r1, external_new_code
+ stg %r1, 0x1b8
+ larl %r1, external_new_mask
+ mvc 0x1b0(8),0(%r1)
+ /* load enabled wait PSW */
+ larl %r1, enabled_wait_psw
+ lpswe 0(%r1)
+
+external_new_code:
+ /* disable service interrupts in cr0 */
+ stctg 0,0,0(15)
+ ni 6(15), 0xfd
+ lctlg 0,0,0(15)
+ br 14
+
+ .align 8
+disabled_wait_psw:
+ .quad 0x0002000180000000,0x0000000000000000
+enabled_wait_psw:
+ .quad 0x0302000180000000,0x0000000000000000
+external_new_mask:
+ .quad 0x0000000180000000
diff --git a/src/pc-bios/s390-ccw/virtio.c b/src/pc-bios/s390-ccw/virtio.c
new file mode 100644
index 0000000..87aed38
--- /dev/null
+++ b/src/pc-bios/s390-ccw/virtio.c
@@ -0,0 +1,441 @@
+/*
+ * Virtio driver bits
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2 or (at
+ * your option) any later version. See the COPYING file in the top-level
+ * directory.
+ */
+
+#include "s390-ccw.h"
+#include "virtio.h"
+
+static struct vring block;
+
+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");
+
+ asm volatile ("diag 2,4,0x500"
+ : "=d" (retval)
+ : "d" (r_nr), "0" (r_param1), "r"(r_param2)
+ : "memory", "cc");
+
+ return retval;
+}
+
+static void virtio_notify(struct subchannel_id schid)
+{
+ kvm_hypercall(KVM_S390_VIRTIO_CCW_NOTIFY, *(u32 *)&schid, 0);
+}
+
+/***********************************************
+ * Virtio functions *
+ ***********************************************/
+
+static int drain_irqs(struct subchannel_id schid)
+{
+ struct irb irb = {};
+ int r = 0;
+
+ while (1) {
+ /* FIXME: make use of TPI, for that enable subchannel and isc */
+ if (tsch(schid, &irb)) {
+ /* Might want to differentiate error codes later on. */
+ if (irb.scsw.cstat) {
+ r = -EIO;
+ } else if (irb.scsw.dstat != 0xc) {
+ r = -EIO;
+ }
+ return r;
+ }
+ }
+}
+
+static int run_ccw(struct subchannel_id schid, int cmd, void *ptr, int len)
+{
+ struct ccw1 ccw = {};
+ struct cmd_orb orb = {};
+ struct schib schib;
+ int r;
+
+ /* start command processing */
+ stsch_err(schid, &schib);
+ schib.scsw.ctrl = SCSW_FCTL_START_FUNC;
+ msch(schid, &schib);
+
+ /* start subchannel command */
+ orb.fmt = 1;
+ orb.cpa = (u32)(long)&ccw;
+ orb.lpm = 0x80;
+
+ ccw.cmd_code = cmd;
+ ccw.cda = (long)ptr;
+ ccw.count = len;
+
+ r = ssch(schid, &orb);
+ /*
+ * XXX Wait until device is done processing the CCW. For now we can
+ * assume that a simple tsch will have finished the CCW processing,
+ * but the architecture allows for asynchronous operation
+ */
+ if (!r) {
+ r = drain_irqs(schid);
+ }
+ return r;
+}
+
+static void virtio_set_status(struct subchannel_id schid,
+ unsigned long dev_addr)
+{
+ unsigned char status = dev_addr;
+ if (run_ccw(schid, CCW_CMD_WRITE_STATUS, &status, sizeof(status))) {
+ virtio_panic("Could not write status to host!\n");
+ }
+}
+
+static void virtio_reset(struct subchannel_id schid)
+{
+ run_ccw(schid, CCW_CMD_VDEV_RESET, NULL, 0);
+}
+
+static void vring_init(struct vring *vr, unsigned int num, void *p,
+ unsigned long align)
+{
+ debug_print_addr("init p", p);
+ vr->num = num;
+ vr->desc = p;
+ vr->avail = p + num*sizeof(struct vring_desc);
+ vr->used = (void *)(((unsigned long)&vr->avail->ring[num] + align-1)
+ & ~(align - 1));
+
+ /* Zero out all relevant field */
+ vr->avail->flags = 0;
+ vr->avail->idx = 0;
+
+ /* We're running with interrupts off anyways, so don't bother */
+ vr->used->flags = VRING_USED_F_NO_NOTIFY;
+ vr->used->idx = 0;
+ vr->used_idx = 0;
+ vr->next_idx = 0;
+
+ debug_print_addr("init vr", vr);
+}
+
+static void vring_notify(struct subchannel_id schid)
+{
+ virtio_notify(schid);
+}
+
+static void vring_send_buf(struct vring *vr, void *p, int len, int flags)
+{
+ /* For follow-up chains we need to keep the first entry point */
+ if (!(flags & VRING_HIDDEN_IS_CHAIN)) {
+ vr->avail->ring[vr->avail->idx % vr->num] = vr->next_idx;
+ }
+
+ vr->desc[vr->next_idx].addr = (ulong)p;
+ vr->desc[vr->next_idx].len = len;
+ vr->desc[vr->next_idx].flags = flags & ~VRING_HIDDEN_IS_CHAIN;
+ vr->desc[vr->next_idx].next = vr->next_idx;
+ vr->desc[vr->next_idx].next++;
+ vr->next_idx++;
+
+ /* Chains only have a single ID */
+ if (!(flags & VRING_DESC_F_NEXT)) {
+ vr->avail->idx++;
+ }
+}
+
+static u64 get_clock(void)
+{
+ u64 r;
+
+ asm volatile("stck %0" : "=Q" (r) : : "cc");
+ return r;
+}
+
+static ulong get_second(void)
+{
+ return (get_clock() >> 12) / 1000000;
+}
+
+/*
+ * Wait for the host to reply.
+ *
+ * timeout is in seconds if > 0.
+ *
+ * Returns 0 on success, 1 on timeout.
+ */
+static int vring_wait_reply(struct vring *vr, int timeout)
+{
+ ulong target_second = get_second() + timeout;
+ struct subchannel_id schid = vr->schid;
+ int r = 0;
+
+ /* Wait until the used index has moved. */
+ while (vr->used->idx == vr->used_idx) {
+ vring_notify(schid);
+ if (timeout && (get_second() >= target_second)) {
+ r = 1;
+ break;
+ }
+ yield();
+ }
+
+ vr->used_idx = vr->used->idx;
+ vr->next_idx = 0;
+ vr->desc[0].len = 0;
+ vr->desc[0].flags = 0;
+
+ return r;
+}
+
+/***********************************************
+ * Virtio block *
+ ***********************************************/
+
+int virtio_read_many(ulong sector, void *load_addr, int sec_num)
+{
+ struct virtio_blk_outhdr out_hdr;
+ u8 status;
+ int r;
+
+ /* Tell the host we want to read */
+ out_hdr.type = VIRTIO_BLK_T_IN;
+ out_hdr.ioprio = 99;
+ 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, virtio_get_block_size() * sec_num,
+ VRING_DESC_F_WRITE | VRING_HIDDEN_IS_CHAIN |
+ VRING_DESC_F_NEXT);
+
+ /* status field */
+ vring_send_buf(&block, &status, sizeof(u8), VRING_DESC_F_WRITE |
+ VRING_HIDDEN_IS_CHAIN);
+
+ /* Now we can tell the host to read */
+ vring_wait_reply(&block, 0);
+
+ r = drain_irqs(block.schid);
+ if (r) {
+ /* Well, whatever status is supposed to contain... */
+ status = 1;
+ }
+ return status;
+}
+
+unsigned long virtio_load_direct(ulong rec_list1, ulong rec_list2,
+ ulong subchan_id, void *load_addr)
+{
+ u8 status;
+ int sec = rec_list1;
+ int sec_num = ((rec_list2 >> 32) & 0xffff) + 1;
+ int sec_len = rec_list2 >> 48;
+ ulong addr = (ulong)load_addr;
+
+ if (sec_len != virtio_get_block_size()) {
+ return -1;
+ }
+
+ sclp_print(".");
+ status = virtio_read_many(sec, (void *)addr, sec_num);
+ if (status) {
+ virtio_panic("I/O Error");
+ }
+ addr += sec_num * virtio_get_block_size();
+
+ return addr;
+}
+
+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;
+ blk_cfg.physical_block_exp = 0;
+}
+
+void virtio_assume_iso9660(void)
+{
+ guessed_disk_nature = true;
+ blk_cfg.blk_size = 2048;
+ blk_cfg.physical_block_exp = 0;
+}
+
+void virtio_assume_eckd(void)
+{
+ guessed_disk_nature = true;
+ blk_cfg.blk_size = 4096;
+ blk_cfg.physical_block_exp = 0;
+
+ /* 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 (virtio_get_block_size() == 512);
+ }
+ return (blk_cfg.geometry.heads == 255)
+ && (blk_cfg.geometry.sectors == 63)
+ && (virtio_get_block_size() == 512);
+}
+
+/*
+ * Other supported value pairs, if any, would need to be added here.
+ * Note: head count is always 15.
+ */
+static inline u8 virtio_eckd_sectors_for_block_size(int size)
+{
+ switch (size) {
+ case 512:
+ return 49;
+ case 1024:
+ return 33;
+ case 2048:
+ return 21;
+ case 4096:
+ return 12;
+ }
+ return 0;
+}
+
+bool virtio_disk_is_eckd(void)
+{
+ const int block_size = virtio_get_block_size();
+
+ if (guessed_disk_nature) {
+ return (block_size == 4096);
+ }
+ return (blk_cfg.geometry.heads == 15)
+ && (blk_cfg.geometry.sectors ==
+ virtio_eckd_sectors_for_block_size(block_size));
+}
+
+bool virtio_ipl_disk_is_valid(void)
+{
+ return virtio_disk_is_scsi() || virtio_disk_is_eckd();
+}
+
+int virtio_get_block_size(void)
+{
+ return blk_cfg.blk_size << blk_cfg.physical_block_exp;
+}
+
+uint8_t virtio_get_heads(void)
+{
+ return blk_cfg.geometry.heads;
+}
+
+uint8_t virtio_get_sectors(void)
+{
+ return blk_cfg.geometry.sectors;
+}
+
+uint64_t virtio_get_blocks(void)
+{
+ return blk_cfg.capacity /
+ (virtio_get_block_size() / VIRTIO_SECTOR_SIZE);
+}
+
+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... */
+ guessed_disk_nature = false;
+
+ 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, ring_area,
+ KVM_S390_VIRTIO_RING_ALIGN);
+
+ info.queue = (unsigned long long) ring_area;
+ info.align = KVM_S390_VIRTIO_RING_ALIGN;
+ info.index = 0;
+ info.num = config.num;
+ block.schid = 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)
+{
+ int r;
+ struct senseid senseid = {};
+
+ /* run sense id command */
+ r = run_ccw(schid, CCW_CMD_SENSE_ID, &senseid, sizeof(senseid));
+ if (r) {
+ return false;
+ }
+ if ((senseid.cu_type != 0x3832) || (senseid.cu_model != VIRTIO_ID_BLOCK)) {
+ return false;
+ }
+
+ return true;
+}
+
+int enable_mss_facility(void)
+{
+ int ret;
+ struct chsc_area_sda *sda_area = (struct chsc_area_sda *) chsc_page;
+
+ memset(sda_area, 0, PAGE_SIZE);
+ sda_area->request.length = 0x0400;
+ sda_area->request.code = 0x0031;
+ sda_area->operation_code = 0x2;
+
+ ret = chsc(sda_area);
+ if ((ret == 0) && (sda_area->response.code == 0x0001)) {
+ return 0;
+ }
+ return -EIO;
+}
diff --git a/src/pc-bios/s390-ccw/virtio.h b/src/pc-bios/s390-ccw/virtio.h
new file mode 100644
index 0000000..afa01a8
--- /dev/null
+++ b/src/pc-bios/s390-ccw/virtio.h
@@ -0,0 +1,208 @@
+/*
+ * Virtio driver bits
+ *
+ * Copyright (c) 2013 Alexander Graf <agraf@suse.de>
+ *
+ * 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 VIRTIO_H
+#define VIRTIO_H
+
+#include "s390-ccw.h"
+
+/* Status byte for guest to report progress, and synchronize features. */
+/* We have seen device and processed generic fields (VIRTIO_CONFIG_F_VIRTIO) */
+#define VIRTIO_CONFIG_S_ACKNOWLEDGE 1
+/* We have found a driver for the device. */
+#define VIRTIO_CONFIG_S_DRIVER 2
+/* Driver has used its parts of the config, and is happy */
+#define VIRTIO_CONFIG_S_DRIVER_OK 4
+/* We've given up on this device. */
+#define VIRTIO_CONFIG_S_FAILED 0x80
+
+enum virtio_dev_type {
+ VIRTIO_ID_NET = 1,
+ VIRTIO_ID_BLOCK = 2,
+ VIRTIO_ID_CONSOLE = 3,
+ VIRTIO_ID_BALLOON = 5,
+};
+
+struct virtio_dev_header {
+ enum virtio_dev_type type : 8;
+ u8 num_vq;
+ u8 feature_len;
+ u8 config_len;
+ u8 status;
+ u8 vqconfig[];
+} __attribute__((packed));
+
+struct virtio_vqconfig {
+ u64 token;
+ u64 address;
+ u16 num;
+ u8 pad[6];
+} __attribute__((packed));
+
+struct vq_info_block {
+ u64 queue;
+ u32 align;
+ u16 index;
+ u16 num;
+} __attribute__((packed));
+
+struct vq_config_block {
+ u16 index;
+ u16 num;
+} __attribute__((packed));
+
+struct virtio_dev {
+ struct virtio_dev_header *header;
+ struct virtio_vqconfig *vqconfig;
+ char *host_features;
+ char *guest_features;
+ char *config;
+};
+
+#define KVM_S390_VIRTIO_RING_ALIGN 4096
+
+#define VRING_USED_F_NO_NOTIFY 1
+
+/* This marks a buffer as continuing via the next field. */
+#define VRING_DESC_F_NEXT 1
+/* This marks a buffer as write-only (otherwise read-only). */
+#define VRING_DESC_F_WRITE 2
+/* This means the buffer contains a list of buffer descriptors. */
+#define VRING_DESC_F_INDIRECT 4
+
+/* Internal flag to mark follow-up segments as such */
+#define VRING_HIDDEN_IS_CHAIN 256
+
+/* Virtio ring descriptors: 16 bytes. These can chain together via "next". */
+struct vring_desc {
+ /* Address (guest-physical). */
+ u64 addr;
+ /* Length. */
+ u32 len;
+ /* The flags as indicated above. */
+ u16 flags;
+ /* We chain unused descriptors via this, too */
+ u16 next;
+} __attribute__((packed));
+
+struct vring_avail {
+ u16 flags;
+ u16 idx;
+ u16 ring[];
+} __attribute__((packed));
+
+/* u32 is used here for ids for padding reasons. */
+struct vring_used_elem {
+ /* Index of start of used descriptor chain. */
+ u32 id;
+ /* Total length of the descriptor chain which was used (written to) */
+ u32 len;
+} __attribute__((packed));
+
+struct vring_used {
+ u16 flags;
+ u16 idx;
+ struct vring_used_elem ring[];
+} __attribute__((packed));
+
+struct vring {
+ unsigned int num;
+ int next_idx;
+ int used_idx;
+ struct vring_desc *desc;
+ struct vring_avail *avail;
+ struct vring_used *used;
+ struct subchannel_id schid;
+};
+
+
+/***********************************************
+ * Virtio block *
+ ***********************************************/
+
+/*
+ * Command types
+ *
+ * Usage is a bit tricky as some bits are used as flags and some are not.
+ *
+ * Rules:
+ * VIRTIO_BLK_T_OUT may be combined with VIRTIO_BLK_T_SCSI_CMD or
+ * VIRTIO_BLK_T_BARRIER. VIRTIO_BLK_T_FLUSH is a command of its own
+ * and may not be combined with any of the other flags.
+ */
+
+/* These two define direction. */
+#define VIRTIO_BLK_T_IN 0
+#define VIRTIO_BLK_T_OUT 1
+
+/* This bit says it's a scsi command, not an actual read or write. */
+#define VIRTIO_BLK_T_SCSI_CMD 2
+
+/* Cache flush command */
+#define VIRTIO_BLK_T_FLUSH 4
+
+/* Barrier before this op. */
+#define VIRTIO_BLK_T_BARRIER 0x80000000
+
+/* This is the first element of the read scatter-gather list. */
+struct virtio_blk_outhdr {
+ /* VIRTIO_BLK_T* */
+ u32 type;
+ /* io priority. */
+ u32 ioprio;
+ /* Sector (ie. 512 byte offset) */
+ 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);
+void virtio_assume_iso9660(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 uint8_t virtio_get_heads(void);
+extern uint8_t virtio_get_sectors(void);
+extern uint64_t virtio_get_blocks(void);
+extern int virtio_read_many(ulong sector, void *load_addr, int sec_num);
+
+#define VIRTIO_SECTOR_SIZE 512
+
+static inline ulong virtio_sector_adjust(ulong sector)
+{
+ return sector * (virtio_get_block_size() / VIRTIO_SECTOR_SIZE);
+}
+
+#endif /* VIRTIO_H */
OpenPOWER on IntegriCloud