fix other mandelbrot variants
[mu.git] / boot.subx
blobc9d78b59729b01e49cf3abf4e50a6f16f002db70
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
10 # uses:
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.
26 == code
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
36   fa/disable-interrupts
38   # initialize segment registers
39   b8/copy-to-ax 0/imm16
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
55   {
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
61   }
62   {
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
68   }
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
78   bb/copy-to-bx 0/imm16
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
94   bb/copy-to-bx 0/imm16
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.
184   # reset es
185   bb/copy-to-bx 0/imm16
186   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
188   # adjust video mode
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
211   # enable paging
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
220 disk_error:
221   # print 'D' to top-left of screen to indicate disk error
222   # *0xb8000 <- 0x0f44
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
229   # loop forever
230   {
231     eb/jump loop/disp8
232   }
234 ## GDT: 3 records of 8 bytes each
235 == data 0x7de0
236 gdt_descriptor:
237   0x17/imm16  # final index of gdt = size of gdt - 1
238   gdt_start/imm32/start
240 gdt_start:
241 # offset 0: gdt_null:  mandatory null descriptor
242   00 00 00 00 00 00 00 00
243 # offset 8: gdt_code
244   ff ff  # limit[0:16]
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
250   00  # base[24:32]
251 # offset 16: gdt_data
252   ff ff  # limit[0:16]
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
257   00  # base[24:32]
258 # gdt_end:
260 == boot-sector-marker 0x7dfe
261 # final 2 bytes of boot sector
262 55 aa
264 ## sector 2 onwards loaded by load_disk, not automatically on boot
266 ## 32-bit code from this point
268 == code 0x7e00
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
293   # exceptions.
294   #   https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode
295   #
296   # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a
297   # debugger.
298   # Reference:
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
305   fb/enable-interrupts
307   (initialize-mouse)
309   ## enable floating point
310   db/floating-point-coprocessor e3/initialize
311   # eax <- cr4
312   0f 20/<-cr 3/mod/direct 0/rm32/eax 4/r32/cr4
313   # eax <- or bit 9
314   0f ba/bit-test 5/subop/bit-test-and-set 3/mod/direct 0/rm32/eax 9/imm8
315   # cr4 <- eax
316   0f 22/->cr 3/mod/direct 0/rm32/eax 4/r32/cr4
318   e9/jump Entry/disp32
320 == data 0x7f00
321 idt_descriptor:
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
327 idt_start:
329 # entry 0
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)
345   00  # unused
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
349 # entry 9: keyboard
350   keyboard-interrupt-handler/imm16  # target[0:16]
351   8/imm16  # segment selector (gdt_code)
352   00  # unused
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
378 # idt_end:
379 # }}}
381 == code
383 timer-interrupt-handler:
384   # prologue
385   fa/disable-interrupts
386   60/push-all-registers
387   9c/push-flags
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:
395   # epilogue
396   9d/pop-flags
397   61/pop-all-registers
398   fb/enable-interrupts
399   cf/return-from-interrupt
401 == data
402 Timer-counter:
403   0/imm32
405 == code
406 keyboard-interrupt-handler:
407   # prologue
408   fa/disable-interrupts
409   60/push-all-registers
410   9c/push-flags
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
424   # al = *dest-addr
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
429   # - read keycode
430   e4/read-port-into-al 0x60/imm8
431   # - key released
432   # if (al == 0xaa) shift = false  # left shift is being lifted
433   {
434     3c/compare-al-and 0xaa/imm8
435     75/jump-if-!= break/disp8
436     # *shift = 0
437     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
438   }
439   # if (al == 0xb6) shift = false  # right shift is being lifted
440   {
441     3c/compare-al-and 0xb6/imm8
442     75/jump-if-!= break/disp8
443     # *shift = 0
444     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
445   }
446   # if (al == 0x9d) ctrl = false  # ctrl is being lifted
447   {
448     3c/compare-al-and 0x9d/imm8
449     75/jump-if-!= break/disp8
450     # *ctrl = 0
451     c7 0/subop/copy *Keyboard-ctrl-pressed? 0/imm32
452   }
453   # if (al & 0x80) a key is being lifted; return
454   50/push-eax
455   24/and-al-with 0x80/imm8
456   3c/compare-al-and 0/imm8
457   58/pop-to-eax
458   75/jump-if-!= $keyboard-interrupt-handler:end/disp8
459   # - key pressed
460   # if (al == 0x2a) shift = true, return  # left shift pressed
461   {
462     3c/compare-al-and 0x2a/imm8
463     75/jump-if-!= break/disp8
464     # *shift = 1
465     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
466     # return
467     eb/jump $keyboard-interrupt-handler:end/disp8
468   }
469   # if (al == 0x36) shift = true, return  # right shift pressed
470   {
471     3c/compare-al-and 0x36/imm8
472     75/jump-if-!= break/disp8
473     # *shift = 1
474     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
475     # return
476     eb/jump $keyboard-interrupt-handler:end/disp8
477   }
478   # if (al == 0x1d) ctrl = true, return
479   {
480     3c/compare-al-and 0x1d/imm8
481     75/jump-if-!= break/disp8
482     # *ctrl = 1
483     c7 0/subop/copy *Keyboard-ctrl-pressed? 1/imm32
484     # return
485     eb/jump $keyboard-interrupt-handler:end/disp8
486   }
487   # - convert key to character
488   # if (shift) use keyboard shift map
489   {
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
496   }
497   # if (ctrl) al = *(ctrl map + al)
498   {
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
504   }
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
510   {
511     3c/compare-al-and 0/imm8
512     74/jump-if-= break/disp8
513     # - store al in keyboard buffer
514     88/<- *ecx 0/r32/al
515     # increment index
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
519   }
520 $keyboard-interrupt-handler:end:
521   # epilogue
522   9d/pop-flags
523   61/pop-all-registers
524   fb/enable-interrupts
525   cf/return-from-interrupt
527 == data
528 Keyboard-shift-pressed?:  # boolean
529   0/imm32
531 Keyboard-ctrl-pressed?:  # boolean
532   0/imm32
534 # var keyboard circular buffer
535 Keyboard-buffer:write:  # nibble
536   0/imm32
537 Keyboard-buffer:read:  # nibble
538   0/imm32
539 Keyboard-buffer:data:  # byte[16]
540   00 00 00 00
541   00 00 00 00
542   00 00 00 00
543   00 00 00 00
545 # Keyboard maps for translating keys to ASCII {{{
546 Keyboard-normal-map:
548 #  es
549    1b
550 #     |<--- digits -------------->| -  =  backspace
551       31 32 33 34 35 36 37 38 39 30 2d 3d 08
552 # 0f
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
555 # 1c
556 #                                         enter (newline)
557                                           0a 00
558 # 1e
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
561                                         # ^ left shift
562 # 2c
563 #     z  x  c  v  b  n  m  ,  .  /     *
564       7a 78 63 76 62 6e 6d 2c 2e 2f 00 2a
565                                   # ^ right shift
566 # 38
567 #                          space
568                         00 20
569 # 3a
570                               00 00 00 00 00 00
571 00 00 00 00 00 00 00 00
572 # 48
573 #                       ↑*       ←*    →*       ↓*
574                         82 00 00 80 00 83 00 00 81
575 # 51
576    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
578 # * - Not a valid ASCII/Unicode value.
580 Keyboard-shift-map:
582 #  es
583    1b
584 #     !  @  #  $  %  ^  &  *  (  )  _  +  backspace
585       21 40 23 24 25 53 26 2a 28 29 5f 2b 08
586 # 0f
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
589 # 1c
590 #                                         enter (newline)
591                                           0a 00
592 # 1e
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
595 # 2c
596 #     Z  X  C  V  B  N  M  <  >  ?     *
597       5a 58 43 56 42 4e 4d 3c 3e 3f 00 2a
598 # 38
599 #                          space
600                         00 20
601 # 3a
602                               00 00 00 00 00 00
603 00 00 00 00 00 00 00 00
604 # 48
605 #                       ↑*       ←*    →*       ↓*
606                         82 00 00 80 00 83 00 00 81
607 # 51
608    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
610 # * - Not a valid ASCII/Unicode value.
612 Keyboard-ctrl-map:
613 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
614 # 10
615 #     ^q ^w ^e ^r ^t ^y ^u tb ^o ^p
616       11 17 05 12 14 19 15 09 1f 10 00 00
617 # 1c
618 #                                         carriage-return
619                                           0d 00
620 # 1e
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
623 # 2c
624 #     ^z ^x ^c ^v ^b ^n ^m       ^/
625       1a 18 03 16 02 0e 0d 00 00 1f 00 00
626 # 38
627 #                          space
628                         00 20
629 # 3a
630                               00 00 00 00 00 00
631 00 00 00 00 00 00 00 00
632 # 48
633 #                       ↑*       ←*    →*       ↓*
634                         82 00 00 80 00 83 00 00 81
635 # 51
636    00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
638 # * - Not a valid ASCII/Unicode value.
639 # }}}
641 Video-mode-info:
642 # video mode info {{{
643   0/imm16  # attributes
644   00  # winA
645   00  # winB
646 # 04
647   0/imm16  # granularity
648   0/imm16  # winsize
649 # 08
650   0/imm16  # segmentA
651   0/imm16  # segmentB
652 # 0c
653   0/imm32  # realFctPtr (who knows)
654 # 10
655   0/imm16  # pitch
656   0/imm16  # Xres
657   0/imm16  # Yres
658   0/imm16  # Wchar Ychar
659 # 18
660   00  # planes
661   00  # bpp
662   00  # banks
663   00  # memory_model
664 # 1c
665   00  # bank_size
666   00  # image_pages
667   00  # reserved
668 # 1f
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
674 # 28
675 Video-memory-addr:
676   0/imm32  # physbase
678 # 2c
679 # reserved for video mode info
680                                     00 00 00 00
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
694 # }}}
696 ## Controlling IDE (ATA) hard disks
697 # Uses 28-bit PIO mode.
698 # Inspired by https://colorforth.github.io/ide.html
700 # Resources:
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
705 == data
707 # code disk
708 # All ports are 8-bit except data-port, which is 16-bit.
709 Primary-bus-primary-drive:
710   # command-port: int (write)
711   0x1f7/imm32
712   # status-port: int (read)
713   0x1f7/imm32
714   # alternative-status-port: int (read)
715   0x3f6/imm32
716   # error-port: int (read)
717   0x1f1/imm32
718   # drive-and-head-port: int
719   0x1f6/imm32
720   # sector-count-port: int
721   0x1f2/imm32
722   # lba-low-port: int
723   0x1f3/imm32
724   # lba-mid-port: int
725   0x1f4/imm32
726   # lba-high-port: int
727   0x1f5/imm32
728   # data-port: int
729   0x1f0/imm32
730   # drive-code: byte                # only drive-specific field
731   0xe0/imm32  # LBA mode also enabled
733 # data disk
734 # All ports are 8-bit except data-port, which is 16-bit.
735 Primary-bus-secondary-drive:
736   # command-port: int (write)
737   0x1f7/imm32
738   # status-port: int (read)
739   0x1f7/imm32
740   # alternative-status-port: int (read)
741   0x3f6/imm32
742   # error-port: int (read)
743   0x1f1/imm32
744   # drive-and-head-port: int
745   0x1f6/imm32
746   # sector-count-port: int
747   0x1f2/imm32
748   # lba-low-port: int
749   0x1f3/imm32
750   # lba-mid-port: int
751   0x1f4/imm32
752   # lba-high-port: int
753   0x1f5/imm32
754   # data-port: int
755   0x1f0/imm32
756   # drive-code: byte                # only drive-specific field
757   0xf0/imm32  # LBA mode also enabled
759 == code
761 # No more than 0x100 sectors
762 read-ata-disk:  # disk: (addr disk), lba: int, n: int, out: (addr stream byte)
763   # . prologue
764   55/push-ebp
765   89/<- %ebp 4/r32/esp
766   # . save registers
767   50/push-eax
768   51/push-ecx
769   52/push-edx
770   # check precondition
771   81 7/subop/compare *(ebp+0x10) 0x100/imm32
772   {
773     7e/jump-if-<= break/disp8
774     (abort "read-ata-disk: no more than 0x100 sectors")
775   }
776   # check for drive
777   (drive-exists? *(ebp+8))  # => eax
778   3d/compare-eax-and 0/imm32/false
779   0f 84/jump-if-= $read-ata-disk:end/disp32
780   # kick off read
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
786   # for each sector
787   {
788     # poll for results
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
797     # emit results
798     31/xor %eax 0/r32/eax
799     b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
800     {
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)
806       49/decrement-ecx
807       c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
808       (append-byte *(ebp+0x14) %eax)
809       49/decrement-ecx
810       eb/jump loop/disp8
811     }
812     # next sector
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)
819     e9/jump loop/disp32
820   }
821 $read-ata-disk:end:
822   # . restore registers
823   5a/pop-to-edx
824   59/pop-to-ecx
825   58/pop-to-eax
826   # . epilogue
827   89/<- %esp 5/r32/ebp
828   5d/pop-to-ebp
829   c3/return
831 write-ata-disk:  # disk: (addr disk), lba: int, n: int, in: (addr stream byte)
832   # . prologue
833   55/push-ebp
834   89/<- %ebp 4/r32/esp
835   # . save registers
836   50/push-eax
837   51/push-ecx
838   52/push-edx
839   53/push-ebx
840   # check for drive
841   (drive-exists? *(ebp+8))  # => eax
842   3d/compare-eax-and 0/imm32/false
843   0f 84/jump-if-= $write-ata-disk:end/disp32
844   # kick off write
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
850   # for each sector
851 #?   (set-cursor-position 0 0 0)
852 #?   (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "0" 7 0)
853   {
854     # wait
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
860     # send data
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:
867     {
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
878       {
879         7e/jump-if-<= break/disp8
880         89/<- %ebx 0/r32/eax
881         eb/jump $write-ata-disk:store-sector/disp8
882       }
883       # otherwise OR it with first-byte and write it out
884       c1/shift 4/subop/left %eax 8/imm8
885       09/or %eax 3/r32/ebx
886       66 ef/write-ax-into-port-dx
887       49/decrement-ecx
888       49/decrement-ecx
889       # reset first-byte
890       bb/copy-to-ebx 0xffff/imm32
891       eb/jump loop/disp8
892     }
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
896     {
897       7f/jump-if-> break/disp8
898       89/<- %eax 3/r32/ebx
899       66 ef/write-ax-into-port-dx
900       49/decrement-ecx
901       49/decrement-ecx
902     }
903     # pad zeroes
904 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "P" 7 0)
905     31/xor %eax 0/r32/eax
906     {
907       81 7/subop/compare %ecx 0/imm32
908       74/jump-if-= break/disp8
909       66 ef/write-ax-into-port-dx
910       49/decrement-ecx
911       49/decrement-ecx
912       eb/jump loop/disp8
913     }
914     # next sector
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)
922     e9/jump loop/disp32
923   }
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)
927 $write-ata-disk:end:
928   # . restore registers
929   5b/pop-to-ebx
930   5a/pop-to-edx
931   59/pop-to-ecx
932   58/pop-to-eax
933   # . epilogue
934   89/<- %esp 5/r32/ebp
935   5d/pop-to-ebp
936   c3/return
938 # disk helpers {{{
940 drive-exists?:  # disk: (addr disk) -> _/eax: boolean
941   # . prologue
942   55/push-ebp
943   89/<- %ebp 4/r32/esp
944   # . save registers
945   52/push-edx
946   # check for floating bus
947   {
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
955   }
956   # identify
957   (ata-drive-select *(ebp+8) 0)
958   (ata-sector-count *(ebp+8) 0)
959   (ata-lba *(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
964   # read status port
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
970   {
971     74/jump-if-= break/disp8
972     b8/copy-to-eax 1/imm32/true
973     eb/jump $drive-exists?:complete-identify/disp8
974   }
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:
978   50/push-eax
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
984   {
985     81 7/subop/compare %ecx 0/imm32
986     74/jump-if-= break/disp8
987     # read 4 bytes
988     ed/read-port-dx-into-eax
989     49/decrement-ecx
990     49/decrement-ecx
991     49/decrement-ecx
992     49/decrement-ecx
993     eb/jump loop/disp8
994   }
995   58/pop-to-eax
996 $drive-exists?:end:
997   # . restore registers
998   5a/pop-to-edx
999   # . epilogue
1000   89/<- %esp 5/r32/ebp
1001   5d/pop-to-ebp
1002   c3/return
1004 ata-drive-select:  # disk: (addr disk), lba: int
1005   # . prologue
1006   55/push-ebp
1007   89/<- %ebp 4/r32/esp
1008   # . save registers
1009   50/push-eax
1010   52/push-edx
1011   56/push-esi
1012   # esi = disk
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
1025   5e/pop-to-esi
1026   5a/pop-to-edx
1027   58/pop-to-eax
1028   # . epilogue
1029   89/<- %esp 5/r32/ebp
1030   5d/pop-to-ebp
1031   c3/return
1033 clear-ata-error:  # disk: (addr disk)
1034   # . prologue
1035   55/push-ebp
1036   89/<- %ebp 4/r32/esp
1037   # . save registers
1038   50/push-eax
1039   52/push-edx
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
1043   #
1044   b8/copy-to-eax 0/imm32
1045   ee/write-al-into-port-dx
1046 $ata-error:end:
1047   # . restore registers
1048   5a/pop-to-edx
1049   58/pop-to-eax
1050   # . epilogue
1051   89/<- %esp 5/r32/ebp
1052   5d/pop-to-ebp
1053   c3/return
1055 ata-sector-count:  # disk: (addr disk), n: byte
1056   # . prologue
1057   55/push-ebp
1058   89/<- %ebp 4/r32/esp
1059   # . save registers
1060   50/push-eax
1061   52/push-edx
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
1065   #
1066   8b/-> *(ebp+0xc) 0/r32/eax
1067   ee/write-al-into-port-dx
1068 $ata-sector-count:end:
1069   # . restore registers
1070   5a/pop-to-edx
1071   58/pop-to-eax
1072   # . epilogue
1073   89/<- %esp 5/r32/ebp
1074   5d/pop-to-ebp
1075   c3/return
1077 ata-lba:  # disk: (addr disk), lba: int
1078   # . prologue
1079   55/push-ebp
1080   89/<- %ebp 4/r32/esp
1081   # . save registers
1082   50/push-eax
1083   52/push-edx
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
1087   # eax = lba
1088   8b/-> *(ebp+0xc) 0/r32/eax
1089   # lo
1090   ee/write-al-into-port-dx
1091   # mid
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
1095   # hi
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
1099 $ata-lba:end:
1100   # . restore registers
1101   5a/pop-to-edx
1102   58/pop-to-eax
1103   # . epilogue
1104   89/<- %esp 5/r32/ebp
1105   5d/pop-to-ebp
1106   c3/return
1108 ata-command:  # disk: (addr disk), cmd: byte
1109   # . prologue
1110   55/push-ebp
1111   89/<- %ebp 4/r32/esp
1112   # . save registers
1113   50/push-eax
1114   52/push-edx
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
1118   #
1119   8b/-> *(ebp+0xc) 0/r32/eax
1120   ee/write-al-into-port-dx
1121 $ata-command:end:
1122   # . restore registers
1123   5a/pop-to-edx
1124   58/pop-to-eax
1125   # . epilogue
1126   89/<- %esp 5/r32/ebp
1127   5d/pop-to-ebp
1128   c3/return
1130 while-ata-busy:  # disk: (addr disk)
1131   # . save registers
1132   50/push-eax
1133   52/push-edx
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
1137   {
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
1141   }
1142 $while-ata-busy:end:
1143   # . restore registers
1144   5a/pop-to-edx
1145   58/pop-to-eax
1146   # . epilogue
1147   c3/return
1149 until-ata-data-available:  # disk: (addr disk)
1150   # . save registers
1151   50/push-eax
1152   52/push-edx
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
1156   {
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
1160   }
1161 $until-ata-data-available:end:
1162   # . restore registers
1163   5a/pop-to-edx
1164   58/pop-to-eax
1165   # . epilogue
1166   c3/return
1168 until-ata-ready-for-data:
1169   (until-ata-data-available)
1170   c3/return
1172 # https://wiki.osdev.org/index.php?title=ATA_PIO_Mode&oldid=25664#400ns_delays
1173 wait-400ns:  # disk: (addr disk)
1174   # . prologue
1175   55/push-ebp
1176   89/<- %ebp 4/r32/esp
1177   # . save registers
1178   50/push-eax
1179   51/push-ecx
1180   52/push-edx
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
1185   #
1186   b9/copy-to-ecx 0x10/imm32
1187   {
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
1192     49/decrement-ecx
1193     eb/jump loop/disp8
1194   }
1195 $wait-400ns:end:
1196   # . restore registers
1197   5a/pop-to-edx
1198   59/pop-to-ecx
1199   58/pop-to-eax
1200   # . epilogue
1201   89/<- %esp 5/r32/ebp
1202   5d/pop-to-ebp
1203   c3/return
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)
1208   # . prologue
1209   55/push-ebp
1210   89/<- %ebp 4/r32/esp
1211   #
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:
1221   # . epilogue
1222   89/<- %esp 5/r32/ebp
1223   5d/pop-to-ebp
1224   c3/return
1226 # }}}
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
1232 # Resources:
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
1238   # . prologue
1239   55/push-ebp
1240   89/<- %ebp 4/r32/esp
1241   # . save registers
1242   52/push-edx
1243   53/push-ebx
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
1264   # eax = dx
1265   89/<- %eax 3/r32/ebx
1266   # if (f1 & 0x10) dx = -dx
1267   {
1268     f6 0/subop/test-bits %dl 0x10/imm8
1269     74/jump-if-zero break/disp8
1270     0d/or-eax-with 0xffffff00/imm32
1271   }
1272   # if (f1 & 0x20) dy = -dy
1273   {
1274     f6 0/subop/test-bits %dl 0x20/imm8
1275     74/jump-if-zero break/disp8
1276     81 1/subop/or %ecx 0xffffff00/imm32
1277   }
1278 $read-mouse-event:end:
1279   # . restore registers
1280   5b/pop-to-ebx
1281   5a/pop-to-edx
1282   # . epilogue
1283   89/<- %esp 5/r32/ebp
1284   5d/pop-to-ebp
1285   c3/return
1287 # mouse helpers {{{
1289 wait-for-mouse-event:
1290   # . save registers
1291   50/push-eax
1292   #
1293   {
1294     (any-mouse-event?)  # => eax
1295     3d/compare-eax-and 0/imm32/false
1296     74/jump-if-= loop/disp8
1297   }
1298 $wait-for-mouse-event:end:
1299   # . restore registers
1300   58/pop-to-eax
1301   # .
1302   c3/return
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
1312   c3/return
1314 initialize-mouse:
1315   (enable-keyboard-controller-aux-device)
1316   # tell mouse to use default settings
1317   (send-mouse-command 0xf6)
1318   # enable mouse
1319   (send-mouse-command 0xf4)
1320   c3/return
1322 enable-keyboard-controller-aux-device:
1323   (command-keyboard-controller 0xa8)
1324   c3/return
1326 send-mouse-command:  # command: byte
1327   # . prologue
1328   55/push-ebp
1329   89/<- %ebp 4/r32/esp
1330   #
1331   (command-keyboard-controller 0xd4)
1332   (send-keyboard-controller-data *(ebp+8))
1333   (wait-for-ack-from-mouse)
1334 $send-mouse-command:end:
1335   # . epilogue
1336   89/<- %esp 5/r32/ebp
1337   5d/pop-to-ebp
1338   c3/return
1340 wait-for-ack-from-mouse:
1341   # . save registers
1342   50/push-eax
1343   {
1344     (read-keyboard-controller-data)  # => eax
1345     3d/compare-eax-with 0xfa/imm32
1346     75/jump-if-!= loop/disp8
1347   }
1348 $wait-for-ack-from-mouse:end:
1349   # . restore registers
1350   58/pop-eax
1351   c3/return
1353 command-keyboard-controller:  # command: byte
1354   # . prologue
1355   55/push-ebp
1356   89/<- %ebp 4/r32/esp
1357   # . save registers
1358   50/push-eax
1359   #
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
1365   58/pop-to-eax
1366   # . epilogue
1367   89/<- %esp 5/r32/ebp
1368   5d/pop-to-ebp
1369   c3/return
1371 send-keyboard-controller-data:  # data: byte
1372   # . prologue
1373   55/push-ebp
1374   89/<- %ebp 4/r32/esp
1375   # . save registers
1376   50/push-eax
1377   #
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
1383   58/pop-to-eax
1384   # . epilogue
1385   89/<- %esp 5/r32/ebp
1386   5d/pop-to-ebp
1387   c3/return
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
1393   c3/return
1395 poll-keyboard-controller-to-write:
1396   # . save registers
1397   50/push-eax
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
1401   {
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
1405   }
1406 $poll-keyboard-controller-to-write:end:
1407   # . restore registers
1408   58/pop-to-eax
1409   # . epilogue
1410   c3/return
1412 poll-keyboard-controller-to-read-data-port:
1413   # . prologue
1414   55/push-ebp
1415   89/<- %ebp 4/r32/esp
1416   # . save registers
1417   50/push-eax
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
1420   {
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
1424   }
1425 $poll-keyboard-controller-to-read-data-port:end:
1426   # . restore registers
1427   58/pop-to-eax
1428   # . epilogue
1429   89/<- %esp 5/r32/ebp
1430   5d/pop-to-ebp
1431   c3/return
1433 # }}}
1435 # vim:ft=subx