https://github.com/akkartik/mu/blob/main/boot.subx
   1 # Code for the first few disk sectors that all programs in this directory need:
   2 #   - load sectors past the first (using BIOS primitives) since only the first is available by default
   3 #     - if this fails, print 'D' at top-left of screen and halt
   4 #   - initialize a minimal graphics mode
   5 #   - switch to 32-bit mode (giving up access to BIOS primitives)
   6 #   - set up a handler for keyboard events
   7 #   - jump to start of program
   8 
   9 # 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
  13 #
  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.
  16 
  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.
  25 
  26 == code
  27 
  28 ## 16-bit entry point: 0x7c00
  29 
  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
  35 
  36   fa/disable-interrupts
  37 
  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
  44 
  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
  52 
  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   }
  69 
  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
  83 
  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
  97 
  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
 111 
 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
 125 
 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
 139 
 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
 153 
 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
 167 
 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
 181 
 182   ### Loading more code tracks would clobber BIOS; we need a new compilation strategy.
 183 
 184   # reset es
 185   bb/copy-to-bx 0/imm16
 186   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 187 
 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
 196 
 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
 204 
 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
 219 
 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   }
 233 
 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
 239 
 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:
 259 
 260 == boot-sector-marker 0x7dfe
 261 # final 2 bytes of boot sector
 262 55 aa
 263 
 264 ## sector 2 onwards loaded by load_disk, not automatically on boot
 265 
 266 ## 32-bit code from this point
 267 
 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
 276 
 277   bc/copy-to-esp 0x02000000/imm32
 278 
 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.
 285 
 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
 291 
 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
 300 
 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
 304 
 305   fb/enable-interrupts
 306 
 307   (initialize-mouse)
 308 
 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
 317 
 318   e9/jump Entry/disp32
 319 
 320 == data 0x7f00
 321 idt_descriptor:
 322   ff 03  # final index of idt = size of idt - 1
 323   idt_start/imm32/start
 324 
 325 +-- 55 lines: # interrupt descriptor table --------------------------------------------------------------------------------------------------------------------------------------------
 380 
 381 == code
 382 
 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
 400 
 401 == data
 402 Timer-counter:
 403   0/imm32
 404 
 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
 526 
 527 == data
 528 Keyboard-shift-pressed?:  # boolean
 529   0/imm32
 530 
 531 Keyboard-ctrl-pressed?:  # boolean
 532   0/imm32
 533 
 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
 544 
 545 +-- 95 lines: # Keyboard maps for translating keys to ASCII ---------------------------------------------------------------------------------------------------------------------------
 640 
 641 Video-mode-info:
 642 +-- 53 lines: # video mode info -------------------------------------------------------------------------------------------------------------------------------------------------------
 695 
 696 ## Controlling IDE (ATA) hard disks
 697 # Uses 28-bit PIO mode.
 698 # Inspired by https://colorforth.github.io/ide.html
 699 #
 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
 704 
 705 == data
 706 
 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
 732 
 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
 758 
 759 == code
 760 
 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
 830 
 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
 937 
 938 +--289 lines: # disk helpers ----------------------------------------------------------------------------------------------------------------------------------------------------------
1227 
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
1231 #
1232 # Resources:
1233 #   https://wiki.osdev.org/Mouse_Input
1234 
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
1286 
1287 +--147 lines: # mouse helpers ---------------------------------------------------------------------------------------------------------------------------------------------------------
1434 
1435 # vim:ft=subx