diff options
author | Alexander Graf <agraf@suse.de> | 2013-04-22 21:02:49 +0200 |
---|---|---|
committer | Alexander Graf <agraf@suse.de> | 2013-04-26 20:18:24 +0200 |
commit | 685d49a63e0665f609973ffe4ba34f06981e1b03 (patch) | |
tree | e65b92e72bd87848e739651c7a7b8fc26e0e6efd /pc-bios/s390-ccw/bootmap.c | |
parent | c9c39d3b5ea870073703141ba04813c7a8779b02 (diff) |
S390: ccw firmware: Add bootmap interpreter
On s390, there is an architected boot map format that we can read to
boot a certain entry off the disk. Implement a simple reader for this
that always boots the first (default) entry.
Signed-off-by: Alexander Graf <agraf@suse.de>
Diffstat (limited to 'pc-bios/s390-ccw/bootmap.c')
-rw-r--r-- | pc-bios/s390-ccw/bootmap.c | 235 |
1 files changed, 235 insertions, 0 deletions
diff --git a/pc-bios/s390-ccw/bootmap.c b/pc-bios/s390-ccw/bootmap.c new file mode 100644 index 0000000000..53a460df84 --- /dev/null +++ b/pc-bios/s390-ccw/bootmap.c @@ -0,0 +1,235 @@ +/* + * 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" + +// #define DEBUG_FALLBACK + +#ifdef DEBUG_FALLBACK +#define dputs(txt) \ + do { sclp_print("zipl: " txt); } while (0) +#else +#define dputs(fmt, ...) \ + 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))); + +/* Check for ZIPL magic. Returns 0 if not matched. */ +static int zipl_magic(uint8_t *ptr) +{ + uint32_t *p = (void*)ptr; + uint32_t *z = (void*)ZIPL_MAGIC; + + if (*p != *z) { + debug_print_int("invalid magic", *p); + virtio_panic("invalid magic"); + } + + return 1; +} + +static int zipl_load_segment(struct component_entry *entry) +{ + const int max_entries = (SECTOR_SIZE / sizeof(struct scsi_blockptr)); + struct scsi_blockptr *bprs = (void*)sec; + uint64_t blockno; + long address; + int i; + + blockno = entry->data.blockno; + address = entry->load_address; + + debug_print_int("loading segment at block", blockno); + debug_print_int("addr", address); + + do { + if (virtio_read(blockno, (uint8_t *)bprs)) { + debug_print_int("failed reading bprs at", blockno); + goto fail; + } + + for (i = 0;; i++) { + u64 *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; + + address = virtio_load_direct(cur_desc[0], cur_desc[1], 0, + (void*)address); + if (address == -1) + goto fail; + } + } 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) +{ + struct component_header *header; + struct component_entry *entry; + void (*ipl)(void); + uint8_t tmp_sec[SECTOR_SIZE]; + + virtio_read(pte->blockno, tmp_sec); + header = (struct component_header *)tmp_sec; + + if (!zipl_magic(tmp_sec)) { + goto fail; + } + + if (header->type != ZIPL_COMP_HEADER_IPL) { + goto fail; + } + + dputs("start loading images\n"); + + /* Load image(s) into RAM */ + entry = (struct component_entry *)(&header[1]); + while (entry->component_type == ZIPL_COMP_ENTRY_LOAD) { + if (zipl_load_segment(entry) < 0) { + goto fail; + } + + entry++; + + if ((uint8_t*)(&entry[1]) > (tmp_sec + SECTOR_SIZE)) { + goto fail; + } + } + + if (entry->component_type != ZIPL_COMP_ENTRY_EXEC) { + goto fail; + } + + /* Ensure the guest output starts fresh */ + sclp_print("\n"); + + /* And run the OS! */ + ipl = (void*)(entry->load_address & 0x7fffffff); + debug_print_addr("set IPL addr to", ipl); + /* should not return */ + ipl(); + + return 0; + +fail: + sclp_print("failed running zipl\n"); + return -1; +} + +int zipl_load(void) +{ + struct mbr *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"); + + if (!zipl_magic(mbr->magic)) { + error = "zipl_magic 1"; + goto fail; + } + + debug_print_int("program table", mbr->blockptr.blockno); + + /* Parse the program table */ + if (virtio_read(mbr->blockptr.blockno, sec)) { + error = "virtio_read"; + goto fail; + } + + if (!zipl_magic(sec)) { + error = "zipl_magic 2"; + goto fail; + } + + ns_end = sec + SECTOR_SIZE; + for (ns = (sec + pte_len); (ns + pte_len) < ns_end; ns++) { + prog_table_entry = (struct scsi_blockptr *)ns; + if (!prog_table_entry->blockno) { + break; + } + + program_table_entries++; + } + + debug_print_int("program table entries", program_table_entries); + + if (!program_table_entries) { + goto fail; + } + + /* Run the default entry */ + + prog_table_entry = (struct scsi_blockptr *)(sec + pte_len); + + return zipl_run(prog_table_entry); + +fail: + sclp_print("failed loading zipl: "); + sclp_print(error); + sclp_print("\n"); + return -1; +} |