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