diff options
author | Peter Maydell <peter.maydell@linaro.org> | 2014-06-29 11:43:30 +0100 |
---|---|---|
committer | Peter Maydell <peter.maydell@linaro.org> | 2014-06-29 11:43:31 +0100 |
commit | de6793e8c2a4d34e28e5ea385276249fc98109ec (patch) | |
tree | d89981b7528e18523fc2617e3e7c77852742dd06 | |
parent | 1045fc0439f02dc12298045ecb768c52d796dd99 (diff) | |
parent | 77416f4075a673a27cfe5a7a34e93c0fa9810e35 (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.img | bin | 9432 -> 17624 bytes | |||
-rw-r--r-- | pc-bios/s390-ccw/bootmap.c | 445 | ||||
-rw-r--r-- | pc-bios/s390-ccw/bootmap.h | 344 | ||||
-rw-r--r-- | pc-bios/s390-ccw/main.c | 13 | ||||
-rw-r--r-- | pc-bios/s390-ccw/s390-ccw.h | 38 | ||||
-rw-r--r-- | pc-bios/s390-ccw/sclp-ascii.c | 4 | ||||
-rw-r--r-- | pc-bios/s390-ccw/virtio.c | 122 | ||||
-rw-r--r-- | pc-bios/s390-ccw/virtio.h | 50 |
8 files changed, 837 insertions, 179 deletions
diff --git a/pc-bios/s390-ccw.img b/pc-bios/s390-ccw.img Binary files differindex 1c7f7640fc..603e19e003 100644 --- a/pc-bios/s390-ccw.img +++ b/pc-bios/s390-ccw.img 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 */ |