basekernel/kernel/kernelcore.S

530 lines
14 KiB
ArmAsm
Raw Normal View History

2024-10-14 23:07:08 +02:00
# Copyright (C) 2015 The University of Notre Dame
# This software is distributed under the GNU General Public License.
# See the file LICENSE for details.
#include "memorylayout.h"
# _start is the initial entry point for the kernel
# Note that we start here merely because it comes
# first in the link order. The name _start is there
# merely to keep the linker happy.
.code16
.text
.global _start
_start:
# First, jump to the real code start,
# skipping over the immediate data that follows
jmp realstart
# At a fixed offset, place an integer that contains
# the length of the kernel in bytes. This is used
# by the bootblock code to figure out how many sectors to load.
.org KERNEL_SIZE_OFFSET
.global kernel_size
kernel_size:
.long _end-_start
realstart:
# While we are briefly still in 16-bit real mode,
# it is safe to call the BIOS to set things up.
# Reset the disk system in order to quiet motors
# and turn off any pending interrupts.
mov $0,%ah
int $0x13
# Turn off the screen cursor, because the
# console will have its own.
mov $1,%ah
mov $0,%cl
mov $0x20,%ch
int $0x10
# Get the amount of memory above 1MB and save it for later.
# There are two ways to do this:
# BIOS call 0xe801 measures memory as follows:
# %ax returns memory above 1MB in 1KB increments, maximum of 16MB.
# %bx returns memory above 64MB in 64KB increments, maximum of 4GB.
# However, this call is relatively new, so if it
# fails, we fall back on memtest2 below.
memtest1:
clc
mov $0, %bx
mov $0xe801,%ax
int $0x15
jc memtest2
shr $10, %ax
shr $4, %bx
add %ax, %bx
mov %bx, total_memory-_start
jmp memdone
# BIOS call 0x0088 measures memory as follows:
# %ax returns memory above 1MB in 1KB increments, maxiumum of 64MB.
memtest2:
clc
mov $0, %ax
mov $0x88, %ah
int $0x15
shr $10, %ax
inc %ax
mov %ax, total_memory-_start
memdone:
# Now, set the video mode using VBE interrupts.
# Keep trying until we find one that works.
# These are documented on page 30 of the VESA-BIOS manual:
# interrupt 0x10
# ax = 0x4f02 "Set VBE Mode"
# bx = mode
# D0-8 = Mode Number
# D9-10 = Reserved (must be 0)
# D11 = 0 Use current default refresh rate.
# D12-13 = 0 Reserved
# D14 = 0 Use windowed frame buffer model.
# = 1 Use linear frame buffer model.
# D15 = 0 Clear display memory.
# ES:DI = Pointer to CRCTCInfoBlock structure.
jmp video640
video1280:
mov $0x4f02, %ax
mov $0x411b, %bx
int $0x10
cmp $0x004f, %ax
je videodone
video1024:
mov $0x4f02, %ax
mov $0x4118, %bx
int $0x10
cmp $0x004f, %ax
je videodone
video800:
mov $0x4f02, %ax
mov $0x4115, %bx
int $0x10
cmp $0x004f, %ax
je videodone
video640:
mov $0x4f02, %ax
mov $0x4112, %bx
int $0x10
cmp $0x004f, %ax
je videodone
video640_lowcolor:
mov $0x4f02, %ax
mov $0x4111, %bx
int $0x10
cmp $0x004f, %ax
je videodone
videofailed:
mov $videomsg, %esi
call bios_putstring
jmp halt
videodone:
# After all that, query the video mode and
# figure out the dimensions and the frame
# buffer address. The set mode is still in bx.
mov %ds, %ax # Set up the extra segment
mov %ax, %es # with the data segment
mov $(video_info-_start),%di
mov $0x4f01, %ax
mov %bx, %cx
int $0x10
# In order to use video resolutions higher than 640x480,
# we must enable the A20 address line. The following
# code works on motherboards with "FAST A20", which should
# be everything since the IBM PS/2
inb $0x92, %al
orb $2, %al
outb %al, $0x92
# Finally, we are ready to enter protected mode.
# To do this, we disable interrupts so that
# handlers will not see an inconsistent state.
# We then load the new interrupt and descriptor
# tables, which are given below. Then, we
# enable the protection bit, and load the
# segment selectors into the appropriate registers.
# Finally, we make a long jump to main,
# atomically loading the new code segment and
# starting the kernel.
cli # clear interrupts
lidt (idt_init-_start) # load the interrupt table
lgdt (gdt_init-_start) # load the descriptor table
mov %cr0, %eax # get the status word
or $0x01, %eax # turn on the P bit
mov %eax, %cr0 # store the status word
# (we are now in protected mode)
mov $2*8, %ax # selector two is flat 4GB data data
mov %ax, %ds # set data, extra, and stack segments to selector two
mov %ax, %es
mov %ax, %ss
mov $5*8, %ax # set TSS to selector five
ltr %ax
mov $0, %ax # unused segments are nulled out
mov %ax, %fs
mov %ax, %gs
mov $INTERRUPT_STACK_TOP, %sp # set up initial C stack
mov $INTERRUPT_STACK_TOP, %bp # set up initial C stack
ljmpl $(1*8), $(kernel_main) # jump to the C main!
# bios_putstring displays an ASCII string pointed to by %si,
# useful for basic startup messages or fatal errors.
bios_putstring:
mov (%si), %al
cmp $0, %al
jz bios_putstring_done
call bios_putchar
inc %si
jmp bios_putstring
bios_putstring_done:
ret
# bios_putchar invokes the bios to display
# one character on the screen.
bios_putchar:
push %ax
push %bx
mov $14,%ah
mov $1,%bl
int $0x10
pop %bx
pop %ax
ret
# The video_info structure is filled in by a BIOS
# call above, and is used to record the basic video
# layout for the graphics subsystem. See page 30
# of the VBE specification for an explanation of this structure.
.align 4
video_info:
.word 0
.byte 0,0
.word 0,0,0,0
.long 0
.global video_xbytes
video_xbytes:
.word 0
.global video_xres
video_xres:
.word 0
.global video_yres
video_yres:
.word 0
.byte 0,0,0,0,0,0,0,0,0
.byte 0,0,0,0,0,0,0,0,0
.global video_buffer
video_buffer:
.long 0
.long 0
.word 0
.word 0
.byte 0,0,0,0,0,0,0,0,0,0
.long 0
.rept 190
.byte 0
.endr
.align 4
videomsg:
.asciz "fatal error: couldn't find suitable video mode!\r\n"
###########################
# 32 BIT CODE BEGINS HERE #
###########################
# All code below this point is 32-bit code and data
# that is invoked by higher levels of the kernel from C code.
.code32
# Rebooting the machine is easy.
# Set up an invalid interrupt table, and the force an interrupt.
# The machine will triple-fault and reboot itself.
.global reboot
reboot:
cli
lidt idt_invalid
int $1
.global halt
halt:
cli
hlt
jmp halt
# This is the global descriptor table to be used by the kernel.
# Because we don't really want to use segmentation, we define
# very simple descriptors for global code and data and the TSS
.align 16
.global gdt
gdt:
.word 0,0,0,0 # seg 0 - null
.word 0xffff, 0x0000, 0x9a00, 0x00cf # seg 1 - kernel flat 4GB code
.word 0xffff, 0x0000, 0x9200, 0x00cf # seg 2 - kernel flat 4GB data
.word 0xffff, 0x0000, 0xfa00, 0x00cf # seg 3 - user flat 4GB code
.word 0xffff, 0x0000, 0xf200, 0x00cf # seg 4 - user flat 4GB data
.word 0x0068, (tss-_start),0x8901, 0x00cf # seg 5 - TSS
# This is the initializer for the global descriptor table.
# It simply tells us the size and location of the table.
gdt_init:
.word gdt_init-gdt
.long gdt
# The TSS is a big task management structure used by the 386.
# We do not use the TSS, but simply rely on pushing variables
# around in stacks. However, we need to use the TSS in order
# to initialize the stack pointer and segment for priv level 0
.align 16
.global tss
tss:
.long 0
.global interrupt_stack_pointer
interrupt_stack_pointer:
.long INTERRUPT_STACK_TOP # initial interrupt stack ptr at 64 KB
.long 2*8 # use segment 2 for the interrupt stack
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.long 0
.global total_memory
total_memory:
.word 32
# First, the internal interrupts.
# Note that some already push their own detail
# code onto the stack. For the others, we push
# a zero, just to get a common layout.
intr00: pushl $0 ; pushl $0 ; jmp intr_handler
intr01: pushl $0 ; pushl $1 ; jmp intr_handler
intr02: pushl $0 ; pushl $2 ; jmp intr_handler
intr03: pushl $0 ; pushl $3 ; jmp intr_handler
intr04: pushl $0 ; pushl $4 ; jmp intr_handler
intr05: pushl $0 ; pushl $5 ; jmp intr_handler
intr06: pushl $0 ; pushl $6 ; jmp intr_handler
intr07: pushl $0 ; pushl $7 ; jmp intr_handler
intr08: pushl $8 ; jmp intr_handler
intr09: pushl $0 ; pushl $9 ; jmp intr_handler
intr10: pushl $10 ; jmp intr_handler
intr11: pushl $11 ; jmp intr_handler
intr12: pushl $12 ; jmp intr_handler
intr13: pushl $13 ; jmp intr_handler
intr14: pushl $14 ; jmp intr_handler
intr15: pushl $0 ; pushl $15 ; jmp intr_handler
intr16: pushl $0 ; pushl $16 ; jmp intr_handler
intr17: pushl $17 ; jmp intr_handler
intr18: pushl $0 ; pushl $18 ; jmp intr_handler
intr19: pushl $0 ; pushl $19 ; jmp intr_handler
# These interrupts are reserved, but could
# conceivably occur on the next processor model
intr20: pushl $0 ; pushl $20 ; jmp intr_handler
intr21: pushl $0 ; pushl $21 ; jmp intr_handler
intr22: pushl $0 ; pushl $22 ; jmp intr_handler
intr23: pushl $0 ; pushl $23 ; jmp intr_handler
intr24: pushl $0 ; pushl $24 ; jmp intr_handler
intr25: pushl $0 ; pushl $25 ; jmp intr_handler
intr26: pushl $0 ; pushl $26 ; jmp intr_handler
intr27: pushl $0 ; pushl $27 ; jmp intr_handler
intr28: pushl $0 ; pushl $28 ; jmp intr_handler
intr29: pushl $0 ; pushl $29 ; jmp intr_handler
intr30: pushl $0 ; pushl $30 ; jmp intr_handler
intr31: pushl $0 ; pushl $31 ; jmp intr_handler
# Now, the external hardware interrupts.
intr32: pushl $0 ; pushl $32 ; jmp intr_handler
intr33: pushl $0 ; pushl $33 ; jmp intr_handler
intr34: pushl $0 ; pushl $34 ; jmp intr_handler
intr35: pushl $0 ; pushl $35 ; jmp intr_handler
intr36: pushl $0 ; pushl $36 ; jmp intr_handler
intr37: pushl $0 ; pushl $37 ; jmp intr_handler
intr38: pushl $0 ; pushl $38 ; jmp intr_handler
intr39: pushl $0 ; pushl $39 ; jmp intr_handler
intr40: pushl $0 ; pushl $40 ; jmp intr_handler
intr41: pushl $0 ; pushl $41 ; jmp intr_handler
intr42: pushl $0 ; pushl $42 ; jmp intr_handler
intr43: pushl $0 ; pushl $43 ; jmp intr_handler
intr44: pushl $0 ; pushl $44 ; jmp intr_handler
intr45: pushl $0 ; pushl $45 ; jmp intr_handler
intr46: pushl $0 ; pushl $46 ; jmp intr_handler
intr47: pushl $0 ; pushl $47 ; jmp intr_handler
intr48: pushl $0 ; pushl $48 ; jmp intr_syscall
intr_handler:
pushl %ds # push segment registers
pushl %es
pushl %fs
pushl %gs
pushl %ebp # push general regs
pushl %edi
pushl %esi
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax
pushl 48(%esp) # push interrupt code from above
pushl 48(%esp) # push interrupt number from above
movl $2*8, %eax # switch to kernel data seg and extra seg
movl %eax, %ds
movl %eax, %es
call interrupt_handler
addl $4, %esp # remove interrupt number
addl $4, %esp # remove interrupt code
jmp intr_return
intr_syscall:
pushl %ds # push segment registers
pushl %es
pushl %fs
pushl %gs
pushl %ebp # push general regs
pushl %edi
pushl %esi
pushl %edx
pushl %ecx
pushl %ebx
pushl %eax # note these *are* the syscall args
movl $2*8, %eax # switch to kernel data seg and extra seg
movl %eax, %ds
movl %eax, %es
call syscall_handler
addl $4, %esp # remove the old eax
jmp syscall_return
.global intr_return
intr_return:
popl %eax
syscall_return:
popl %ebx
popl %ecx
popl %edx
popl %esi
popl %edi
popl %ebp
popl %gs
popl %fs
popl %es
popl %ds
addl $4, %esp # remove interrupt num
addl $4, %esp # remove detail code
iret # iret gets the intr context
.align 2
idt:
.word intr00-_start,1*8,0x8e00,0x0001
.word intr01-_start,1*8,0x8e00,0x0001
.word intr02-_start,1*8,0x8e00,0x0001
.word intr03-_start,1*8,0x8e00,0x0001
.word intr04-_start,1*8,0x8e00,0x0001
.word intr05-_start,1*8,0x8e00,0x0001
.word intr06-_start,1*8,0x8e00,0x0001
.word intr07-_start,1*8,0x8e00,0x0001
.word intr08-_start,1*8,0x8e00,0x0001
.word intr09-_start,1*8,0x8e00,0x0001
.word intr10-_start,1*8,0x8e00,0x0001
.word intr11-_start,1*8,0x8e00,0x0001
.word intr12-_start,1*8,0x8e00,0x0001
.word intr13-_start,1*8,0x8e00,0x0001
.word intr14-_start,1*8,0x8e00,0x0001
.word intr15-_start,1*8,0x8e00,0x0001
.word intr16-_start,1*8,0x8e00,0x0001
.word intr17-_start,1*8,0x8e00,0x0001
.word intr18-_start,1*8,0x8e00,0x0001
.word intr19-_start,1*8,0x8e00,0x0001
.word intr20-_start,1*8,0x8e00,0x0001
.word intr21-_start,1*8,0x8e00,0x0001
.word intr22-_start,1*8,0x8e00,0x0001
.word intr23-_start,1*8,0x8e00,0x0001
.word intr24-_start,1*8,0x8e00,0x0001
.word intr25-_start,1*8,0x8e00,0x0001
.word intr26-_start,1*8,0x8e00,0x0001
.word intr27-_start,1*8,0x8e00,0x0001
.word intr28-_start,1*8,0x8e00,0x0001
.word intr29-_start,1*8,0x8e00,0x0001
.word intr30-_start,1*8,0x8e00,0x0001
.word intr31-_start,1*8,0x8e00,0x0001
.word intr32-_start,1*8,0x8e00,0x0001
.word intr33-_start,1*8,0x8e00,0x0001
.word intr34-_start,1*8,0x8e00,0x0001
.word intr35-_start,1*8,0x8e00,0x0001
.word intr36-_start,1*8,0x8e00,0x0001
.word intr37-_start,1*8,0x8e00,0x0001
.word intr38-_start,1*8,0x8e00,0x0001
.word intr39-_start,1*8,0x8e00,0x0001
.word intr40-_start,1*8,0x8e00,0x0001
.word intr41-_start,1*8,0x8e00,0x0001
.word intr42-_start,1*8,0x8e00,0x0001
.word intr43-_start,1*8,0x8e00,0x0001
.word intr44-_start,1*8,0x8e00,0x0001
.word intr45-_start,1*8,0x8e00,0x0001
.word intr46-_start,1*8,0x8e00,0x0001
.word intr47-_start,1*8,0x8e00,0x0001
.word intr48-_start,1*8,0xee00,0x0001
# This is the initializer for the global interrupt table.
# It simply gives the size and location of the interrupt table
idt_init:
.word idt_init-idt
.long idt
# This is the initializer for an invalid interrupt table.
# Its only purpose is to assist with the reboot routine.
idt_invalid:
.word 0
.long 0