1 # Code for the first 2 disk sectors, that all programs in this directory need: 2 # - load sectors past the first (using BIOS primitives) since only the first is available by default 3 # - if this fails, print 'D' at top-left of screen and halt 4 # - initialize a minimal graphics mode 5 # - switch to 32-bit mode (giving up access to BIOS primitives) 6 # - set up a handler for keyboard events 7 # - jump to start of program 8 # 9 # To convert to a disk image, first prepare a realistically sized disk image: 10 # dd if=/dev/zero of=disk.img count=20160 # 512-byte sectors, so 10MB 11 # Create initial sectors from this file: 12 # ./bootstrap run apps/hex < baremetal/boot.hex > boot.bin 13 # Translate other sectors into a file called a.img 14 # Load all sectors into the disk image: 15 # cat boot.bin a.img > disk.bin 16 # dd if=disk.bin of=disk.img conv=notrunc 17 # To run: 18 # qemu-system-i386 disk.img 19 # Or: 20 # bochs -f apps/boot.bochsrc # boot.bochsrc loads disk.img 21 # 22 # Since we start out in 16-bit mode, we need instructions SubX doesn't 23 # support. 24 # This file contains just lowercase hex bytes and comments. Zero 25 # error-checking. Make liberal use of: 26 # - comments documenting expected offsets 27 # - size checks on the emitted file (currently: 512 bytes) 28 # - xxd to eyeball that offsets contain expected bytes 29 # 30 # Programs using this initialization: 31 # - can't use any syscalls 32 # - can't print text to video memory (past these boot sectors) 33 # - must only print raw pixels (256 colors) to video memory (resolution 1280x1024) 34 # - must store their entry-point at address 0x8000 35 36 ## 16-bit entry point 37 38 # Upon reset, the IBM PC: 39 # - loads the first sector (512 bytes) 40 # from some bootable image (see the boot sector marker at the end of this file) 41 # to the address range [0x7c00, 0x7e00) 42 # - starts executing code at address 0x7c00 43 44 # offset 00 (address 0x7c00): 45 # disable interrupts for this initialization 46 fa # cli 47 48 # initialize segment registers 49 # this isn't always needed, but the recommendation is to not make assumptions 50 b8 00 00 # ax <- 0 51 8e d8 # ds <- ax 52 8e d0 # ss <- ax 53 8e c0 # es <- ax 54 8e e0 # fs <- ax 55 8e e8 # gs <- ax 56 57 # We don't read or write the stack before we get to 32-bit mode. No function 58 # calls, so we don't need to initialize the stack. 59 60 # 0e: 61 # load second sector from disk 62 b4 02 # ah <- 2 # read sectors from disk 63 # dl comes conveniently initialized at boot time with the index of the device being booted 64 b5 00 # ch <- 0 # cylinder 0 65 b6 00 # dh <- 0 # track 0 66 b1 02 # cl <- 2 # second sector, 1-based 67 b0 04 # al <- 4 # number of sectors to read 68 # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment 69 bb 00 00 # bx <- 0 70 8e c3 # es <- bx 71 bb 00 7e # bx <- 0x7e00 72 cd 13 # int 13h, BIOS disk service 73 0f 82 76 00 # jump-if-carry disk-error 74 75 # 26: 76 # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line 77 # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S 78 # seta20.1: 79 e4 64 # al <- port 0x64 80 a8 02 # set zf if bit 1 (second-least significant) is not set 81 75 fa # if zf not set, goto seta20.1 (-6) 82 83 b0 d1 # al <- 0xd1 84 e6 64 # port 0x64 <- al 85 86 # 30: 87 # seta20.2: 88 e4 64 # al <- port 0x64 89 a8 02 # set zf if bit 1 (second-least significant) is not set 90 75 fa # if zf not set, goto seta20.2 (-6) 91 92 b0 df # al <- 0xdf 93 e6 64 # port 0x64 <- al 94 95 # 3a: 96 # adjust video mode 97 b4 4f # ah <- 4f (VBE) 98 b0 02 # al <- 02 (set video mode) 99 bb 07 01 # bx <- 0x0107 (graphics 1280x1024x256) 100 # fallback: 0x0101 (640x480x256) 101 # for other choices see http://www.ctyme.com/intr/rb-0069.htm 102 cd 10 # int 10h, Vesa BIOS extensions 103 104 # 43: 105 # switch to 32-bit mode 106 0f 01 16 # lgdt 00/mod/indirect 010/subop 110/rm/use-disp16 107 80 7c # *gdt_descriptor 108 0f 20 c0 # eax <- cr0 109 66 83 c8 01 # eax <- or 0x1 110 0f 22 c0 # cr0 <- eax 111 ea c0 7c 08 00 # far jump to initialize_32bit_mode after setting cs to the record at offset 8 in the gdt (gdt_code) 112 113 # padding 114 # 57: 115 00 00 00 00 00 00 00 00 00 116 117 ## GDT: 3 records of 8 bytes each 118 119 # 60: 120 # gdt_start: 121 # gdt_null: mandatory null descriptor 122 00 00 00 00 00 00 00 00 123 # gdt_code: (offset 8 from gdt_start) 124 ff ff # limit[0:16] 125 00 00 00 # base[0:24] 126 9a # 1/present 00/privilege 1/descriptor type = 1001b 127 # 1/code 0/conforming 1/readable 0/accessed = 1010b 128 cf # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b 129 # limit[16:20] = 1111b 130 00 # base[24:32] 131 # gdt_data: (offset 16 from gdt_start) 132 ff ff # limit[0:16] 133 00 00 00 # base[0:24] 134 92 # 1/present 00/privilege 1/descriptor type = 1001b 135 # 0/data 0/conforming 1/readable 0/accessed = 0010b 136 cf # same as gdt_code 137 00 # base[24:32] 138 # gdt_end: 139 140 # padding 141 # 78: 142 00 00 00 00 00 00 00 00 143 144 # 80: 145 # gdt_descriptor: 146 17 00 # final index of gdt = gdt_end - gdt_start - 1 147 60 7c 00 00 # start = gdt_start 148 149 # padding 150 # 85: 151 00 00 00 00 00 00 00 00 00 00 152 153 # 90: 154 # disk_error: 155 # print 'D' to top-left of screen to indicate disk error 156 # *0xb8000 <- 0x0f44 157 # bx <- 0xb800 158 bb 00 b8 159 # ds <- bx 160 8e db # 11b/mod 011b/reg/ds 011b/rm/bx 161 # al <- 'D' 162 b0 44 163 # ah <- 0x0f # white on black 164 b4 0f 165 # bx <- 0 166 bb 00 00 167 # *ds:bx <- ax 168 89 07 # 00b/mod/indirect 000b/reg/ax 111b/rm/bx 169 170 e9 fb ff # loop forever 171 172 # padding 173 # a1: 174 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 175 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 176 177 ## 32-bit code from this point (still some instructions not in SubX) 178 179 # c0: 180 # initialize_32bit_mode: 181 66 b8 10 00 # ax <- offset 16 from gdt_start 182 8e d8 # ds <- ax 183 8e d0 # ss <- ax 184 8e c0 # es <- ax 185 8e e0 # fs <- ax 186 8e e8 # gs <- ax 187 188 # load interrupt handlers 189 0f 01 1d # lidt 00/mod/indirect 011/subop 101/rm32/use-disp32 190 00 7f 00 00 # *idt_descriptor 191 192 # enable keyboard IRQ 193 b0 fd # al <- 0xfd # enable just IRQ1 194 e6 21 # port 0x21 <- al 195 196 # initialization is done; enable interrupts 197 fb 198 e9 21 03 00 00 # jump to 0x8000 199 200 # padding 201 # df: 202 00 203 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 204 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 205 206 # 100: 207 # null interrupt handler: 208 cf # iret 209 210 # padding 211 # 101: 212 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 213 214 # 110: 215 # keyboard interrupt handler: 216 # prologue 217 fa # disable interrupts 218 60 # push all registers to stack 219 # acknowledge interrupt 220 b0 20 # al <- 0x20 221 e6 20 # port 0x20 <- al 222 # TODO: perhaps we should check keyboard status 223 # read keycode into eax 224 31 c0 # eax <- xor eax; 11/direct 000/r32/eax 000/rm32/eax 225 e4 60 # al <- port 0x60 226 # map key '1' to ascii; if eax == 2, eax = 0x31 227 3d 02 00 00 00 # compare eax with 0x02 228 75 0b # if not equal, goto epilogue 229 b8 31 0f 00 00 # eax <- 0x0f31 230 # print eax to top-left of screen (*0xb8000) 231 89 # copy r32 to rm32 232 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 233 # disp32 234 00 80 0b 00 235 # epilogue 236 61 # pop all registers 237 fb # enable interrupts 238 cf # iret 239 240 # padding 241 # 12f 242 00 243 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 244 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 245 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 246 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 247 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 248 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 249 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 250 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 251 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 252 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 253 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 254 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 255 00 00 00 00 00 00 00 00 00 00 00 00 00 00 256 257 # final 2 bytes of boot sector 258 55 aa 259 260 ## sector 2 261 # loaded by load_disk, not automatically on boot 262 263 # offset 200 (address 0x7e00): interrupt descriptor table 264 # 32 entries * 8 bytes each = 256 bytes (0x100) 265 # idt_start: 266 267 00 00 00 00 00 00 00 00 268 00 00 00 00 00 00 00 00 269 00 00 00 00 00 00 00 00 270 00 00 00 00 00 00 00 00 271 00 00 00 00 00 00 00 00 272 00 00 00 00 00 00 00 00 273 00 00 00 00 00 00 00 00 274 00 00 00 00 00 00 00 00 275 276 # entry 8: clock 277 00 7d # target[0:16] = null interrupt handler 278 08 00 # segment selector (gdt_code) 279 00 # unused 280 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 281 00 00 # target[16:32] 282 283 # entry 9: keyboard 284 10 7d # target[0:16] = keyboard interrupt handler 285 08 00 # segment selector (gdt_code) 286 00 # unused 287 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 288 00 00 # target[16:32] 289 290 00 00 00 00 00 00 00 00 291 00 00 00 00 00 00 00 00 292 00 00 00 00 00 00 00 00 293 00 00 00 00 00 00 00 00 294 00 00 00 00 00 00 00 00 295 00 00 00 00 00 00 00 00 296 00 00 00 00 00 00 00 00 297 00 00 00 00 00 00 00 00 298 00 00 00 00 00 00 00 00 299 00 00 00 00 00 00 00 00 300 00 00 00 00 00 00 00 00 301 00 00 00 00 00 00 00 00 302 00 00 00 00 00 00 00 00 303 00 00 00 00 00 00 00 00 304 00 00 00 00 00 00 00 00 305 00 00 00 00 00 00 00 00 306 00 00 00 00 00 00 00 00 307 00 00 00 00 00 00 00 00 308 00 00 00 00 00 00 00 00 309 00 00 00 00 00 00 00 00 310 00 00 00 00 00 00 00 00 311 00 00 00 00 00 00 00 00 312 # idt_end: 313 314 # offset 300 (address 0x7f00): 315 # idt_descriptor: 316 ff 00 # idt_end - idt_start - 1 317 00 7e 00 00 # start = idt_start 318 319 # padding 320 00 00 00 00 00 00 00 00 00 00 321 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 322 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 323 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 324 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 325 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 326 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 327 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 328 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 329 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 330 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 331 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 332 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 333 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 334 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 335 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 336 # offset 400 (address 0x8000) 337 338 # vim:ft=subx