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 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 20 # al <- 32 # number of sectors to read; all sectors must be in a single track 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 35 # 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 23 # 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 19 # 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 # store al in keyboard buffer 266 88 # copy r8 to m8 at r32 267 81 # 10/mod/*+disp32 000/r8/al 001/rm32/ecx 268 d0 7d 00 00 # disp32 [label] 269 # 148: 270 # increment index 271 fe # increment byte 272 05 # 00/mod/indirect 000/subop/increment 101/rm32/use-disp32 273 c8 7d 00 00 # disp32 [label] 274 # clear top nibble of index (keyboard buffer is circular) 275 80 # and byte 276 25 # 00/mod/indirect 100/subop/and 101/rm32/use-disp32 277 c8 7d 00 00 # disp32 [label] 278 0f # imm8 279 # 155: 280 # epilogue 281 61 # pop all registers 282 fb # enable interrupts 283 cf # iret 284 285 # padding 286 # 158: 287 00 00 00 00 00 00 00 00 288 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 289 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 290 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 291 00 00 00 00 00 00 00 00 00 00 00 00 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 295 296 # 1c8: 297 # var keyboard circular buffer 298 # write index: nibble 299 # still take up 4 bytes so SubX can handle it 300 00 00 00 00 301 # 1cc: 302 # read index: nibble 303 # still take up 4 bytes so SubX can handle it 304 00 00 00 00 305 # 1d0: 306 # circular buffer: byte[16] 307 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 308 309 # padding 310 # 1e0: 311 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 312 00 00 00 00 00 00 00 00 313 314 # 1f8: 315 # idt_descriptor: 316 ff 00 # idt_end - idt_start - 1 317 00 7e 00 00 # start = idt_start [label] 318 319 # 1fe: 320 # final 2 bytes of boot sector 321 55 aa 322 323 ## sector 2 324 # loaded by load_disk, not automatically on boot 325 326 # offset 200 (address 0x7e00): interrupt descriptor table 327 # 32 entries * 8 bytes each = 256 bytes (0x100) 328 # idt_start: 329 330 00 00 00 00 00 00 00 00 331 00 00 00 00 00 00 00 00 332 00 00 00 00 00 00 00 00 333 00 00 00 00 00 00 00 00 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 339 # entry 8: clock 340 00 7d # target[0:16] = null interrupt handler [label] 341 08 00 # segment selector (gdt_code) 342 00 # unused 343 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 344 00 00 # target[16:32] 345 346 # entry 9: keyboard 347 10 7d # target[0:16] = keyboard interrupt handler [label] 348 08 00 # segment selector (gdt_code) 349 00 # unused 350 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 351 00 00 # target[16:32] 352 353 00 00 00 00 00 00 00 00 354 00 00 00 00 00 00 00 00 355 00 00 00 00 00 00 00 00 356 00 00 00 00 00 00 00 00 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 # idt_end: 376 377 # offset 300 (address 0x7f00): 378 # video mode info: 379 00 00 # attributes 380 00 # winA 381 00 # winB 382 # 304 383 00 00 # granularity 384 00 00 # winsize 385 # 308 386 00 00 # segmentA 387 00 00 # segmentB 388 # 30c 389 00 00 00 00 # realFctPtr (who knows) 390 # 310 391 00 00 # pitch 392 00 00 # Xres 393 # 314 394 00 00 # Yres 395 00 00 # Wchar Ychar 396 # 318 397 00 # planes 398 00 # bpp 399 00 # banks 400 00 # memory_model 401 # 31c 402 00 # bank_size 403 00 # image_pages 404 00 # reserved 405 # 31f 406 00 00 # red_mask red_position 407 00 00 # green_mask green_position 408 00 00 # blue_mask blue_position 409 00 00 # rsv_mask rsv_position 410 00 # directcolor_attributes 411 # 328 412 00 00 00 00 # physbase <== linear frame buffer 413 414 # 32c 415 # reserved for video mode info 416 00 00 00 00 417 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 418 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 419 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 420 00 00 00 00 00 00 00 00 00 00 00 00 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 431 ## the rest of this file has data 432 433 # offset 400 (address 0x8000): 434 +--158 lines: # translating keys to ASCII ----------------------------------------------------------------------------------------------------------------------------------------------- 592 593 # offset c00 (address 0x8800) 594 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------ 830 831 # offset 1400 (address 0x9000) 832 833 # vim:ft=subx