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