1 # A minimal bootable image that: 2 # - loads more sectors past the first boot sector (using BIOS primitives) 3 # - switches to 32-bit mode (giving up access to BIOS primitives) 4 # - sets up a handler for keyboard events 5 # - as an example program, prints '1' to the top-left position on screen (by writing to memory-mapped VGA memory) when the '1' key is typed 6 # 7 # When it's ready to accept keys, it prints 'H' to the top-left of the screen. 8 # 9 # If the initial load fails, it prints 'D' to the top-left of the screen and 10 # halts. 11 # 12 # To convert to a disk image, first prepare a realistically sized disk image: 13 # dd if=/dev/zero of=disk.img count=20160 # 512-byte sectors, so 10MB 14 # Now fill in sectors: 15 # ./bootstrap run apps/hex < baremetal/boot0.hex > boot.bin 16 # dd if=boot.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 ## 16-bit entry point 31 32 # Upon reset, the IBM PC 33 # loads the first sector (512 bytes) 34 # from some bootable image (see the boot sector marker at the end of this file) 35 # to the address range [0x7c00, 0x7e00) 36 37 # offset 00 (address 0x7c00): 38 # disable interrupts for this initialization 39 fa # cli 40 41 # initialize segment registers 42 # this isn't always needed, but the recommendation is to not make assumptions 43 b8 00 00 # ax <- 0 44 8e d8 # ds <- ax 45 8e d0 # ss <- ax 46 8e c0 # es <- ax 47 8e e0 # fs <- ax 48 8e e8 # gs <- ax 49 50 # We don't read or write the stack before we get to 32-bit mode. No function 51 # calls, so we don't need to initialize the stack. 52 53 # 0e: 54 # load more sectors from disk 55 b4 02 # ah <- 2 # read sectors from disk 56 # dl comes conveniently initialized at boot time with the index of the device being booted 57 b5 00 # ch <- 0 # cylinder 0 58 b6 00 # dh <- 0 # track 0 59 b1 02 # cl <- 2 # second sector, 1-based 60 b0 01 # al <- 1 # number of sectors to read 61 # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment 62 bb 00 00 # bx <- 0 63 8e c3 # es <- bx 64 bb 00 7e # bx <- 0x7e00 65 cd 13 # int 13h, BIOS disk service 66 0f 82 76 00 # jump-if-carry disk-error 67 68 # 26: 69 # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line 70 # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S 71 # seta20.1: 72 e4 64 # al <- port 0x64 73 a8 02 # set zf if bit 1 (second-least significant) is not set 74 75 fa # if zf not set, goto seta20.1 (-6) 75 76 b0 d1 # al <- 0xd1 77 e6 64 # port 0x64 <- al 78 79 # 30: 80 # seta20.2: 81 e4 64 # al <- port 0x64 82 a8 02 # set zf if bit 1 (second-least significant) is not set 83 75 fa # if zf not set, goto seta20.2 (-6) 84 85 b0 df # al <- 0xdf 86 e6 64 # port 0x64 <- al 87 88 # 3a: 89 # switch to 32-bit mode 90 0f 01 16 # lgdt 00/mod/indirect 010/subop 110/rm/use-disp16 91 80 7c # *gdt_descriptor 92 # 3f: 93 0f 20 c0 # eax <- cr0 94 66 83 c8 01 # eax <- or 0x1 95 0f 22 c0 # cr0 <- eax 96 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) 97 98 # padding 99 # 4e: 100 00 00 101 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 102 103 ## GDT: 3 records of 8 bytes each 104 105 # 60: 106 # gdt_start: 107 # gdt_null: mandatory null descriptor 108 00 00 00 00 00 00 00 00 109 # gdt_code: (offset 8 from gdt_start) 110 ff ff # limit[0:16] 111 00 00 00 # base[0:24] 112 9a # 1/present 00/privilege 1/descriptor type = 1001b 113 # 1/code 0/conforming 1/readable 0/accessed = 1010b 114 cf # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b 115 # limit[16:20] = 1111b 116 00 # base[24:32] 117 # gdt_data: (offset 16 from gdt_start) 118 ff ff # limit[0:16] 119 00 00 00 # base[0:24] 120 92 # 1/present 00/privilege 1/descriptor type = 1001b 121 # 0/data 0/conforming 1/readable 0/accessed = 0010b 122 cf # same as gdt_code 123 00 # base[24:32] 124 # gdt_end: 125 126 # padding 127 # 78: 128 00 00 00 00 00 00 00 00 129 130 # 80: 131 # gdt_descriptor: 132 17 00 # final index of gdt = gdt_end - gdt_start - 1 133 60 7c 00 00 # start = gdt_start 134 135 # padding 136 # 85: 137 00 00 00 00 00 00 00 00 00 00 138 139 # 90: 140 # disk_error: 141 # print 'D' to top-left of screen to indicate disk error 142 # *0xb8000 <- 0x0f44 143 # bx <- 0xb800 144 bb 00 b8 145 # ds <- bx 146 8e db # 11b/mod 011b/reg/ds 011b/rm/bx 147 # al <- 'D' 148 b0 44 149 # ah <- 0x0f # white on black 150 b4 0f 151 # bx <- 0 152 bb 00 00 153 # *ds:bx <- ax 154 89 07 # 00b/mod/indirect 000b/reg/ax 111b/rm/bx 155 156 e9 fb ff # loop forever 157 158 # padding 159 # a1: 160 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 161 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 162 163 ## 32-bit code from this point (still some instructions not in SubX) 164 165 # c0: 166 # initialize_32bit_mode: 167 66 b8 10 00 # ax <- offset 16 from gdt_start 168 8e d8 # ds <- ax 169 8e d0 # ss <- ax 170 8e c0 # es <- ax 171 8e e0 # fs <- ax 172 8e e8 # gs <- ax 173 174 # load interrupt handlers 175 0f 01 1d # lidt 00/mod/indirect 011/subop 101/rm32/use-disp32 176 00 7f 00 00 # *idt_descriptor 177 178 # enable keyboard IRQ 179 b0 fd # al <- 0xfd # enable just IRQ1 180 e6 21 # port 0x21 <- al 181 182 # initialization is done; enable interrupts 183 fb 184 e9 21 00 00 00 # jump to 0x7d00 185 186 # padding 187 # df: 188 00 189 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 190 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 191 192 ## 'application' SubX code: print one character to top-left of screen 193 194 # offset 100 (address 0x7d00): 195 # Entry: 196 # eax <- *0x7ff4 # random address in second segment containing 'H' 197 8b # copy rm32 to r32 198 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 199 # disp32 200 f4 7f 00 00 201 # *0xb8000 <- eax 202 89 # copy r32 to rm32 203 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 204 # disp32 205 00 80 0b 00 206 207 e9 fb ff ff ff # loop forever 208 209 # padding 210 # 111: 211 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 212 213 # 120: 214 # null interrupt handler: 215 cf # iret 216 217 # padding 218 # 121: 219 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 220 221 # 130: 222 # keyboard interrupt handler: 223 # prologue 224 fa # disable interrupts 225 60 # push all registers to stack 226 # acknowledge interrupt 227 b0 20 # al <- 0x20 228 e6 20 # port 0x20 <- al 229 # read keyboard status (TODO: why bit 0? Doesn't line up with https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html) 230 #? e4 64 # al <- port 0x64 231 #? a8 01 # set zf if bit 0 (least significant) is not set 232 #? 74 11 # if bit 0 is not set, skip to epilogue 233 # read keycode into eax 234 31 c0 # eax <- xor eax; 11/direct 000/r32/eax 000/rm32/eax 235 e4 60 # al <- port 0x60 236 # map key '1' to ascii; if eax == 2, eax = 0x31 237 3d 02 00 00 00 # compare eax with 0x02 238 75 0b # if not equal, goto epilogue 239 b8 31 0f 00 00 # eax <- 0x0f31 240 # print eax to top-left of screen (*0xb8000) 241 89 # copy r32 to rm32 242 05 # 00/mod/indirect 000/r32/eax 101/rm32/use-disp32 243 # disp32 244 00 80 0b 00 245 # epilogue 246 61 # pop all registers 247 fb # enable interrupts 248 cf # iret 249 250 # padding 251 # 14f 252 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 00 00 256 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 257 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 258 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 259 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 260 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 261 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 262 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 263 00 00 00 00 00 00 00 00 00 00 00 00 00 00 264 265 # final 2 bytes of boot sector 266 55 aa 267 268 ## sector 2 269 # loaded by load_disk, not automatically on boot 270 271 # offset 200 (address 0x7e00): interrupt descriptor table 272 # 32 entries * 8 bytes each = 256 bytes (0x100) 273 # idt_start: 274 275 00 00 00 00 00 00 00 00 276 00 00 00 00 00 00 00 00 277 00 00 00 00 00 00 00 00 278 00 00 00 00 00 00 00 00 279 00 00 00 00 00 00 00 00 280 00 00 00 00 00 00 00 00 281 00 00 00 00 00 00 00 00 282 00 00 00 00 00 00 00 00 283 284 # entry 8: clock 285 20 7d # target[0:16] = null interrupt handler 286 08 00 # segment selector (gdt_code) 287 00 # unused 288 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 289 00 00 # target[16:32] 290 291 # entry 9: keyboard 292 30 7d # target[0:16] = keyboard interrupt handler 293 08 00 # segment selector (gdt_code) 294 00 # unused 295 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate 296 00 00 # target[16:32] 297 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 00 00 00 00 00 00 00 00 313 00 00 00 00 00 00 00 00 314 00 00 00 00 00 00 00 00 315 00 00 00 00 00 00 00 00 316 00 00 00 00 00 00 00 00 317 00 00 00 00 00 00 00 00 318 00 00 00 00 00 00 00 00 319 00 00 00 00 00 00 00 00 320 # idt_end: 321 322 # offset 300 (address 0x7f00): 323 # idt_descriptor: 324 ff 00 # idt_end - idt_start - 1 325 00 7e 00 00 # start = idt_start 326 327 # padding 328 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 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 337 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 338 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 339 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 340 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 341 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 342 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 343 00 00 00 00 48 0f 00 00 00 00 00 00 00 00 00 00 # spot the 'H' with attributes 344 # offset 400 (address 0x8000) 345 346 # vim:ft=conf