https://github.com/akkartik/mu/blob/main/baremetal/boot.hex
  1 # Code for the first few 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 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: 5120 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   # For now, not bothering reprogramming the IRQ to not conflict with software
203   # exceptions.
204   #   https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode
205   #
206   # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a
207   # debugger.
208   # Reference:
209   #   https://wiki.osdev.org/Exceptions
210 
211   # enable keyboard IRQ (1)
212   b0 fd  # al <- 0xfd  # disable mask for IRQ1
213   e6 21  # port 0x21 <- al
214 
215   # initialization is done; enable interrupts
216   fb
217   e9 01 13 00 00  # jump to 0x9000 [label]
218 
219 # padding
220 # ff:
221                                              00
222 
223 # 100:
224 # null interrupt handler:
225   cf  # iret
226 
227 # padding
228 # 101:
229    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
230 
231 # 110:
232 # keyboard interrupt handler:
233   # prologue
234   fa  # disable interrupts
235   60  # push all registers to stack
236   # acknowledge interrupt
237   b0 20  # al <- 0x20
238   e6 20  # port 0x20 <- al
239   # read status into eax
240   31 c0  # eax <- xor eax;  11/direct 000/r32/eax 000/rm32/eax
241   e4 64  # al <- port 0x64
242   # if (status & 0x1) == 0, return
243   24 01  # al <- and 0x1
244   3c 00  # compare al, 0
245   74 39  # jump to epilogue if = [label]
246 # 120:
247   # if keyboard buffer is full, return
248   31 c9  # ecx <- xor ecx;  11/direct 001/r32/ecx 001/rm32/ecx
249   # . var index/ecx: byte
250   8a  # copy m8 at r32 to r8
251     0d  # 00/mod/indirect 001/r8/cl 101/rm32/use-disp32
252     c8 7d 00 00  # disp32 [label]
253   # . al = *(keyboard buffer + index)
254   8a  # copy m8 at r32 to r8
255     81  # 10/mod/*+disp32 000/r8/al 001/rm32/ecx
256     d0 7d 00 00  # disp32 [label]
257   # . if (al != 0) return
258   3c 00  # compare al, 0
259 # 130:
260   75 27  # jump to epilogue if != [label]
261   # read keycode into al
262   e4 60  # al <- port 0x60
263   # if (al & 0x80) a key is being lifted; return
264   50  # push eax
265   24 80  # al <- and 0x80
266   3c 00  # compare al, 0
267   58  # pop to eax (without touching flags)
268   75 1d  # jump to epilogue if != [label]
269 # 13c:
270   # al <- *(keyboard normal map + eax)
271   8a  # copy m8 at rm32 to r8
272     80  # 10/mod/*+disp32 000/r8/al 000/rm32/eax
273     00 80 00 00  # disp32 [label]
274   # if there's no character mapping, return
275   3c 00  # compare al, 0
276   74 13  # jump to epilogue if = [label]
277 # 146:
278   # store al in keyboard buffer
279   88  # copy r8 to m8 at r32
280     81  # 10/mod/*+disp32 000/r8/al 001/rm32/ecx
281     d0 7d 00 00  # disp32 [label]
282 # 14c:
283   # increment index
284   fe  # increment byte
285     05  # 00/mod/indirect 000/subop/increment 101/rm32/use-disp32
286     c8 7d 00 00  # disp32 [label]
287   # clear top nibble of index (keyboard buffer is circular)
288   80  # and byte
289     25  # 00/mod/indirect 100/subop/and 101/rm32/use-disp32
290     c8 7d 00 00  # disp32 [label]
291     0f  # imm8
292 # 159:
293   # epilogue
294   61  # pop all registers
295   fb  # enable interrupts
296   cf  # iret
297 
298 # padding
299 # 15c:
300                                     00 00 00 00
301 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
302 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
303 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
304 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
305 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
306 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
307 00 00 00 00 00 00 00 00
308 
309 # 1c8:
310 # var keyboard circular buffer
311 # write index: nibble
312 # still take up 4 bytes so SubX can handle it
313   00 00 00 00
314 # 1cc:
315 # read index: nibble
316 # still take up 4 bytes so SubX can handle it
317   00 00 00 00
318 # 1d0:
319 # circular buffer: byte[16]
320   00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
321 
322 # padding
323 # 1e0:
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
326 
327 # 1f8:
328 # idt_descriptor:
329   ff 00  # idt_end - idt_start - 1
330   00 7e 00 00  # start = idt_start [label]
331 
332 # 1fe:
333 # final 2 bytes of boot sector
334 55 aa
335 
336 ## sector 2
337 # loaded by load_disk, not automatically on boot
338 
339 # offset 200 (address 0x7e00): interrupt descriptor table
340 # 32 entries * 8 bytes each = 256 bytes (0x100)
341 # idt_start:
342 
343 00 00 00 00 00 00 00 00
344 00 00 00 00 00 00 00 00
345 00 00 00 00 00 00 00 00
346 00 00 00 00 00 00 00 00
347 00 00 00 00 00 00 00 00
348 00 00 00 00 00 00 00 00
349 00 00 00 00 00 00 00 00
350 00 00 00 00 00 00 00 00
351 
352 # By default, BIOS maps IRQ0-7 to interrupt vectors 8-15.
353 # https://wiki.osdev.org/index.php?title=Interrupts&oldid=25102#Default_PC_Interrupt_Vector_Assignment
354 
355 # entry 8: clock
356   00 7d  # target[0:16] = null interrupt handler [label]
357   08 00  # segment selector (gdt_code)
358   00  # unused
359   8e  # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
360   00 00  # target[16:32]
361 
362 # entry 9: keyboard
363   10 7d  # target[0:16] = keyboard interrupt handler [label]
364   08 00  # segment selector (gdt_code)
365   00  # unused
366   8e  # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
367   00 00  # target[16:32]
368 
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 00 00 00 00 00 00 00 00
380 00 00 00 00 00 00 00 00
381 00 00 00 00 00 00 00 00
382 00 00 00 00 00 00 00 00
383 00 00 00 00 00 00 00 00
384 00 00 00 00 00 00 00 00
385 00 00 00 00 00 00 00 00
386 00 00 00 00 00 00 00 00
387 00 00 00 00 00 00 00 00
388 00 00 00 00 00 00 00 00
389 00 00 00 00 00 00 00 00
390 00 00 00 00 00 00 00 00
391 # idt_end:
392 
393 # offset 300 (address 0x7f00):
394 # video mode info:
395   00 00  # attributes
396   00  # winA
397   00  # winB
398 # 304
399   00 00  # granularity
400   00 00  # winsize
401 # 308
402   00 00  # segmentA
403   00 00  # segmentB
404 # 30c
405   00 00 00 00  # realFctPtr (who knows)
406 # 310
407   00 00  # pitch
408   00 00  # Xres
409 # 314
410   00 00  # Yres
411   00 00  # Wchar Ychar
412 # 318
413   00  # planes
414   00  # bpp
415   00  # banks
416   00  # memory_model
417 # 31c
418   00  # bank_size
419   00  # image_pages
420   00  # reserved
421 # 31f
422   00 00  # red_mask red_position
423   00 00  # green_mask green_position
424   00 00  # blue_mask blue_position
425   00 00  # rsv_mask rsv_position
426   00  # directcolor_attributes
427 # 328
428   00 00 00 00  # physbase <== linear frame buffer
429 
430 # 32c
431 # reserved for video mode info
432                                     00 00 00 00
433 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
434 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
435 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
436 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
437 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
438 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
439 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
440 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
441 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
442 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
443 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
444 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
445 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
446 
447 ## the rest of this file has data
448 
449 # offset 400 (address 0x8000):
450 +--161 lines: # translating keys to ASCII -----------------------------------------------------------------------------------------------------------------------------------------------
611 
612 # offset c00 (address 0x8800)
613 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------
849 
850 # offset 1400 (address 0x9000)
851 
852 # vim:ft=subx