| /* |
| * S390 IPL (boot) from a real DASD device via vfio framework. |
| * |
| * Copyright (c) 2019 Jason J. Herne <jjherne@us.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. |
| */ |
| |
| #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(); |
| } |