genode/repos/base-hw/src/bootstrap/spec/x86_64/platform.cc

336 lines
9.9 KiB
C++

/*
* \brief Platform implementations specific for x86_64
* \author Reto Buerki
* \author Stefan Kalkowski
* \author Alexander Boettcher
* \date 2015-05-04
*/
/*
* Copyright (C) 2015-2018 Genode Labs GmbH
*
* This file is part of the Genode OS framework, which is distributed
* under the terms of the GNU Affero General Public License version 3.
*/
/* core includes */
#include <bios_data_area.h>
#include <platform.h>
#include <multiboot.h>
#include <multiboot2.h>
#include <hw/spec/x86_64/acpi.h>
using namespace Genode;
/* contains Multiboot MAGIC value (either version 1 or 2) */
extern "C" Genode::addr_t __initial_ax;
/* contains physical pointer to multiboot */
extern "C" Genode::addr_t __initial_bx;
/* pointer to stack base */
extern "C" Genode::addr_t __bootstrap_stack;
/* number of booted CPUs */
extern "C" Genode::addr_t __cpus_booted;
/* stack size per CPU */
extern "C" Genode::addr_t const bootstrap_stack_size;
/* hardcoded physical page or AP CPUs boot code */
enum { AP_BOOT_CODE_PAGE = 0x1000 };
extern "C" void * _start;
extern "C" void * _ap;
static Hw::Acpi_rsdp search_rsdp(addr_t area, addr_t area_size)
{
if (area && area_size && area < area + area_size) {
for (addr_t addr = 0; addr + sizeof(Hw::Acpi_rsdp) <= area_size;
addr += sizeof(Hw::Acpi_rsdp::signature))
{
Hw::Acpi_rsdp * rsdp = reinterpret_cast<Hw::Acpi_rsdp *>(area + addr);
if (rsdp->valid())
return *rsdp;
}
}
Hw::Acpi_rsdp invalid;
return invalid;
}
Bootstrap::Platform::Board::Board()
: core_mmio(Memory_region { 0, 0x1000 },
Memory_region { Hw::Cpu_memory_map::lapic_phys_base(), 0x1000 },
Memory_region { Hw::Cpu_memory_map::MMIO_IOAPIC_BASE,
Hw::Cpu_memory_map::MMIO_IOAPIC_SIZE },
Memory_region { __initial_bx & ~0xFFFUL,
get_page_size() })
{
Hw::Acpi_rsdp & acpi_rsdp = info.acpi_rsdp;
static constexpr size_t initial_map_max = 1024 * 1024 * 1024;
auto lambda = [&] (addr_t base, addr_t size) {
/*
* Exclude first physical page, so that it will become part of the
* MMIO allocator. The framebuffer requests this page as MMIO.
*/
if (base == 0 && size >= get_page_size()) {
base = get_page_size();
size -= get_page_size();
}
if (base >= initial_map_max) {
late_ram_regions.add(Memory_region { base, size });
return;
}
if (base + size <= initial_map_max) {
early_ram_regions.add(Memory_region { base, size });
return;
}
size_t low_size = initial_map_max - base;
early_ram_regions.add(Memory_region { base, low_size });
late_ram_regions.add(Memory_region { initial_map_max, size - low_size });
};
if (__initial_ax == Multiboot2_info::MAGIC) {
Multiboot2_info mbi2(__initial_bx);
mbi2.for_each_tag([&] (Multiboot2_info::Memory const & m) {
uint32_t const type = m.read<Multiboot2_info::Memory::Type>();
if (type != Multiboot2_info::Memory::Type::MEMORY)
return;
uint64_t const base = m.read<Multiboot2_info::Memory::Addr>();
uint64_t const size = m.read<Multiboot2_info::Memory::Size>();
lambda(base, size);
},
[&] (Hw::Acpi_rsdp const &rsdp) {
/* prefer higher acpi revisions */
if (!acpi_rsdp.valid() || acpi_rsdp.revision < rsdp.revision)
acpi_rsdp = rsdp;
},
[&] (Hw::Framebuffer const &fb) {
info.framebuffer = fb;
},
[&] (uint64_t const efi_sys_tab) {
info.efi_system_table = efi_sys_tab;
});
} else if (__initial_ax == Multiboot_info::MAGIC) {
for (unsigned i = 0; true; i++) {
using Mmap = Multiboot_info::Mmap;
Mmap v(Multiboot_info(__initial_bx).phys_ram_mmap_base(i));
if (!v.base()) break;
Mmap::Addr::access_t base = v.read<Mmap::Addr>();
Mmap::Length::access_t size = v.read<Mmap::Length>();
lambda(base, size);
}
/* search ACPI RSDP pointer at known places */
/* BIOS range to scan for */
enum { BIOS_BASE = 0xe0000, BIOS_SIZE = 0x20000 };
acpi_rsdp = search_rsdp(BIOS_BASE, BIOS_SIZE);
if (!acpi_rsdp.valid()) {
/* page 0 is remapped to 2M - 4k - see crt_translation table */
addr_t const bios_addr = 2 * 1024 * 1024 - 4096;
/* search EBDA (BIOS addr + 0x40e) */
addr_t ebda_phys = (*reinterpret_cast<uint16_t *>(bios_addr + 0x40e)) << 4;
if (ebda_phys < 0x1000)
ebda_phys = bios_addr;
acpi_rsdp = search_rsdp(ebda_phys, 0x1000 /* EBDA size */);
}
} else {
error("invalid multiboot magic value: ", Hex(__initial_ax));
}
/* remember max supported CPUs and use ACPI to get the actual number */
unsigned const max_cpus = cpus;
cpus = 0;
/* scan ACPI tables to find out number of CPUs in this machine */
if (acpi_rsdp.valid()) {
uint64_t const table_addr = acpi_rsdp.xsdt ? acpi_rsdp.xsdt : acpi_rsdp.rsdt;
if (table_addr) {
Hw::Acpi_generic * table = reinterpret_cast<Hw::Acpi_generic *>(table_addr);
if (!memcmp(table->signature, "RSDT", 4)) {
Hw::for_each_rsdt_entry(*table, [&](uint32_t paddr_table) {
addr_t const table_virt_addr = paddr_table;
Hw::Acpi_generic * table = reinterpret_cast<Hw::Acpi_generic *>(table_virt_addr);
if (memcmp(table->signature, "APIC", 4))
return;
Hw::for_each_apic_struct(*table,[&](Hw::Apic_madt const *e){
if (e->type == Hw::Apic_madt::LAPIC) {
Hw::Apic_madt::Lapic lapic(e);
/* check if APIC is enabled in hardware */
if (lapic.valid())
cpus ++;
}
});
});
} else if (!memcmp(table->signature, "XSDT", 4)) {
Hw::for_each_xsdt_entry(*table, [&](uint64_t paddr_table) {
addr_t const table_virt_addr = paddr_table;
Hw::Acpi_generic * table = reinterpret_cast<Hw::Acpi_generic *>(table_virt_addr);
if (memcmp(table->signature, "APIC", 4))
return;
Hw::for_each_apic_struct(*table,[&](Hw::Apic_madt const *e){
if (e->type == Hw::Apic_madt::LAPIC) {
Hw::Apic_madt::Lapic lapic(e);
/* check if APIC is enabled in hardware */
if (lapic.valid())
cpus ++;
}
});
});
}
}
}
if (!cpus || cpus > max_cpus) {
Genode::warning("CPU count is unsupported ", cpus, "/", max_cpus,
acpi_rsdp.valid() ? " - invalid or missing RSDT/XSDT"
: " - invalid RSDP");
cpus = !cpus ? 1 : max_cpus;
}
if (cpus > 1) {
/* copy 16 bit boot code for AP CPUs */
addr_t ap_code_size = (addr_t)&_start - (addr_t)&_ap;
memcpy((void *)AP_BOOT_CODE_PAGE, &_ap, ap_code_size);
}
}
struct Lapic : Mmio
{
struct Svr : Register<0x0f0, 32>
{
struct APIC_enable : Bitfield<8, 1> { };
};
struct Icr_low : Register<0x300, 32> {
struct Vector : Bitfield< 0, 8> { };
struct Delivery_mode : Bitfield< 8, 3> {
enum Mode { INIT = 5, SIPI = 6 };
};
struct Delivery_status : Bitfield<12, 1> { };
struct Level_assert : Bitfield<14, 1> { };
struct Dest_shorthand : Bitfield<18, 2> {
enum { ALL_OTHERS = 3 };
};
};
struct Icr_high : Register<0x310, 32> {
struct Destination : Bitfield<24, 8> { };
};
Lapic(addr_t const addr) : Mmio(addr) { }
};
static inline Genode::uint64_t rdtsc()
{
Genode::uint32_t lo, hi;
asm volatile("rdtsc" : "=a" (lo), "=d" (hi));
return (Genode::uint64_t)hi << 32 | lo;
}
static inline void ipi_to_all(Lapic &lapic, unsigned const boot_frame,
Lapic::Icr_low::Delivery_mode::Mode const mode)
{
/* wait until ready */
while (lapic.read<Lapic::Icr_low::Delivery_status>())
asm volatile ("pause":::"memory");
unsigned const apic_cpu_id = 0; /* unused for IPI to all */
Lapic::Icr_low::access_t icr_low = 0;
Lapic::Icr_low::Vector::set(icr_low, boot_frame);
Lapic::Icr_low::Delivery_mode::set(icr_low, mode);
Lapic::Icr_low::Level_assert::set(icr_low);
Lapic::Icr_low::Level_assert::set(icr_low);
Lapic::Icr_low::Dest_shorthand::set(icr_low, Lapic::Icr_low::Dest_shorthand::ALL_OTHERS);
/* program */
lapic.write<Lapic::Icr_high::Destination>(apic_cpu_id);
lapic.write<Lapic::Icr_low>(icr_low);
}
unsigned Bootstrap::Platform::enable_mmu()
{
using ::Board::Cpu;
/* enable PAT if available */
Cpu::Cpuid_1_edx::access_t cpuid1 = Cpu::Cpuid_1_edx::read();
if (Cpu::Cpuid_1_edx::Pat::get(cpuid1)) {
Cpu::IA32_pat::access_t pat = Cpu::IA32_pat::read();
if (Cpu::IA32_pat::Pa1::get(pat) != Cpu::IA32_pat::Pa1::WRITE_COMBINING) {
Cpu::IA32_pat::Pa1::set(pat, Cpu::IA32_pat::Pa1::WRITE_COMBINING);
Cpu::IA32_pat::write(pat);
}
}
Cpu::Cr3::write(Cpu::Cr3::Pdb::masked((addr_t)core_pd->table_base));
addr_t const stack_base = reinterpret_cast<addr_t>(&__bootstrap_stack);
addr_t const this_stack = reinterpret_cast<addr_t>(&stack_base);
addr_t const cpu_id = (this_stack - stack_base) / bootstrap_stack_size;
/* we like to use local APIC */
Cpu::IA32_apic_base::access_t lapic_msr = Cpu::IA32_apic_base::read();
Cpu::IA32_apic_base::Lapic::set(lapic_msr);
Cpu::IA32_apic_base::write(lapic_msr);
/* skip the SMP when ACPI parsing did not reveal the number of CPUs */
if (board.cpus <= 1)
return cpu_id;
Lapic lapic(board.core_mmio.virt_addr(Hw::Cpu_memory_map::lapic_phys_base()));
/* enable local APIC if required */
if (!lapic.read<Lapic::Svr::APIC_enable>())
lapic.write<Lapic::Svr::APIC_enable>(true);
if (!Cpu::IA32_apic_base::Bsp::get(lapic_msr))
/* AP - done */
return cpu_id;
/* BSP - we're primary CPU - wake now all other CPUs */
/* see Intel Multiprocessor documentation - we need to do INIT-SIPI-SIPI */
ipi_to_all(lapic, 0 /* unused */, Lapic::Icr_low::Delivery_mode::INIT);
/* wait 10 ms - debates ongoing whether this is still required */
ipi_to_all(lapic, AP_BOOT_CODE_PAGE >> 12, Lapic::Icr_low::Delivery_mode::SIPI);
/* wait 200 us - debates ongoing whether this is still required */
/* debates ongoing whether the second SIPI is still required */
ipi_to_all(lapic, AP_BOOT_CODE_PAGE >> 12, Lapic::Icr_low::Delivery_mode::SIPI);
return cpu_id;
}
addr_t Bios_data_area::_mmio_base_virt() { return 0x1ff000; }
Board::Serial::Serial(addr_t, size_t, unsigned baudrate)
:X86_uart(Bios_data_area::singleton()->serial_port(), 0, baudrate) {}