aboutsummaryrefslogtreecommitdiffstats
path: root/tools/firmware/hvmloader/tests.c
blob: 52772aa5d0549e8080aabae213806f9b7b6f5f13 (plain)
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
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
/*
 * tests.c: HVM environment tests.
 *
 * Copyright (c) 2008, Citrix Systems, Inc.
 * 
 * Authors:
 *    Keir Fraser <keir@xen.org>
 * 
 * This program is free software; you can redistribute it and/or modify it
 * under the terms and conditions of the GNU General Public License,
 * version 2, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307 USA.
 */

#include "util.h"

#define TEST_FAIL 0
#define TEST_PASS 1
#define TEST_SKIP 2

/*
 * Memory layout during tests:
 *  4MB to 8MB is cleared.
 *  Page directory resides at 8MB.
 *  4 page table pages reside at 8MB+4kB to 8MB+20kB.
 *  Pagetables identity-map 0-16MB, except 4kB at va 6MB maps to pa 5MB.
 */
#define PD_START (8ul << 20)
#define PT_START (PD_START + 4096)

static void setup_paging(void)
{
    uint32_t *pd = (uint32_t *)PD_START;
    uint32_t *pt = (uint32_t *)PT_START;
    uint32_t i;

    /* Identity map 0-16MB. */
    for ( i = 0; i < 4; i++ )
        pd[i] = (unsigned long)pt + (i<<12) + 3;
    for ( i = 0; i < (4*1024); i++ )
        pt[i] = (i << 12) + 3;

    /* Page at virtual 6MB maps to physical 5MB. */
    pt[6u<<8] -= 0x100000u;
}

static void start_paging(void)
{
    asm volatile (
        "mov %%eax,%%cr3; mov %%cr0,%%eax; "
        "orl $0x80000000,%%eax; mov %%eax,%%cr0; "
        "jmp 1f; 1:"
        : : "a" (PD_START) : "memory" );
}

static void stop_paging(void)
{
    asm volatile (
        "mov %%cr0,%%eax; andl $0x7fffffff,%%eax; mov %%eax,%%cr0; "
        "jmp 1f; 1:"
        : : : "eax", "memory" );
}

/*
 * rep_io_test: Tests REP INSB both forwards and backwards (EF.DF={0,1}) across
 * a discontiguous page boundary.
 */
static int rep_io_test(void)
{
    uint32_t *p;
    uint32_t i, p0, p1, p2;
    int okay = TEST_PASS;

    static const struct {
        unsigned long addr;
        uint32_t expected;
    } check[] = {
        { 0x00500000, 0x987654ff },
        { 0x00500ffc, 0xff000000 },
        { 0x005ffffc, 0xff000000 },
        { 0x00601000, 0x000000ff },
        { 0, 0 }
    };

    start_paging();

    /* Phys 5MB = 0xdeadbeef */
    *(uint32_t *)0x500000ul = 0xdeadbeef;

    /* Phys 5MB = 0x98765432 */
    *(uint32_t *)0x600000ul = 0x98765432;

    /* Phys 0x5fffff = Phys 0x500000 = 0xff (byte) */
    asm volatile (
        "rep insb"
        : "=d" (p0), "=c" (p1), "=D" (p2)
        : "0" (0x5f), "1" (2), "2" (0x5ffffful) : "memory" );

    /* Phys 0x500fff = Phys 0x601000 = 0xff (byte) */
    asm volatile (
        "std ; rep insb ; cld"
        : "=d" (p0), "=c" (p1), "=D" (p2)
        : "0" (0x5f), "1" (2), "2" (0x601000ul) : "memory" );

    stop_paging();

    i = 0;
    for ( p = (uint32_t *)0x400000ul; p < (uint32_t *)0x700000ul; p++ )
    {
        uint32_t expected = 0;
        if ( check[i].addr == (unsigned long)p )
        {
            expected = check[i].expected;
            i++;
        }
        if ( *p != expected )
        {
            printf("Bad value at 0x%08lx: saw %08x expected %08x\n",
                   (unsigned long)p, *p, expected);
            okay = TEST_FAIL;
        }
    }

    return okay;
}

