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