Implement a proper block layer with partition support.
[pintos-anon] / src / threads / loader.S
1 #include "threads/loader.h"
2
3 #### Kernel loader.
4
5 #### This code should be stored in the first sector of a hard disk.
6 #### When the BIOS runs, it loads this code at physical address
7 #### 0x7c00-0x7e00 (512 bytes) and jumps to the beginning of it,
8 #### in real mode.  The loader loads the kernel into memory and jumps
9 #### to its entry point, which is the start function in start.S.
10 ####
11 #### The BIOS passes in the drive that the loader was read from as
12 #### DL, with floppy drives numbered 0x00, 0x01, ... and hard drives
13 #### numbered 0x80, 0x81, ...  We want to support booting a kernel on
14 #### a different drive from the loader, so we don't take advantage of
15 #### this.
16
17 # Runs in real mode, which is a 16-bit segment.
18         .code16
19
20 # Set up segment registers.
21 # Set stack to grow downward from 60 kB (after boot, the kernel
22 # continues to use this stack for its initial thread).
23
24         sub %ax, %ax
25         mov %ax, %ds
26         mov %ax, %ss
27         mov $0xf000, %esp
28
29 # Configure serial port so we can report progress without connected VGA.
30 # See [IntrList] for details.
31         sub %dx, %dx                    # Serial port 0.
32         mov $0x00e3, %ax                # 9600 bps, N-8-1.
33         int $0x14                       # Destroys AX.
34
35         call puts
36         .string "Pintos loader"
37
38 #### Read the partition table on each system hard disk and scan for a
39 #### partition of type 0x20, which is the type that we use for a
40 #### Pintos kernel.
41 ####
42 #### Read [Partitions] for a description of the partition table format
43 #### that we parse.
44 ####
45 #### We print out status messages to show the disk and partition being
46 #### scanned, e.g. hda1234 as we scan four partitions on the first
47 #### hard disk.
48
49         mov $0x80, %dl                  # Hard disk 0.
50 read_mbr:
51         sub %ebx, %ebx                  # Sector 0.
52         mov $0x2000, %ax                # Use 0x20000 for buffer.
53         mov %ax, %es
54         call read_sector
55         jc no_such_drive
56
57         # Print hd[a-z].
58         call puts
59         .string " hd"
60         mov %dl, %al
61         add $'a' - 0x80, %al
62         call putc
63
64         # Check for MBR signature--if not present, it's not a
65         # partitioned hard disk.
66         cmpw $0xaa55, %es:510
67         jne next_drive
68
69         mov $446, %si                   # Offset of partition table entry 1.
70         mov $'1', %al
71 check_partition:
72         # Is it an unused partition?
73         cmpl $0, %es:(%si)
74         je next_partition
75
76         # Print [1-4].
77         call putc
78
79         # Is it a Pintos kernel partition?
80         cmpb $0x20, %es:4(%si)
81         jne next_partition
82
83         # Is it a bootable partition?
84         cmpb $0x80, %es:(%si)
85         je load_kernel
86
87 next_partition:
88         # No match for this partition, go on to the next one.
89         add $16, %si                    # Offset to next partition table entry.
90         inc %al
91         cmp $510, %si
92         jb check_partition
93
94 next_drive:
95         # No match on this drive, go on to the next one.
96         inc %dl
97         jnc read_mbr
98
99 no_such_drive:
100 no_boot_partition:
101         # Didn't find a Pintos kernel partition anywhere, give up.
102         call puts
103         .string "\rNot found\r"
104
105         # Notify BIOS that boot failed.  See [IntrList].
106         int $0x18
107
108 #### We found a kernel.  The kernel's drive is in DL.  The partition
109 #### table entry for the kernel's partition is at ES:SI.  Our job now
110 #### is to read the kernel from disk and jump to its start address.
111
112 load_kernel:
113         call puts
114         .string "\rLoading"
115
116         # Figure out number of sectors to read.  A Pintos kernel is
117         # just an ELF format object, which doesn't have an
118         # easy-to-read field to identify its own size (see [ELF1]).
119         # But we limit Pintos kernels to 512 kB for other reasons, so
120         # it's easy enough to just read the entire contents of the
121         # partition or 512 kB from disk, whichever is smaller.
122         mov %es:12(%si), %ecx           # EBP = number of sectors
123         cmp $1024, %ecx                 # Cap size at 512 kB
124         jbe 1f
125         mov $1024, %cx
126 1:
127
128         mov %es:8(%si), %ebx            # EBX = first sector
129         mov $0x2000, %ax                # Start load address: 0x20000
130
131 next_sector:
132         # Read one sector into memory.
133         mov %ax, %es                    # ES:0000 -> load address
134         call read_sector
135         jc read_failed
136
137         # Print '.' as progress indicator once every 16 sectors == 8 kB.
138         test $15, %bl
139         jnz 1f
140         call puts
141         .string "."
142 1:
143
144         # Advance memory pointer and disk sector.
145         add $0x20, %ax
146         inc %bx
147         loop next_sector
148
149         call puts
150         .string "\r"
151
152 #### Transfer control to the kernel that we loaded.  We read the start
153 #### address out of the ELF header (see [ELF1]) and convert it from a
154 #### 32-bit linear address into a 16:16 segment:offset address for
155 #### real mode, then jump to the converted address.  The 80x86 doesn't
156 #### have an instruction to jump to an absolute segment:offset kept in
157 #### registers, so in fact we store the address in a temporary memory
158 #### location, then jump indirectly through that location.  To save 4
159 #### bytes in the loader, we reuse 4 bytes of the loader's code for
160 #### this temporary pointer.
161
162         mov $0x2000, %ax
163         mov %ax, %es
164         mov %es:0x18, %dx
165         mov %dx, start
166         movw $0x2000, start + 2
167         ljmp *start
168
169 read_failed:
170 start:
171         # Disk sector read failed.
172         call puts
173 1:      .string "\rBad read\r"
174
175         # Notify BIOS that boot failed.  See [IntrList].
176         int $0x18
177
178 #### Print string subroutine.  To save space in the loader, this
179 #### subroutine takes its null-terminated string argument from the
180 #### code stream just after the call, and then returns to the byte
181 #### just after the terminating null.  This subroutine preserves all
182 #### general-purpose registers.
183
184 puts:   xchg %si, %ss:(%esp)
185         push %ax
186 next_char:
187         mov %cs:(%si), %al
188         inc %si
189         test %al, %al
190         jz 1f
191         call putc
192         jmp next_char
193 1:      pop %ax
194         xchg %si, %ss:(%esp)
195         ret
196
197 #### Character output subroutine.  Prints the character in AL to the
198 #### VGA display and serial port 0, using BIOS services (see
199 #### [IntrList]).  Preserves all general-purpose registers.
200 ####
201 #### If called upon to output a carriage return, this subroutine
202 #### automatically supplies the following line feed.
203
204 putc:   pusha
205
206 1:      sub %bh, %bh                    # Page 0.
207         mov $0x0e, %ah                  # Teletype output service.
208         int $0x10
209
210         mov $0x01, %ah                  # Serial port output service.
211         sub %dx, %dx                    # Serial port 0.
212         int $0x14                       # Destroys AH.
213
214         cmp $'\r', %al
215         jne popa_ret
216         mov $'\n', %al
217         jmp 1b
218
219 #### Sector read subroutine.  Takes a drive number in DL (0x80 = hard
220 #### disk 0, 0x81 = hard disk 1, ...) and a sector number in EBX, and
221 #### reads the specified sector into memory at ES:0000.  Returns with
222 #### carry set on error, clear otherwise.  Preserves all
223 #### general-purpose registers.
224
225 read_sector:
226         pusha
227         sub %eax, %eax
228         push %eax                       # LBA sector number [32:63]
229         push %ebx                       # LBA sector number [0:31]
230         push %es                        # Buffer segment
231         push %ax                        # Buffer offset (always 0)
232         push $1                         # Number of sectors to read
233         push $16                        # Packet size
234         mov $0x42, %ah                  # Extended read
235         mov %sp, %si                    # DS:SI -> packet
236         int $0x13                       # Error code in CF
237         popa                            # Pop 16 bytes, preserve flags
238 popa_ret:
239         popa
240         ret                             # Error code still in CF
241
242 #### Command-line arguments and their count.
243 #### This is written by the `pintos' utility and read by the kernel.
244 #### The loader itself does not do anything with the command line.
245         .org LOADER_ARG_CNT - LOADER_BASE
246         .fill LOADER_ARG_CNT_LEN, 1, 0
247
248         .org LOADER_ARGS - LOADER_BASE
249         .fill LOADER_ARGS_LEN, 1, 0
250
251 #### Partition table.
252         .org LOADER_PARTS - LOADER_BASE
253         .fill LOADER_PARTS_LEN, 1, 0
254
255 #### Boot-sector signature for BIOS inspection.
256         .org LOADER_SIG - LOADER_BASE
257         .word 0xaa55