1/*
2 * Copyright 2019, Data61, CSIRO (ABN 41 687 119 230)
3 *
4 * SPDX-License-Identifier: GPL-2.0-only
5 */
6
7/* Guest specific booting to do bootinfo manipulation */
8
9#include <autoconf.h>
10#include <sel4vm/gen_config.h>
11
12#include <stdio.h>
13#include <stdlib.h>
14#include <string.h>
15
16#include <cpio/cpio.h>
17#include <sel4utils/mapping.h>
18#include <vka/capops.h>
19
20#include <sel4vm/guest_vm.h>
21#include <sel4vm/arch/guest_x86_context.h>
22#include <sel4vm/guest_ram.h>
23#include <sel4vm/guest_memory_helpers.h>
24#include <sel4vm/arch/vmcs_fields.h>
25#include <sel4vm/guest_memory.h>
26
27#include <sel4vmmplatsupport/guest_memory_util.h>
28#include <sel4vmmplatsupport/arch/guest_boot_init.h>
29#include <sel4vmmplatsupport/arch/guest_boot_info.h>
30#include <sel4vmmplatsupport/arch/e820.h>
31#include <sel4vmmplatsupport/arch/acpi.h>
32
33#include <sel4/arch/bootinfo_types.h>
34
35static inline uint32_t vmm_plat_vesa_fbuffer_size(seL4_VBEModeInfoBlock_t *block)
36{
37    assert(block);
38    return ALIGN_UP(block->vbe_common.bytesPerScanLine * block->vbe12_part1.yRes, 65536);
39}
40
41static int make_guest_cmd_line_continued(vm_t *vm, uintptr_t phys, void *vaddr, size_t size, size_t offset,
42                                         void *cookie)
43{
44    /* Copy the string to this area. */
45    const char *cmdline = (const char *)cookie;
46    memcpy(vaddr, cmdline + offset, size);
47    return 0;
48}
49
50static int make_guest_cmd_line(vm_t *vm, const char *cmdline, uintptr_t *guest_cmd_addr, size_t *guest_cmd_len)
51{
52    /* Allocate command line from guest ram */
53    int len = strlen(cmdline);
54    uintptr_t cmd_addr = vm_ram_allocate(vm, len + 1);
55    if (cmd_addr == 0) {
56        ZF_LOGE("Failed to allocate guest cmdline (length %d)", len);
57        return -1;
58    }
59    printf("Constructing guest cmdline at 0x%x of size %d\n", (unsigned int)cmd_addr, len);
60    *guest_cmd_addr = cmd_addr;
61    *guest_cmd_len = len;
62    return vm_ram_touch(vm, cmd_addr, len + 1, make_guest_cmd_line_continued, (void *)cmdline);
63}
64
65static void make_guest_screen_info(vm_t *vm, struct screen_info *info)
66{
67    /* VESA information */
68    seL4_X86_BootInfo_VBE vbeinfo;
69    ssize_t result;
70    int error;
71    result = simple_get_extended_bootinfo(vm->simple, SEL4_BOOTINFO_HEADER_X86_VBE, &vbeinfo,
72                                          sizeof(seL4_X86_BootInfo_VBE));
73    uintptr_t base = 0;
74    size_t fbuffer_size;
75    if (config_set(CONFIG_VMM_PLATSUPPORT_VESA_FRAMEBUFFER) && result != -1) {
76        /* map the protected mode interface at the same location we are told about it at.
77         * this is just to guarantee that it ends up within the segment addressable range */
78        uintptr_t pm_base = (vbeinfo.vbeInterfaceSeg << 4) + vbeinfo.vbeInterfaceOff;
79        error = 0;
80        if (pm_base > 0xc000) {
81            /* construct a page size and aligned region to map */
82            uintptr_t aligned_pm = ROUND_DOWN(pm_base, PAGE_SIZE_4K);
83            int size = vbeinfo.vbeInterfaceLen + (pm_base - aligned_pm);
84            size = ROUND_UP(size, PAGE_SIZE_4K);
85            vm_memory_reservation_t *reservation = vm_reserve_memory_at(vm, aligned_pm, size,
86                                                                        default_error_fault_callback, NULL);
87            if (reservation) {
88                error = map_ut_alloc_reservation(vm, reservation);
89            } else {
90                error = -1;
91            }
92        }
93        if (error) {
94            ZF_LOGE("Failed to map vbe protected mode interface for VESA frame buffer. Disabling");
95        } else {
96            fbuffer_size = vmm_plat_vesa_fbuffer_size(&vbeinfo.vbeModeInfoBlock);
97            vm_memory_reservation_t *reservation = vm_reserve_anon_memory(vm, fbuffer_size,
98                                                                          default_error_fault_callback, NULL, &base);
99            if (!reservation) {
100                ZF_LOGE("Failed to reserve base pointer for VESA frame buffer. Disabling");
101            } else {
102                error = map_ut_alloc_reservation_with_base_paddr(vm, vbeinfo.vbeModeInfoBlock.vbe20.physBasePtr, reservation);
103                if (error) {
104                    ZF_LOGE("Failed to map base pointer for VESA frame buffer. Disabling");
105                    base = 0;
106                }
107            }
108        }
109    }
110    if (base) {
111        info->orig_video_isVGA = 0x23; // Tell Linux it's a VESA mode
112        info->lfb_width = vbeinfo.vbeModeInfoBlock.vbe12_part1.xRes;
113        info->lfb_height = vbeinfo.vbeModeInfoBlock.vbe12_part1.yRes;
114        info->lfb_depth = vbeinfo.vbeModeInfoBlock.vbe12_part1.bitsPerPixel;
115
116        info->lfb_base = base;
117        info->lfb_size = fbuffer_size >> 16;
118        info->lfb_linelength = vbeinfo.vbeModeInfoBlock.vbe_common.bytesPerScanLine;
119
120        info->red_size = vbeinfo.vbeModeInfoBlock.vbe12_part2.redLen;
121        info->red_pos = vbeinfo.vbeModeInfoBlock.vbe12_part2.redOff;
122        info->green_size = vbeinfo.vbeModeInfoBlock.vbe12_part2.greenLen;
123        info->green_pos = vbeinfo.vbeModeInfoBlock.vbe12_part2.greenOff;
124        info->blue_size = vbeinfo.vbeModeInfoBlock.vbe12_part2.blueLen;
125        info->blue_pos = vbeinfo.vbeModeInfoBlock.vbe12_part2.blueOff;
126        info->rsvd_size = vbeinfo.vbeModeInfoBlock.vbe12_part2.rsvdLen;
127        info->rsvd_pos = vbeinfo.vbeModeInfoBlock.vbe12_part2.rsvdOff;
128        info->vesapm_seg = vbeinfo.vbeInterfaceSeg;
129        info->vesapm_off = vbeinfo.vbeInterfaceOff;
130        info->pages = vbeinfo.vbeModeInfoBlock.vbe12_part1.planes;
131    } else {
132        memset(info, 0, sizeof(*info));
133    }
134}
135
136static int make_guest_e820_map(struct e820entry *e820, vm_mem_t *guest_memory)
137{
138    int i;
139    int entry = 0;
140    /* Create an initial entry at 0 that is reserved */
141    e820[entry].addr = 0;
142    e820[entry].size = 0;
143    e820[entry].type = E820_RESERVED;
144    assert(guest_memory->num_ram_regions > 0);
145    for (i = 0; i < guest_memory->num_ram_regions; i++) {
146        /* Check for discontinuity. We need this check since we can have multiple
147         * regions that are contiguous if they have different allocation flags.
148         * However we are reporting ALL of this memory to the guest */
149        if (e820[entry].addr + e820[entry].size != guest_memory->ram_regions[i].start) {
150            /* Finish region. Unless it was zero sized */
151            if (e820[entry].size != 0) {
152                entry++;
153                assert(entry < E820MAX);
154                e820[entry].addr = e820[entry - 1].addr + e820[entry - 1].size;
155                e820[entry].type = E820_RESERVED;
156            }
157            /* Pad region */
158            e820[entry].size = guest_memory->ram_regions[i].start - e820[entry].addr;
159            /* Now start a new RAM region */
160            entry++;
161            assert(entry < E820MAX);
162            e820[entry].addr = guest_memory->ram_regions[i].start;
163            e820[entry].type = E820_RAM;
164        }
165        /* Increase region to size */
166        e820[entry].size = guest_memory->ram_regions[i].start - e820[entry].addr + guest_memory->ram_regions[i].size;
167    }
168    /* Create empty region at the end */
169    entry++;
170    assert(entry < E820MAX);
171    e820[entry].addr = e820[entry - 1].addr + e820[entry - 1].size;
172    e820[entry].size = 0x100000000ull - e820[entry].addr;
173    e820[entry].type = E820_RESERVED;
174    printf("Final e820 map is:\n");
175    for (i = 0; i <= entry; i++) {
176        printf("\t0x%x - 0x%x type %d\n", (unsigned int)e820[i].addr, (unsigned int)(e820[i].addr + e820[i].size),
177               e820[i].type);
178        assert(e820[i].addr < e820[i].addr + e820[i].size);
179    }
180    return entry + 1;
181}
182
183static int make_guest_boot_info(vm_t *vm, uintptr_t guest_cmd_addr, size_t guest_cmd_len,
184                                guest_kernel_image_t guest_kernel_image, guest_image_t guest_ramdisk_image,
185                                uintptr_t *guest_boot_info_addr)
186{
187    /* TODO: Bootinfo struct needs to be allocated in location accessable by real mode? */
188    uintptr_t addr = vm_ram_allocate(vm, sizeof(struct boot_params));
189    if (addr == 0) {
190        ZF_LOGE("Failed to allocate %zu bytes for guest boot info struct", sizeof(struct boot_params));
191        return -1;
192    }
193    printf("Guest boot info allocated at %p. Populating...\n", (void *)addr);
194    /* Map in BIOS boot info structure. */
195    struct boot_params boot_info;
196    memset(&boot_info, 0, sizeof(struct boot_params));
197
198    /* Initialise basic bootinfo structure. Src: Linux kernel Documentation/x86/boot.txt */
199    boot_info.hdr.header = 0x53726448; /* Magic number 'HdrS' */
200    boot_info.hdr.boot_flag = 0xAA55; /* Magic number for Linux. */
201    boot_info.hdr.type_of_loader = 0xFF; /* Undefined loeader type. */
202    boot_info.hdr.code32_start = guest_kernel_image.kernel_image.load_paddr;
203    boot_info.hdr.kernel_alignment = guest_kernel_image.kernel_image.alignment;
204    boot_info.hdr.relocatable_kernel = true;
205
206    /* Set up screen information. */
207    /* Tell Guest OS about VESA mode. */
208    make_guest_screen_info(vm, &boot_info.screen_info);
209
210    /* Create e820 memory map */
211    boot_info.e820_entries = make_guest_e820_map(boot_info.e820_map, &vm->mem);
212
213    /* Pass in the command line string. */
214    boot_info.hdr.cmd_line_ptr = guest_cmd_addr;
215    boot_info.hdr.cmdline_size = guest_cmd_len;
216
217    /* These are not needed to be precise, because Linux uses these values
218     * only to raise an error when the decompression code cannot find good
219     * space. ref: GRUB2 source code loader/i386/linux.c */
220    boot_info.alt_mem_k = 0;//((32 * 0x100000) >> 10);
221
222    /* Pass in initramfs. */
223    if (guest_ramdisk_image.load_paddr) {
224        boot_info.hdr.ramdisk_image = (uint32_t) guest_ramdisk_image.load_paddr;
225        boot_info.hdr.ramdisk_size = guest_ramdisk_image.size;
226        boot_info.hdr.root_dev = 0x0100;
227        boot_info.hdr.version = 0x0204; /* Report version 2.04 in order to report ramdisk_image. */
228    } else {
229        boot_info.hdr.version = 0x0202;
230    }
231    int err = vm_ram_touch(vm, addr, sizeof(boot_info), vm_guest_ram_write_callback, &boot_info);
232    if (err) {
233        ZF_LOGE("Failed to populalte guest boot info region");
234        return -1;
235    }
236    *guest_boot_info_addr = addr;
237    return 0;
238}
239
240/* Init the guest page directory, cmd line args and boot info structures. */
241int vmm_plat_init_guest_boot_structure(vm_t *vm, const char *cmdline,
242                                       guest_kernel_image_t guest_kernel_image, guest_image_t guest_ramdisk_image,
243                                       uintptr_t *guest_boot_info_addr)
244{
245    int UNUSED err;
246    uintptr_t guest_cmd_addr;
247    size_t guest_cmd_size;
248
249    err = make_guest_cmd_line(vm, cmdline, &guest_cmd_addr, &guest_cmd_size);
250    if (err) {
251        return -1;
252    }
253
254    err = make_guest_boot_info(vm, guest_cmd_addr, guest_cmd_size,
255                               guest_kernel_image, guest_ramdisk_image, guest_boot_info_addr);
256    if (err) {
257        return -1;
258    }
259
260    err = make_guest_acpi_tables(vm);
261    return err;
262}
263
264int vmm_plat_init_guest_thread_state(vm_vcpu_t *vcpu, uintptr_t guest_entry_addr,
265                                     uintptr_t guest_boot_info_addr)
266{
267    int err;
268    seL4_VCPUContext context;
269    err = vm_get_thread_context(vcpu, &context);
270    if (err) {
271        return -1;
272    }
273    context.eax = 0;
274    context.ebx = 0;
275    context.ecx = 0;
276    context.edx = 0;
277    context.esi = (unsigned int)guest_boot_info_addr;
278    err = vm_set_thread_context(vcpu, context);
279    if (err) {
280        ZF_LOGE("Failed to init guest state: Unable to set inital thread context");
281        return -1;
282    }
283
284    /* Entry point. */
285    printf("Initializing guest to start running at 0x%x\n", (unsigned int) guest_entry_addr);
286    err = vm_set_vmcs_field(vcpu, VMX_GUEST_RIP, (unsigned int) guest_entry_addr);
287    if (err) {
288        ZF_LOGE("Failed to init guest state: Unable set Guest RIP");
289    }
290    return err;
291}
292