static int shadow_gs_test(void)
{
    uint64_t *pd = (uint64_t *)PD_START;
    uint32_t i, eax, ebx, ecx, edx;

    /* Skip this test if the CPU does not support long mode. */
    cpuid(0x80000000, &eax, &ebx, &ecx, &edx);
    if ( eax < 0x80000001 )
        return TEST_SKIP;
    cpuid(0x80000001, &eax, &ebx, &ecx, &edx);
    if ( !(edx & (1u<<29)) )
        return TEST_SKIP;

    /* Long mode pagetable setup: Identity map 0-16MB with 2MB mappings. */
    *pd = (unsigned long)pd + 0x1007; /* Level 4 */
    pd += 512;
    *pd = (unsigned long)pd + 0x1007; /* Level 3 */
    pd += 512;
    for ( i = 0; i < 8; i++ )         /* Level 2 */
        *pd++ = (i << 21) + 0x1e3;

    asm volatile (
        /* CR4.PAE=1 */
        "mov $0x20,%%ebx; "
        "mov %%ebx,%%cr4; "
        /* CR3 */
        "mov %%eax,%%cr3; "
        /* EFER.LME=1 */
        "mov $0xc0000080,%%ecx; rdmsr; btsl $8,%%eax; wrmsr; "
        /* CR0.PG=1 */
        "mov %%cr0,%%eax; btsl $31,%%eax; mov %%eax,%%cr0; "
        "jmp 1f; 1: "
        /* GS_BASE=2; SHADOW_GS_BASE=3 */
        "mov $0xc0000101,%%ecx; xor %%edx,%%edx; mov $2,%%eax; wrmsr; "
        "mov $0xc0000102,%%ecx; xor %%edx,%%edx; mov $3,%%eax; wrmsr; "
        /* Push LRETQ stack frame. */
        "pushl $0; pushl $"STR(SEL_CODE32)"; pushl $0; pushl $2f; "
        /* Jump to 64-bit mode. */
        "ljmp $"STR(SEL_CODE64)",$1f; 1: "
        /* Swap GS_BASE and SHADOW_GS_BASE */
        ".byte 0x0f,0x01,0xf8; " /* SWAPGS */
        /* Jump to 32-bit mode. */
        ".byte 0x89, 0xe4; "     /* MOV ESP,ESP */
        ".byte 0x48, 0xcb; 2: "  /* LRETQ */
        /* Read SHADOW_GS_BASE: should now contain 2 */
        "mov $0xc0000102,%%ecx; rdmsr; mov %%eax,%%ebx; "
        /* CR0.PG=0 */
        "mov %%cr0,%%eax; btcl $31,%%eax; mov %%eax,%%cr0; "
        "jmp 1f; 1:"
        /* EFER.LME=0 */
        "mov $0xc0000080,%%ecx; rdmsr; btcl $8,%%eax; wrmsr; "
        /* CR4.PAE=0 */
        "xor %%eax,%%eax; mov %%eax,%%cr4; "
        : "=b" (ebx) : "a" (PD_START) : "ecx", "edx", "memory" );

    return (ebx == 2) ? TEST_PASS : TEST_FAIL;
}

void perform_tests(void)
{
    int i, passed, skipped;

    static struct {
        int (* const test)(void);
        const char *description;
    } tests[] = {
        { rep_io_test, "REP INSB across page boundaries" },
        { shadow_gs_test, "GS base MSRs and SWAPGS" },
        { NULL, NULL }
    };

    printf("Testing HVM environment:\n");

    if ( hvm_info->low_mem_pgend < 0x1000 )
    {
        printf("Skipping tests due to insufficient memory (<16MB)\n");
        return;
    }

    passed = skipped = 0;
    for ( i = 0; tests[i].test; i++ )
    {
        printf(" - %s ... ", tests[i].description);
        memset((char *)(4ul << 20), 0, 4ul << 20);
        setup_paging();
        switch ( (*tests[i].test)() )
        {
        case TEST_PASS:
            printf("passed\n");
            passed++;
            break;
        case TEST_FAIL:
            printf("failed\n");
            break;
        case TEST_SKIP:
            printf("skipped\n");
            skipped++;
            break;
        }
    }

    printf("Passed %d of %d tests\n", passed, i);
    if ( skipped != 0 )
        printf("Skipped %d of %d tests\n", skipped, i);
    if ( (passed + skipped) != i )
    {
        printf("FAILED %d of %d tests\n", i - passed - skipped, i);
        BUG();
    }
}

/*
 * Local variables:
 * mode: C
 * c-file-style: "BSD"
 * c-basic-offset: 4
 * tab-width: 4
 * indent-tabs-mode: nil
 * End:
 */