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
9 # Code in this file needs to be more deliberate about the SubX facilities it
11 # - sigils only support 32-bit general-purpose registers, so don't work with segment registers or 16-bit or 8-bit registers
12 # - metadata like rm32 and r32 can sometimes misleadingly refer to only the bottom 16 bits of the register; pay attention to the register name
14 # While most of Mu is thoroughly tested, this file is not. I don't yet
15 # understand hardware interfaces well enough to explain to others.
17 # Memory map of a Mu computer:
18 # code: [0x00007c00, 0x0007de00)
19 # system font: [0x00100000, 0x00f00000)
20 # stack: (0x02000000, 0x01000000]
21 # heap: [0x02000000, 0x80000000)
22 # see 120allocate.subx; Qemu initializes with 128MB RAM by default; simulating 2GB RAM is known to work
23 # Consult https://wiki.osdev.org/Memory_Map_(x86) before modifying any of
24 # this. And don't forget to keep *stack-debug.subx in sync.
28 ## 16-bit entry point: 0x7c00
30 # Upon reset, the IBM PC:
31 # - loads the first sector (512 bytes)
32 # from some bootable image (look for the boot-sector-marker further down this file)
33 # to the address range [0x7c00, 0x7e00)
34 # - starts executing code at address 0x7c00
38 # initialize segment registers
40 8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
41 8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
42 8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
43 8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
45 # Temporarily initialize stack to 0x00070000 in real mode.
46 # We don't read or write the stack before we get to 32-bit mode, but BIOS
47 # calls do. We need to move the stack in case BIOS initializes it to some
48 # low address that we want to write code into.
49 b8/copy-to-ax 0x7000/imm16
50 8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
51 bc/copy-to-esp 0/imm16
53 # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
54 # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
56 e4/read-port-into-al 0x64/imm8
57 a8/test-bits-in-al 0x02/imm8 # set zf if bit 1 (second-least significant) is not set
58 75/jump-if-!zero loop/disp8
59 b0/copy-to-al 0xd1/imm8
60 e6/write-al-into-port 0x64/imm8
63 e4/read-port-into-al 0x64/imm8
64 a8/test-bits-in-al 0x02/imm8 # set zf if bit 1 (second-least significant) is not set
65 75/jump-if-!zero loop/disp8
66 b0/copy-to-al 0xdf/imm8
67 e6/write-al-into-port 0x64/imm8
70 # load remaining sectors from first two tracks of disk into addresses [0x7e00, 0x17800)
71 b4/copy-to-ah 2/imm8/read-drive
72 # dl comes conveniently initialized at boot time with the index of the device being booted
73 b5/copy-to-ch 0/imm8/cylinder
74 b6/copy-to-dh 0/imm8/head # <====
75 b1/copy-to-cl 2/imm8/sector # 1-based
76 b0/copy-to-al 0x7d/imm8/num-sectors # 2*63 - 1 = 125
77 # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
79 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
80 bb/copy-to-bx 0x7e00/imm16 # <====
81 cd/syscall 0x13/imm8/bios-disk-services
82 0f 82/jump-if-carry disk_error/disp16
84 # load two more tracks of disk into addresses [0x17800, 0x27400)
85 b4/copy-to-ah 2/imm8/read-drive
86 # dl comes conveniently initialized at boot time with the index of the device being booted
87 b5/copy-to-ch 0/imm8/cylinder
88 b6/copy-to-dh 2/imm8/head # <====
89 b1/copy-to-cl 1/imm8/sector # 1-based
90 b0/copy-to-al 0x7e/imm8/num-sectors # 2*63 = 126
91 # address to write sectors to = es:bx = 0x17800, contiguous with boot segment
92 bb/copy-to-bx 0x1780/imm16 # <====
93 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
95 cd/syscall 0x13/imm8/bios-disk-services
96 0f 82/jump-if-carry disk_error/disp16
98 # load two more tracks of disk into addresses [0x27400, 0x37000)
99 b4/copy-to-ah 2/imm8/read-drive
100 # dl comes conveniently initialized at boot time with the index of the device being booted
101 b5/copy-to-ch 0/imm8/cylinder
102 b6/copy-to-dh 4/imm8/head # <====
103 b1/copy-to-cl 1/imm8/sector # 1-based
104 b0/copy-to-al 0x7e/imm8/num-sectors # 2*63 = 126
105 # address to write sectors to = es:bx = 0x27400, contiguous with boot segment
106 bb/copy-to-bx 0x2740/imm16 # <====
107 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
108 bb/copy-to-bx 0/imm16
109 cd/syscall 0x13/imm8/bios-disk-services
110 0f 82/jump-if-carry disk_error/disp16
112 # load two more tracks of disk into addresses [0x37000, 0x46c00)
113 b4/copy-to-ah 2/imm8/read-drive
114 # dl comes conveniently initialized at boot time with the index of the device being booted
115 b5/copy-to-ch 0/imm8/cylinder
116 b6/copy-to-dh 6/imm8/head # <====
117 b1/copy-to-cl 1/imm8/sector # 1-based
118 b0/copy-to-al 0x7e/imm8/num-sectors # 2*63 = 126
119 # address to write sectors to = es:bx = 0x37000, contiguous with boot segment
120 bb/copy-to-bx 0x3700/imm16 # <====
121 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
122 bb/copy-to-bx 0/imm16
123 cd/syscall 0x13/imm8/bios-disk-services
124 0f 82/jump-if-carry disk_error/disp16
126 # load two more tracks of disk into addresses [0x46c00, 0x56800)
127 b4/copy-to-ah 2/imm8/read-drive
128 # dl comes conveniently initialized at boot time with the index of the device being booted
129 b5/copy-to-ch 0/imm8/cylinder
130 b6/copy-to-dh 8/imm8/head # <====
131 b1/copy-to-cl 1/imm8/sector # 1-based
132 b0/copy-to-al 0x7e/imm8/num-sectors # 2*63 = 126
133 # address to write sectors to = es:bx = 0x46c00, contiguous with boot segment
134 bb/copy-to-bx 0x46c0/imm16 # <====
135 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
136 bb/copy-to-bx 0/imm16
137 cd/syscall 0x13/imm8/bios-disk-services
138 0f 82/jump-if-carry disk_error/disp16
140 # load two more tracks of disk into addresses [0x56800, 0x66400)
141 b4/copy-to-ah 2/imm8/read-drive
142 # dl comes conveniently initialized at boot time with the index of the device being booted
143 b5/copy-to-ch 0/imm8/cylinder
144 b6/copy-to-dh 0xa/imm8/head # <====
145 b1/copy-to-cl 1/imm8/sector # 1-based
146 b0/copy-to-al 0x7e/imm8/num-sectors # 2*63 = 126
147 # address to write sectors to = es:bx = 0x56800, contiguous with boot segment
148 bb/copy-to-bx 0x5680/imm16 # <====
149 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
150 bb/copy-to-bx 0/imm16
151 cd/syscall 0x13/imm8/bios-disk-services
152 0f 82/jump-if-carry disk_error/disp16
154 # load two more tracks of disk into addresses [0x66400, 0x76000)
155 b4/copy-to-ah 2/imm8/read-drive
156 # dl comes conveniently initialized at boot time with the index of the device being booted
157 b5/copy-to-ch 0/imm8/cylinder
158 b6/copy-to-dh 0xc/imm8/head # <====
159 b1/copy-to-cl 1/imm8/sector # 1-based
160 b0/copy-to-al 0x7e/imm8/num-sectors # 2*63 = 126
161 # address to write sectors to = es:bx = 0x56800, contiguous with boot segment
162 bb/copy-to-bx 0x6640/imm16 # <====
163 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
164 bb/copy-to-bx 0/imm16
165 cd/syscall 0x13/imm8/bios-disk-services
166 0f 82/jump-if-carry disk_error/disp16
168 # load one final track of disk into addresses [0x76000, 0x7de00)
169 b4/copy-to-ah 2/imm8/read-drive
170 # dl comes conveniently initialized at boot time with the index of the device being booted
171 b5/copy-to-ch 0/imm8/cylinder
172 b6/copy-to-dh 0xe/imm8/head # <====
173 b1/copy-to-cl 1/imm8/sector # 1-based
174 b0/copy-to-al 0x3f/imm8/num-sectors=63
175 # address to write sectors to = es:bx = 0x56800, contiguous with boot segment
176 bb/copy-to-bx 0x7600/imm16 # <====
177 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
178 bb/copy-to-bx 0/imm16
179 cd/syscall 0x13/imm8/bios-disk-services
180 0f 82/jump-if-carry disk_error/disp16
182 ### Loading more code tracks would clobber BIOS; we need a new compilation strategy.
185 bb/copy-to-bx 0/imm16
186 8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
189 b4/copy-to-ah 0x4f/imm8 # VBE commands
190 b0/copy-to-al 2/imm8 # set video mode
191 bb/copy-to-bx 0x4105/imm16 # 0x0105 | 0x4000
192 # 0x0105 = graphics mode 1024x768x256
193 # (alternative candidate: 0x0101 for 640x480x256)
194 # 0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively
195 cd/syscall 0x10/imm8/bios-video-services
197 # load information for the (hopefully) current video mode
198 # mostly just for the address to the linear frame buffer
199 b4/copy-to-ah 0x4f/imm8 # VBE commands
200 b0/copy-to-al 1/imm8 # get video mode info
201 b9/copy-to-cx 0x0105/imm16 # mode we requested
202 bf/copy-to-di Video-mode-info/imm16
203 cd/syscall 0x10/imm8/bios-video-services
205 ## switch to 32-bit mode
206 # load global descriptor table
207 # We can't refer to the label directly because SubX doesn't do the right
208 # thing for lgdt, so rather than make errors worse in most places we instead
209 # pin gdt_descriptor below.
210 0f 01 2/subop/lgdt 0/mod/indirect 6/rm32/use-disp16 0x7de0/disp16/gdt_descriptor
212 0f 20/<-cr 3/mod/direct 0/rm32/eax 0/r32/cr0
213 66 83 1/subop/or 3/mod/direct 0/rm32/eax 1/imm8 # eax <- or 0x1
214 0f 22/->cr 3/mod/direct 0/rm32/eax 0/r32/cr0
215 # far jump to initialize_32bit_mode that sets cs to offset 8 in the gdt in the process
216 # We can't refer to the label directly because SubX doesn't have syntax for
217 # segment selectors. So we instead pin initialize_32bit_mode below.
218 ea/jump-far-absolute 0x00087e00/disp32 # address 0x7e00 in offset 8 of the gdt
221 # print 'D' to top-left of screen to indicate disk error
223 bb/copy-to-bx 0xb800/imm16
224 8e/->seg 3/mod/direct 3/rm32/bx 3/r32/ds
225 b0/copy-to-al 0x44/imm8/D
226 b4/copy-to-ah 0x0f/imm8/white-on-black
227 bb/copy-to-bx 0/imm16
228 89/<- 0/mod/indirect 7/rm32/bx 0/r32/ax # *ds:bx <- ax
234 ## GDT: 3 records of 8 bytes each
237 0x17/imm16 # final index of gdt = size of gdt - 1
238 gdt_start/imm32/start
241 # offset 0: gdt_null: mandatory null descriptor
242 00 00 00 00 00 00 00 00
245 00 00 00 # base[0:24]
246 9a # 1/present 00/privilege 1/descriptor type = 1001b
247 # 1/code 0/conforming 1/readable 0/accessed = 1010b
248 cf # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b
249 # limit[16:20] = 1111b
251 # offset 16: gdt_data
253 00 00 00 # base[0:24]
254 92 # 1/present 00/privilege 1/descriptor type = 1001b
255 # 0/data 0/conforming 1/readable 0/accessed = 0010b
256 cf # same as gdt_code
260 == boot-sector-marker 0x7dfe
261 # final 2 bytes of boot sector
264 ## sector 2 onwards loaded by load_disk, not automatically on boot
266 ## 32-bit code from this point
269 initialize_32bit_mode:
270 66 b8/copy-to-ax 0x10/imm16 # offset 16 from gdt_start
271 8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
272 8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
273 8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
274 8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
275 8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
277 bc/copy-to-esp 0x02000000/imm32
279 ## install the font somewhere non-contiguous (keep sync'd with memory map up top)
280 c7 0/subop/copy *0x00100000 0/imm32/read
281 c7 0/subop/copy *0x00100004 0/imm32/write
282 c7 0/subop/copy *0x00100008 0x00e00000/imm32/size
283 (load-sectors Primary-bus-primary-drive 0x2328 0x200 0x00100000) # source 0x2328 = sector 9000 on disk, destination 0x00100000
284 # Font is now loaded starting at 0x0010000c.
286 ## load interrupt handlers
287 # We can't refer to the label directly because SubX doesn't do the right
288 # thing for lidt, so rather than make errors worse in most places we instead
289 # pin idt_descriptor below.
290 0f 01 3/subop/lidt 0/mod/indirect 5/rm32/use-disp32 0x7f00/disp32/idt_descriptor
292 # For now, not bothering reprogramming the IRQ to not conflict with software
294 # https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode
296 # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a
299 # https://wiki.osdev.org/Exceptions
301 # enable timer IRQ0 and keyboard IRQ1
302 b0/copy-to-al 0xfc/imm8 # disable mask for IRQ0 and IRQ1
303 e6/write-al-into-port 0x21/imm8
309 ## enable floating point
310 db/floating-point-coprocessor e3/initialize
312 0f 20/<-cr 3/mod/direct 0/rm32/eax 4/r32/cr4
314 0f ba/bit-test 5/subop/bit-test-and-set 3/mod/direct 0/rm32/eax 9/imm8
316 0f 22/->cr 3/mod/direct 0/rm32/eax 4/r32/cr4
322 ff 03 # final index of idt = size of idt - 1
323 idt_start/imm32/start
325 # interrupt descriptor table {{{
326 # 32 entries of 8 bytes each
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
339 # By default, BIOS maps IRQ0-7 to interrupt vectors 8-15.
340 # https://wiki.osdev.org/index.php?title=Interrupts&oldid=25102#Default_PC_Interrupt_Vector_Assignment
342 # entry 8: https://wiki.osdev.org/Programmable_Interval_Timer
343 timer-interrupt-handler/imm16 # target[0:16]
344 8/imm16 # segment selector (gdt_code)
346 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
347 0/imm16 # target[16:32] -- timer-interrupt-handler must be within address 0x10000
350 keyboard-interrupt-handler/imm16 # target[0:16]
351 8/imm16 # segment selector (gdt_code)
353 8e # 1/p 00/dpl 0 1110/type/32-bit-interrupt-gate
354 0/imm16 # target[16:32] -- keyboard-interrupt-handler must be within address 0x10000
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 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
383 timer-interrupt-handler:
385 fa/disable-interrupts
386 60/push-all-registers
388 # acknowledge interrupt
389 b0/copy-to-al 0x20/imm8
390 e6/write-al-into-port 0x20/imm8
391 31/xor %eax 0/r32/eax
392 # update *Timer-current-color
393 ff 0/subop/increment *Timer-counter
394 $timer-interrupt-handler:epilogue:
399 cf/return-from-interrupt
406 keyboard-interrupt-handler:
408 fa/disable-interrupts
409 60/push-all-registers
411 # acknowledge interrupt
412 b0/copy-to-al 0x20/imm8
413 e6/write-al-into-port 0x20/imm8
414 31/xor %eax 0/r32/eax
415 # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html)
416 e4/read-port-into-al 0x64/imm8
417 a8/test-bits-in-al 0x01/imm8 # set zf if bit 0 (least significant) is not set
418 0f 84/jump-if-not-set $keyboard-interrupt-handler:end/disp32
419 # - if keyboard buffer is full, return
420 # var dest-addr/ecx: (addr byte) = (keyboard-buffer + *keyboard-buffer:write)
421 31/xor %ecx 1/r32/ecx
422 8a/byte-> *Keyboard-buffer:write 1/r32/cl
423 81 0/subop/add %ecx Keyboard-buffer:data/imm32
425 8a/byte-> *ecx 0/r32/al
426 # if (al != 0) return
427 3c/compare-al-and 0/imm8
428 0f 85/jump-if-!= $keyboard-interrupt-handler:end/disp32
430 e4/read-port-into-al 0x60/imm8
432 # if (al == 0xaa) shift = false # left shift is being lifted
434 3c/compare-al-and 0xaa/imm8
435 75/jump-if-!= break/disp8
437 c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
439 # if (al == 0xb6) shift = false # right shift is being lifted
441 3c/compare-al-and 0xb6/imm8
442 75/jump-if-!= break/disp8
444 c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
446 # if (al == 0x9d) ctrl = false # ctrl is being lifted
448 3c/compare-al-and 0x9d/imm8
449 75/jump-if-!= break/disp8
451 c7 0/subop/copy *Keyboard-ctrl-pressed? 0/imm32
453 # if (al & 0x80) a key is being lifted; return
455 24/and-al-with 0x80/imm8
456 3c/compare-al-and 0/imm8
458 75/jump-if-!= $keyboard-interrupt-handler:end/disp8
460 # if (al == 0x2a) shift = true, return # left shift pressed
462 3c/compare-al-and 0x2a/imm8
463 75/jump-if-!= break/disp8
465 c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
467 eb/jump $keyboard-interrupt-handler:end/disp8
469 # if (al == 0x36) shift = true, return # right shift pressed
471 3c/compare-al-and 0x36/imm8
472 75/jump-if-!= break/disp8
474 c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
476 eb/jump $keyboard-interrupt-handler:end/disp8
478 # if (al == 0x1d) ctrl = true, return
480 3c/compare-al-and 0x1d/imm8
481 75/jump-if-!= break/disp8
483 c7 0/subop/copy *Keyboard-ctrl-pressed? 1/imm32
485 eb/jump $keyboard-interrupt-handler:end/disp8
487 # - convert key to character
488 # if (shift) use keyboard shift map
490 81 7/subop/compare *Keyboard-shift-pressed? 0/imm32
491 74/jump-if-= break/disp8
492 # sigils don't currently support labels inside *(eax+label)
493 05/add-to-eax Keyboard-shift-map/imm32
494 8a/byte-> *eax 0/r32/al
495 eb/jump $keyboard-interrupt-handler:select-map-done/disp8
497 # if (ctrl) al = *(ctrl map + al)
499 81 7/subop/compare *Keyboard-ctrl-pressed? 0/imm32
500 74/jump-if-= break/disp8
501 05/add-to-eax Keyboard-ctrl-map/imm32
502 8a/byte-> *eax 0/r32/al
503 eb/jump $keyboard-interrupt-handler:select-map-done/disp8
505 # otherwise al = *(normal map + al)
506 05/add-to-eax Keyboard-normal-map/imm32
507 8a/byte-> *eax 0/r32/al
508 $keyboard-interrupt-handler:select-map-done:
509 # - if there's no character mapping, return
511 3c/compare-al-and 0/imm8
512 74/jump-if-= break/disp8
513 # - store al in keyboard buffer
516 fe/increment-byte *Keyboard-buffer:write
517 # clear top nibble of index (keyboard buffer is circular)
518 80 4/subop/and-byte *Keyboard-buffer:write 0x0f/imm8
520 $keyboard-interrupt-handler:end:
525 cf/return-from-interrupt
528 Keyboard-shift-pressed?: # boolean
531 Keyboard-ctrl-pressed?: # boolean
534 # var keyboard circular buffer
535 Keyboard-buffer:write: # nibble
537 Keyboard-buffer:read: # nibble
539 Keyboard-buffer:data: # byte[16]
545 # Keyboard maps for translating keys to ASCII {{{
550 # |<--- digits -------------->| - = backspace
551 31 32 33 34 35 36 37 38 39 30 2d 3d 08
553 # tab q w e r t y u i o p [ ]
554 09 71 77 65 72 74 79 75 69 6f 70 5b 5d
559 # a s d f g h j k l ; ' ` \
560 61 73 64 66 67 68 6a 6b 6c 3b 27 60 00 5c
563 # z x c v b n m , . / *
564 7a 78 63 76 62 6e 6d 2c 2e 2f 00 2a
571 00 00 00 00 00 00 00 00
574 82 00 00 80 00 83 00 00 81
576 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
578 # * - Not a valid ASCII/Unicode value.
584 # ! @ # $ % ^ & * ( ) _ + backspace
585 21 40 23 24 25 53 26 2a 28 29 5f 2b 08
587 # tab Q W E R T Y U I O P { }
588 09 51 57 45 52 54 59 55 49 5f 50 7b 7d
593 # A S D F G H J K L : " ~ |
594 41 53 44 46 47 48 4a 4b 4c 3a 22 7e 00 7c
596 # Z X C V B N M < > ? *
597 5a 58 43 56 42 4e 4d 3c 3e 3f 00 2a
603 00 00 00 00 00 00 00 00
606 82 00 00 80 00 83 00 00 81
608 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
610 # * - Not a valid ASCII/Unicode value.
613 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
615 # ^q ^w ^e ^r ^t ^y ^u tb ^o ^p
616 11 17 05 12 14 19 15 09 1f 10 00 00
621 # ^a ^s ^d ^f ^g ^h ^j ^k ^l ^\
622 01 13 04 06 07 08 0a 0b 0c 00 00 00 00 1c
624 # ^z ^x ^c ^v ^b ^n ^m ^/
625 1a 18 03 16 02 0e 0d 00 00 1f 00 00
631 00 00 00 00 00 00 00 00
634 82 00 00 80 00 83 00 00 81
636 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
638 # * - Not a valid ASCII/Unicode value.
642 # video mode info {{{
647 0/imm16 # granularity
653 0/imm32 # realFctPtr (who knows)
658 0/imm16 # Wchar Ychar
669 0/imm16 # red_mask red_position
670 0/imm16 # green_mask green_position
671 0/imm16 # blue_mask blue_position
672 0/imm16 # rsv_mask rsv_position
673 00 # directcolor_attributes
679 # reserved for video mode info
681 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
682 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
683 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
684 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
685 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
686 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
687 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
688 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
689 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
690 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
691 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
692 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
693 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
696 ## Controlling IDE (ATA) hard disks
697 # Uses 28-bit PIO mode.
698 # Inspired by https://colorforth.github.io/ide.html
701 # https://wiki.osdev.org/ATA_PIO_Mode
702 # https://forum.osdev.org/viewtopic.php?f=1&p=167798
703 # read-sector, according to https://www.scs.stanford.edu/11wi-cs140/pintos/specs/ata-3-std.pdf
708 # All ports are 8-bit except data-port, which is 16-bit.
709 Primary-bus-primary-drive:
710 # command-port: int (write)
712 # status-port: int (read)
714 # alternative-status-port: int (read)
716 # error-port: int (read)
718 # drive-and-head-port: int
720 # sector-count-port: int
730 # drive-code: byte # only drive-specific field
731 0xe0/imm32 # LBA mode also enabled
734 # All ports are 8-bit except data-port, which is 16-bit.
735 Primary-bus-secondary-drive:
736 # command-port: int (write)
738 # status-port: int (read)
740 # alternative-status-port: int (read)
742 # error-port: int (read)
744 # drive-and-head-port: int
746 # sector-count-port: int
756 # drive-code: byte # only drive-specific field
757 0xf0/imm32 # LBA mode also enabled
761 # No more than 0x100 sectors
762 read-ata-disk: # disk: (addr disk), lba: int, n: int, out: (addr stream byte)
771 81 7/subop/compare *(ebp+0x10) 0x100/imm32
773 7e/jump-if-<= break/disp8
774 (abort "read-ata-disk: no more than 0x100 sectors")
777 (drive-exists? *(ebp+8)) # => eax
778 3d/compare-eax-and 0/imm32/false
779 0f 84/jump-if-= $read-ata-disk:end/disp32
781 (ata-drive-select *(ebp+8) *(ebp+0xc))
782 (clear-ata-error *(ebp+8))
783 (ata-sector-count *(ebp+8) *(ebp+0x10))
784 (ata-lba *(ebp+8) *(ebp+0xc))
785 (ata-command *(ebp+8) 0x20) # read sectors with retries
789 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "waiting for sector.." 7 0)
790 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "." 7 0)
791 (while-ata-busy *(ebp+8))
792 (until-ata-data-available *(ebp+8))
793 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "reading\n" 7 0)
794 # var data-port/edx = disk->data-port
795 8b/-> *(ebp+8) 0/r32/eax
796 8b/-> *(eax+0x24) 2/r32/edx
798 31/xor %eax 0/r32/eax
799 b9/copy-to-ecx 0x200/imm32 # 512 bytes per sector
801 81 7/subop/compare %ecx 0/imm32
802 74/jump-if-= break/disp8
803 66 ed/read-port-dx-into-ax
804 # write 2 bytes to stream one at a time
805 (append-byte *(ebp+0x14) %eax)
807 c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
808 (append-byte *(ebp+0x14) %eax)
813 ff 1/subop/decrement *(ebp+0x10)
814 #? (draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+0x10) 0xc 0)
815 81 7/subop/compare *(ebp+0x10) 0/imm32
816 7e/jump-if-<= break/disp8
817 (wait-400ns *(ebp+8))
818 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "next sector\n" 7 0)
822 # . restore registers
831 write-ata-disk: # disk: (addr disk), lba: int, n: int, in: (addr stream byte)
841 (drive-exists? *(ebp+8)) # => eax
842 3d/compare-eax-and 0/imm32/false
843 0f 84/jump-if-= $write-ata-disk:end/disp32
845 (ata-drive-select *(ebp+8) *(ebp+0xc))
846 (clear-ata-error *(ebp+8))
847 (ata-sector-count *(ebp+8) *(ebp+0x10))
848 (ata-lba *(ebp+8) *(ebp+0xc))
849 (ata-command *(ebp+8) 0x30) # write sectors with retries
851 #? (set-cursor-position 0 0 0)
852 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "0" 7 0)
855 (while-ata-busy *(ebp+8))
856 (until-ata-ready-for-data *(ebp+8))
857 # var data-port/edx = disk->data-port
858 8b/-> *(ebp+8) 0/r32/eax
859 8b/-> *(eax+0x24) 2/r32/edx
861 b9/copy-to-ecx 0x200/imm32 # 512 bytes per sector
862 # . var first-byte/ebx: byte
863 # . when it's more than 0xff, we're at an even-numbered byte
864 bb/copy-to-ebx 0xffff/imm32
865 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "D" 7 0)
866 $write-ata-disk:store-sector:
868 81 7/subop/compare %ecx 0/imm32
869 74/jump-if-= break/disp8
870 # this loop is slow, but the ATA spec also requires a small delay
871 (stream-empty? *(ebp+0x14)) # => eax
872 3d/compare-eax-and 0/imm32/false
873 75/jump-if-!= break/disp8
874 # read byte from stream
875 (read-byte *(ebp+0x14)) # => eax
876 # if we're at an odd-numbered byte, save it to first-byte
877 81 7/subop/compare %ebx 0xff/imm32
879 7e/jump-if-<= break/disp8
881 eb/jump $write-ata-disk:store-sector/disp8
883 # otherwise OR it with first-byte and write it out
884 c1/shift 4/subop/left %eax 8/imm8
886 66 ef/write-ax-into-port-dx
890 bb/copy-to-ebx 0xffff/imm32
893 # write out final first-byte if necessary
894 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "I" 7 0)
895 81 7/subop/compare %ebx 0xff/imm32
897 7f/jump-if-> break/disp8
899 66 ef/write-ax-into-port-dx
904 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "P" 7 0)
905 31/xor %eax 0/r32/eax
907 81 7/subop/compare %ecx 0/imm32
908 74/jump-if-= break/disp8
909 66 ef/write-ax-into-port-dx
915 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "N" 7 0)
916 ff 1/subop/decrement *(ebp+0x10)
917 81 7/subop/compare *(ebp+0x10) 0/imm32
918 7e/jump-if-<= break/disp8
919 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "W" 7 0)
920 (wait-400ns *(ebp+8))
921 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "L" 7 0)
924 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "F" 7 0)
925 (flush-ata-cache *(ebp+8))
926 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "Y" 7 0)
928 # . restore registers
940 drive-exists?: # disk: (addr disk) -> _/eax: boolean
946 # check for floating bus
948 31/xor %eax 0/r32/eax
949 ba/copy-to-edx 0x1f7/imm32
950 ec/read-port-dx-into-al
951 3d/compare-eax-and 0xff/imm32
952 # if eax is 0xff, primary bus has no drives
953 b8/copy-to-eax 0/imm32/false
954 0f 84/jump-if-= $drive-exists?:end/disp32
957 (ata-drive-select *(ebp+8) 0)
958 (ata-sector-count *(ebp+8) 0)
960 (ata-command *(ebp+8) 0xec) # identify
961 # var status-port/edx = disk->status-port
962 8b/-> *(ebp+8) 0/r32/eax
963 8b/-> *(eax+4) 2/r32/edx # 4 = status-port offset
965 # TODO: might need to spin here for 400ns: https://wiki.osdev.org/index.php?title=ATA_PIO_Mode&oldid=25664#400ns_delays
966 31/xor %eax 0/r32/eax
967 ec/read-port-dx-into-al
968 # if eax is 0, drive does not exist
969 3d/compare-eax-and 0/imm32
971 74/jump-if-= break/disp8
972 b8/copy-to-eax 1/imm32/true
973 eb/jump $drive-exists?:complete-identify/disp8
975 # TODO: might need to perform remaining steps at https://wiki.osdev.org/index.php?title=ATA_PIO_Mode&oldid=25664#IDENTIFY_command
976 b8/copy-to-eax 0/imm32/false
977 $drive-exists?:complete-identify:
979 # var data-port/edx = disk->data-port
980 8b/-> *(ebp+8) 0/r32/eax
981 8b/-> *(eax+0x24) 2/r32/edx # 0x24 = data-port offset
982 # clear FIFO from the drive
983 b9/copy-to-ecx 0x200/imm32
985 81 7/subop/compare %ecx 0/imm32
986 74/jump-if-= break/disp8
988 ed/read-port-dx-into-eax
997 # . restore registers
1000 89/<- %esp 5/r32/ebp
1004 ata-drive-select: # disk: (addr disk), lba: int
1007 89/<- %ebp 4/r32/esp
1013 8b/-> *(ebp+8) 6/r32/esi
1014 # var drive-head/edx: byte = lba >> 24
1015 8b/-> *(ebp+0xc) 2/r32/edx
1016 c1/shift 5/subop/right-padding-zeroes %edx 0x18/imm8
1017 # var drive-code/eax: byte = disk->drive-code | drive-head
1018 8b/-> *(esi+0x28) 0/r32/eax # 0x28 = drive-code offset
1019 09/or= %eax 2/r32/edx
1020 # var drive-and-head-port/edx: int
1021 8b/-> *(esi+0x10) 2/r32/edx # 0x10 = drive-and-head-port offset
1022 ee/write-al-into-port-dx
1023 $ata-drive-select:end:
1024 # . restore registers
1029 89/<- %esp 5/r32/ebp
1033 clear-ata-error: # disk: (addr disk)
1036 89/<- %ebp 4/r32/esp
1040 # var error-port/edx = disk->error-port
1041 8b/-> *(ebp+8) 0/r32/eax
1042 8b/-> *(eax+0xc) 2/r32/edx # 0xc = error-port offset
1044 b8/copy-to-eax 0/imm32
1045 ee/write-al-into-port-dx
1047 # . restore registers
1051 89/<- %esp 5/r32/ebp
1055 ata-sector-count: # disk: (addr disk), n: byte
1058 89/<- %ebp 4/r32/esp
1062 # var sector-count-port/edx = disk->sector-count-port
1063 8b/-> *(ebp+8) 0/r32/eax
1064 8b/-> *(eax+0x14) 2/r32/edx # 0x14 = sector-count-port offset
1066 8b/-> *(ebp+0xc) 0/r32/eax
1067 ee/write-al-into-port-dx
1068 $ata-sector-count:end:
1069 # . restore registers
1073 89/<- %esp 5/r32/ebp
1077 ata-lba: # disk: (addr disk), lba: int
1080 89/<- %ebp 4/r32/esp
1084 # var port/edx = disk->port
1085 8b/-> *(ebp+8) 0/r32/eax
1086 8b/-> *(eax+0x18) 2/r32/edx # 0x18 = lba-low-port offset
1088 8b/-> *(ebp+0xc) 0/r32/eax
1090 ee/write-al-into-port-dx
1092 42/increment-dx # lba-mid-port
1093 c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
1094 ee/write-al-into-port-dx
1096 42/increment-dx # lba-high-port
1097 c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
1098 ee/write-al-into-port-dx
1100 # . restore registers
1104 89/<- %esp 5/r32/ebp
1108 ata-command: # disk: (addr disk), cmd: byte
1111 89/<- %ebp 4/r32/esp
1115 # var command-port/edx = disk->command-port
1116 8b/-> *(ebp+8) 0/r32/eax
1117 8b/-> *(eax+0) 2/r32/edx # 0 = command-port offset
1119 8b/-> *(ebp+0xc) 0/r32/eax
1120 ee/write-al-into-port-dx
1122 # . restore registers
1126 89/<- %esp 5/r32/ebp
1130 while-ata-busy: # disk: (addr disk)
1134 # var status-port/edx = disk->status-port
1135 8b/-> *(ebp+8) 0/r32/eax
1136 8b/-> *(eax+4) 2/r32/edx # 4 = status-port offset
1138 ec/read-port-dx-into-al
1139 a8/test-bits-in-al 0x80/imm8/bsy # set zf if bit 7 (most significant) is not set
1140 75/jump-if-zf-not-set-and-bit-7-set loop/disp8
1142 $while-ata-busy:end:
1143 # . restore registers
1149 until-ata-data-available: # disk: (addr disk)
1153 # var status-port/edx = disk->status-port
1154 8b/-> *(ebp+8) 0/r32/eax
1155 8b/-> *(eax+4) 2/r32/edx # 4 = status-port offset
1157 ec/read-port-dx-into-al
1158 a8/test-bits-in-al 8/imm8/drq # set zf if bit 3 is not set
1159 74/jump-if-zf-set-and-bit-3-not-set loop/disp8
1161 $until-ata-data-available:end:
1162 # . restore registers
1168 until-ata-ready-for-data:
1169 (until-ata-data-available)
1172 # https://wiki.osdev.org/index.php?title=ATA_PIO_Mode&oldid=25664#400ns_delays
1173 wait-400ns: # disk: (addr disk)
1176 89/<- %ebp 4/r32/esp
1181 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "waiting 400ns\n" 7 0)
1182 # var status-port/edx = disk->status-port
1183 8b/-> *(ebp+8) 0/r32/eax
1184 8b/-> *(eax+4) 2/r32/edx # 4 = status-port offset
1186 b9/copy-to-ecx 0x10/imm32
1188 81 7/subop/compare %ecx 0/imm32
1189 74/jump-if-= break/disp8
1190 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "." 7 0)
1191 ec/read-port-dx-into-al
1196 # . restore registers
1201 89/<- %esp 5/r32/ebp
1205 # Flush cache isn't in ATA 3, but it shows up by the ATA 5 spec:
1206 # http://hddguru.com/download/documentation/ATA-ATAPI-standard-5/ATA-ATAPI-5.pdf
1207 flush-ata-cache: # disk: (addr disk)
1210 89/<- %ebp 4/r32/esp
1212 (ata-drive-select *(ebp+8) 0)
1213 (ata-command *(ebp+8) 0xe7) # flush cache
1214 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "W" 7 0)
1215 (while-ata-busy *(ebp+8))
1216 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "X" 7 0)
1217 # TODO: seems unneeded? works for a single sector but Qemu hangs with multiple
1218 # sectors. Data is still written.
1219 #? (until-ata-ready-for-data *(ebp+8))
1220 $flush-ata-cache:end:
1222 89/<- %esp 5/r32/ebp
1228 ## Controlling a PS/2 mouse
1229 # Uses no IRQs, just polling.
1230 # Thanks Dave Long: https://github.com/jtauber/cleese/blob/master/necco/kernel/bochs/py8042.py
1233 # https://wiki.osdev.org/Mouse_Input
1235 # results x/eax, y/ecx range from -256 to +255
1236 # See https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Format_of_First_3_Packet_Bytes
1237 read-mouse-event: # -> _/eax: int, _/ecx: int
1240 89/<- %ebp 4/r32/esp
1244 # if no event, return 0, 0
1245 b8/copy-to-eax 0/imm32
1246 b9/copy-to-ecx 0/imm32
1247 (any-mouse-event?) # => eax
1248 3d/compare-eax-and 0/imm32/false
1249 74/jump-if-= $read-mouse-event:end/disp8
1250 # var f1/edx: byte = inb(0x60)
1251 31/xor %eax 0/r32/eax
1252 e4/read-port-into-al 0x60/imm8
1253 89/<- %edx 0/r32/eax
1254 (wait-for-mouse-event)
1255 # var dx/ebx: byte = inb(0x60)
1256 31/xor %eax 0/r32/eax
1257 e4/read-port-into-al 0x60/imm8
1258 89/<- %ebx 0/r32/eax
1259 (wait-for-mouse-event)
1260 # var dy/ecx: byte = inb(0x60)
1261 31/xor %eax 0/r32/eax
1262 e4/read-port-into-al 0x60/imm8
1263 89/<- %ecx 0/r32/eax
1265 89/<- %eax 3/r32/ebx
1266 # if (f1 & 0x10) dx = -dx
1268 f6 0/subop/test-bits %dl 0x10/imm8
1269 74/jump-if-zero break/disp8
1270 0d/or-eax-with 0xffffff00/imm32
1272 # if (f1 & 0x20) dy = -dy
1274 f6 0/subop/test-bits %dl 0x20/imm8
1275 74/jump-if-zero break/disp8
1276 81 1/subop/or %ecx 0xffffff00/imm32
1278 $read-mouse-event:end:
1279 # . restore registers
1283 89/<- %esp 5/r32/ebp
1289 wait-for-mouse-event:
1294 (any-mouse-event?) # => eax
1295 3d/compare-eax-and 0/imm32/false
1296 74/jump-if-= loop/disp8
1298 $wait-for-mouse-event:end:
1299 # . restore registers
1304 any-mouse-event?: # -> _/eax: boolean
1305 31/xor %eax 0/r32/eax
1306 # 0x1 bit: there's data from the keyboard controller
1307 # 0x20 bit: it's data from the aux port (the mouse)
1308 e4/read-port-into-al 0x60/imm8
1309 24/and-al-with 0x21/imm8
1310 3c/compare-al-with 0x21/imm8
1311 0f 94/set-byte-if-= %al
1315 (enable-keyboard-controller-aux-device)
1316 # tell mouse to use default settings
1317 (send-mouse-command 0xf6)
1319 (send-mouse-command 0xf4)
1322 enable-keyboard-controller-aux-device:
1323 (command-keyboard-controller 0xa8)
1326 send-mouse-command: # command: byte
1329 89/<- %ebp 4/r32/esp
1331 (command-keyboard-controller 0xd4)
1332 (send-keyboard-controller-data *(ebp+8))
1333 (wait-for-ack-from-mouse)
1334 $send-mouse-command:end:
1336 89/<- %esp 5/r32/ebp
1340 wait-for-ack-from-mouse:
1344 (read-keyboard-controller-data) # => eax
1345 3d/compare-eax-with 0xfa/imm32
1346 75/jump-if-!= loop/disp8
1348 $wait-for-ack-from-mouse:end:
1349 # . restore registers
1353 command-keyboard-controller: # command: byte
1356 89/<- %ebp 4/r32/esp
1360 (poll-keyboard-controller-to-write)
1361 8b/-> *(ebp+8) 0/r32/eax
1362 e6/write-al-into-port 0x64/imm8
1363 $command-keyboard-controller:end:
1364 # . restore registers
1367 89/<- %esp 5/r32/ebp
1371 send-keyboard-controller-data: # data: byte
1374 89/<- %ebp 4/r32/esp
1378 (poll-keyboard-controller-to-write)
1379 8b/-> *(ebp+8) 0/r32/eax
1380 e6/write-al-into-port 0x60/imm8
1381 $send-keyboard-controller-data:end:
1382 # . restore registers
1385 89/<- %esp 5/r32/ebp
1389 read-keyboard-controller-data: # -> _/eax: byte
1390 (poll-keyboard-controller-to-read-data-port)
1391 31/xor %eax 0/r32/eax
1392 e4/read-port-into-al 0x60/imm8
1395 poll-keyboard-controller-to-write:
1398 # "All output to port 0x60 or 0x64 must be preceded by waiting for bit 1
1399 # (value=2) of port 0x64 to become clear."
1400 # https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Waiting_to_Send_Bytes_to_Port_0x60_and_0x64
1402 e4/read-port-into-al 0x64/imm8
1403 a8/test-bits-in-al 2/imm8 # set zf if bit 1 (second-least significant) is not set
1404 75/jump-if-zf-not-set-and-bit-1-set loop/disp8
1406 $poll-keyboard-controller-to-write:end:
1407 # . restore registers
1412 poll-keyboard-controller-to-read-data-port:
1415 89/<- %ebp 4/r32/esp
1418 # "Bytes cannot be read from port 0x60 until bit 0 (value=1) of port 0x64 is set."
1419 # https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Waiting_to_Send_Bytes_to_Port_0x60_and_0x64
1421 e4/read-port-into-al 0x64/imm8
1422 a8/test-bits-in-al 1/imm8 # set zf if bit 0 (least significant) is not set
1423 74/jump-if-zf-set-and-bit-0-not-set loop/disp8
1425 $poll-keyboard-controller-to-read-data-port:end:
1426 # . restore registers
1429 89/<- %esp 5/r32/ebp