1 # Code for the first few 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 baremetal/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: 5120 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 1024x768) 34 # - must store their entry-point at address 0x9000 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 some sectors 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 80 # al <- 128 # number of sectors to read; TODO - all sectors might need to be in a single track on real hardware (so 63 sectors at most including the boot sector) 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 [label] 72 cd 13 # int 13h, BIOS disk service 73 0f 82 8a 00 # jump-if-carry disk-error [label] 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 05 41 # bx <- 0x0105 (graphics 1024x768x256 100 # 0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively) 101 # fallback mode: 0x0101 (640x480x256) 102 cd 10 # int 10h, Vesa BIOS extensions 103 104 # 43: 105 # load information for the (hopefully) current video mode 106 # mostly just for the address to the linear frame buffer 107 b4 4f # ah <- 4f (VBE) 108 b0 01 # al <- 01 (get video mode) 109 b9 07 01 # cx <- 0x0107 (mode we requested) 110 bf 00 7f # di <- 0x7f00 (video mode info) [label] 111 cd 10 112 113 # 4f: 114 # switch to 32-bit mode 115 0f 01 16 # lgdt 00/mod/indirect 010/subop 110/rm/use-disp16 116 a0 7c # *gdt_descriptor [label] 117 0f 20 c0 # eax <- cr0 118 66 83 c8 01 # eax <- or 0x1 119 0f 22 c0 # cr0 <- eax 120 ea e0 7c 08 00 # far jump to initialize_32bit_mode after setting cs to the record at offset 8 in the gdt (gdt_code) [label] 121 122 # padding 123 # 63: 124 00 00 00 00 00 00 00 00 00 00 00 00 00 125 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 126 127 ## GDT: 3 records of 8 bytes each 128 129 # 80: 130 # gdt_start: 131 # gdt_null: mandatory null descriptor 132 00 00 00 00 00 00 00 00 133 # gdt_code: (offset 8 from gdt_start) 134 ff ff # limit[0:16] 135 00 00 00 # base[0:24] 136 9a # 1/present 00/privilege 1/descriptor type = 1001b 137 # 1/code 0/conforming 1/readable 0/accessed = 1010b 138 cf # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b 139 # limit[16:20] = 1111b 140 00 # base[24:32] 141 # gdt_data: (offset 16 from gdt_start) 142 ff ff # limit[0:16] 143 00 00 00 # base[0:24] 144 92 # 1/present 00/privilege 1/descriptor type = 1001b 145 # 0/data 0/conforming 1/readable 0/accessed = 0010b 146 cf # same as gdt_code 147 00 # base[24:32] 148 # gdt_end: 149 150 # padding 151 # 98: 152 00 00 00 00 00 00 00 00 153 154 # a0: 155 # gdt_descriptor: 156 17 00 # final index of gdt = gdt_end - gdt_start - 1 157 80 7c 00 00 # start = gdt_start [label] 158 159 # padding 160 # a5: 161 00 00 00 00 00 00 00 00 00 00 162 163 # b0: 164 # disk_error: 165 # print 'D' to top-left of screen to indicate disk error 166 # *0xb8000 <- 0x0f44 167 # bx <- 0xb800 168 bb 00 b8 169 # ds <- bx 170 8e db # 11b/mod 011b/reg/ds 011b/rm/bx 171 # al <- 'D' 172 b0 44 173 # ah <- 0x0f # white on black 174 b4 0f 175 # bx <- 0 176 bb 00 00 177 # *ds:bx <- ax 178 89 07 # 00b/mod/indirect 000b/reg/ax 111b/rm/bx 179 180 e9 fb ff # loop forever 181 182 # padding 183 # c1: 184 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 185 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 186 187 ## 32-bit code from this point (still some instructions not in SubX) 188 189 # e0: 190 # initialize_32bit_mode: 191 66 b8 10 00 # ax <- offset 16 from gdt_start 192 8e d8 # ds <- ax 193 8e d0 # ss <- ax 194 8e c0 # es <- ax 195 8e e0 # fs <- ax 196 8e e8 # gs <- ax 197 198 # load interrupt handlers 199 0f 01 1d # lidt 00/mod/indirect 011/subop 101/rm32/use-disp32 200 f8 7d 00 00 # *idt_descriptor [label] 201 202 # For now, not bothering reprogramming the IRQ to not conflict with software 203 # exceptions. 204 # https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode 205 # 206 # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a 207 # debugger. 208 # Reference: 209 # https://wiki.osdev.org/Exceptions 210 211 # enable keyboard IRQ (1) 212 b0 fd # al <- 0xfd # disable mask for IRQ1 213 e6 21 # port 0x21 <- al 214 215 # initialization is done; enable interrupts 216 fb 217 e9 01 13 00 00 # jump to 0x9000 [label] 218 219 # padding 220 # ff: 221 00 222 223 # 100: 224 # null interrupt handler: 225 cf # iret 226 227 # padding 228 # 101: 229 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 230 231 # 110: 232 # keyboard interrupt handler: 233 # prologue 234 fa # disable interrupts 235 60 # push all registers to stack 236 # acknowledge interrupt 237 b0 20 # al <- 0x20 238 e6 20 # port 0x20 <- al 239 # read status into eax 240 31 c0 # eax <- xor eax; 11/direct 000/r32/eax 000/rm32/eax 241 e4 64 # al <- port 0x64 242 # if (status & 0x1) == 0, return 243 24 01 # al <- and 0x1 244 3c 00 # compare al, 0 245 74 39 # jump to epilogue if = [label] 246 # 120: 247 # if keyboard buffer is full, return 248 31 c9 # ecx <- xor ecx; 11/direct 001/r32/ecx 001/rm32/ecx 249 # . var index/ecx: byte 250 8a # copy m8 at r32 to r8 251 0d # 00/mod/indirect 001/r8/cl 101/rm32/use-disp32 252 c8 7d 00 00 # disp32 [label] 253 # . al = *(keyboard buffer + index) 254 8a # copy m8 at r32 to r8 255 81 # 10/mod/*+disp32 000/r8/al 001/rm32/ecx 256 d0 7d 00 00 # disp32 [label] 257 # . if (al != 0) return 258 3c 00 # compare al, 0 259 # 130: 260 75 27 # jump to epilogue if != [label] 261 # read keycode into al 262 e4 60 # al <- port 0x60 263 # if (al & 0x80) a key is being lifted; return 264 50 # push eax 265 24 80 # al <- and 0x80 266 3c 00 # compare al, 0 267 58 # pop to eax (without touching flags) 268 75 1d # jump to epilogue if != [label] 269 # 13c: 270 # al <- *(keyboard normal map + eax) 271 8a # copy m8 at rm32 to r8 272 80 # 10/mod/*+disp32 000/r8/al 000/rm32/eax 273 00 80 00 00 # disp32 [label] 274 # if there's no character mapping, return 275 3c 00 # compare al, 0 276 74 13 # jump to epilogue if = [label] 277 # 146: 278 # store al in keyboard buffer 279 88 # copy r8 to m8 at r32 280 81 # 10/mod/*+disp32 000/r8/al 001/rm32/ecx 281 d0 7d 00 00 # disp32 [label] 282 # 14c: 283 # increment index 284 fe # increment byte 285 05 # 00/mod/indirect 000/subop/increment 101/rm32/use-disp32 286 c8 7d 00 00 # disp32 [label] 287 # clear top nibble of index (keyboard buffer is circular) 288 80 # and byte 289 25 # 00/mod/indirect 100/subop/and 101/rm32/use-disp32 290 c8 7d 00 00 # disp32 [label] 291 0f # imm8 292 # 159: 293 # epilogue 294 61 # pop all registers 295 fb # enable interrupts 296 cf # iret 297 298 # padding 299 # 15c: 300 00 00 00 00 301 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 302 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 303 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 304 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 305 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 306 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 307 00 00 00 00 00 00 00 00 308 309 # 1c8: 310 # var keyboard circular buffer 311 # write index: nibble 312 # still take up 4 bytes so SubX can handle it 313 00 00 00 00 314 # 1cc: 315 # read index: nibble 316 # still take up 4 bytes so SubX can handle it 317 00 00 00 00 318 # 1d0: 319 # circular buffer: byte[16] 320 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 321 322 # padding 323 # 1e0: 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 326 327 # 1f8: 328 # idt_descriptor: 329 ff 00 # idt_end - idt_start - 1 330 00 7e 00 00 # start = idt_start [label] 331 332 # 1fe: 333 # final 2 bytes of boot sector 334 55 aa 335 336 ## sector 2 337 # loaded by load_disk, not automatically on boot 338 339 # offset 200 (address 0x7e00): interrupt descriptor table 340 # 32 entries * 8 bytes each = 256 bytes (0x100) 341 # idt_start: 342 343 00 00 00 00 00 00 00 00 344 00 00 00 00 00 00 00 00 345 00 00 00 00 00 00 00 00 346 00 00 00 00 00 00 00 00 347 00 00 00 00 00 00 00 00 348 00 00 00 00 00 00 00 00 349 00 00 00 00 00 00 00 00 350 00 00 00 00 00 00 00 00 351 352 # By default, BIOS maps IRQ0-7 to interrupt vectors 8-15. 353 # https://wiki.osdev.org/index.php?title=Interrupts&oldid=25102#Default_PC_Interrupt_Vector_Assignment 354 355 # entry 8: clock 356 00 7d # target[0:16] = null interrupt handler [label] 357 08 00 # segment selector (gdt_code) 358 00 # unused 359 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 360 00 00 # target[16:32] 361 362 # entry 9: keyboard 363 10 7d # target[0:16] = keyboard interrupt handler [label] 364 08 00 # segment selector (gdt_code) 365 00 # unused 366 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 367 00 00 # target[16:32] 368 369 00 00 00 00 00 00 00 00 370 00 00 00 00 00 00 00 00 371 00 00 00 00 00 00 00 00 372 00 00 00 00 00 00 00 00 373 00 00 00 00 00 00 00 00 374 00 00 00 00 00 00 00 00 375 00 00 00 00 00 00 00 00 376 00 00 00 00 00 00 00 00 377 00 00 00 00 00 00 00 00 378 00 00 00 00 00 00 00 00 379 00 00 00 00 00 00 00 00 380 00 00 00 00 00 00 00 00 381 00 00 00 00 00 00 00 00 382 00 00 00 00 00 00 00 00 383 00 00 00 00 00 00 00 00 384 00 00 00 00 00 00 00 00 385 00 00 00 00 00 00 00 00 386 00 00 00 00 00 00 00 00 387 00 00 00 00 00 00 00 00 388 00 00 00 00 00 00 00 00 389 00 00 00 00 00 00 00 00 390 00 00 00 00 00 00 00 00 391 # idt_end: 392 393 # offset 300 (address 0x7f00): 394 # video mode info: 395 00 00 # attributes 396 00 # winA 397 00 # winB 398 # 304 399 00 00 # granularity 400 00 00 # winsize 401 # 308 402 00 00 # segmentA 403 00 00 # segmentB 404 # 30c 405 00 00 00 00 # realFctPtr (who knows) 406 # 310 407 00 00 # pitch 408 00 00 # Xres 409 # 314 410 00 00 # Yres 411 00 00 # Wchar Ychar 412 # 318 413 00 # planes 414 00 # bpp 415 00 # banks 416 00 # memory_model 417 # 31c 418 00 # bank_size 419 00 # image_pages 420 00 # reserved 421 # 31f 422 00 00 # red_mask red_position 423 00 00 # green_mask green_position 424 00 00 # blue_mask blue_position 425 00 00 # rsv_mask rsv_position 426 00 # directcolor_attributes 427 # 328 428 00 00 00 00 # physbase <== linear frame buffer 429 430 # 32c 431 # reserved for video mode info 432 00 00 00 00 433 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 434 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 435 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 436 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 437 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 438 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 439 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 440 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 441 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 442 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 443 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 444 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 445 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 446 447 ## the rest of this file has data 448 449 # offset 400 (address 0x8000): 450 +--161 lines: # translating keys to ASCII ----------------------------------------------------------------------------------------------------------------------------------------------- 611 612 # offset c00 (address 0x8800) 613 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------ 849 850 # offset 1400 (address 0x9000) 851 852 # vim:ft=subx