dboot_startkern.c revision ae115bc77f6fcde83175c75b4206dc2e50747966
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
#include <sys/machparam.h>
#include <sys/x86_archext.h>
#include <sys/mach_mmu.h>
#include <sys/multiboot.h>
extern multiboot_header_t mb_header;
extern int have_cpuid(void);
#include <sys/inttypes.h>
#include <sys/bootinfo.h>
#include <sys/mach_mmu.h>
#include <sys/boot_console.h>
#include "dboot_printf.h"
#include "dboot_xboot.h"
#include "dboot_elfload.h"
/*
* This file contains code that runs to transition us from either a multiboot
* compliant loader (32 bit non-paging) or Xen domain loader to regular kernel
* execution. Its task is to setup the kernel memory image and page tables.
*
* The code executes as:
* - 32 bits under GRUB (for 32 or 64 bit Solaris)
* - 32 bit program for Xen 32 bit
* - 64 bit program for Xen 64 bit (at least that's my assumption for now)
*
* Under Xen, we must create mappings for any memory beyond the initial
* start of day allocation (such as the kernel itself).
*
* When not under Xen, the mapping between maddr_t and paddr_t is 1:1.
* Since we are running in real mode, so all such memory is accessible.
*/
/*
* Standard bits used in PTE (page level) and PTP (internal levels)
*/
/*
* This is the target addresses (physical) where the kernel text and data
* nucleus pages will be unpacked. On Xen this is actually a virtual address.
*/
/*
* The stack is setup in assembler before entering startup_kernel()
*/
char stack_space[STACK_SIZE];
/*
* Used to track physical memory allocation
*/
static paddr_t next_avail_addr = 0;
/*
* This contains information passed to the kernel
*/
struct xboot_info *bi;
/*
* Page table and memory stuff.
*/
/*
* Information about processor MMU
*/
int amd64_support = 0;
int largepage_support = 0;
int pae_support = 0;
int pge_support = 0;
int NX_support = 0;
/*
* Low 32 bits of kernel entry address passed back to assembler.
* When running a 64 bit kernel, the high 32 bits are 0xffffffff.
*/
/*
* Memlists for the kernel. We shouldn't need a lot of these.
*/
#define MAX_MEMLIST (10)
uint_t memlists_used = 0;
#define MAX_MODULES (10)
uint_t modules_used = 0;
/*
* Debugging macros
*/
uint_t prom_debug = 0;
/*
*/
static void
sort_physinstall(void)
{
int i;
int j;
struct boot_memlist tmp;
/*
* Now sort the memlists, in case they weren't in order.
* Yeah, this is a bubble sort; small, simple and easy to get right.
*/
DBG_MSG("Sorting phys-installed list\n");
for (j = memlists_used - 1; j > 0; --j) {
for (i = 0; i < j; ++i) {
continue;
}
}
/*
* Merge any memlists that don't have holes between them.
*/
for (i = 0; i <= memlists_used - 1; ++i) {
continue;
if (prom_debug)
--i; /* after merging we need to reexamine, so do this */
}
if (prom_debug) {
dboot_printf("\nFinal memlists:\n");
for (i = 0; i < memlists_used; ++i) {
}
}
/*
* link together the memlists with native size pointers
*/
for (i = 1; i < memlists_used; ++i) {
}
}
{
if (pae_support)
}
/*ARGSUSED*/
void
{
if (pae_support)
else
reload_cr3();
}
{
else
if (map_debug)
dboot_printf("new page table lvl=%d paddr=0x%lx ptp=0x%"
return (new_table);
}
x86pte_t *
{
}
#if 0 /* useful if debugging */
/*
* dump out the contents of page tables...
*/
static void
dump_tables(void)
{
uint_t l;
int index;
int i;
char *table;
static char *tablist = "\t\t\t";
dboot_printf("Finished pagetables:\n");
table = (char *)top_page_table;
l = top_level;
va = 0;
if (pae_support)
else
if (pteval == 0)
goto next_entry;
/*
* Don't try to walk hypervisor private pagetables
*/
save_table[l] = table;
save_index[l] = index;
--l;
index = -1;
goto recursion;
}
/*
* shorten dump for consecutive mappings
*/
if (pae_support)
else
if (pteval == 0)
break;
break;
}
if (i > 2) {
index += i - 2;
}
va = 0xffff800000000000ull;
;
}
if (l < top_level) {
++l;
index = save_index[l];
table = save_table[l];
goto recursion;
}
}
#endif
/*
* Add a mapping for the physical page at the given virtual address.
*/
static void
{
if (level > 0)
pteval |= PT_PAGESIZE;
/*
* Find the pte that will map this address. This creates any
* missing intermediate level page tables
*/
/*
* On Xen we must use hypervisor calls to modify the PTE, since
* paging is active. On real hardware we just write to the pagetables
* which aren't in use yet.
*/
if (pae_support)
else
}
/*
* During memory allocation, find the highest address not used yet.
*/
static void
{
if (a < next_avail_addr)
return;
}
/*
* Walk through the module information finding the last used address.
* The first available address will become the top level page table.
*
* We then build the phys_install memlist from the multiboot information.
*/
static void
init_mem_alloc(void)
{
extern char _end[];
int i;
DBG_MSG("Entered init_mem_alloc()\n");
/*
* search the modules to find the last used address
* we'll build the module list while we're walking through here
*/
DBG_MSG("\nFinding Modules\n");
i < mb_info->mods_count;
++mod, ++i) {
if (prom_debug) {
dboot_printf("\tmodule #%d: %s at: 0x%lx, len 0x%lx\n",
}
}
/*
* Walk through the memory map from multiboot and build our memlist
* structures. Note these will have native format pointers.
*/
DBG_MSG("\nFinding Memory Map\n");
max_mem = 0;
if (prom_debug) {
}
/*
* only type 1 is usable RAM
*/
continue;
/*
* page align start and end
*/
end &= ~page_offset;
continue;
++memlists_used; /* no overflow check */
}
} else {
dboot_panic("No memory info from boot loader!!!\n");
}
/*
* finish processing the physinstall list
*/
}
/*
* Simple memory allocator, allocates aligned physical memory.
* Note that startup_kernel() only allocates memory, never frees.
* Memory usage just grows in an upward direction.
*/
static void *
{
uint_t i;
/*
* make sure size is a multiple of pagesize
*/
/*
* a really large bootarchive that causes you to run out of memory
* may cause this to blow up
*/
/* LINTED E_UNEXPECTED_UINT_PROMOTION */
for (i = 0; i < memlists_used; ++i) {
/*
* did we find the desired address?
*/
goto done;
}
/*
* if not is this address the best so far?
*/
}
/*
* We didn't find exactly the address we wanted, due to going off the
* end of a memory region. Return the best found memory address.
*/
done:
}
void *
{
}
/*
* Build page tables to map all of memory used so far as well as the kernel.
*/
static void
build_page_tables(void)
{
uint32_t i;
/*
* If we're not using Xen, we need to create the top level pagetable.
*/
/*
* Determine if we'll use large mappings for kernel, then map it.
*/
if (largepage_support) {
level = 1;
} else {
level = 0;
}
DBG_MSG("Mapping kernel\n");
/*
* The kernel will need a 1 page window to work with page tables
*/
/*
* Under multiboot we need 1:1 mappings for all of low memory, which
* includes our pagetables. The following code works because our
* simple memory allocator only grows usage in an upwards direction.
*
* We map *all* possible addresses below 1 Meg, since things like
* the video RAM are down there.
*
* Skip memory between 1M and _start, this acts as a reserve
* of memory usable for DMA.
*/
if (map_debug)
dboot_printf("1:1 map pa=0..1Meg\n");
for (i = 0; i < memlists_used; ++i) {
if (start < next_mapping)
if (map_debug)
start += MMU_PAGESIZE;
}
}
DBG_MSG("\nPage tables constructed\n");
}
#define NO_MULTIBOOT \
"multiboot is no longer used to boot the Solaris Operating System.\n\
The grub entry should be changed to:\n\
module$ /platform/i86pc/$ISADIR/boot_archive\n\
See http://www.sun.com/msg/SUNOS-8000-AK for details.\n"
/*
* startup_kernel has a pretty simple job. It builds pagetables which reflect
* 1:1 mappings for all memory in use. It then also adds mappings for
* the kernel nucleus at virtual address of target_kernel_text using large page
* mappings. The page table pages are also accessible at 1:1 mapped
* virtual addresses.
*/
/*ARGSUSED*/
void
startup_kernel(void)
{
char *cmdline;
/*
* At this point we are executing in a 32 bit real mode.
*/
DBG_MSG("\n\nSolaris prekernel set: ");
DBG_MSG("\n");
}
/*
* boot info must be 16 byte aligned for 64 bit kernel ABI
*/
/*
* Need correct target_kernel_text value
*/
#if defined(_BOOT_TARGET_amd64)
#else
#endif
/*
* use cpuid to enable MMU features
*/
if (have_cpuid()) {
eax = 1;
if (edx & CPUID_INTC_EDX_PSE)
largepage_support = 1;
if (edx & CPUID_INTC_EDX_PGE)
pge_support = 1;
if (edx & CPUID_INTC_EDX_PAE)
pae_support = 1;
eax = 0x80000000;
if (eax >= 0x80000001) {
eax = 0x80000001;
if (edx & CPUID_AMD_EDX_LM)
amd64_support = 1;
if (edx & CPUID_AMD_EDX_NX)
NX_support = 1;
}
} else {
dboot_printf("cpuid not supported\n");
}
#if defined(_BOOT_TARGET_amd64)
if (amd64_support == 0)
dboot_panic("long mode not supported, rebooting\n");
else if (pae_support == 0)
dboot_panic("long mode, but no PAE; rebooting\n");
#endif
/*
* initialize our memory allocator
*/
/*
* configure mmu information
*/
#if !defined(_BOOT_TARGET_amd64)
#endif
ptes_per_table = 512;
pte_size = 8;
#if defined(_BOOT_TARGET_amd64)
top_level = 3;
#else
top_level = 2;
#endif
#if !defined(_BOOT_TARGET_amd64)
} else {
pae_support = 0;
NX_support = 0;
ptes_per_table = 1024;
pte_size = 4;
top_level = 1;
}
#endif
#if defined(_BOOT_TARGET_amd64)
/*
* For grub, copy kernel bits from the ELF64 file to final place.
*/
DBG_MSG("\nAllocating nucleus pages.\n");
if (ktext_phys == 0)
dboot_panic("failed to allocate aligned kernel memory\n");
dboot_panic("failed to parse kernel ELF image, rebooting\n");
#endif
/*
* Allocate page tables.
*/
/*
* return to assembly code to switch to running kernel
*/
#if 0 /* useful if debugging initial page tables */
if (prom_debug)
dump_tables();
#endif
DBG_MSG("\n\n*** DBOOT DONE -- back to asm to jump to kernel\n\n");
}