diff --git a/.gitignore b/.gitignore index 087d952d0..d71e45608 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ /platform/*/*/image/kernel/* /platform/*/*/image/virtdisk/* /platform/*/*/image/bootloader/out +/platform/*/*/image/mb2_bootloader/out /platform/*/*/image/iso/boot/kernel/* /platform/*/*/image/iso/boot/hvisor /tools/hvisor @@ -41,4 +42,4 @@ jenkins/__pycache__ jenkins-cli.jar tools/kconfig/.venv/ tools/kconfig/__pycache__/ -kernel_build/ \ No newline at end of file +kernel_build/ diff --git a/platform/x86_64/nuc14mnk/board.rs b/platform/x86_64/nuc14mnk/board.rs index 9ee5d4c7c..1768ecea9 100644 --- a/platform/x86_64/nuc14mnk/board.rs +++ b/platform/x86_64/nuc14mnk/board.rs @@ -53,7 +53,7 @@ const ROOT_ZONE_UEFI_REGION: HvConfigMemoryRegion = HvConfigMemoryRegion { const ROOT_ZONE_UEFI_REGION_ID: usize = 0x3; pub const ROOT_ZONE_NAME: &str = "root-linux"; -pub const ROOT_ZONE_CMDLINE: &str = "video=vesafb console=tty0 nointremap no_timer_check efi=noruntime pci=pcie_scan_all root=/dev/nvme0n1p5 rw init=/init rootwait\0"; +pub const ROOT_ZONE_CMDLINE: &str = "video=vesafb console=tty0 earlycon=efifb nointremap no_timer_check efi=noruntime pci=pcie_scan_all root=/dev/nvme0n1p5 rw init=/init rootwait\0"; // pub const ROOT_ZONE_CMDLINE: &str = "video=vesafb console=ttyS0 earlyprintk=serial nointremap no_timer_check pci=pcie_scan_all root=/dev/vda rw init=/init\0"; //"console=ttyS0 earlyprintk=serial rdinit=/init nokaslr nointremap\0"; // noapic // video=vesafb @@ -113,9 +113,9 @@ pub const ROOT_ZONE_MEMORY_REGIONS: [HvConfigMemoryRegion; 15] = [ }, // pnp 00:05 HvConfigMemoryRegion { mem_type: MEM_TYPE_RESERVED, - physical_start: 0xfe01_1000, - virtual_start: 0xfe01_1000, - size: 0x40_0000, + physical_start: 0xfe00_0000, + virtual_start: 0xfe00_0000, + size: 0xd0_0000, }, // reserved HvConfigMemoryRegion { mem_type: MEM_TYPE_RESERVED, diff --git a/platform/x86_64/qemu/board.rs b/platform/x86_64/qemu/board.rs index fc25028db..a3023b509 100644 --- a/platform/x86_64/qemu/board.rs +++ b/platform/x86_64/qemu/board.rs @@ -54,11 +54,18 @@ const ROOT_ZONE_UEFI_REGION_ID: usize = 0x3; pub const ROOT_ZONE_NAME: &str = "root-linux"; pub const ROOT_ZONE_CMDLINE: &str = - "console=tty0 console=ttyS0 earlycon=efifb earlyprintk=serial nointremap no_timer_check efi=noruntime pci=pcie_scan_all,lastbus=1 root=/dev/vda rw init=/init\0"; +// "BOOT_IMAGE=/boot/aster-kernel-osdk-bin SHELL=/bin/sh LOGNAME=root HOME=/ USER=root PATH=/bin:/benchmark ostd.log_level=info console=ttyS0 console=tty0 -- sh -l"; +// TODO: Asterinas +"console=tty0 console=ttyS0 earlycon=efifb earlyprintk=serial nointremap no_timer_check efi=noruntime pci=pcie_scan_all,lastbus=1 root=/dev/vda rw init=/init\0"; //"console=ttyS0 earlyprintk=serial rdinit=/init nokaslr nointremap\0"; // noapic // video=vesafb // /lib/systemd/systemd +/// Reserved HPA range for zone1 high memory (0x1_0000_0000 -> 0x2_E000_0000). +/// Reserved as a placeholder for future zone1 high-memory regions; the current +/// Asterinas config does not use this range under QEMU's default 4G RAM. +const ZONE1_HIGH_RESERVED_SIZE: u64 = 0x1_E000_0000; + pub const ROOT_ZONE_MEMORY_REGIONS: [HvConfigMemoryRegion; 10] = [ HvConfigMemoryRegion { mem_type: MEM_TYPE_RAM, @@ -104,11 +111,13 @@ pub const ROOT_ZONE_MEMORY_REGIONS: [HvConfigMemoryRegion; 10] = [ mem_type: MEM_TYPE_RESERVED, physical_start: 0x1_0000_0000, virtual_start: 0x1_0000_0000, - size: 0x7000_0000, + size: ZONE1_HIGH_RESERVED_SIZE, }, // zone 1 ]; const ROOT_ZONE_CMDLINE_ADDR: GuestPhysAddr = 0x9000; +// TODO: Asterinas +// const ROOT_ZONE_SETUP_ADDR: GuestPhysAddr = 0xf_f000; const ROOT_ZONE_SETUP_ADDR: GuestPhysAddr = 0xa000; const ROOT_ZONE_VMLINUX_ENTRY_ADDR: GuestPhysAddr = 0x10_0000; const ROOT_ZONE_SCREEN_BASE_ADDR: GuestPhysAddr = 0x7000_0000; @@ -122,8 +131,9 @@ pub const ROOT_ARCH_ZONE_CONFIG: HvArchZoneConfig = HvArchZoneConfig { kernel_entry_gpa: ROOT_ZONE_VMLINUX_ENTRY_ADDR, cmdline_load_gpa: ROOT_ZONE_CMDLINE_ADDR, setup_load_gpa: ROOT_ZONE_SETUP_ADDR, - initrd_load_gpa: 0, // 0x1500_0000, - initrd_size: 0, //0x26_b000, + // TODO: Asterinas + initrd_load_gpa: 0, // 0x1530_0000, + initrd_size: 0, // 0x210_0000, //0x26_b000, rsdp_memory_region_id: ROOT_ZONE_RSDP_REGION_ID, acpi_memory_region_id: ROOT_ZONE_ACPI_REGION_ID, uefi_memory_region_id: ROOT_ZONE_UEFI_REGION_ID, @@ -149,11 +159,13 @@ pub const ROOT_PCI_CONFIG: [HvPciConfig; 1] = [HvPciConfig { }]; pub const ROOT_PCI_MAX_BUS: usize = 1; -pub const ROOT_PCI_DEVS: [HvPciDevConfig; 7] = [ +pub const ROOT_PCI_DEVS: [HvPciDevConfig; 8] = [ pci_dev!(0x0, 0x0, 0x0, 0x0 => 0x0, 0x0, 0x0, VpciDevType::Physical), // host bridge pci_dev!(0x0, 0x0, 0x1, 0x0 => 0x0, 0x1, 0x0, VpciDevType::Physical), // VGA controller pci_dev!(0x0, 0x0, 0x2, 0x0 => 0x0, 0x2, 0x0, VpciDevType::Physical), // Ethernet controller pci_dev!(0x0, 0x0, 0x3, 0x0 => 0x0, 0x3, 0x0, VpciDevType::Physical), // PCI bridge + // Asterinas + pci_dev!(0x0, 0x0, 0x4, 0x0 => 0x0, 0x4, 0x0, VpciDevType::Physical), // PCI bridge pci_dev!(0x0, 0x0, 0x1f, 0x0 => 0x0, 0x1f, 0x0, VpciDevType::Physical), // ISA bridge pci_dev!(0x0, 0x0, 0x1f, 0x2 => 0x0, 0x1f, 0x2, VpciDevType::Physical), // SATA controller // pci_dev!(0x0, 0x0, 0x1f, 0x3 => 0x0, 0x1f, 0x3, VpciDevType::Physical), // SMBus diff --git a/platform/x86_64/qemu/configs/virtio_cfg_asterinas.json b/platform/x86_64/qemu/configs/virtio_cfg_asterinas.json new file mode 100644 index 000000000..177f002fc --- /dev/null +++ b/platform/x86_64/qemu/configs/virtio_cfg_asterinas.json @@ -0,0 +1,40 @@ +{ + "zones": [ + { + "id": 1, + "memory_region": [ + { + "zone0_ipa": "0x40300000", + "zonex_ipa": "0x0", + "size": "0x20000000" + } + ], + "devices": [ + { + "type": "console", + "status": "enable", + "addr": "0xFEB00000", + "len": "0x200", + "irq": "16" + }, + { + "type": "net", + "status": "enable", + "addr": "0xFEB00200", + "len": "0x200", + "irq": "17", + "tap": "tap0", + "mac": ["0x00", "0x16", "0x3e", "0x10", "0x10", "0x10"] + }, + { + "type": "blk", + "status": "enable", + "addr": "0xFEB00400", + "len": "0x200", + "irq": "18", + "img": "/zone1_disk.img" + } + ] + } + ] +} diff --git a/platform/x86_64/qemu/configs/zone1-asterinas.json b/platform/x86_64/qemu/configs/zone1-asterinas.json new file mode 100644 index 000000000..efab01fe2 --- /dev/null +++ b/platform/x86_64/qemu/configs/zone1-asterinas.json @@ -0,0 +1,84 @@ +{ + "name": "asterinas", + "zone_id": 1, + "cpus": [2, 3], + "memory_regions": [ + { + "type": "ram", + "physical_start": "0x40300000", + "virtual_start": "0x0", + "size": "0xe0000" + }, + { + "type": "ram", + "physical_start": "0x403e0000", + "virtual_start": "0xe0000", + "size": "0x20000" + }, + { + "type": "ram", + "physical_start": "0x40400000", + "virtual_start": "0x100000", + "size": "0x1fe00000" + }, + { + "type": "ram", + "physical_start": "0x60200000", + "virtual_start": "0x1ff00000", + "size": "0x100000" + }, + { + "type": "virtio", + "physical_start": "0xFEB00000", + "virtual_start": "0xFEB00000", + "size": "0x2000" + } + ], + "interrupts": [], + "ivc_configs": [], + "kernel_filepath": "./aster-kernel-osdk-bin", + "dtb_filepath": "null", + "kernel_load_paddr": "0x8000000", + "dtb_load_paddr": "0x00000000", + "entry_point": "0x8000", + "multiboot_enabled": true, + "multiboot_info_paddr": "0x9000000", + "kernel_cmdline": "console=hvc0 ostd.log_level=error SHELL=/bin/sh LOGNAME=root HOME=/ USER=root PATH=/bin:/benchmark init=/init -- sh -l", + "initramfs_filepath": "./initramfs.cpio.gz", + "initramfs_load_gpa": "0x10000000", + "arch_config": { + "ioapic_base": "0xfec00000", + "ioapic_size": "0x1000", + "boot_filepath": "mb2_boot.bin", + "boot_load_paddr": "0x8000", + "kernel_entry_gpa": "0x8001238", + "cmdline_load_gpa": "0x9020000", + "setup_load_gpa": "0x80000", + "initrd_load_gpa": "0x0", + "initrd_size": "0x0", + "rsdp_memory_region_id": 1, + "acpi_memory_region_id": 3, + "uefi_memory_region_id": 0, + "screen_base": "0x0" + }, + "pci_config": [ + { + "ecam_base": "0xe0000000", + "ecam_size": "0x200000", + "io_base": "0x0", + "io_size": "0x0", + "pci_io_base": "0x0", + "mem32_base": "0x0", + "mem32_size": "0x0", + "pci_mem32_base": "0x0", + "mem64_base": "0x0", + "mem64_size": "0x0", + "pci_mem64_base": "0x0", + "bus_range_begin": "0x0", + "bus_range_end": "0x1f", + "domain": "0x0" + } + ], + "num_pci_devs": 0, + "alloc_pci_devs": [] +} diff --git a/platform/x86_64/qemu/image/iso/boot/grub/grub.cfg b/platform/x86_64/qemu/image/iso/boot/grub/grub.cfg index c2a54ae3b..93796f90c 100644 --- a/platform/x86_64/qemu/image/iso/boot/grub/grub.cfg +++ b/platform/x86_64/qemu/image/iso/boot/grub/grub.cfg @@ -3,13 +3,22 @@ set default=0 # default menu entry index insmod all_video + menuentry "Hvisor" { multiboot2 /boot/hvisor # use multiboot spec to boot module2 /boot/kernel/boot.bin 0 module2 /boot/kernel/boot.bin 5008000 module2 /boot/kernel/setup.bin 500a000 module2 /boot/kernel/vmlinux.bin 5100000 - module2 /boot/kernel/initramfs.cpio.gz 1a000000 + boot +} + +menuentry "Asterinas" { + multiboot2 /boot/hvisor # use multiboot spec to boot + module2 /boot/kernel/boot.bin 0 + module2 /boot/kernel/boot.bin 5008000 + module2 /boot/kernel/aster-kernel-osdk-bin 50ff000 + module2 /boot/kernel/initramfs.cpio.gz 1a300000 boot } diff --git a/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.S b/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.S new file mode 100644 index 000000000..eb0cfa2b7 --- /dev/null +++ b/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.S @@ -0,0 +1,68 @@ +# Multiboot2 bootloader for Asterinas zone1 +# Transitions from 16-bit real mode to 32-bit protected mode, +# sets up GDT with TSS, then jumps to the kernel entry point. +# +# Entry state (from VMCS guest_regs via unrestricted guest): +# EAX = Multiboot2 magic (0x36D76289) +# EBX = Multiboot2 info pointer (GPA) +# ESI = Kernel entry point (GPA) + +.section .text +.code16 +.global mb2_entry16 +mb2_entry16: + cli + cld + + mov ebp, eax # save multiboot magic + mov edi, ebx # save multiboot info pointer + mov ecx, esi # save kernel entry point + + xor ax, ax + mov ds, ax + mov es, ax + mov ss, ax + + lgdt [mb2_gdt_desc] + mov eax, cr0 + or eax, 0x1 + mov cr0, eax + + ljmp 0x08, mb2_entry32 + +.code32 +.global mb2_entry32 +mb2_entry32: + mov ax, 0x10 + mov ds, ax + mov es, ax + mov ss, ax + mov fs, ax + mov gs, ax + + # Load TSS (entry 4 at selector 0x20) + mov ax, 0x20 + ltr ax + + # Restore Multiboot2 parameters + mov eax, ebp + mov ebx, edi + + # Set up stack + mov esp, MB2_STACK + + # Jump to kernel entry + jmp ecx + +.align 16 +mb2_gdt: + .quad 0x0000000000000000 # 0x00: null + .quad 0x00cf9b000000ffff # 0x08: 32-bit code + .quad 0x00cf93000000ffff # 0x10: 32-bit data + .quad 0x0000000000000000 # 0x18: unused + .quad MB2_TSS_DESCRIPTOR # 0x20: 32-bit available TSS +mb2_gdt_end: + +mb2_gdt_desc: + .short mb2_gdt_end - mb2_gdt - 1 + .long mb2_gdt diff --git a/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.ld b/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.ld new file mode 100644 index 000000000..dc5e86666 --- /dev/null +++ b/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.ld @@ -0,0 +1,15 @@ +OUTPUT_ARCH(i386) +BASE_ADDRESS = 0x8000; + +ENTRY(mb2_entry16) +SECTIONS +{ + . = BASE_ADDRESS; + .text : { + *(.text .text.*) + } + + /DISCARD/ : { + *(.eh_frame) *(.eh_frame_hdr) + } +} diff --git a/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.mk b/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.mk new file mode 100644 index 000000000..9917a74eb --- /dev/null +++ b/platform/x86_64/qemu/image/mb2_bootloader/mb2_boot.mk @@ -0,0 +1,40 @@ +mb2_boot_dir := $(image_dir)/mb2_bootloader +mb2_boot_out_dir := $(image_dir)/mb2_bootloader/out + +mb2_boot_src := $(mb2_boot_dir)/mb2_boot.S +mb2_boot_lds := $(mb2_boot_dir)/mb2_boot.ld + +mb2_boot_o := $(mb2_boot_out_dir)/mb2_boot.o +mb2_boot_elf := $(mb2_boot_out_dir)/mb2_boot.elf +mb2_boot_bin := $(mb2_boot_out_dir)/mb2_boot.bin + +AS ?= as +LD ?= ld +OBJCOPY ?= objcopy + +# TSS descriptor: base=0x1fffc000, limit=103, type=0x89 (32-bit available TSS) +# Encoded bytes: 67 00 00 c0 ff 89 00 1f +# Stack/TSS live near the top of zone1 low RAM, outside the kernel LOAD range. +# The stack at 0x1fffe000 must not grow more than 8KB into the TSS below. +MB2_TSS_DESC := 0x1F0089FFC0000067 +MB2_STACK := 0x1fffe000 + +mb2_boot_flags := --32 -msyntax=intel -mnaked-reg +mb2_boot_flags += --defsym MB2_STACK=$(MB2_STACK) +mb2_boot_flags += --defsym MB2_TSS_DESCRIPTOR=$(MB2_TSS_DESC) + +mb2_boot: | $(mb2_boot_out_dir) $(mb2_boot_bin) + +$(mb2_boot_out_dir): + mkdir -p $@ + +$(mb2_boot_o): $(mb2_boot_src) + $(AS) $(mb2_boot_flags) $< -o $@ + +$(mb2_boot_elf): $(mb2_boot_o) $(mb2_boot_lds) + $(LD) -T$(mb2_boot_lds) $< -o $@ + +$(mb2_boot_bin): $(mb2_boot_elf) + $(OBJCOPY) $< --strip-all -O binary $@ + +.PHONY: mb2_boot diff --git a/platform/x86_64/qemu/platform.mk b/platform/x86_64/qemu/platform.mk index 3e3d8cb2e..4327405c7 100644 --- a/platform/x86_64/qemu/platform.mk +++ b/platform/x86_64/qemu/platform.mk @@ -1,8 +1,10 @@ QEMU := qemu-system-x86_64 zone0_boot := $(image_dir)/bootloader/out/boot.bin +zone1_mb2_boot := $(image_dir)/mb2_bootloader/out/mb2_boot.bin zone0_setup := $(image_dir)/kernel/setup.bin zone0_vmlinux := $(image_dir)/kernel/vmlinux.bin +zone0_asterinas := $(image_dir)/kernel/aster-kernel-osdk-bin zone0_initrd := $(image_dir)/virtdisk/initramfs.cpio.gz zone0_rootfs := $(image_dir)/virtdisk/rootfs1.img zone1_rootfs := $(image_dir)/virtdisk/rootfs2.img @@ -10,20 +12,29 @@ zone1_rootfs := $(image_dir)/virtdisk/rootfs2.img QEMU_ARGS := -machine q35,kernel-irqchip=split QEMU_ARGS += -cpu host,+x2apic,+invtsc,+vmx -accel kvm QEMU_ARGS += -smp 4 +# TODO: Asterinas QEMU_ARGS += -serial mon:stdio QEMU_ARGS += -m 4G QEMU_ARGS += -bios /usr/share/ovmf/OVMF.fd QEMU_ARGS += -vga std -# QEMU_ARGS += -nographic +QEMU_ARGS += -nographic QEMU_ARGS += -nodefaults QEMU_ARGS += -net nic -net user QEMU_ARGS += -device intel-iommu,intremap=on,eim=on,caching-mode=on,device-iotlb=on,aw-bits=48 QEMU_ARGS += -device ioh3420,id=pcie.1,chassis=1 + +# TODO: Asterinas QEMU_ARGS += -drive if=none,file="$(zone0_rootfs)",id=X10008000,format=raw QEMU_ARGS += -device virtio-blk-pci,bus=pcie.1,drive=X10008000,disable-legacy=on,disable-modern=off,iommu_platform=on,ats=on +# TODO: Asterinas +# QEMU_ARGS += -device virtio-serial +# QEMU_ARGS += -device virtconsole,chardev=mux +# QEMU_ARGS += -serial chardev:mux +# QEMU_ARGS += -chardev stdio,id=mux,mux=on,signal=off + # QEMU_ARGS += -drive if=none,file="$(zone0_rootfs)",id=X10009000,format=raw # QEMU_ARGS += -device nvme,serial=deadbeef,drive=X10009000 # QEMU_ARGS += -drive if=none,file="$(zone1_rootfs)",id=X10009000,format=raw @@ -45,7 +56,7 @@ QEMU_ARGS += -nographic # QEMU_ARGS += -device loader,file="$(zone0_initrd)",addr=0x1a000000,force-raw=on # QEMU_ARGS += -append "initrd_size=$(shell stat -c%s $(zone0_initrd))" -$(hvisor_bin): elf boot +$(hvisor_bin): elf boot mb2_boot $(OBJCOPY) $(hvisor_elf) --strip-all -O binary $@ cp $(hvisor_elf) $(image_dir)/iso/boot mkdir -p $(image_dir)/iso/boot/kernel @@ -68,12 +79,28 @@ $(hvisor_bin): elf boot echo "Warning: $(zone0_vmlinux) not found, skipping"; \ fi + if [ -f $(zone0_asterinas) ]; then \ + cp $(zone0_asterinas) $(image_dir)/iso/boot/kernel; \ + else \ + echo "Warning: $(zone0_asterinas) not found, skipping"; \ + fi + mkdir -p $(image_dir)/virtdisk + if [ -f $(zone0_initrd) ]; then \ + cp $(zone0_initrd) $(image_dir)/iso/boot/kernel; \ + else \ + echo "Warning: $(zone0_initrd) not found, skipping"; \ + fi + if [ -f $(zone1_mb2_boot) ]; then \ + cp $(zone1_mb2_boot) $(image_dir)/iso/boot/kernel; \ + fi + if command -v xorriso >/dev/null 2>&1; then \ grub-mkrescue /usr/lib/grub/x86_64-efi -o $(image_dir)/virtdisk/hvisor.iso $(image_dir)/iso; \ else \ echo "Warning: xorriso not installed, skipping ISO creation"; \ fi -include $(image_dir)/bootloader/boot.mk \ No newline at end of file +include $(image_dir)/bootloader/boot.mk +include $(image_dir)/mb2_bootloader/mb2_boot.mk \ No newline at end of file diff --git a/src/arch/x86_64/acpi.rs b/src/arch/x86_64/acpi.rs index 701b5dffb..123b26971 100644 --- a/src/arch/x86_64/acpi.rs +++ b/src/arch/x86_64/acpi.rs @@ -697,6 +697,13 @@ pub fn root_get_config_space_info() -> Option<(usize, usize)> { Some((acpi.config_space_base, acpi.config_space_size)) } +pub fn try_get_cpu_id(apic_id: usize) -> Option { + ROOT_ACPI + .get() + .and_then(|acpi| acpi.apic_id_to_cpu_id.get(&apic_id)) + .copied() +} + fn contains_apic_id(apic_id: usize) -> bool { ROOT_ACPI .get() diff --git a/src/arch/x86_64/boot.rs b/src/arch/x86_64/boot.rs index c3ec46367..473ef428e 100644 --- a/src/arch/x86_64/boot.rs +++ b/src/arch/x86_64/boot.rs @@ -22,7 +22,10 @@ use crate::{ memory::{GuestPhysAddr, HostPhysAddr, MemFlags, MemoryRegion, MemorySet, PAGE_SIZE}, platform::MEM_TYPE_RESERVED, }; -use alloc::string::{String, ToString}; +use alloc::{ + string::{String, ToString}, + vec::Vec, +}; use bit_field::BitField; use core::{ arch::{self, global_asm}, @@ -177,7 +180,6 @@ impl BootParams { if config.arch_config.setup_load_gpa == 0 { panic!("setup addr not set yet!"); } - let boot_params_hpa = unsafe { gpm.page_table_query(config.arch_config.setup_load_gpa) .unwrap() @@ -185,7 +187,7 @@ impl BootParams { } as HostPhysAddr; let boot_params = unsafe { &mut *(boot_params_hpa as *mut BootParams) }; - // info!("boot_proto_version: {:x?}", boot_params.boot_proto_version); + info!("boot_proto_version: {:x?}", boot_params.boot_proto_version); if boot_params.boot_proto_version < 0x0204 { panic!("kernel boot protocol version older than 2.04 not supported!"); } @@ -415,7 +417,7 @@ impl BootParams { self.screen_info.red_pos = 16; self.screen_info.alpha_size = 8; self.screen_info.alpha_pos = 24; - self.screen_info.orig_video_is_vga = VIDEO_TYPE_EFI; + self.screen_info.orig_video_is_vga = VIDEO_TYPE_VLFB; self.screen_info.capabilities = 0; self.vid_mode = 0xffff; @@ -493,7 +495,6 @@ pub fn multiboot_init(info_addr: usize) { // println!("{:#x?}", tag_type); match tag_type { - multiboot_tag::MODULES => {} multiboot_tag::MEMORY_MAP => { multiboot_tags.memory_map_addr = Some(cur); } @@ -534,50 +535,337 @@ pub fn print_memory_map() { } } +/// Construct Multiboot2 info structure in guest memory at `multiboot_info_paddr`. +pub fn multiboot2_info_fill( + config: &HvZoneConfig, + gpm: &mut MemorySet, + multiboot_info_paddr: GuestPhysAddr, +) -> HvResult { + let info_gpa = multiboot_info_paddr; + let (info_hpa, _, _) = unsafe { + gpm.page_table_query(info_gpa) + .expect("multiboot2 info GPA unmapped") + }; + + // Read raw cmdline from guest memory FIRST, before writing tags + // (cmdline is at the same GPA as multiboot info, so writing tags + // would overwrite it) + let mut cmdline_buf = [0u8; 256]; + let cmdline_len = { + let cmdline_gpa = config.arch_config.cmdline_load_gpa as GuestPhysAddr; + if cmdline_gpa != 0 { + if let Ok((cmdline_hpa, _, _)) = unsafe { gpm.page_table_query(cmdline_gpa) } { + let src = cmdline_hpa as *const u8; + let mut len = 0usize; + while len < 255 && unsafe { *src.add(len) } != 0 { + cmdline_buf[len] = unsafe { *src.add(len) }; + len += 1; + } + len + } else { + 0 + } + } else { + 0 + } + }; + + let info_ptr = info_hpa as *mut u8; + + const TAG_END: u32 = 0; + const TAG_CMDLINE: u32 = 1; + const TAG_BASIC_MEMINFO: u32 = 4; + const TAG_MMAP: u32 = 6; + const MB_MEM_AVAILABLE: u32 = 1; + const LOW_MEM_TOP: u64 = 0xC0000000; + const LOW_MMIO_TOP: u64 = 0x100000000; + + let mut offset = 8usize; + + // Tag 1: Basic memory info + let total_ram: u64 = config + .memory_regions() + .iter() + .filter(|r| r.mem_type == MEM_TYPE_RAM) + .map(|r| r.size) + .sum(); + let mem_upper_kb = if total_ram > 0x100000 { + ((total_ram - 0x100000) / 1024) as u32 + } else { + 0 + }; + unsafe { + let t = info_ptr.add(offset); + *(t as *mut u32) = TAG_BASIC_MEMINFO; + *(t.add(4) as *mut u32) = 16; + *(t.add(8) as *mut u32) = 640; + *(t.add(12) as *mut u32) = mem_upper_kb; + } + offset += 16; + + // Tag 2: Memory map. Available RAM is carved around reserved regions so + // Asterinas cannot reclaim the bootloader, kernel, cmdline, info buffer, + // or initramfs pages. + const MB_MEM_RESERVED: u32 = 2; + const BOOTLOADER_RESERVED_END: u64 = 0x10000; + + let entry_size = 24usize; + let mmap_hdr = offset; + offset += 16; + let mut entry_count = 0u32; + + let mut reserved: Vec<(u64, u64)> = Vec::new(); + if config.kernel_size > 0 { + let kernel_start = config.kernel_load_paddr; + reserved.push((kernel_start, kernel_start + config.kernel_size)); + } + reserved.push((info_gpa as u64, info_gpa as u64 + PAGE_SIZE as u64)); + if config.arch_config.cmdline_load_gpa != 0 + && config.arch_config.cmdline_load_gpa as u64 != info_gpa as u64 + { + let cmdline_gpa = config.arch_config.cmdline_load_gpa as u64; + reserved.push((cmdline_gpa, cmdline_gpa + PAGE_SIZE as u64)); + } + let initrd_gpa = config.arch_config.initrd_load_gpa as u64; + let initrd_size = config.arch_config.initrd_size as u64; + if initrd_size > 0 { + reserved.push((initrd_gpa, initrd_gpa + initrd_size)); + } + reserved.push((0x8000, BOOTLOADER_RESERVED_END)); + reserved.sort_unstable(); + + let mut available: Vec<(u64, u64)> = Vec::new(); + for r in config + .memory_regions() + .iter() + .filter(|r| r.mem_type == MEM_TYPE_RAM) + { + let start = r.virtual_start; + let end = start + r.size; + let mut cursor = start; + for &(reserved_start, reserved_end) in &reserved { + if reserved_end <= cursor || reserved_start >= end { + continue; + } + let s = reserved_start.max(cursor); + if s > cursor { + available.push((cursor, s.min(end))); + } + cursor = reserved_end.max(cursor); + if cursor >= end { + break; + } + } + if cursor < end { + available.push((cursor, end)); + } + } + + for (start, end) in available { + if start < LOW_MEM_TOP { + let s = start; + let e = end.min(LOW_MEM_TOP); + if e > s { + unsafe { + let p = info_ptr.add(offset); + *(p as *mut u64) = s; + *(p.add(8) as *mut u64) = e - s; + *(p.add(16) as *mut u32) = MB_MEM_AVAILABLE; + *(p.add(20) as *mut u32) = 0; + } + offset += entry_size; + entry_count += 1; + } + } + if end > LOW_MMIO_TOP { + let s = start.max(LOW_MMIO_TOP); + if end > s { + unsafe { + let p = info_ptr.add(offset); + *(p as *mut u64) = s; + *(p.add(8) as *mut u64) = end - s; + *(p.add(16) as *mut u32) = MB_MEM_AVAILABLE; + *(p.add(20) as *mut u32) = 0; + } + offset += entry_size; + entry_count += 1; + } + } + } + + let mmap_tag_size = (16 + entry_count as usize * entry_size) as u32; + unsafe { + *(info_ptr.add(mmap_hdr) as *mut u32) = TAG_MMAP; + *(info_ptr.add(mmap_hdr + 4) as *mut u32) = mmap_tag_size; + *(info_ptr.add(mmap_hdr + 8) as *mut u32) = entry_size as u32; + *(info_ptr.add(mmap_hdr + 12) as *mut u32) = 0; + } + offset = (offset + 7) & !7; + + // Tag 3: Command line (from pre-read buffer) + if cmdline_len > 0 { + let tag_size = ((8 + cmdline_len + 1 + 7) & !7) as u32; + unsafe { + *(info_ptr.add(offset) as *mut u32) = TAG_CMDLINE; + *(info_ptr.add(offset + 4) as *mut u32) = tag_size; + for i in 0..cmdline_len { + *info_ptr.add(offset + 8 + i) = cmdline_buf[i]; + } + *info_ptr.add(offset + 8 + cmdline_len) = 0; + } + offset += tag_size as usize; + } + + // Tag 4: Module (initramfs) + let initrd_gpa = config.arch_config.initrd_load_gpa as u64; + let initrd_size = config.arch_config.initrd_size as u64; + if initrd_size > 0 { + const TAG_MODULE: u32 = 3; + let mod_cmd = b"./initramfs.cpio.gz\0"; + let mod_cmd_len = mod_cmd.len(); + let tag_size = ((8 + 8 + mod_cmd_len + 7) & !7) as u32; + unsafe { + *(info_ptr.add(offset) as *mut u32) = TAG_MODULE; + *(info_ptr.add(offset + 4) as *mut u32) = tag_size; + *(info_ptr.add(offset + 8) as *mut u32) = initrd_gpa as u32; + *(info_ptr.add(offset + 12) as *mut u32) = (initrd_gpa + initrd_size) as u32; + for i in 0..mod_cmd_len { + *info_ptr.add(offset + 16 + i) = mod_cmd[i]; + } + } + offset += tag_size as usize; + } + + // End tag + unsafe { + *(info_ptr.add(offset) as *mut u32) = TAG_END; + *(info_ptr.add(offset + 4) as *mut u32) = 8; + } + offset += 8; + + // Header + unsafe { + *(info_ptr as *mut u32) = offset as u32; + *(info_ptr.add(4) as *mut u32) = 0; + } + + info!( + "Multiboot2 info: {} bytes, {} mmap entries, RAM {:#x}", + offset, entry_count, total_ram + ); + Ok(()) +} + /// copy kernel modules to the right place pub fn module_init(info_addr: usize) { println!("module_init"); + + const MAX_MODULES: usize = 16; + + #[derive(Clone, Copy)] + struct ModuleInfo { + start: usize, + end: usize, + dst: usize, + string_ptr: usize, + } + + let mut modules = [ModuleInfo { + start: 0, + end: 0, + dst: 0, + string_ptr: 0, + }; MAX_MODULES]; + let mut module_count = 0; + let mut cur = info_addr; let total_size = unsafe { *(cur as *const u32) } as usize; - - let mut cnt = 0; cur += 8; - while cur < info_addr + total_size { + while cur < info_addr + total_size && module_count < MAX_MODULES { let tag_type = unsafe { *(cur as *const u32) }; - let ptr = cur as *const multiboot_tag::Modules; - cur += ((unsafe { *((cur + 4) as *const u32) } as usize + 7) & (!7)); - if tag_type == multiboot_tag::END { break; } - if tag_type != multiboot_tag::MODULES { - continue; + if tag_type == multiboot_tag::MODULES { + let ptr = cur as *const multiboot_tag::Modules; + let module = unsafe { *ptr }; + let string_ptr = (ptr as usize) + size_of::(); + modules[module_count] = ModuleInfo { + start: module.mod_start as usize, + end: module.mod_end as usize, + dst: 0, // parse later + string_ptr, + }; + module_count += 1; } + cur += ((unsafe { *((cur + 4) as *const u32) } as usize + 7) & (!7)); + } - let module = unsafe { *ptr }; - let dst = unsafe { - usize::from_str_radix( - CStr::from_ptr(((ptr as usize) + size_of::()) as *const c_char) - .to_str() - .unwrap(), - 16, - ) - .unwrap() - }; - println!("module: {:#x?}, addr: {:#x?}", module, dst); - cnt += 1; + // parse dst + for i in 0..module_count { + let cstr = unsafe { CStr::from_ptr(modules[i].string_ptr as *const c_char) }; + modules[i].dst = usize::from_str_radix(cstr.to_str().unwrap(), 16).unwrap(); + println!( + "module: start={:#x}, end={:#x}, dst={:#x}", + modules[i].start, modules[i].end, modules[i].dst + ); + } - if dst == 0x0 { - continue; + // now move in order + let mut moved = [false; MAX_MODULES]; + let mut moved_count = 0; + while moved_count < module_count { + let mut found = false; + for i in 0..module_count { + if moved[i] { + continue; + } + let dst = modules[i].dst; + let dst_end = dst + (modules[i].end - modules[i].start); + let mut can_move = true; + for j in 0..module_count { + if moved[j] || i == j { + continue; + } + let start = modules[j].start; + let end = modules[j].end; + if dst < end && dst_end > start { + can_move = false; + break; + } + } + if can_move { + if modules[i].dst != 0 { + let size = modules[i].end - modules[i].start + 1; + let dst_end = modules[i].dst + size; + let overlaps_self = + modules[i].dst < modules[i].end && dst_end > modules[i].start; + unsafe { + if overlaps_self { + core::ptr::copy( + modules[i].start as *const u8, + modules[i].dst as *mut u8, + size, + ); + } else { + core::ptr::copy_nonoverlapping( + modules[i].start as *const u8, + modules[i].dst as *mut u8, + size, + ); + } + } + } + moved[i] = true; + moved_count += 1; + found = true; + break; + } + } + if !found { + panic!("Cannot move modules due to overlapping addresses"); } - - unsafe { - core::ptr::copy( - module.mod_start as *mut u8, - dst as *mut u8, - (module.mod_end - module.mod_start + 1) as usize, - ) - }; } - println!("module cnt: {:x}", cnt); + + println!("module cnt: {:x}", module_count); } diff --git a/src/arch/x86_64/cpu.rs b/src/arch/x86_64/cpu.rs index 97d69ccd5..cf53ca63b 100644 --- a/src/arch/x86_64/cpu.rs +++ b/src/arch/x86_64/cpu.rs @@ -319,6 +319,13 @@ impl ArchCpu { self.vm_launch_guest_regs.rsi = rsi; } + pub fn set_multiboot_boot_regs(&mut self, multiboot_info_addr: u64, kernel_entry: u64) { + const MULTIBOOT2_MAGIC: u64 = 0x36D76289; + self.vm_launch_guest_regs.rax = MULTIBOOT2_MAGIC; + self.vm_launch_guest_regs.rbx = multiboot_info_addr; + self.vm_launch_guest_regs.rsi = kernel_entry; + } + fn activate_vmx(&mut self) -> HvResult { if self.vmx_on { return Ok(()); @@ -383,9 +390,11 @@ impl ArchCpu { Vmcs::clear(start_paddr)?; Vmcs::load(start_paddr)?; + // Setup VMCS control fields first (includes secondary controls like UNRESTRICTED_GUEST) + // This must be done before setup_vmcs_guest so that guest state is properly initialized + self.setup_vmcs_control()?; self.setup_vmcs_host(&self.host_stack_top as *const _ as usize)?; self.setup_vmcs_guest(entry, ROOT_ZONE_BOOT_STACK)?; - self.setup_vmcs_control()?; Ok(()) } @@ -462,6 +471,7 @@ impl ArchCpu { VmcsControl32::VMEXIT_MSR_STORE_COUNT.write(0)?; VmcsControl32::VMEXIT_MSR_LOAD_COUNT.write(0)?; VmcsControl32::VMENTRY_MSR_LOAD_COUNT.write(0)?; + VmcsControl32::VMENTRY_INTERRUPTION_INFO_FIELD.write(0)?; // pass-through exceptions, set I/O bitmap and MSR bitmaps VmcsControl32::EXCEPTION_BITMAP.write(0)?; diff --git a/src/arch/x86_64/cpuid.rs b/src/arch/x86_64/cpuid.rs index 989666d55..b76922752 100644 --- a/src/arch/x86_64/cpuid.rs +++ b/src/arch/x86_64/cpuid.rs @@ -21,6 +21,7 @@ pub enum CpuIdEax { VendorInfo = 0x0, FeatureInfo = 0x1, StructuredExtendedFeatureInfo = 0x7, + TscInfo = 0x15, ProcessorFrequencyInfo = 0x16, HypervisorInfo = 0x4000_0000, HypervisorFeatures = 0x4000_0001, diff --git a/src/arch/x86_64/idt.rs b/src/arch/x86_64/idt.rs index 97f284e50..299aa0cbf 100644 --- a/src/arch/x86_64/idt.rs +++ b/src/arch/x86_64/idt.rs @@ -24,6 +24,7 @@ const VECTOR_CNT: usize = 256; #[allow(non_snake_case)] pub mod IdtVector { + pub const I8042_KEYBOARD_VECTOR: u8 = 0x21; pub const VIRT_IPI_VECTOR: u8 = 0xef; pub const APIC_ERROR_VECTOR: u8 = 0xfc; pub const APIC_SPURIOUS_VECTOR: u8 = 0xfd; diff --git a/src/arch/x86_64/msr.rs b/src/arch/x86_64/msr.rs index 80585c5b9..c74e27528 100644 --- a/src/arch/x86_64/msr.rs +++ b/src/arch/x86_64/msr.rs @@ -195,27 +195,57 @@ impl MsrBitmap { }; bitmap.set_read_intercept(IA32_APIC_BASE, true); - bitmap.set_read_intercept(IA32_X2APIC_APICID, true); - bitmap.set_read_intercept(IA32_X2APIC_LDR, true); - bitmap.set_read_intercept(IA32_X2APIC_LVT_TIMER, true); - bitmap.set_write_intercept(IA32_APIC_BASE, true); - bitmap.set_write_intercept(IA32_X2APIC_EOI, true); - bitmap.set_write_intercept(IA32_X2APIC_ICR, true); - bitmap.set_write_intercept(IA32_X2APIC_LVT_TIMER, true); - for addr in (IA32_X2APIC_ISR0 as u32)..(IA32_X2APIC_ISR7 as u32 + 1) { - if let Ok(msr) = Msr::try_from(addr) { - bitmap.set_read_intercept(msr, true); - } + // Intercept the x2APIC registers that this hypervisor actually + // virtualizes or forwards. Leave TPR/TMR/CMCI and other unvirtualized + // registers on the pass-through path to preserve host APIC behavior. + let x2apic_reads = [ + IA32_X2APIC_APICID, + IA32_X2APIC_VERSION, + IA32_X2APIC_LDR, + IA32_X2APIC_SIVR, + IA32_X2APIC_ISR0, + IA32_X2APIC_ISR1, + IA32_X2APIC_ISR2, + IA32_X2APIC_ISR3, + IA32_X2APIC_ISR4, + IA32_X2APIC_ISR5, + IA32_X2APIC_ISR6, + IA32_X2APIC_ISR7, + IA32_X2APIC_IRR0, + IA32_X2APIC_IRR1, + IA32_X2APIC_IRR2, + IA32_X2APIC_IRR3, + IA32_X2APIC_IRR4, + IA32_X2APIC_IRR5, + IA32_X2APIC_IRR6, + IA32_X2APIC_IRR7, + IA32_X2APIC_ESR, + IA32_X2APIC_LVT_TIMER, + IA32_X2APIC_INIT_COUNT, + IA32_X2APIC_CUR_COUNT, + IA32_X2APIC_DIV_CONF, + ]; + for msr in x2apic_reads { + bitmap.set_read_intercept(msr, true); } - for addr in (IA32_X2APIC_IRR0 as u32)..(IA32_X2APIC_IRR7 as u32 + 1) { - if let Ok(msr) = Msr::try_from(addr) { - bitmap.set_read_intercept(msr, true); - } + let x2apic_writes = [ + IA32_X2APIC_EOI, + IA32_X2APIC_ICR, + IA32_X2APIC_SIVR, + IA32_X2APIC_LVT_TIMER, + IA32_X2APIC_INIT_COUNT, + IA32_X2APIC_DIV_CONF, + ]; + for msr in x2apic_writes { + bitmap.set_write_intercept(msr, true); } + bitmap.set_read_intercept(IA32_TSC_DEADLINE, true); + bitmap.set_write_intercept(IA32_TSC_DEADLINE, true); + bitmap } diff --git a/src/arch/x86_64/pio.rs b/src/arch/x86_64/pio.rs index 255e76ddb..229d41005 100644 --- a/src/arch/x86_64/pio.rs +++ b/src/arch/x86_64/pio.rs @@ -27,6 +27,7 @@ pub const UART_COM1_BASE_PORT: u16 = 0x3f8; pub const UART_COM1_PORT: Range = 0x3f8..0x400; pub const PCI_CONFIG_ADDR_PORT: Range = 0xcf8..0xcfc; pub const PCI_CONFIG_DATA_PORT: Range = 0xcfc..0xd00; +pub const I8042_PORT: Range = 0x60..0x65; static mut PIO_BITMAP_MAP: Option> = None; @@ -94,7 +95,7 @@ impl PortIoBitmap { // } // i8042, we won't use it, but intercept its ports might block linux init - bitmap.set_range_intercept(0x60..0x65, false); + bitmap.set_range_intercept(I8042_PORT, false); bitmap } diff --git a/src/arch/x86_64/trap.rs b/src/arch/x86_64/trap.rs index 865c8308b..dcc30f1bb 100644 --- a/src/arch/x86_64/trap.rs +++ b/src/arch/x86_64/trap.rs @@ -22,6 +22,7 @@ use crate::{ idt::{IdtStruct, IdtVector}, ipi, msr::Msr::{self, *}, + pio::I8042_PORT, s2pt::Stage2PageFaultInfo, vmcs::*, vmx::{VmxCrAccessInfo, VmxExitInfo, VmxExitReason, VmxInterruptInfo, VmxIoExitInfo}, @@ -102,11 +103,13 @@ pub fn arch_handle_trap(tf: &mut TrapFrame) { } fn handle_irq(vector: u8) { + let is_timer = vector == this_cpu_data().arch_cpu.virt_lapic.virt_timer_vector; + match vector { - IdtVector::VIRT_IPI_VECTOR => { + IdtVector::VIRT_IPI_VECTOR if !is_timer => { ipi::handle_virt_ipi(); } - IdtVector::APIC_SPURIOUS_VECTOR | IdtVector::APIC_ERROR_VECTOR => {} + IdtVector::APIC_SPURIOUS_VECTOR | IdtVector::APIC_ERROR_VECTOR if !is_timer => {} _ => { if vector >= 0x20 && this_cpu_data().vcpu_state.is_running() { inject_vector(this_cpu_id(), vector, None, false); @@ -153,6 +156,12 @@ fn handle_cpuid(arch_cpu: &mut ArchCpu) -> HvResult { res } + CpuIdEax::TscInfo => CpuIdResult { + eax: 1, // Numerator for TSC frequency + ebx: 1, // Denominator for TSC frequency + ecx: hpet::get_tsc_freq_mhz().unwrap_or(0) * 1_000_000, // TSC frequency in Hz + edx: 0, // Reserved, typically 0 + }, CpuIdEax::ProcessorFrequencyInfo => { if let Some(freq_mhz) = hpet::get_tsc_freq_mhz() { CpuIdResult { @@ -215,6 +224,9 @@ fn handle_external_interrupt() -> HvResult { let int_info = VmxInterruptInfo::new()?; trace!("VM-exit: external interrupt: {:#x?}", int_info); assert!(int_info.valid); + if int_info.vector == 0x21 { + // info!("External interrupt: IRQ1 (keyboard)"); + } handle_irq(int_info.vector); Ok(()) } @@ -276,11 +288,8 @@ fn handle_io_instruction(arch_cpu: &mut ArchCpu, exit_info: &VmxExitInfo) -> HvR handle_pci_config_port_write(&io_info, value); } else if UART_COM1_PORT.contains(&io_info.port) { virt_console_io_write(io_info.port, value); - } else { - /* info!( - "unhandled port io write {:x} value: {:x}", - io_info.port, value - ); */ + } else if I8042_PORT.contains(&io_info.port) { + // info!("unhandled port io write {:x} value: {:x}", io_info.port, value); } } else { if PCI_CONFIG_ADDR_PORT.contains(&io_info.port) @@ -289,6 +298,8 @@ fn handle_io_instruction(arch_cpu: &mut ArchCpu, exit_info: &VmxExitInfo) -> HvR value = handle_pci_config_port_read(&io_info); } else if UART_COM1_PORT.contains(&io_info.port) { value = virt_console_io_read(io_info.port); + } else if I8042_PORT.contains(&io_info.port) { + value = 0xff; } else { // info!("unhandled port io read {:x}", io_info.port); value = 0x0; @@ -315,27 +326,32 @@ fn handle_io_instruction(arch_cpu: &mut ArchCpu, exit_info: &VmxExitInfo) -> HvR fn handle_msr_read(arch_cpu: &mut ArchCpu) -> HvResult { let rcx = arch_cpu.regs().rcx as u32; - if let Ok(msr) = Msr::try_from(rcx) { - let res = if msr == IA32_APIC_BASE { - let mut apic_base = unsafe { IA32_APIC_BASE.read() }; - // info!("APIC BASE: {:x}", apic_base); - apic_base |= 1 << 11 | 1 << 10; // enable xAPIC and x2APIC - Ok(apic_base) - } else if VirtLocalApic::msr_range().contains(&rcx) { + let res = if rcx == IA32_APIC_BASE as u32 { + let mut apic_base = unsafe { IA32_APIC_BASE.read() }; + // info!("APIC BASE: {:x}", apic_base); + apic_base |= 1 << 11 | 1 << 10; // enable xAPIC and x2APIC + Ok(apic_base) + } else if VirtLocalApic::msr_range().contains(&rcx) { + match Msr::try_from(rcx) { + Ok(msr) => arch_cpu.virt_lapic.rdmsr(msr), + Err(_) => Ok(0), + } + } else if let Ok(msr) = Msr::try_from(rcx) { + if msr == IA32_TSC_DEADLINE { arch_cpu.virt_lapic.rdmsr(msr) } else { hv_result_err!(ENOSYS) - }; - - if let Ok(value) = res { - debug!("VM exit: RDMSR({:#x}) -> {:#x}", rcx, value); - arch_cpu.regs_mut().rax = value & 0xffff_ffff; - arch_cpu.regs_mut().rdx = value >> 32; - } else { - warn!("Failed to handle RDMSR({:#x}): {:?}", rcx, res); } } else { - // warn!("Unrecognized RDMSR({:#x})", rcx); + hv_result_err!(ENOSYS) + }; + + if let Ok(value) = res { + debug!("VM exit: RDMSR({:#x}) -> {:#x}", rcx, value); + arch_cpu.regs_mut().rax = value & 0xffff_ffff; + arch_cpu.regs_mut().rdx = value >> 32; + } else { + warn!("Failed to handle RDMSR({:#x}): {:?}", rcx, res); } arch_cpu.advance_guest_rip(VM_EXIT_INSTR_LEN_RDMSR)?; @@ -344,22 +360,33 @@ fn handle_msr_read(arch_cpu: &mut ArchCpu) -> HvResult { fn handle_msr_write(arch_cpu: &mut ArchCpu) -> HvResult { let rcx = arch_cpu.regs().rcx as u32; - let msr = Msr::try_from(rcx).unwrap(); + let msr = Msr::try_from(rcx); let value = (arch_cpu.regs().rax & 0xffff_ffff) | (arch_cpu.regs().rdx << 32); debug!("VM exit: WRMSR({:#x}) <- {:#x}", rcx, value); - let res = if msr == IA32_APIC_BASE { + let res = if rcx == IA32_APIC_BASE as u32 { Ok(()) // ignore - } else if VirtLocalApic::msr_range().contains(&rcx) || msr == IA32_TSC_DEADLINE { - arch_cpu.virt_lapic.wrmsr(msr, value) + } else if VirtLocalApic::msr_range().contains(&rcx) { + match msr { + Ok(msr) => arch_cpu.virt_lapic.wrmsr(msr, value), + Err(_) => Ok(()), + } + } else if msr == Ok(IA32_TSC_DEADLINE) { + arch_cpu.virt_lapic.wrmsr(IA32_TSC_DEADLINE, value) } else { hv_result_err!(ENOSYS) }; - if res.is_err() { + if let Err(e) = res { + if e.num == crate::error::HvErrorNum::EINVAL { + // Invalid WRMSR values must fault in the guest instead of being + // silently accepted with RIP advanced. + inject_vector(this_cpu_id(), 13, Some(0), false); + return Ok(()); + } warn!( "Failed to handle WRMSR({:#x}) <- {:#x}: {:?}\n{:#x?}", - rcx, value, res, arch_cpu + rcx, value, e, arch_cpu ); } arch_cpu.advance_guest_rip(VM_EXIT_INSTR_LEN_WRMSR)?; diff --git a/src/arch/x86_64/zone.rs b/src/arch/x86_64/zone.rs index d671fe515..a448476ac 100644 --- a/src/arch/x86_64/zone.rs +++ b/src/arch/x86_64/zone.rs @@ -22,7 +22,7 @@ use crate::{ error::HvResult, memory::{GuestPhysAddr, HostPhysAddr, MemFlags, MemoryRegion, MemorySet}, platform::MEM_TYPE_RESERVED, - zone::Zone, + zone::{zone_boot_mode, Zone}, }; use alloc::vec::Vec; @@ -72,14 +72,20 @@ impl Zone { } match mem_region.mem_type { MEM_TYPE_RAM | MEM_TYPE_IO | MEM_TYPE_RESERVED => { - inner.gpm_mut().insert(MemoryRegion::new_with_offset_mapper( - mem_region.virtual_start as GuestPhysAddr, - mem_region.physical_start as HostPhysAddr, - mem_region.size as _, - flags, - )); + inner + .gpm_mut() + .insert(MemoryRegion::new_with_offset_mapper( + mem_region.virtual_start as GuestPhysAddr, + mem_region.physical_start as HostPhysAddr, + mem_region.size as _, + flags, + ))?; } MEM_TYPE_VIRTIO => { + info!( + "Registering virtio mmio region: physical_start: {:#x}, size: {:#x}", + mem_region.physical_start, mem_region.size + ); inner.mmio_region_register( mem_region.physical_start as _, mem_region.size as _, @@ -101,17 +107,34 @@ impl Zone { /// called after cpu_set is initialized pub fn arch_zone_pre_configuration(&mut self, config: &HvZoneConfig) -> HvResult { + let zone_id = config.zone_id as usize; let inner = self.read(); - inner.cpu_set().iter().for_each(|cpuid| { - let cpu_data = get_cpu_data(cpuid); - // boot cpu - if cpuid == inner.cpu_set().first_cpu().unwrap() { - cpu_data.arch_cpu.set_boot_cpu_vm_launch_regs( - config.arch_config.kernel_entry_gpa as _, - config.arch_config.setup_load_gpa as _, - ); - } - }); + let boot_mode = zone_boot_mode(zone_id); + + if zone_id != 0 && boot_mode.multiboot_enabled != 0 { + info!("[ZONE{}] Using Multiboot2 boot mode", zone_id); + + inner.cpu_set().iter().for_each(|cpuid| { + let cpu_data = get_cpu_data(cpuid); + if cpuid == inner.cpu_set().first_cpu().unwrap() { + cpu_data.arch_cpu.set_multiboot_boot_regs( + boot_mode.multiboot_info_paddr as _, + config.arch_config.kernel_entry_gpa as _, + ); + } + }); + } else { + inner.cpu_set().iter().for_each(|cpuid| { + let cpu_data = get_cpu_data(cpuid); + // boot cpu + if cpuid == inner.cpu_set().first_cpu().unwrap() { + cpu_data.arch_cpu.set_boot_cpu_vm_launch_regs( + config.arch_config.kernel_entry_gpa as _, + config.arch_config.setup_load_gpa as _, + ); + } + }); + } drop(inner); set_msr_bitmap(config.zone_id as _); @@ -148,9 +171,17 @@ impl Zone { self.pci_bars_register(&config.pci_config); }*/ - boot::BootParams::fill(&config, inner.gpm_mut()); + let boot_mode = zone_boot_mode(self.id()); + if boot_mode.multiboot_enabled != 0 { + boot::multiboot2_info_fill( + config, + inner.gpm_mut(), + boot_mode.multiboot_info_paddr as _, + )?; + } else { + boot::BootParams::fill(&config, inner.gpm_mut()); + } acpi::copy_to_guest_memory_region(&config, &inner.cpu_set()); - Ok(()) } diff --git a/src/config.rs b/src/config.rs index 9fd10ea7e..fb0ce57a1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -108,6 +108,14 @@ pub struct HvZoneConfig { pub alloc_pci_devs: [HvPciDevConfig; CONFIG_MAX_PCI_DEV], } +#[repr(C)] +#[derive(Debug, Clone, Copy, Default)] +pub struct HvZoneBootMode { + pub zone_id: u32, + pub multiboot_enabled: u32, + pub multiboot_info_paddr: u64, +} + impl HvZoneConfig { pub fn new( zone_id: u32, diff --git a/src/device/irqchip/pic/ioapic.rs b/src/device/irqchip/pic/ioapic.rs index 75c7b1607..4286698f1 100644 --- a/src/device/irqchip/pic/ioapic.rs +++ b/src/device/irqchip/pic/ioapic.rs @@ -16,12 +16,13 @@ use crate::{ arch::{ - acpi::{get_apic_id, get_cpu_id}, - cpu::this_cpu_id, + acpi::try_get_cpu_id, + cpu::{this_apic_id, this_cpu_id}, idt, ipi, mmio::MMIoDevice, zone::HvArchZoneConfig, }, + cpu_data::this_zone, device::irqchip::pic::inject_vector, error::HvResult, memory::{GuestPhysAddr, MMIOAccess}, @@ -95,6 +96,11 @@ impl VirtIoApic { mut reg => { reg -= IoApicReg::TABLE_BASE; let index = (reg >> 1) as usize; + // info!("ioapic read index: {:x}", index); + if this_zone_id() != 0 && index == 4 { + //FIXME: we do not allow non-root to use uart interrupt + return Ok(0xffff_ffff_ffff_ffff); + } if let Some(entry) = inner.rte.get(index) { if reg % 2 == 0 { Ok((*entry).get_bits(0..=31)) @@ -132,7 +138,26 @@ impl VirtIoApic { if reg % 2 == 0 { entry.set_bits(0..=31, value.get_bits(0..=31)); } else { - entry.set_bits(32..=63, value.get_bits(0..=31)); + let original_dest = value.get_bits(24..=31); + if let Some(dest_cpu) = try_get_cpu_id(original_dest as usize) { + if this_zone().read().cpu_set().contains_cpu(dest_cpu) { + entry.set_bits(56..=63, original_dest); + } else { + let dest = this_apic_id() as u64; + info!( + "redirect irq {:x} to cpu {:x} in another zone! entry: {:x?}", + index, dest, *entry + ); + entry.set_bits(56..=63, dest); + } + } else { + let dest = this_apic_id() as u64; + info!( + "redirect irq {:x} to cpu {:x} in another zone, unknown dest {:x}! entry: {:x?}", + index, dest, original_dest, *entry + ); + entry.set_bits(56..=63, dest); + } /*if zone_id == 0 { // info!("1 write {:x} entry: {:x?}", index, *entry); @@ -153,7 +178,7 @@ impl VirtIoApic { fn get_irq_cpu(&self, irq: usize, zone_id: usize) -> Option { let ioapic = self.inner.get(zone_id).unwrap(); if let Some(entry) = ioapic.lock().rte.get(irq) { - let dest = get_cpu_id(entry.get_bits(56..=63) as usize); + let dest = try_get_cpu_id(entry.get_bits(56..=63) as usize).unwrap_or_else(this_cpu_id); return Some(dest); } None @@ -164,10 +189,9 @@ impl VirtIoApic { let ioapic = self.inner.get(zone_id).unwrap(); if let Some(entry) = ioapic.lock().rte.get(irq) { // TODO: physical & logical mode - let dest = get_cpu_id(entry.get_bits(56..=63) as usize); + let dest = try_get_cpu_id(entry.get_bits(56..=63) as usize).unwrap_or_else(this_cpu_id); let masked = entry.get_bit(16); let vector = entry.get_bits(0..=7) as u8; - // info!("trigger hv: {:x} zone: {:x}", vector, zone_id); if !masked && vector >= 0x20 { inject_vector(dest, vector, None, allow_repeat); } diff --git a/src/device/irqchip/pic/lapic.rs b/src/device/irqchip/pic/lapic.rs index 8ba43c182..7283ae7c6 100644 --- a/src/device/irqchip/pic/lapic.rs +++ b/src/device/irqchip/pic/lapic.rs @@ -28,12 +28,18 @@ use crate::{ }; use bit_field::BitField; use core::{ops::Range, u32}; -use x2apic::lapic::{LocalApic, LocalApicBuilder, TimerMode}; +use x2apic::lapic::{LocalApic, LocalApicBuilder}; +use x86::msr::{rdmsr, wrmsr}; + +const LVT_VECTOR_MIN: u64 = 0x20; +const APIC_TIMER_DIV_RESERVED_MIN: u64 = 0b0100; +const APIC_TIMER_DIV_RESERVED_MAX: u64 = 0b0111; pub struct VirtLocalApic { pub phys_lapic: LocalApic, pub virt_timer_vector: u8, virt_lvt_timer_bits: u32, + virt_svr: u32, } impl VirtLocalApic { @@ -46,6 +52,7 @@ impl VirtLocalApic { ), virt_timer_vector: IdtVector::APIC_TIMER_VECTOR as _, virt_lvt_timer_bits: (1 << 16) as _, // masked + virt_svr: 0x1FD, // APIC enabled, spurious vector 0xFD } } @@ -89,14 +96,20 @@ impl VirtLocalApic { Ok(0) } IA32_X2APIC_LVT_TIMER => Ok(self.virt_lvt_timer_bits as _), - _ => hv_result_err!(ENOSYS), + IA32_X2APIC_VERSION => Ok(unsafe { rdmsr(IA32_X2APIC_VERSION as u32) }), + IA32_X2APIC_SIVR => Ok(self.virt_svr as _), + IA32_X2APIC_ESR => Ok(0), + IA32_X2APIC_INIT_COUNT + | IA32_X2APIC_CUR_COUNT + | IA32_X2APIC_DIV_CONF + | IA32_TSC_DEADLINE => Ok(unsafe { rdmsr(msr as u32) }), + _ => Ok(0), } } pub fn wrmsr(&mut self, msr: Msr, value: u64) -> HvResult { match msr { IA32_X2APIC_EOI => { - // info!("eoi"); pop_vector(this_cpu_id()); Ok(()) } @@ -106,32 +119,41 @@ impl VirtLocalApic { Ok(()) } IA32_X2APIC_LVT_TIMER => { - self.virt_lvt_timer_bits = value as u32; - let timer = value.get_bits(0..=7) as u8; - if timer != self.virt_timer_vector { - self.virt_timer_vector = timer; - self.phys_lapic = Self::new_phys_lapic( - timer as _, - IdtVector::APIC_ERROR_VECTOR as _, - IdtVector::APIC_SPURIOUS_VECTOR as _, - ) + let value = value & 0xffff_ffff; + let timer_mode = value.get_bits(17..19); + let vector = value.get_bits(0..=7); + if timer_mode == 3 || vector < LVT_VECTOR_MIN { + return hv_result_err!(EINVAL, "invalid LVT timer value"); } + self.virt_lvt_timer_bits = value as u32; + self.virt_timer_vector = vector as u8; unsafe { - self.phys_lapic - .set_timer_mode(match value.get_bits(17..19) { - 0 => TimerMode::OneShot, - 1 => TimerMode::Periodic, - _ => TimerMode::TscDeadline, - }); - if value.get_bit(16) { - self.phys_lapic.disable_timer(); - } else { - self.phys_lapic.enable_timer(); - } + wrmsr(IA32_X2APIC_LVT_TIMER as u32, value); + } + Ok(()) + } + IA32_X2APIC_SIVR => { + self.virt_svr = value as u32; + Ok(()) + } + IA32_X2APIC_INIT_COUNT => { + unsafe { wrmsr(IA32_X2APIC_INIT_COUNT as u32, value & 0xffff_ffff) }; + Ok(()) + } + IA32_X2APIC_DIV_CONF => { + let value = value & 0xffff_ffff; + let divider = value.get_bits(0..=3); + if (APIC_TIMER_DIV_RESERVED_MIN..=APIC_TIMER_DIV_RESERVED_MAX).contains(÷r) { + return hv_result_err!(EINVAL, "invalid APIC timer divider"); } + unsafe { wrmsr(IA32_X2APIC_DIV_CONF as u32, value) }; + Ok(()) + } + IA32_TSC_DEADLINE => { + unsafe { wrmsr(IA32_TSC_DEADLINE as u32, value) }; Ok(()) } - _ => hv_result_err!(ENOSYS), + _ => Ok(()), } } } diff --git a/src/hypercall/mod.rs b/src/hypercall/mod.rs index ca4f33890..6ed59088f 100644 --- a/src/hypercall/mod.rs +++ b/src/hypercall/mod.rs @@ -17,7 +17,7 @@ #![allow(unreachable_patterns)] use crate::arch::cpu::get_target_cpu; -use crate::config::HvZoneConfig; +use crate::config::{HvZoneBootMode, HvZoneConfig}; use crate::consts::{INVALID_ADDRESS, MAX_CPU_NUM, MAX_WAIT_TIMES, PAGE_SIZE}; use crate::cpu_data::{get_cpu_data, PerCpu, VcpuState}; use crate::device::virtio_trampoline::{ @@ -27,7 +27,8 @@ use crate::device::virtio_trampoline::{ use crate::error::HvResult; use crate::pci::pci_config::GLOBAL_PCIE_LIST; use crate::zone::{ - add_zone, all_zones_info, find_zone, is_this_root_zone, remove_zone, zone_create, ZoneInfo, + add_zone, all_zones_info, clear_zone_boot_mode, find_zone, is_this_root_zone, remove_zone, + set_zone_boot_mode, zone_create, ZoneInfo, }; use crate::event::{ @@ -51,6 +52,7 @@ numeric_enum! { HvIvcInfo = 5, HvConfigCheck = 6, HvVirtioPCI = 7, + HvZoneSetBootMode = 8, } } pub const SGI_IPI_ID: u64 = 7; @@ -102,6 +104,7 @@ impl<'a> HyperCall<'a> { HyperCallCode::HvIvcInfo => self.hv_ivc_info(arg0), HyperCallCode::HvConfigCheck => self.hv_zone_config_check(arg0 as *mut u64), HyperCallCode::HvVirtioPCI => self.hv_virtio_pci(arg0, arg1), + HyperCallCode::HvZoneSetBootMode => self.hv_zone_set_boot_mode(arg0, arg1), _ => { warn!("hypercall id={} unsupported!", code as u64); Ok(0) @@ -179,13 +182,48 @@ impl<'a> HyperCall<'a> { HyperCallResult::Ok(0) } + pub fn hv_zone_set_boot_mode( + &mut self, + boot_mode_ipa: u64, + boot_mode_size: u64, + ) -> HyperCallResult { + if !is_this_root_zone() { + return hv_result_err!( + EPERM, + "Set zone boot mode over non-root zones: unsupported!" + ); + } + if boot_mode_size != core::mem::size_of::() as _ { + return hv_result_err!( + EINVAL, + format!( + "hv_zone_set_boot_mode: config size should be {} bytes, but got {}", + core::mem::size_of::(), + boot_mode_size + ) + ); + } + + let boot_mode_pa = self.hv_get_real_pa(boot_mode_ipa); + let boot_mode = unsafe { &*(boot_mode_pa as *const HvZoneBootMode) }; + if boot_mode.zone_id == 0 { + return hv_result_err!(EINVAL, "boot mode is not supported for the root zone"); + } + + set_zone_boot_mode(boot_mode.zone_id as usize, *boot_mode); + info!( + "hv_zone_set_boot_mode: zone_id={}, multiboot_enabled={}, info_paddr={:#x}", + boot_mode.zone_id, boot_mode.multiboot_enabled, boot_mode.multiboot_info_paddr + ); + HyperCallResult::Ok(0) + } + pub fn hv_zone_start(&mut self, config: &HvZoneConfig, config_size: u64) -> HyperCallResult { let config_ipa = config as *const HvZoneConfig as u64; let config_pa = self.hv_get_real_pa(config_ipa); let config = unsafe { &*(config_pa as *const HvZoneConfig) }; info!("hv_zone_start: config: {:#x?},pa:{:#x}", config, config_pa); - // return HyperCallResult::Ok(0); if !is_this_root_zone() { return hv_result_err!( EPERM, @@ -202,7 +240,9 @@ impl<'a> HyperCall<'a> { ) ); } - let zone = zone_create(config)?; + let zone_result = zone_create(config); + clear_zone_boot_mode(config.zone_id as usize); + let zone = zone_result?; let boot_cpu = zone.read().cpu_set().first_cpu().unwrap(); let target_data = get_cpu_data(boot_cpu as _); diff --git a/src/zone.rs b/src/zone.rs index b9e68bc87..ce8172ce6 100644 --- a/src/zone.rs +++ b/src/zone.rs @@ -18,16 +18,15 @@ use alloc::vec::Vec; // use psci::error::INVALID_ADDRESS; use crate::consts::{INVALID_ADDRESS, MAX_CPU_NUM}; use crate::pci::pci_struct::VirtualRootComplex; -use spin::{RwLock, RwLockReadGuard, RwLockWriteGuard}; +use alloc::collections::BTreeMap; +use spin::{Mutex, RwLock, RwLockReadGuard, RwLockWriteGuard}; #[cfg(dwc_pcie)] use crate::pci::{config_accessors::dwc_atu::AtuConfig, PciConfigAddress}; -#[cfg(dwc_pcie)] -use alloc::collections::btree_map::BTreeMap; use crate::arch::mm::new_s2_memory_set; use crate::arch::s2pt::Stage2PageTable; -use crate::config::{HvZoneConfig, CONFIG_NAME_MAXLEN}; +use crate::config::{HvZoneBootMode, HvZoneConfig, CONFIG_NAME_MAXLEN}; use crate::cpu_data::{get_cpu_data, this_zone, CpuSet}; use crate::error::HvResult; @@ -729,6 +728,23 @@ impl ZoneInner { } static ZONE_LIST: RwLock>> = RwLock::new(vec![]); +static ZONE_BOOT_MODES: Mutex> = Mutex::new(BTreeMap::new()); + +pub fn set_zone_boot_mode(zone_id: usize, mode: HvZoneBootMode) { + ZONE_BOOT_MODES.lock().insert(zone_id, mode); +} + +pub fn zone_boot_mode(zone_id: usize) -> HvZoneBootMode { + ZONE_BOOT_MODES + .lock() + .get(&zone_id) + .copied() + .unwrap_or_default() +} + +pub fn clear_zone_boot_mode(zone_id: usize) { + ZONE_BOOT_MODES.lock().remove(&zone_id); +} pub fn root_zone() -> Arc { ZONE_LIST.read().get(0).cloned().unwrap()