https://github.com/akkartik/mu/blob/master/baremetal/boot.hex
  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