diff --git a/kernel/kernelcore.S b/kernel/kernelcore.S new file mode 100644 index 0000000..b13ca46 --- /dev/null +++ b/kernel/kernelcore.S @@ -0,0 +1,529 @@ +# 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