-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbootstart.S
135 lines (116 loc) · 5.25 KB
/
bootstart.S
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
###############################################################################
# BOOT ENTRY POINT
#
# After the BIOS initializes the hardware on startup or system reset,
# it loads the first 512-byte sector of the hard disk
# into physical addresses 0x7C00-0x7DFF.
# Then it jumps to address 0x7C00 -- and the OS starts running!
#
# This file contains the code loaded at that address.
# The 'start' routine switches the CPU out of compatibility mode, which
# requires some subtle assembly programming.
# Then it calls the "bootmain" routine to finish the booting
# process. This routine is defined in the C programming language, in one
# of the *-boot.c files.
#
# There is no need to understand this code in detail!
# (You may stop reading now.)
#
###############################################################################
###############################################################################
# For Your Information: COMPATIBILITY MODES
#
# The Intel x86 architecture has many compatibility modes, going back to
# the 8086, which supported only 16-bit addresses. When the BIOS calls
# into the OS, it is running in the "most compatible" mode, 16-bit real
# mode. The machine acts like addresses are only 16 bits long,
# there's no paging support, and there isn't even any support for
# user-mode applications -- the processor is always privileged, so there's
# no way to implement memory protection! We want to get into "386" mode,
# with 32-bit addresses and memory protection, as soon as possible. And
# that's what this code does.
#
# Again, there is no need to understand this code in detail!
#
###############################################################################
# Define some constants
.set SEGSEL_BOOT_CODE,0x8 # code segment selector
.set SEGSEL_BOOT_DATA,0x10 # data segment selector
.set CR0_PE_ON,0x1 # protected mode enable flag
.globl start # Entry point
start: .code16 # This runs in real mode
cli # Disable interrupts
cld # String operations increment
# Set up the important data segment registers (DS, ES, SS).
xorw %ax,%ax # Segment number zero
movw %ax,%ds # -> Data Segment
movw %ax,%es # -> Extra Segment
movw %ax,%ss # -> Stack Segment
# Set up the stack pointer, growing downward from 0x7c00.
movw $start,%sp # Stack Pointer
# Enable A20:
# For fascinating historical reasons (related to the fact that
# the earliest 8086-based PCs could only address 1MB of physical memory
# and subsequent 80286-based PCs wanted to retain maximum compatibility),
# physical address line 20 is tied to low when the machine boots.
# Obviously this a bit of a drag for us, especially when trying to
# address memory above 1MB. This code undoes this.
seta20.1: inb $0x64,%al # Get status
testb $0x2,%al # Busy?
jnz seta20.1 # Yes
movb $0xd1,%al # Command: Write
outb %al,$0x64 # output port
seta20.2: inb $0x64,%al # Get status
testb $0x2,%al # Busy?
jnz seta20.2 # Yes
movb $0xdf,%al # Enable
outb %al,$0x60 # A20
# Switch from real to protected mode:
# Up until now, there's been no protection, so we've gotten along perfectly
# well without explicitly telling the processor how to translate addresses.
# When we switch to protected mode, this is no longer true!
# We need at least to set up some "segments" that tell the processor it's
# OK to run code at any address, or write to any address.
# The 'gdt' and 'gdtdesc' tables below define these segments.
# This code loads them into the processor.
# We need this setup to ensure the transition to protected mode is smooth.
real_to_prot: cli # Don't allow interrupts: mandatory,
# since we didn't set up an interrupt
# descriptor table for handling them
lgdt gdtdesc # load GDT: mandatory in protected mode
movl %cr0, %eax # Turn on protected mode
orl $CR0_PE_ON, %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 simultaneously loads CS with
# $SEGSEL_BOOT_CODE.
ljmp $SEGSEL_BOOT_CODE, $protcseg
.code32 # run in 32-bit protected mode
# Set up the protected-mode data segment registers
protcseg: movw $SEGSEL_BOOT_DATA, %ax # Our data segment selector
movw %ax, %ds # -> DS: Data Segment
movw %ax, %es # -> ES: Extra Segment
movw %ax, %fs # -> FS
movw %ax, %gs # -> GS
movw %ax, %ss # -> SS: Stack Segment
call bootmain # finish the boot! Shouldn't return,
spinloop: jmp spinloop # ..but in case it does, spin.
# Segment descriptors
# These macros are used to define segment descriptors.
#define SEG_NULL \
.word 0, 0; \
.byte 0, 0, 0, 0
#define SEG(type,base,lim) \
.word (((lim) >> 12) & 0xffff), ((base) & 0xffff); \
.byte (((base) >> 16) & 0xff), (0x90 | (type)), \
(0xC0 | (((lim) >> 28) & 0xf)), (((base) >> 24) & 0xff)
#define STA_X 0x8 // Executable segment
#define STA_W 0x2 // Writeable (non-executable segments)
#define STA_R 0x2 // Readable (executable segments)
.p2align 2 # force 4 byte alignment
gdt: SEG_NULL # null seg
SEG(STA_X|STA_R, 0x0, 0xffffffff) # code seg
SEG(STA_W, 0x0, 0xffffffff) # data seg
gdtdesc: .word 0x17 # sizeof(gdt) - 1
.long gdt # address gdt