X-Git-Url: https://pintos-os.org/cgi-bin/gitweb.cgi?p=pintos-anon;a=blobdiff_plain;f=src%2Fthreads%2Floader.S;h=a5819dd28429080f7e6e5f7b70dd277579d12950;hp=5a0262b58733315296f85ae9ff429711c8d81854;hb=a03618133f7df0954802a470a4bee7674f7aed45;hpb=8e05e0a5473ccd3988c98f95e5ec3afad225685b diff --git a/src/threads/loader.S b/src/threads/loader.S index 5a0262b..a5819dd 100644 --- a/src/threads/loader.S +++ b/src/threads/loader.S @@ -1,222 +1,257 @@ -#include "mmu.h" -#include "loader.h" - -############################################################################## -# Kernel loader. -# -# This code should be stored in the first sector of the hard disk. When the -# BIOS runs, it loads this code at physical address 0x7c00-0x7e00 (512 bytes). -# Then it jumps to the beginning of it, in real mode. -# This code switches into protected mode (32-bit mode) so that all of -# memory can accessed, loads the kernel into memory, and jumps to the -# first byte of the kernel, where start.S is linked. -############################################################################## - -.globl start # Entry point -start: .code16 # This runs in real mode - cli # Disable interrupts - cld # String ops inc - xorw %ax,%ax # Zero - movw %ax,%es # Address - movw %ax,%ds # data - movw %ax,%ss # Set up - movw $start,%sp # stack (grows down) - -#### Enable A20: -#### Address line 20 is tied to low when the machine boots, -#### obviously this a bit of a drag, such as when trying to -#### address memory above 1MB. This code undoes this. - -1: inb $0x64,%al # Get status - testb $0x2,%al # Busy? - jnz 1b # Yes - movb $0xd1,%al # Command: Write - outb %al,$0x64 # output port -2: inb $0x64,%al # Get status - testb $0x2,%al # Busy? - jnz 2b # Yes - movb $0xdf,%al # Enable - outb %al,$0x60 # A20 - -#### Get memory size, via interrupt 15h function 88h. -#### Returns CF clear if successful, with AX = (kB of physical memory) - 1024. -#### This only works for memory sizes <= 65 MB, which should be fine for our purposes. - - movb $0x88,%ah - int $0x15 - jc panic # Carry flag set on error -2: addl $1024,%eax # Total kB - shrl $2,%eax # Total 4 kB pages - movl %eax, ram_pages - -#### switch from real to protected mode -#### The segments in GDT allow all of physical memory to be accessed. -#### Furthermore, the segments have base addresses of 0, so that the -#### segment translation is a NOP, ie. virtual addresses are identical to -#### their physical addresses. With this setup, it appears to this code -#### that it is running directly on physical memory. - - cli # Mandatory since we dont set up an IDT - lgdt gdtdesc # load GDT -- mandatory in protected mode - movl %cr0, %eax # turn on protected mode - orl $CR0_PE, %eax # - movl %eax, %cr0 # - ### CPU magic: jump to relocation, flush prefetch queue, and - ### reload %cs Has the effect of just jmp to the next - ### instruction, but simultaneous loads CS with - ### $PROT_MODE_CSEG. - ljmp $SEL_KCSEG, $protcseg - -#### We are in protected mode in a 32-bit segment (hence the .code32) -protcseg: - .code32 - movw $SEL_KDSEG, %ax # set up data segment registers - movw %ax, %ds - movw %ax, %es - movw %ax, %fs - movw %ax, %gs - movw %ax, %ss - -#### Load kernel starting at physical address LOADER_KERN_BASE by -#### frobbing the IDE controller directly. - - movl $1, %ebx - movl $LOADER_KERN_BASE, %edi -read_sector: +#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 $0x00e3, %ax # 9600 bps, N-8-1. + int $0x14 # Destroys AX. + + call puts + .string "Pintos loader" + +#### 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. - ### Poll status register while controller busy. - movl $0x1f7, %edx -1: inb %dx, %al - testb $0x80, %al - jnz 1b - - ### Read a single sector. - movl $0x1f2, %edx - movb $1, %al - outb %al, %dx - - ### Sector number to write in low 28 bits. - ### LBA mode, device 0 in top 4 bits. - movl %ebx, %eax - andl $0x0fffffff, %eax - orl $0xe0000000, %eax - - ### Dump %eax to ports 0x1f3...0x1f6. - movl $4, %ecx -2: incw %dx - outb %al, %dx - shrl $8, %eax - loop 2b - - ### READ command to command register. - incw %dx - movb $0x20, %al - outb %al, %dx - - ### Poll status register while controller busy. -1: inb %dx, %al - testb $0x80, %al - jnz 1b - - ### Poll status register until data ready. -1: inb %dx, %al - testb $0x08, %al - jz 1b - - ### Transfer sector. - movl $512 / 4, %ecx - movl $0x1f0, %edx - rep insl - - ### Next sector. - incl %ebx - cmpl $KERNEL_LOAD_PAGES*8 + 1, %ebx - jnz read_sector - -##### Create temporary PDE and PTE, set page directory pointer, and turn -##### on paging. -##### FIXME? We could use a single 4 MB page instead of 1024 4 kB pages. - - # Create PDE at 64 kB. - movl $0x10000, %edi - movl %edi, %cr3 - - # Fill PDE with zeroes. - subl %eax, %eax - movl $0x400, %ecx - rep stosl - - # Set PDE entries for 0 and LOADER_PHYS_BASE to point to the - # PTE. - movl $0x11000 | PG_U | PG_W | PG_P, %eax - movl %eax, 0x10000 - movl %eax, 0x10000 | (LOADER_PHYS_BASE >> 20) - - # Initialize PTE. - movl $PG_U | PG_W | PG_P, %eax - movl $0x400, %ecx -1: stosl - addl $0x1000, %eax - loop 1b - - # Enable paging. - movl %cr0, %eax - orl $CR0_PG, %eax - movl %eax, %cr0 - jmp 1f +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: -##### Turn on EM bit in CR0, forcing most floating-point instructions -##### to trap. We don't support floating-point or MMX. - - movl %cr0, %eax - orl $CR0_EM, %eax - movl %eax, %cr0 - -##### Jump to kernel entry point. - - movl $LOADER_PHYS_BASE + LOADER_BASE, %esp - movl $LOADER_PHYS_BASE + LOADER_KERN_BASE, %eax - jmp *%eax - -##### GDT - -gdt: - .quad 0x0000000000000000 # null seg - .quad 0x00cf9a000000ffff # code seg - .quad 0x00cf92000000ffff # data seg - -gdtdesc: - .word 0x17 # sizeof (gdt) - 1 - .long gdt # address gdt - -##### To panic, we print panicmsg (with help from the BIOS) and spin. -panic: .code16 # We only panic in real mode. - movw $panicmsg, %si - movb $0xe, %ah - xorb %bh, %bh -1: lodsb + # 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 -2: jz 2b # Spin. + 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. + int $0x14 # Destroys AH. + + cmp $'\r', %al + jne popa_ret + mov $'\n', %al jmp 1b -panicmsg: - .ascii "Loader panic!\r\n" - .byte 0 +#### 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 %eax, %eax + push %eax # LBA sector number [32:63] + 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 -##### Memory size in 4 kB pages. - .org LOADER_RAM_PAGES -ram_pages: - .long 0 + .org LOADER_ARGS - LOADER_BASE + .fill LOADER_ARGS_LEN, 1, 0 -##### Command-line arguments inserted by another utility. -##### The loader doesn't use these, but we note their -##### location here for easy reference. - .org LOADER_CMD_LINE -cmd_line: - .fill 0x80, 1, 0 +#### Partition table. + .org LOADER_PARTS - LOADER_BASE + .fill LOADER_PARTS_LEN, 1, 0 -##### Boot-sector signature for BIOS inspection. - .org LOADER_BIOS_SIG +#### Boot-sector signature for BIOS inspection. + .org LOADER_SIG - LOADER_BASE .word 0xaa55