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