/* * S390 IPL (boot) from a real DASD device via vfio framework. * * Copyright (c) 2019 Jason J. Herne * * 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 "libc.h" #include "s390-ccw.h" #include "s390-arch.h" #include "dasd-ipl.h" #include "helper.h" static char prefix_page[PAGE_SIZE * 2] __attribute__((__aligned__(PAGE_SIZE * 2))); static void enable_prefixing(void) { memcpy(&prefix_page, lowcore, 4096); set_prefix(ptr2u32(&prefix_page)); } static void disable_prefixing(void) { set_prefix(0); /* Copy io interrupt info back to low core */ memcpy((void *)&lowcore->subchannel_id, prefix_page + 0xB8, 12); } static bool is_read_tic_ccw_chain(Ccw0 *ccw) { Ccw0 *next_ccw = ccw + 1; return ((ccw->cmd_code == CCW_CMD_DASD_READ || ccw->cmd_code == CCW_CMD_DASD_READ_MT) && ccw->chain && next_ccw->cmd_code == CCW_CMD_TIC); } static bool dynamic_cp_fixup(uint32_t ccw_addr, uint32_t *next_cpa) { Ccw0 *cur_ccw = (Ccw0 *)(uint64_t)ccw_addr; Ccw0 *tic_ccw; while (true) { /* Skip over inline TIC (it might not have the chain bit on) */ if (cur_ccw->cmd_code == CCW_CMD_TIC && cur_ccw->cda == ptr2u32(cur_ccw) - 8) { cur_ccw += 1; continue; } if (!cur_ccw->chain) { break; } if (is_read_tic_ccw_chain(cur_ccw)) { /* * Breaking a chain of CCWs may alter the semantics or even the * validity of a channel program. The heuristic implemented below * seems to work well in practice for the channel programs * generated by zipl. */ tic_ccw = cur_ccw + 1; *next_cpa = tic_ccw->cda; cur_ccw->chain = 0; return true; } cur_ccw += 1; } return false; } static int run_dynamic_ccw_program(SubChannelId schid, uint16_t cutype, uint32_t cpa) { bool has_next; uint32_t next_cpa = 0; int rc; do { has_next = dynamic_cp_fixup(cpa, &next_cpa); print_int("executing ccw chain at ", cpa); enable_prefixing(); rc = do_cio(schid, cutype, cpa, CCW_FMT0); disable_prefixing(); if (rc) { break; } cpa = next_cpa; } while (has_next); return rc; } static void make_readipl(void) { Ccw0 *ccwIplRead = (Ccw0 *)0x00; /* Create Read IPL ccw at address 0 */ ccwIplRead->cmd_code = CCW_CMD_READ_IPL; ccwIplRead->cda = 0x00; /* Read into address 0x00 in main memory */ ccwIplRead->chain = 0; /* Chain flag */ ccwIplRead->count = 0x18; /* Read 0x18 bytes of data */ } static void run_readipl(SubChannelId schid, uint16_t cutype) { if (do_cio(schid, cutype, 0x00, CCW_FMT0)) { panic("dasd-ipl: Failed to run Read IPL channel program\n"); } } /* * The architecture states that IPL1 data should consist of a psw followed by * format-0 READ and TIC CCWs. Let's sanity check. */ static void check_ipl1(void) { Ccw0 *ccwread = (Ccw0 *)0x08; Ccw0 *ccwtic = (Ccw0 *)0x10; if (ccwread->cmd_code != CCW_CMD_DASD_READ || ccwtic->cmd_code != CCW_CMD_TIC) { panic("dasd-ipl: IPL1 data invalid. Is this disk really bootable?\n"); } } static void check_ipl2(uint32_t ipl2_addr) { Ccw0 *ccw = u32toptr(ipl2_addr); if (ipl2_addr == 0x00) { panic("IPL2 address invalid. Is this disk really bootable?\n"); } if (ccw->cmd_code == 0x00) { panic("IPL2 ccw data invalid. Is this disk really bootable?\n"); } } static uint32_t read_ipl2_addr(void) { Ccw0 *ccwtic = (Ccw0 *)0x10; return ccwtic->cda; } static void ipl1_fixup(void) { Ccw0 *ccwSeek = (Ccw0 *) 0x08; Ccw0 *ccwSearchID = (Ccw0 *) 0x10; Ccw0 *ccwSearchTic = (Ccw0 *) 0x18; Ccw0 *ccwRead = (Ccw0 *) 0x20; CcwSeekData *seekData = (CcwSeekData *) 0x30; CcwSearchIdData *searchData = (CcwSearchIdData *) 0x38; /* move IPL1 CCWs to make room for CCWs needed to locate record 2 */ memcpy(ccwRead, (void *)0x08, 16); /* Disable chaining so we don't TIC to IPL2 channel program */ ccwRead->chain = 0x00; ccwSeek->cmd_code = CCW_CMD_DASD_SEEK; ccwSeek->cda = ptr2u32(seekData); ccwSeek->chain = 1; ccwSeek->count = sizeof(*seekData); seekData->reserved = 0x00; seekData->cyl = 0x00; seekData->head = 0x00; ccwSearchID->cmd_code = CCW_CMD_DASD_SEARCH_ID_EQ; ccwSearchID->cda = ptr2u32(searchData); ccwSearchID->chain = 1; ccwSearchID->count = sizeof(*searchData); searchData->cyl = 0; searchData->head = 0; searchData->record = 2; /* Go back to Search CCW if correct record not yet found */ ccwSearchTic->cmd_code = CCW_CMD_TIC; ccwSearchTic->cda = ptr2u32(ccwSearchID); } static void run_ipl1(SubChannelId schid, uint16_t cutype) { uint32_t startAddr = 0x08; if (do_cio(schid, cutype, startAddr, CCW_FMT0)) { panic("dasd-ipl: Failed to run IPL1 channel program\n"); } } static void run_ipl2(SubChannelId schid, uint16_t cutype, uint32_t addr) { if (run_dynamic_ccw_program(schid, cutype, addr)) { panic("dasd-ipl: Failed to run IPL2 channel program\n"); } } /* * Limitations in vfio-ccw support complicate the IPL process. Details can * be found in docs/devel/s390-dasd-ipl.txt */ void dasd_ipl(SubChannelId schid, uint16_t cutype) { PSWLegacy *pswl = (PSWLegacy *) 0x00; uint32_t ipl2_addr; /* Construct Read IPL CCW and run it to read IPL1 from boot disk */ make_readipl(); run_readipl(schid, cutype); ipl2_addr = read_ipl2_addr(); check_ipl1(); /* * Fixup IPL1 channel program to account for vfio-ccw limitations, then run * it to read IPL2 channel program from boot disk. */ ipl1_fixup(); run_ipl1(schid, cutype); check_ipl2(ipl2_addr); /* * Run IPL2 channel program to read operating system code from boot disk */ run_ipl2(schid, cutype, ipl2_addr); /* Transfer control to the guest operating system */ pswl->mask |= PSW_MASK_EAMODE; /* Force z-mode */ pswl->addr |= PSW_MASK_BAMODE; /* ... */ jump_to_low_kernel(); }