1 # Code for the first disk sector 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: 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 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 # enable keyboard IRQ 203 b0 fd # al <- 0xfd # enable just IRQ1 204 e6 21 # port 0x21 <- al 205 206 # initialization is done; enable interrupts 207 fb 208 e9 01 13 00 00 # jump to 0x9000 [label] 209 210 # padding 211 # ff: 212 00 213 214 # 100: 215 # null interrupt handler: 216 cf # iret 217 218 # padding 219 # 101: 220 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 221 222 # 110: 223 # keyboard interrupt handler: 224 # prologue 225 fa # disable interrupts 226 60 # push all registers to stack 227 # acknowledge interrupt 228 b0 20 # al <- 0x20 229 e6 20 # port 0x20 <- al 230 # read status into eax 231 31 c0 # eax <- xor eax; 11/direct 000/r32/eax 000/rm32/eax 232 e4 64 # al <- port 0x64 233 # if (status & 0x1) == 0, return 234 24 01 # al <- and 0x1 235 3c 00 # compare al, 0 236 74 39 # jump to epilogue if = [label] 237 # 120: 238 # if keyboard buffer is full, return 239 31 c9 # ecx <- xor ecx; 11/direct 001/r32/ecx 001/rm32/ecx 240 # . var index/ecx: byte 241 8a # copy m8 at r32 to r8 242 0d # 00/mod/indirect 001/r8/cl 101/rm32/use-disp32 243 c8 7d 00 00 # disp32 [label] 244 # . al = *(keyboard buffer + index) 245 8a # copy m8 at r32 to r8 246 81 # 10/mod/*+disp32 000/r8/al 001/rm32/ecx 247 d0 7d 00 00 # disp32 [label] 248 # . if (al != 0) return 249 3c 00 # compare al, 0 250 # 130: 251 75 27 # jump to epilogue if != [label] 252 # read keycode into al 253 e4 60 # al <- port 0x60 254 # if (al & 0x80) a key is being lifted; return 255 50 # push eax 256 24 80 # al <- and 0x80 257 3c 00 # compare al, 0 258 58 # pop to eax (without touching flags) 259 75 1d # jump to epilogue if != [label] 260 # 13c: 261 # al <- *(keyboard normal map + eax) 262 8a # copy m8 at rm32 to r8 263 80 # 10/mod/*+disp32 000/r8/al 000/rm32/eax 264 00 80 00 00 # disp32 [label] 265 # if there's no character mapping, return 266 3c 00 # compare al, 0 267 74 13 # jump to epilogue if = [label] 268 # 146: 269 # store al in keyboard buffer 270 88 # copy r8 to m8 at r32 271 81 # 10/mod/*+disp32 000/r8/al 001/rm32/ecx 272 d0 7d 00 00 # disp32 [label] 273 # 14c: 274 # increment index 275 fe # increment byte 276 05 # 00/mod/indirect 000/subop/increment 101/rm32/use-disp32 277 c8 7d 00 00 # disp32 [label] 278 # clear top nibble of index (keyboard buffer is circular) 279 80 # and byte 280 25 # 00/mod/indirect 100/subop/and 101/rm32/use-disp32 281 c8 7d 00 00 # disp32 [label] 282 0f # imm8 283 # 159: 284 # epilogue 285 61 # pop all registers 286 fb # enable interrupts 287 cf # iret 288 289 # padding 290 # 15c: 291 00 00 00 00 292 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 293 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 294 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 295 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 296 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 297 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 298 00 00 00 00 00 00 00 00 299 300 # 1c8: 301 # var keyboard circular buffer 302 # write index: nibble 303 # still take up 4 bytes so SubX can handle it 304 00 00 00 00 305 # 1cc: 306 # read index: nibble 307 # still take up 4 bytes so SubX can handle it 308 00 00 00 00 309 # 1d0: 310 # circular buffer: byte[16] 311 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 312 313 # padding 314 # 1e0: 315 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 316 00 00 00 00 00 00 00 00 317 318 # 1f8: 319 # idt_descriptor: 320 ff 00 # idt_end - idt_start - 1 321 00 7e 00 00 # start = idt_start [label] 322 323 # 1fe: 324 # final 2 bytes of boot sector 325 55 aa 326 327 ## sector 2 328 # loaded by load_disk, not automatically on boot 329 330 # offset 200 (address 0x7e00): interrupt descriptor table 331 # 32 entries * 8 bytes each = 256 bytes (0x100) 332 # idt_start: 333 334 00 00 00 00 00 00 00 00 335 00 00 00 00 00 00 00 00 336 00 00 00 00 00 00 00 00 337 00 00 00 00 00 00 00 00 338 00 00 00 00 00 00 00 00 339 00 00 00 00 00 00 00 00 340 00 00 00 00 00 00 00 00 341 00 00 00 00 00 00 00 00 342 343 # entry 8: clock 344 00 7d # target[0:16] = null interrupt handler [label] 345 08 00 # segment selector (gdt_code) 346 00 # unused 347 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 348 00 00 # target[16:32] 349 350 # entry 9: keyboard 351 10 7d # target[0:16] = keyboard interrupt handler [label] 352 08 00 # segment selector (gdt_code) 353 00 # unused 354 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 355 00 00 # target[16:32] 356 357 00 00 00 00 00 00 00 00 358 00 00 00 00 00 00 00 00 359 00 00 00 00 00 00 00 00 360 00 00 00 00 00 00 00 00 361 00 00 00 00 00 00 00 00 362 00 00 00 00 00 00 00 00 363 00 00 00 00 00 00 00 00 364 00 00 00 00 00 00 00 00 365 00 00 00 00 00 00 00 00 366 00 00 00 00 00 00 00 00 367 00 00 00 00 00 00 00 00 368 00 00 00 00 00 00 00 00 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 # idt_end: 380 381 # offset 300 (address 0x7f00): 382 # video mode info: 383 00 00 # attributes 384 00 # winA 385 00 # winB 386 # 304 387 00 00 # granularity 388 00 00 # winsize 389 # 308 390 00 00 # segmentA 391 00 00 # segmentB 392 # 30c 393 00 00 00 00 # realFctPtr (who knows) 394 # 310 395 00 00 # pitch 396 00 00 # Xres 397 # 314 398 00 00 # Yres 399 00 00 # Wchar Ychar 400 # 318 401 00 # planes 402 00 # bpp 403 00 # banks 404 00 # memory_model 405 # 31c 406 00 # bank_size 407 00 # image_pages 408 00 # reserved 409 # 31f 410 00 00 # red_mask red_position 411 00 00 # green_mask green_position 412 00 00 # blue_mask blue_position 413 00 00 # rsv_mask rsv_position 414 00 # directcolor_attributes 415 # 328 416 00 00 00 00 # physbase <== linear frame buffer 417 418 # 32c 419 # reserved for video mode info 420 00 00 00 00 421 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 422 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 423 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 424 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 425 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 426 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 427 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 428 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 429 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 430 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 431 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 432 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 433 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 434 435 ## the rest of this file has data 436 437 # offset 400 (address 0x8000): 438 +--161 lines: # translating keys to ASCII ----------------------------------------------------------------------------------------------------------------------------------------------- 599 600 # offset c00 (address 0x8800) 601 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------ 837 838 # offset 1400 (address 0x9000) 839 840 # vim:ft=subx