264 lines
7.1 KiB
ArmAsm
264 lines
7.1 KiB
ArmAsm
|
#include "threads/loader.h"
|
||
|
|
||
|
#### Kernel loader.
|
||
|
|
||
|
#### This code should be stored in the first sector of a hard disk.
|
||
|
#### When the BIOS runs, it loads this code at physical address
|
||
|
#### 0x7c00-0x7e00 (512 bytes) and jumps to the beginning of it,
|
||
|
#### in real mode. The loader loads the kernel into memory and jumps
|
||
|
#### to its entry point, which is the start function in start.S.
|
||
|
####
|
||
|
#### The BIOS passes in the drive that the loader was read from as
|
||
|
#### DL, with floppy drives numbered 0x00, 0x01, ... and hard drives
|
||
|
#### numbered 0x80, 0x81, ... We want to support booting a kernel on
|
||
|
#### a different drive from the loader, so we don't take advantage of
|
||
|
#### this.
|
||
|
|
||
|
# Runs in real mode, which is a 16-bit segment.
|
||
|
.code16
|
||
|
|
||
|
# Set up segment registers.
|
||
|
# Set stack to grow downward from 60 kB (after boot, the kernel
|
||
|
# continues to use this stack for its initial thread).
|
||
|
|
||
|
sub %ax, %ax
|
||
|
mov %ax, %ds
|
||
|
mov %ax, %ss
|
||
|
mov $0xf000, %esp
|
||
|
|
||
|
# Configure serial port so we can report progress without connected VGA.
|
||
|
# See [IntrList] for details.
|
||
|
sub %dx, %dx # Serial port 0.
|
||
|
mov $0xe3, %al # 9600 bps, N-8-1.
|
||
|
# AH is already 0 (Initialize Port).
|
||
|
int $0x14 # Destroys AX.
|
||
|
|
||
|
call puts
|
||
|
.string "PiLo"
|
||
|
|
||
|
#### Read the partition table on each system hard disk and scan for a
|
||
|
#### partition of type 0x20, which is the type that we use for a
|
||
|
#### Pintos kernel.
|
||
|
####
|
||
|
#### Read [Partitions] for a description of the partition table format
|
||
|
#### that we parse.
|
||
|
####
|
||
|
#### We print out status messages to show the disk and partition being
|
||
|
#### scanned, e.g. hda1234 as we scan four partitions on the first
|
||
|
#### hard disk.
|
||
|
|
||
|
mov $0x80, %dl # Hard disk 0.
|
||
|
read_mbr:
|
||
|
sub %ebx, %ebx # Sector 0.
|
||
|
mov $0x2000, %ax # Use 0x20000 for buffer.
|
||
|
mov %ax, %es
|
||
|
call read_sector
|
||
|
jc no_such_drive
|
||
|
|
||
|
# Print hd[a-z].
|
||
|
call puts
|
||
|
.string " hd"
|
||
|
mov %dl, %al
|
||
|
add $'a' - 0x80, %al
|
||
|
call putc
|
||
|
|
||
|
# Check for MBR signature--if not present, it's not a
|
||
|
# partitioned hard disk.
|
||
|
cmpw $0xaa55, %es:510
|
||
|
jne next_drive
|
||
|
|
||
|
mov $446, %si # Offset of partition table entry 1.
|
||
|
mov $'1', %al
|
||
|
check_partition:
|
||
|
# Is it an unused partition?
|
||
|
cmpl $0, %es:(%si)
|
||
|
je next_partition
|
||
|
|
||
|
# Print [1-4].
|
||
|
call putc
|
||
|
|
||
|
# Is it a Pintos kernel partition?
|
||
|
cmpb $0x20, %es:4(%si)
|
||
|
jne next_partition
|
||
|
|
||
|
# Is it a bootable partition?
|
||
|
cmpb $0x80, %es:(%si)
|
||
|
je load_kernel
|
||
|
|
||
|
next_partition:
|
||
|
# No match for this partition, go on to the next one.
|
||
|
add $16, %si # Offset to next partition table entry.
|
||
|
inc %al
|
||
|
cmp $510, %si
|
||
|
jb check_partition
|
||
|
|
||
|
next_drive:
|
||
|
# No match on this drive, go on to the next one.
|
||
|
inc %dl
|
||
|
jnc read_mbr
|
||
|
|
||
|
no_such_drive:
|
||
|
no_boot_partition:
|
||
|
# Didn't find a Pintos kernel partition anywhere, give up.
|
||
|
call puts
|
||
|
.string "\rNot found\r"
|
||
|
|
||
|
# Notify BIOS that boot failed. See [IntrList].
|
||
|
int $0x18
|
||
|
|
||
|
#### We found a kernel. The kernel's drive is in DL. The partition
|
||
|
#### table entry for the kernel's partition is at ES:SI. Our job now
|
||
|
#### is to read the kernel from disk and jump to its start address.
|
||
|
|
||
|
load_kernel:
|
||
|
call puts
|
||
|
.string "\rLoading"
|
||
|
|
||
|
# Figure out number of sectors to read. A Pintos kernel is
|
||
|
# just an ELF format object, which doesn't have an
|
||
|
# easy-to-read field to identify its own size (see [ELF1]).
|
||
|
# But we limit Pintos kernels to 512 kB for other reasons, so
|
||
|
# it's easy enough to just read the entire contents of the
|
||
|
# partition or 512 kB from disk, whichever is smaller.
|
||
|
mov %es:12(%si), %ecx # EBP = number of sectors
|
||
|
cmp $1024, %ecx # Cap size at 512 kB
|
||
|
jbe 1f
|
||
|
mov $1024, %cx
|
||
|
1:
|
||
|
|
||
|
mov %es:8(%si), %ebx # EBX = first sector
|
||
|
mov $0x2000, %ax # Start load address: 0x20000
|
||
|
|
||
|
next_sector:
|
||
|
# Read one sector into memory.
|
||
|
mov %ax, %es # ES:0000 -> load address
|
||
|
call read_sector
|
||
|
jc read_failed
|
||
|
|
||
|
# Print '.' as progress indicator once every 16 sectors == 8 kB.
|
||
|
test $15, %bl
|
||
|
jnz 1f
|
||
|
call puts
|
||
|
.string "."
|
||
|
1:
|
||
|
|
||
|
# Advance memory pointer and disk sector.
|
||
|
add $0x20, %ax
|
||
|
inc %bx
|
||
|
loop next_sector
|
||
|
|
||
|
call puts
|
||
|
.string "\r"
|
||
|
|
||
|
#### Transfer control to the kernel that we loaded. We read the start
|
||
|
#### address out of the ELF header (see [ELF1]) and convert it from a
|
||
|
#### 32-bit linear address into a 16:16 segment:offset address for
|
||
|
#### real mode, then jump to the converted address. The 80x86 doesn't
|
||
|
#### have an instruction to jump to an absolute segment:offset kept in
|
||
|
#### registers, so in fact we store the address in a temporary memory
|
||
|
#### location, then jump indirectly through that location. To save 4
|
||
|
#### bytes in the loader, we reuse 4 bytes of the loader's code for
|
||
|
#### this temporary pointer.
|
||
|
|
||
|
mov $0x2000, %ax
|
||
|
mov %ax, %es
|
||
|
mov %es:0x18, %dx
|
||
|
mov %dx, start
|
||
|
movw $0x2000, start + 2
|
||
|
ljmp *start
|
||
|
|
||
|
read_failed:
|
||
|
start:
|
||
|
# Disk sector read failed.
|
||
|
call puts
|
||
|
1: .string "\rBad read\r"
|
||
|
|
||
|
# Notify BIOS that boot failed. See [IntrList].
|
||
|
int $0x18
|
||
|
|
||
|
#### Print string subroutine. To save space in the loader, this
|
||
|
#### subroutine takes its null-terminated string argument from the
|
||
|
#### code stream just after the call, and then returns to the byte
|
||
|
#### just after the terminating null. This subroutine preserves all
|
||
|
#### general-purpose registers.
|
||
|
|
||
|
puts: xchg %si, %ss:(%esp)
|
||
|
push %ax
|
||
|
next_char:
|
||
|
mov %cs:(%si), %al
|
||
|
inc %si
|
||
|
test %al, %al
|
||
|
jz 1f
|
||
|
call putc
|
||
|
jmp next_char
|
||
|
1: pop %ax
|
||
|
xchg %si, %ss:(%esp)
|
||
|
ret
|
||
|
|
||
|
#### Character output subroutine. Prints the character in AL to the
|
||
|
#### VGA display and serial port 0, using BIOS services (see
|
||
|
#### [IntrList]). Preserves all general-purpose registers.
|
||
|
####
|
||
|
#### If called upon to output a carriage return, this subroutine
|
||
|
#### automatically supplies the following line feed.
|
||
|
|
||
|
putc: pusha
|
||
|
|
||
|
1: sub %bh, %bh # Page 0.
|
||
|
mov $0x0e, %ah # Teletype output service.
|
||
|
int $0x10
|
||
|
|
||
|
mov $0x01, %ah # Serial port output service.
|
||
|
sub %dx, %dx # Serial port 0.
|
||
|
2: int $0x14 # Destroys AH.
|
||
|
test $0x80, %ah # Output timed out?
|
||
|
jz 3f
|
||
|
movw $0x9090, 2b # Turn "int $0x14" above into NOPs.
|
||
|
|
||
|
3:
|
||
|
cmp $'\r', %al
|
||
|
jne popa_ret
|
||
|
mov $'\n', %al
|
||
|
jmp 1b
|
||
|
|
||
|
#### Sector read subroutine. Takes a drive number in DL (0x80 = hard
|
||
|
#### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and
|
||
|
#### reads the specified sector into memory at ES:0000. Returns with
|
||
|
#### carry set on error, clear otherwise. Preserves all
|
||
|
#### general-purpose registers.
|
||
|
|
||
|
read_sector:
|
||
|
pusha
|
||
|
sub %ax, %ax
|
||
|
push %ax # LBA sector number [48:63]
|
||
|
push %ax # LBA sector number [32:47]
|
||
|
push %ebx # LBA sector number [0:31]
|
||
|
push %es # Buffer segment
|
||
|
push %ax # Buffer offset (always 0)
|
||
|
push $1 # Number of sectors to read
|
||
|
push $16 # Packet size
|
||
|
mov $0x42, %ah # Extended read
|
||
|
mov %sp, %si # DS:SI -> packet
|
||
|
int $0x13 # Error code in CF
|
||
|
popa # Pop 16 bytes, preserve flags
|
||
|
popa_ret:
|
||
|
popa
|
||
|
ret # Error code still in CF
|
||
|
|
||
|
#### Command-line arguments and their count.
|
||
|
#### This is written by the `pintos' utility and read by the kernel.
|
||
|
#### The loader itself does not do anything with the command line.
|
||
|
.org LOADER_ARG_CNT - LOADER_BASE
|
||
|
.fill LOADER_ARG_CNT_LEN, 1, 0
|
||
|
|
||
|
.org LOADER_ARGS - LOADER_BASE
|
||
|
.fill LOADER_ARGS_LEN, 1, 0
|
||
|
|
||
|
#### Partition table.
|
||
|
.org LOADER_PARTS - LOADER_BASE
|
||
|
.fill LOADER_PARTS_LEN, 1, 0
|
||
|
|
||
|
#### Boot-sector signature for BIOS inspection.
|
||
|
.org LOADER_SIG - LOADER_BASE
|
||
|
.word 0xaa55
|