summaryrefslogtreecommitdiffstats
path: root/pc-bios/s390-ccw/dasd-ipl.c
blob: 71cbae2f16e17818b8884eb1b65f16551bb468af (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
/*
 * 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;

    /* Clear out any existing data */
    memset(ccwIplRead, 0, sizeof(Ccw0));

    /* 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();
}