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