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)
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1.4.11 (GNU/Linux)
 
 iQIcBAABAgAGBQJTrU+CAAoJEN7Pa5PG8C+vKp8P/22BKb0ga75s1hbzmMcSETTU
 EgyHefOtYx3pjaBOo5k5j83KUblPaoPC7KCr5ZZWsvCvtgRFddqA6L5nS9QNhzSz
 XepwFC1UaNB2DNDWdqOibOguILSoD6d2SDNqnV4obGyJ2TPnMjZfw6jmHmP7Mxbf
 +P8sUhbdZ4Esp/DQ5dQxfxcgfaf5KZXrl8mTVhfSRacvQxqOWM8+aor+rfX7F0pC
 eKJtWgHT98RUQSZyt0cwyd4/0yzChlZRElc+GQYbpdToSlYzDwM8AyLFPRzLJsaB
 06EKw0KlvAz55unMfwW0eEX3NrIOxwV2STaTOIKpaiTt48wymC/pH8fyfRCIywT2
 s8+K5nivebTir1D3Vis9xYMif/4NOjIif0cPFbfqh5E5ViSn1I4wjp18Cutd9dXB
 AztbRM95g1wzGCWs1XNfXNxPyzDD/1jQM0hW1anj43sEEQoqV2is15AG7RUqSkS3
 mBHVoh5P//SUCf+KPaEnryXlHfNgLcdSMEfC3KQUs8L96cMoTkLFbVHbx+eLb0g3
 hZfTsB54J+HoiRcCUnG3m+iU4hOAjXT5tjNfuA4HH51W3nQ8kZt9SQbmtZujt5jO
 WaL330Xd/5qgDn9fe6O3BnPOP0u1AIth6DB0fu2wN9Cy5sGS0jm9c6rVcbkV7ESx
 IL3z3X+Rr5WRyp7b3tqU
 =hh2q
 -----END PGP SIGNATURE-----

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>
This commit is contained in:
Peter Maydell 2014-06-29 11:43:30 +01:00
commit de6793e8c2
8 changed files with 843 additions and 185 deletions

Binary file not shown.

View File

@ -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)
{
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;
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.");
}
#define FREE_SPACE_FILLER '\xAA'
static inline bool unused_space(const void *p, unsigned int size)
static bool eckd_valid_address(BootMapPointer *p)
{
int i;
const unsigned char *m = p;
const uint64_t cylinder = p->eckd.cylinder
+ ((p->eckd.head & 0xfff0) << 12);
const uint64_t head = p->eckd.head & 0x000f;
for (i = 0; i < size; i++) {
if (m[i] != FREE_SPACE_FILLER) {
return false;
}
if (head >= virtio_get_heads()
|| p->eckd.sector > virtio_get_sectors()
|| p->eckd.sector <= 0) {
return false;
}
if (!virtio_guessed_disk_nature() && cylinder >= virtio_get_cylinders()) {
return false;
}
return true;
}
static int zipl_load_segment(struct component_entry *entry)
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 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)
{
const int max_entries = (SECTOR_SIZE / sizeof(struct scsi_blockptr));
struct scsi_blockptr *bprs = (void*)sec;
const int bprs_size = sizeof(sec);
uint64_t blockno;
long address;
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");
}
}
/***********************************************************************
* 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;
@ -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 = "";
const int pte_len = sizeof(ScsiBlockPtr);
ScsiBlockPtr *prog_table_entry;
/* Grab the MBR */
virtio_read(0, (void*)mbr);
dputs("checking magic\n");
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);
fail:
sclp_print("failed loading zipl: ");
sclp_print(error);
sclp_print("\n");
return -1;
zipl_run(prog_table_entry); /* 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 */
}
/* 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");
}

344
pc-bios/s390-ccw/bootmap.h Normal file
View File

@ -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 */

View File

@ -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 */
}

View File

@ -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 */

View File

@ -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;

View File

@ -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)

View File

@ -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 */