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