/*-
* Copyright (c) 2005, Kohsuke Ohtani
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the author nor the names of any co-contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
/*
* locore.S - low level platform support
*/
#include <conf/config.h>
#include <platform.h>
#include <cpu.h>
#define ENTRY(x) .global x; .align 4,0x90; x##:
/*
* Macro to save/restore registers
*
* This macro builds the trap frame by pushing registers.
* If you change the push order of these macro, you must change the
* trap frame structure in arch.h. In addition, the system call stub
* will depend on this register format.
*/
#define SAVE_ALL \
cld; \
pushl %es; \
pushl %ds; \
pushl %eax; \
pushl %ebp; \
pushl %edi; \
pushl %esi; \
pushl %edx; \
pushl %ecx; \
pushl %ebx;
#define RESTORE_ALL \
popl %ebx; \
popl %ecx; \
popl %edx; \
popl %esi; \
popl %edi; \
popl %ebp; \
popl %eax; \
popl %ds; \
popl %es;
#define SETUP_SEG \
movl $(KERNEL_DS), %edx; \
movl %edx, %ds; \
movl %edx, %es;
/*
* Pointer to boot infomation
*/
.section ".rodata"
ENTRY(boot_info)
.long PAGE_OFFSET + BOOT_INFO
/*
* Interrupt nest counter.
*
* This counter is incremented in the entry of interrupt handler
* to switch the interrupt stack. Since all interrupt handlers
* share same one interrupt stack, each handler must pay attention
* to the stack overflow.
*/
.section ".bss"
irq_nesting:
.long 0
.section ".text"
/*
* Kernel start point
*
* The kernel assumes that the following state is already set by
* the kernel loader.
* - CPU is in protected mode
* - Segment registers are set as 32-bit flat segment
* - A20 line is enabled for 32-bit addressing
* - Paging is turned off
* - The boot information is loaded to address 1000h-1fffh
*/
/*
* Note: The linker will generate an address for kernel_start as 0x80010000.
* But, the loader will load the kernel to 0x10000 (physical address).
* So, the linear address pointer can not be used until paging is enabled.
*/
ENTRY(kernel_start)
cli /* Disable interrupt */
cld
#ifdef CONFIG_MMU
/*
* Init page table
* The physical address 0-4M is mapped to virtual address 2G.
*/
movl $(KERNEL_PGD), %edi /* Setup kernel page directory */
xorl %eax, %eax /* Invalidate all address */
movl $0x1000, %ecx
rep
stosb
movl $(KERNEL_PGD+0x800), %edi
movl $(BOOT_PTE0+0x07), (%edi)
movl $1024, %ecx /* Fill boot page table entry */
movl $(BOOT_PTE0), %edi
movl $0007, %eax
fill_pte0:
stosl
add $0x1000, %eax
loop fill_pte0
/*
* Enable paging
* The physical address 0-4M is mapped to virtial address 0-4M
* temporary. This is needed to enable paging.
*/
movl $(KERNEL_PGD), %edi /* Map address 0-4M */
movl $(BOOT_PTE0+0x07), (%edi)
movl $(KERNEL_PGD), %eax /* Set page directory pointer */
movl %eax, %cr3
movl %cr0, %eax /* Enable paging bit */
orl $0x80000000, %eax
movl %eax, %cr0
jmp pipeline_flush /* Flush processor pipeline */
pipeline_flush:
movl $cs_reset, %eax
jmp *%eax
cs_reset:
movl $(KERNEL_BASE+KERNEL_PGD), %edi /* Unmap 0-4M */
movl $0, (%edi)
movl %cr3, %eax
movl %eax, %cr3
#endif /* CONFIG_MMU */
/*
* Clear kernel BSS
*/
xorl %eax, %eax
movl $__bss, %edi
movl $__end, %ecx
subl %edi, %ecx
rep
stosb
/*
* Setup boot stack
*/
movl $(PAGE_OFFSET+BOOT_STACK), %edi
movl $0x800, %ecx
rep
stosb
movl $(PAGE_OFFSET+BOOT_STACK+0x800), %esp
movl %esp, %ebp
/*
* Call kernel main routine
*/
call kernel_main
/* NOTREACHED */
cli
hlt
/*
* Common entry for all interrupts
* Setup interrupt stack for outermost interrupt.
* The code should be written to prevent the stack overflow
* by continuous interrupt as much as it can.
*/
ENTRY(interrupt_common)
SAVE_ALL
SETUP_SEG
incl irq_nesting
cmpl $1, irq_nesting /* Outermost interrupt ? */
jne nested_irq
mov %esp, %ebp /* Save current stack */
movl $(PAGE_OFFSET+INT_STACK+0x1000), %esp /* Switch stack */
call sched_lock /* Lock scheduler */
pushl %ebp /* Push trap frame */
call interrupt_handler /* Process interrupt */
movl %ebp, %esp /* Restore original stack */
decl irq_nesting
call sched_unlock /* Try to preempt */
testl $3, 0x30(%esp) /* Return to kernel mode ? */
jz interrupt_ret
call exception_deliver /* Check exception */
interrupt_ret:
RESTORE_ALL
addl $8, %esp
iret
nested_irq:
push %esp /* Push trap frame */
call interrupt_handler /* Process interrupt */
addl $4, %esp
decl irq_nesting
jmp interrupt_ret
/*
* Macro to build interrupt entry
*/
#define INTR_ENTRY(irq) \
ENTRY(intr_##irq) \
pushl $0; \
pushl $(irq); \
jmp interrupt_common
INTR_ENTRY(0)
INTR_ENTRY(1)
INTR_ENTRY(2)
INTR_ENTRY(3)
INTR_ENTRY(4)
INTR_ENTRY(5)
INTR_ENTRY(6)
INTR_ENTRY(7)
INTR_ENTRY(8)
INTR_ENTRY(9)
INTR_ENTRY(10)
INTR_ENTRY(11)
INTR_ENTRY(12)
INTR_ENTRY(13)
INTR_ENTRY(14)
INTR_ENTRY(15)
/*
* Common entry for all traps
* New thread will start from trap_ret.
*/
ENTRY(trap_common)
SAVE_ALL
SETUP_SEG
pushl %esp
call trap_handler
addl $4, %esp
trap_ret:
RESTORE_ALL
addl $8, %esp
iret
/*
* Default trap entry
*/
ENTRY(trap_default)
pushl $0
pushl $(INVALID_INT)
jmp trap_common
/*
* Macro to build trap entry
* Some trap will push the error code into stack.
*/
#define TRAP_ENTRY(id) \
ENTRY(trap_##id) \
pushl $0; \
pushl $(id); \
jmp trap_common;
#define TRAP_ERR_ENTRY(id) \
ENTRY(trap_##id) \
pushl $(id); \
jmp trap_common;
TRAP_ENTRY ( 0) /* Divide error */
TRAP_ENTRY ( 1) /* Debug trap */
TRAP_ENTRY ( 2) /* NMI */
TRAP_ENTRY ( 3) /* Breakpoint */
TRAP_ENTRY ( 4) /* Overflow */
TRAP_ENTRY ( 5) /* Bounds check */
TRAP_ENTRY ( 6) /* Invalid opecode */
TRAP_ENTRY ( 7) /* Device not available */
TRAP_ERR_ENTRY( 8) /* Double fault */
TRAP_ERR_ENTRY( 9) /* Coprocessor overrun */
TRAP_ERR_ENTRY(10) /* Invalid TSS */
TRAP_ERR_ENTRY(11) /* Segment not present */
TRAP_ERR_ENTRY(12) /* Stack bounds */
TRAP_ERR_ENTRY(13) /* General Protection */
TRAP_ERR_ENTRY(14) /* Page fault */
TRAP_ENTRY (15) /* (reserved) */
TRAP_ENTRY (16) /* Coprocessor error */
TRAP_ERR_ENTRY(17) /* Alignment check */
TRAP_ERR_ENTRY(18) /* Cache flush denied */
/*
* System call entry
*/
.global syscall_ret
ENTRY(syscall_entry)
pushl $0 /* Dummy for error code */
pushl $(SYSCALL_INT) /* Trap number */
SAVE_ALL
SETUP_SEG
cmpl nr_syscalls, %eax
jae bad_syscall
call *syscall_table(,%eax,4) /* Call function */
movl %eax, 0x18(%esp) /* Set return value to eax */
call exception_deliver /* Check exception */
syscall_ret:
RESTORE_ALL
addl $8, %esp /* Discard err/trap no */
iret
bad_syscall:
movl $22, 0x18(%esp) /* Set EINVAL error to eax */
jmp syscall_ret
/*
* Switch register context.
* Interrupts must be disabled by caller.
*
* syntax - void cpu_switch(kern_regs *prev, kern_regs *next)
*
* Note: GCC assumes ebp,edi,esi registers are not changed in each routine.
*/
ENTRY(cpu_switch)
movl 4(%esp), %ebx /* Point ebx to previous registers */
movl (%esp), %eax /* Get return address */
movl %eax, 0(%ebx) /* Save it as eip */
movl %edi, 4(%ebx) /* Save edi */
movl %esi, 8(%ebx) /* Save esi */
movl %ebp, 12(%ebx) /* Save ebp */
movl %esp, 16(%ebx) /* Save esp */
movl 8(%esp), %ebx /* Point ebx to next registers */
movl 4(%ebx), %edi /* Restore edi */
movl 8(%ebx), %esi /* Restore esi */
movl 12(%ebx), %ebp /* Restore ebp */
movl 16(%ebx), %esp /* Restore esp */
movl 0(%ebx), %eax /* Get eip */
movl %eax, (%esp) /* Restore it as return address */
ret
/*
* Copy data from user to kernel space.
* Returns 0 on success, or EFAULT on page fault.
*
* syntax - int umem_copyin(void *uaddr, void *kaddr, size_t len)
*/
.global known_fault1
ENTRY(umem_copyin)
pushl %esi
pushl %edi
pushl $14 /* Set EFAULT as default return */
movl 16(%esp), %esi
movl 20(%esp), %edi
movl 24(%esp), %ecx
movl %esi, %edx /* Check if valid user address */
addl %ecx, %edx
jc umem_fault
cmpl $(USER_MAX), %edx
jae umem_fault
cld
known_fault1: /* May be fault here */
rep
movsb
popl %eax
xorl %eax, %eax /* Set no error */
popl %edi
popl %esi
ret
/*
* Copy data to user from kernel space.
* Returns 0 on success, or EFAULT on page fault.
*
* syntax - int umem_copyout(void *kaddr, void *uaddr, size_t len)
*/
.global known_fault2
ENTRY(umem_copyout)
pushl %esi
pushl %edi
pushl $14 /* Set EFAULT as default return */
movl 16(%esp), %esi
movl 20(%esp), %edi
movl 24(%esp), %ecx
movl %edi, %edx
addl %ecx, %edx
jc umem_fault
cmpl $(USER_MAX), %edx
jae umem_fault
cld
known_fault2:
rep
movsb
popl %eax
xorl %eax, %eax /* Set no error */
popl %edi
popl %esi
ret
/*
* umem_strnlen - Get length of string in user space
* Returns 0 on success, or EFAULT on page fault.
*
* syntax - int umem_strnlen(const char *uaddr, size_t maxlen, size_t *len)
*
* Note: The returned length value does NOT include the NULL terminator.
*/
.global known_fault3
ENTRY(umem_strnlen)
pushl %esi
pushl %edi
pushl $14 /* Set EFAULT as default return */
movl 16(%esp), %edi
movl 20(%esp), %ecx
movl 24(%esp), %esi
movl %edi, %edx
cmpl $(USER_MAX), %edx
jae umem_fault
movl %ecx, %edx
cld
xor %eax, %eax
known_fault3:
repne
scasb
subl %ecx, %edx
decl %edx /* Adjust for terminator */
movl %edx, (%esi)
popl %eax
xorl %eax, %eax /* Set no error */
popl %edi
popl %esi
ret
/*
* Fault entry for user access
*/
ENTRY(umem_fault)
popl %eax /* Get return value from stack */
popl %edi
popl %esi
ret
/*
* Reset cpu
* Use triple fault
*/
ENTRY(cpu_reset)
cli
movl $null_idt, %eax /* Reset by triple fault */
lidt (%eax)
int $3
hlt
.align 4
null_idt:
.word 0
.long 0