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: currently 4 tracks loaded from the primary disk to [0x00007c00, 0x00048600)
  19 #   stack: grows down from 0x02000000 to 0x01000000
  20 #   heap: [0x02000000, 0x08000000)
  21 #     see 120allocate.subx; Qemu initializes with 128MB RAM by default
  22 # Consult https://wiki.osdev.org/Memory_Map_(x86) before modifying any of
  23 # this. And don't forget to keep *stack-debug.subx in sync.
  24 
  25 == code
  26 
  27 ## 16-bit entry point: 0x7c00
  28 
  29 # Upon reset, the IBM PC:
  30 #   - loads the first sector (512 bytes)
  31 #     from some bootable image (look for the boot-sector-marker further down this file)
  32 #     to the address range [0x7c00, 0x7e00)
  33 #   - starts executing code at address 0x7c00
  34 
  35   fa/disable-interrupts
  36 
  37   # initialize segment registers
  38   b8/copy-to-ax 0/imm16
  39   8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
  40   8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
  41   8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
  42   8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
  43 
  44   # Temporarily initialize stack to 0x00070000 in real mode.
  45   # We don't read or write the stack before we get to 32-bit mode, but BIOS
  46   # calls do. We need to move the stack in case BIOS initializes it to some
  47   # low address that we want to write code into.
  48   b8/copy-to-ax 0x7000/imm16
  49   8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
  50   bc/copy-to-esp 0/imm16
  51 
  52   # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
  53   # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
  54   {
  55     e4/read-port-into-al 0x64/imm8
  56     a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
  57     75/jump-if-!zero loop/disp8
  58     b0/copy-to-al 0xd1/imm8
  59     e6/write-al-into-port 0x64/imm8
  60   }
  61   {
  62     e4/read-port-into-al 0x64/imm8
  63     a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
  64     75/jump-if-!zero loop/disp8
  65     b0/copy-to-al 0xdf/imm8
  66     e6/write-al-into-port 0x64/imm8
  67   }
  68 
  69   # load remaining sectors from first two tracks of disk into addresses [0x7e00, 0x17800)
  70   b4/copy-to-ah 2/imm8/read-drive
  71   # dl comes conveniently initialized at boot time with the index of the device being booted
  72   b5/copy-to-ch 0/imm8/cylinder
  73   b6/copy-to-dh 0/imm8/head                   # <====
  74   b1/copy-to-cl 2/imm8/sector  # 1-based
  75   b0/copy-to-al 0x7d/imm8/num-sectors  # 2*63 - 1 = 125
  76   # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
  77   bb/copy-to-bx 0/imm16
  78   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
  79   bb/copy-to-bx 0x7e00/imm16                  # <====
  80   cd/syscall 0x13/imm8/bios-disk-services
  81   0f 82/jump-if-carry disk_error/disp16
  82 
  83   # load two more tracks of disk into addresses [0x17800, 0x27400)
  84   b4/copy-to-ah 2/imm8/read-drive
  85   # dl comes conveniently initialized at boot time with the index of the device being booted
  86   b5/copy-to-ch 0/imm8/cylinder
  87   b6/copy-to-dh 2/imm8/head                   # <====
  88   b1/copy-to-cl 1/imm8/sector  # 1-based
  89   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
  90   # address to write sectors to = es:bx = 0x17800, contiguous with boot segment
  91   bb/copy-to-bx 0x1780/imm16                  # <====
  92   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
  93   bb/copy-to-bx 0/imm16
  94   cd/syscall 0x13/imm8/bios-disk-services
  95   0f 82/jump-if-carry disk_error/disp16
  96 
  97   # load two more tracks of disk into addresses [0x27400, 0x37000)
  98   b4/copy-to-ah 2/imm8/read-drive
  99   # dl comes conveniently initialized at boot time with the index of the device being booted
 100   b5/copy-to-ch 0/imm8/cylinder
 101   b6/copy-to-dh 4/imm8/head                   # <====
 102   b1/copy-to-cl 1/imm8/sector  # 1-based
 103   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 104   # address to write sectors to = es:bx = 0x27400, contiguous with boot segment
 105   bb/copy-to-bx 0x2740/imm16                  # <====
 106   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 107   bb/copy-to-bx 0/imm16
 108   cd/syscall 0x13/imm8/bios-disk-services
 109   0f 82/jump-if-carry disk_error/disp16
 110 
 111   # load two more tracks of disk into addresses [0x37000, 0x46c00)
 112   b4/copy-to-ah 2/imm8/read-drive
 113   # dl comes conveniently initialized at boot time with the index of the device being booted
 114   b5/copy-to-ch 0/imm8/cylinder
 115   b6/copy-to-dh 6/imm8/head                   # <====
 116   b1/copy-to-cl 1/imm8/sector  # 1-based
 117   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 118   # address to write sectors to = es:bx = 0x37000, contiguous with boot segment
 119   bb/copy-to-bx 0x3700/imm16                  # <====
 120   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 121   bb/copy-to-bx 0/imm16
 122   cd/syscall 0x13/imm8/bios-disk-services
 123   0f 82/jump-if-carry disk_error/disp16
 124 
 125   # load two more tracks of disk into addresses [0x46c00, 0x56800)
 126   b4/copy-to-ah 2/imm8/read-drive
 127   # dl comes conveniently initialized at boot time with the index of the device being booted
 128   b5/copy-to-ch 0/imm8/cylinder
 129   b6/copy-to-dh 8/imm8/head                   # <====
 130   b1/copy-to-cl 1/imm8/sector  # 1-based
 131   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 132   # address to write sectors to = es:bx = 0x46c00, contiguous with boot segment
 133   bb/copy-to-bx 0x46c0/imm16                  # <====
 134   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 135   bb/copy-to-bx 0/imm16
 136   cd/syscall 0x13/imm8/bios-disk-services
 137   0f 82/jump-if-carry disk_error/disp16
 138 
 139   # load two more tracks of disk into addresses [0x56800, 0x66400)
 140   b4/copy-to-ah 2/imm8/read-drive
 141   # dl comes conveniently initialized at boot time with the index of the device being booted
 142   b5/copy-to-ch 0/imm8/cylinder
 143   b6/copy-to-dh 0xa/imm8/head                 # <====
 144   b1/copy-to-cl 1/imm8/sector  # 1-based
 145   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 146   # address to write sectors to = es:bx = 0x56800, contiguous with boot segment
 147   bb/copy-to-bx 0x5680/imm16                  # <====
 148   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 149   bb/copy-to-bx 0/imm16
 150   cd/syscall 0x13/imm8/bios-disk-services
 151   0f 82/jump-if-carry disk_error/disp16
 152 
 153   # reset es
 154   bb/copy-to-bx 0/imm16
 155   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 156 
 157   # adjust video mode
 158   b4/copy-to-ah 0x4f/imm8  # VBE commands
 159   b0/copy-to-al 2/imm8  # set video mode
 160   bb/copy-to-bx 0x4105/imm16  # 0x0105 | 0x4000
 161                               # 0x0105 = graphics mode 1024x768x256
 162                               #  (alternative candidate: 0x0101 for 640x480x256)
 163                               # 0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively
 164   cd/syscall 0x10/imm8/bios-video-services
 165 
 166   # load information for the (hopefully) current video mode
 167   # mostly just for the address to the linear frame buffer
 168   b4/copy-to-ah 0x4f/imm8  # VBE commands
 169   b0/copy-to-al 1/imm8  # get video mode info
 170   b9/copy-to-cx 0x0105/imm16  # mode we requested
 171   bf/copy-to-di Video-mode-info/imm16
 172   cd/syscall 0x10/imm8/bios-video-services
 173 
 174   ## switch to 32-bit mode
 175   # load global descriptor table
 176   # We can't refer to the label directly because SubX doesn't do the right
 177   # thing for lgdt, so rather than make errors worse in most places we instead
 178   # pin gdt_descriptor below.
 179   0f 01 2/subop/lgdt 0/mod/indirect 6/rm32/use-disp16 0x7de0/disp16/gdt_descriptor
 180   # enable paging
 181   0f 20/<-cr 3/mod/direct 0/rm32/eax 0/r32/cr0
 182   66 83 1/subop/or 3/mod/direct 0/rm32/eax 1/imm8  # eax <- or 0x1
 183   0f 22/->cr 3/mod/direct 0/rm32/eax 0/r32/cr0
 184   # far jump to initialize_32bit_mode that sets cs to offset 8 in the gdt in the process
 185   # We can't refer to the label directly because SubX doesn't have syntax for
 186   # segment selectors. So we instead pin initialize_32bit_mode below.
 187   ea/jump-far-absolute 0x00087e00/disp32  # address 0x7e00 in offset 8 of the gdt
 188 
 189 disk_error:
 190   # print 'D' to top-left of screen to indicate disk error
 191   # *0xb8000 <- 0x0f44
 192   bb/copy-to-bx 0xb800/imm16
 193   8e/->seg 3/mod/direct 3/rm32/bx 3/r32/ds
 194   b0/copy-to-al 0x44/imm8/D
 195   b4/copy-to-ah 0x0f/imm8/white-on-black
 196   bb/copy-to-bx 0/imm16
 197   89/<- 0/mod/indirect 7/rm32/bx 0/r32/ax  # *ds:bx <- ax
 198   # loop forever
 199   {
 200     eb/jump loop/disp8
 201   }
 202 
 203 ## GDT: 3 records of 8 bytes each
 204 == data 0x7de0
 205 gdt_descriptor:
 206   0x17/imm16  # final index of gdt = size of gdt - 1
 207   gdt_start/imm32/start
 208 
 209 gdt_start:
 210 # offset 0: gdt_null:  mandatory null descriptor
 211   00 00 00 00 00 00 00 00
 212 # offset 8: gdt_code
 213   ff ff  # limit[0:16]
 214   00 00 00  # base[0:24]
 215   9a  # 1/present 00/privilege 1/descriptor type = 1001b
 216       # 1/code 0/conforming 1/readable 0/accessed = 1010b
 217   cf  # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b
 218       # limit[16:20] = 1111b
 219   00  # base[24:32]
 220 # offset 16: gdt_data
 221   ff ff  # limit[0:16]
 222   00 00 00  # base[0:24]
 223   92  # 1/present 00/privilege 1/descriptor type = 1001b
 224       # 0/data 0/conforming 1/readable 0/accessed = 0010b
 225   cf  # same as gdt_code
 226   00  # base[24:32]
 227 # gdt_end:
 228 
 229 == boot-sector-marker 0x7dfe
 230 # final 2 bytes of boot sector
 231 55 aa
 232 
 233 ## sector 2 onwards loaded by load_disk, not automatically on boot
 234 
 235 ## 32-bit code from this point
 236 
 237 == code 0x7e00
 238 initialize_32bit_mode:
 239   66 b8/copy-to-ax 0x10/imm16  # offset 16 from gdt_start
 240   8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
 241   8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
 242   8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
 243   8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
 244   8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
 245 
 246   bc/copy-to-esp 0x02000000/imm32
 247 
 248   ## load interrupt handlers
 249   # We can't refer to the label directly because SubX doesn't do the right
 250   # thing for lidt, so rather than make errors worse in most places we instead
 251   # pin idt_descriptor below.
 252   0f 01 3/subop/lidt 0/mod/indirect 5/rm32/use-disp32 0x7f00/disp32/idt_descriptor
 253 
 254   # For now, not bothering reprogramming the IRQ to not conflict with software
 255   # exceptions.
 256   #   https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode
 257   #
 258   # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a
 259   # debugger.
 260   # Reference:
 261   #   https://wiki.osdev.org/Exceptions
 262 
 263   # enable timer IRQ0 and keyboard IRQ1
 264   b0/copy-to-al 0xfc/imm8  # disable mask for IRQ0 and IRQ1
 265   e6/write-al-into-port 0x21/imm8
 266 
 267   fb/enable-interrupts
 268 
 269   (initialize-mouse)
 270 
 271   ## enable floating point
 272   db/floating-point-coprocessor e3/initialize
 273   # eax <- cr4
 274   0f 20/<-cr 3/mod/direct 0/rm32/eax 4/r32/cr4
 275   # eax <- or bit 9
 276   0f ba/bit-test 5/subop/bit-test-and-set 3/mod/direct 0/rm32/eax 9/imm8
 277   # cr4 <- eax
 278   0f 22/->cr 3/mod/direct 0/rm32/eax 4/r32/cr4
 279 
 280   e9/jump Entry/disp32
 281 
 282 == data 0x7f00
 283 idt_descriptor:
 284   ff 03  # final index of idt = size of idt - 1
 285   idt_start/imm32/start
 286 
 287 +-- 55 lines: # interrupt descriptor table ----------------------------------------------------------------------------------------------------------------------------------------------
 342 
 343 == code
 344 
 345 null-interrupt-handler:
 346   # prologue
 347   # Don't disable interrupts; the timer has the highest priority anyway,
 348   # and this interrupt triggers extremely frequently.
 349   fa/disable-interrupts
 350   60/push-all-registers
 351   9c/push-flags
 352   # acknowledge interrupt
 353   b0/copy-to-al 0x20/imm8
 354   e6/write-al-into-port 0x20/imm8
 355   31/xor %eax 0/r32/eax
 356 $null-interrupt-handler:epilogue:
 357   # epilogue
 358   9d/pop-flags
 359   61/pop-all-registers
 360   fb/enable-interrupts
 361   cf/return-from-interrupt
 362 
 363 timer-interrupt-handler:
 364   # prologue
 365   # Don't disable interrupts; the timer has the highest priority anyway,
 366   # and this interrupt triggers extremely frequently.
 367   fa/disable-interrupts
 368   60/push-all-registers
 369   9c/push-flags
 370   # acknowledge interrupt
 371   b0/copy-to-al 0x20/imm8
 372   e6/write-al-into-port 0x20/imm8
 373   31/xor %eax 0/r32/eax
 374 $timer-interrupt-handler:epilogue:
 375   # epilogue
 376   9d/pop-flags
 377   61/pop-all-registers
 378   fb/enable-interrupts
 379   cf/return-from-interrupt
 380 
 381 keyboard-interrupt-handler:
 382   # prologue
 383   fa/disable-interrupts
 384   60/push-all-registers
 385   9c/push-flags
 386   # acknowledge interrupt
 387   b0/copy-to-al 0x20/imm8
 388   e6/write-al-into-port 0x20/imm8
 389   31/xor %eax 0/r32/eax
 390   # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html)
 391   e4/read-port-into-al 0x64/imm8
 392   a8/test-bits-in-al 0x01/imm8  # set zf if bit 0 (least significant) is not set
 393   0f 84/jump-if-not-set $keyboard-interrupt-handler:epilogue/disp32
 394   # - if keyboard buffer is full, return
 395   # var dest-addr/ecx: (addr byte) = (keyboard-buffer + *keyboard-buffer:write)
 396   31/xor %ecx 1/r32/ecx
 397   8a/byte-> *Keyboard-buffer:write 1/r32/cl
 398   81 0/subop/add %ecx Keyboard-buffer:data/imm32
 399   # al = *dest-addr
 400   8a/byte-> *ecx 0/r32/al
 401   # if (al != 0) return
 402   3c/compare-al-and 0/imm8
 403   0f 85/jump-if-!= $keyboard-interrupt-handler:epilogue/disp32
 404   # - read keycode
 405   e4/read-port-into-al 0x60/imm8
 406   # - key released
 407   # if (al == 0xaa) shift = false  # left shift is being lifted
 408   {
 409     3c/compare-al-and 0xaa/imm8
 410     75/jump-if-!= break/disp8
 411     # *shift = 0
 412     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
 413   }
 414   # if (al == 0xb6) shift = false  # right shift is being lifted
 415   {
 416     3c/compare-al-and 0xb6/imm8
 417     75/jump-if-!= break/disp8
 418     # *shift = 0
 419     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
 420   }
 421   # if (al == 0x9d) ctrl = false  # ctrl is being lifted
 422   {
 423     3c/compare-al-and 0x9d/imm8
 424     75/jump-if-!= break/disp8
 425     # *ctrl = 0
 426     c7 0/subop/copy *Keyboard-ctrl-pressed? 0/imm32
 427   }
 428   # if (al & 0x80) a key is being lifted; return
 429   50/push-eax
 430   24/and-al-with 0x80/imm8
 431   3c/compare-al-and 0/imm8
 432   58/pop-to-eax
 433   75/jump-if-!= $keyboard-interrupt-handler:epilogue/disp8
 434   # - key pressed
 435   # if (al == 0x2a) shift = true, return  # left shift pressed
 436   {
 437     3c/compare-al-and 0x2a/imm8
 438     75/jump-if-!= break/disp8
 439     # *shift = 1
 440     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
 441     # return
 442     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 443   }
 444   # if (al == 0x36) shift = true, return  # right shift pressed
 445   {
 446     3c/compare-al-and 0x36/imm8
 447     75/jump-if-!= break/disp8
 448     # *shift = 1
 449     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
 450     # return
 451     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 452   }
 453   # if (al == 0x1d) ctrl = true, return
 454   {
 455     3c/compare-al-and 0x1d/imm8
 456     75/jump-if-!= break/disp8
 457     # *ctrl = 1
 458     c7 0/subop/copy *Keyboard-ctrl-pressed? 1/imm32
 459     # return
 460     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 461   }
 462   # - convert key to character
 463   # if (shift) use keyboard shift map
 464   {
 465     81 7/subop/compare *Keyboard-shift-pressed? 0/imm32
 466     74/jump-if-= break/disp8
 467     # sigils don't currently support labels inside *(eax+label)
 468     05/add-to-eax Keyboard-shift-map/imm32
 469     8a/byte-> *eax 0/r32/al
 470     eb/jump $keyboard-interrupt-handler:select-map-done/disp8
 471   }
 472   # if (ctrl) al = *(ctrl map + al)
 473   {
 474     81 7/subop/compare *Keyboard-ctrl-pressed? 0/imm32
 475     74/jump-if-= break/disp8
 476     05/add-to-eax Keyboard-ctrl-map/imm32
 477     8a/byte-> *eax 0/r32/al
 478     eb/jump $keyboard-interrupt-handler:select-map-done/disp8
 479   }
 480   # otherwise al = *(normal map + al)
 481   05/add-to-eax Keyboard-normal-map/imm32
 482   8a/byte-> *eax 0/r32/al
 483 $keyboard-interrupt-handler:select-map-done:
 484   # - if there's no character mapping, return
 485   {
 486     3c/compare-al-and 0/imm8
 487     74/jump-if-= break/disp8
 488     # - store al in keyboard buffer
 489     88/<- *ecx 0/r32/al
 490     # increment index
 491     fe/increment-byte *Keyboard-buffer:write
 492     # clear top nibble of index (keyboard buffer is circular)
 493     80 4/subop/and-byte *Keyboard-buffer:write 0x0f/imm8
 494   }
 495 $keyboard-interrupt-handler:epilogue:
 496   # epilogue
 497   9d/pop-flags
 498   61/pop-all-registers
 499   fb/enable-interrupts
 500   cf/return-from-interrupt
 501 
 502 == data
 503 Keyboard-shift-pressed?:  # boolean
 504   0/imm32
 505 
 506 Keyboard-ctrl-pressed?:  # boolean
 507   0/imm32
 508 
 509 # var keyboard circular buffer
 510 Keyboard-buffer:write:  # nibble
 511   0/imm32
 512 Keyboard-buffer:read:  # nibble
 513   0/imm32
 514 Keyboard-buffer:data:  # byte[16]
 515   00 00 00 00
 516   00 00 00 00
 517   00 00 00 00
 518   00 00 00 00
 519 
 520 +-- 95 lines: # Keyboard maps for translating keys to ASCII -----------------------------------------------------------------------------------------------------------------------------
 615 
 616 Video-mode-info:
 617 +-- 53 lines: # video mode info ---------------------------------------------------------------------------------------------------------------------------------------------------------
 670 
 671 Font:
 672 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------
 908 
 909 ## Controlling IDE (ATA) hard disks
 910 # Uses 28-bit PIO mode.
 911 # Inspired by https://colorforth.github.io/ide.html
 912 #
 913 # Resources:
 914 #   https://wiki.osdev.org/ATA_PIO_Mode
 915 #   https://forum.osdev.org/viewtopic.php?f=1&p=167798
 916 #   read-sector, according to https://www.scs.stanford.edu/11wi-cs140/pintos/specs/ata-3-std.pdf
 917 
 918 == data
 919 
 920 # code disk
 921 # All ports are 8-bit except data-port, which is 16-bit.
 922 Primary-bus-primary-drive:
 923   # command-port: int (write)
 924   0x1f7/imm32
 925   # status-port: int (read)
 926   0x1f7/imm32
 927   # alternative-status-port: int (read)
 928   0x3f6/imm32
 929   # error-port: int (read)
 930   0x1f1/imm32
 931   # drive-and-head-port: int
 932   0x1f6/imm32
 933   # sector-count-port: int
 934   0x1f2/imm32
 935   # lba-low-port: int
 936   0x1f3/imm32
 937   # lba-mid-port: int
 938   0x1f4/imm32
 939   # lba-high-port: int
 940   0x1f5/imm32
 941   # data-port: int
 942   0x1f0/imm32
 943   # drive-code: byte                # only drive-specific field
 944   0xe0/imm32  # LBA mode also enabled
 945 
 946 # data disk
 947 # All ports are 8-bit except data-port, which is 16-bit.
 948 Primary-bus-secondary-drive:
 949   # command-port: int (write)
 950   0x1f7/imm32
 951   # status-port: int (read)
 952   0x1f7/imm32
 953   # alternative-status-port: int (read)
 954   0x3f6/imm32
 955   # error-port: int (read)
 956   0x1f1/imm32
 957   # drive-and-head-port: int
 958   0x1f6/imm32
 959   # sector-count-port: int
 960   0x1f2/imm32
 961   # lba-low-port: int
 962   0x1f3/imm32
 963   # lba-mid-port: int
 964   0x1f4/imm32
 965   # lba-high-port: int
 966   0x1f5/imm32
 967   # data-port: int
 968   0x1f0/imm32
 969   # drive-code: byte                # only drive-specific field
 970   0xf0/imm32  # LBA mode also enabled
 971 
 972 == code
 973 
 974 load-sectors:  # disk: (addr disk), lba: int, n: int, out: (addr stream byte)
 975   # . prologue
 976   55/push-ebp
 977   89/<- %ebp 4/r32/esp
 978   # . save registers
 979   50/push-eax
 980   51/push-ecx
 981   52/push-edx
 982   # check for drive
 983   (drive-exists? *(ebp+8))  # => eax
 984   3d/compare-eax-and 0/imm32/false
 985   0f 84/jump-if-= $load-sectors:end/disp32
 986   # kick off read
 987   (ata-drive-select *(ebp+8) *(ebp+0xc))
 988   (clear-ata-error *(ebp+8))
 989   (ata-sector-count *(ebp+8) *(ebp+0x10))
 990   (ata-lba *(ebp+8) *(ebp+0xc))
 991   (ata-command *(ebp+8) 0x20)  # read sectors with retries
 992   # for each sector
 993   {
 994     # poll for results
 995 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "waiting for sector.." 7 0)
 996 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "." 7 0)
 997     (while-ata-busy *(ebp+8))
 998     (until-ata-data-available *(ebp+8))
 999 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "reading\n" 7 0)
1000     # var data-port/edx = disk->data-port
1001     8b/-> *(ebp+8) 0/r32/eax
1002     8b/-> *(eax+0x24) 2/r32/edx
1003     # emit results
1004     31/xor %eax 0/r32/eax
1005     b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
1006     {
1007       81 7/subop/compare %ecx 0/imm32
1008       74/jump-if-= break/disp8
1009       66 ed/read-port-dx-into-ax
1010       # write 2 bytes to stream one at a time
1011       (append-byte *(ebp+0x14) %eax)
1012       49/decrement-ecx
1013       c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
1014       (append-byte *(ebp+0x14) %eax)
1015       49/decrement-ecx
1016       eb/jump loop/disp8
1017     }
1018     # next sector
1019     ff 1/subop/decrement *(ebp+0x10)
1020 #?     (draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+0x10) 0xc 0)
1021     81 7/subop/compare *(ebp+0x10) 0/imm32
1022     7e/jump-if-<= break/disp8
1023     (wait-400ns *(ebp+8))
1024 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "next sector\n" 7 0)
1025     e9/jump loop/disp32
1026   }
1027 $load-sectors:end:
1028   # . restore registers
1029   5a/pop-to-edx
1030   59/pop-to-ecx
1031   58/pop-to-eax
1032   # . epilogue
1033   89/<- %esp 5/r32/ebp
1034   5d/pop-to-ebp
1035   c3/return
1036 
1037 store-sectors:  # disk: (addr disk), lba: int, n: int, in: (addr stream byte)
1038   # . prologue
1039   55/push-ebp
1040   89/<- %ebp 4/r32/esp
1041   # . save registers
1042   50/push-eax
1043   51/push-ecx
1044   52/push-edx
1045   53/push-ebx
1046   # check for drive
1047   (drive-exists? *(ebp+8))  # => eax
1048   3d/compare-eax-and 0/imm32/false
1049   0f 84/jump-if-= $store-sectors:end/disp32
1050   # kick off write
1051   (ata-drive-select *(ebp+8) *(ebp+0xc))
1052   (clear-ata-error *(ebp+8))
1053   (ata-sector-count *(ebp+8) *(ebp+0x10))
1054   (ata-lba *(ebp+8) *(ebp+0xc))
1055   (ata-command *(ebp+8) 0x30)  # write sectors with retries
1056   # for each sector
1057 #?   (set-cursor-position 0 0 0)
1058 #?   (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "0" 7 0)
1059   {
1060     # wait
1061     (while-ata-busy *(ebp+8))
1062     (until-ata-ready-for-data *(ebp+8))
1063     # var data-port/edx = disk->data-port
1064     8b/-> *(ebp+8) 0/r32/eax
1065     8b/-> *(eax+0x24) 2/r32/edx
1066     # send data
1067     b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
1068     # . var first-byte/ebx: byte
1069     # . when it's more than 0xff, we're at an even-numbered byte
1070     bb/copy-to-ebx 0xffff/imm32
1071 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "D" 7 0)
1072 $store-sectors:store-sector:
1073     {
1074       81 7/subop/compare %ecx 0/imm32
1075       74/jump-if-= break/disp8
1076       # this loop is slow, but the ATA spec also requires a small delay
1077       (stream-empty? *(ebp+0x14))  # => eax
1078       3d/compare-eax-and 0/imm32/false
1079       75/jump-if-!= break/disp8
1080       # read byte from stream
1081       (read-byte *(ebp+0x14))  # => eax
1082       # if we're at an odd-numbered byte, save it to first-byte
1083       81 7/subop/compare %ebx 0xff/imm32
1084       {
1085         7e/jump-if-<= break/disp8
1086         89/<- %ebx 0/r32/eax
1087         eb/jump $store-sectors:store-sector/disp8
1088       }
1089       # otherwise OR it with first-byte and write it out
1090       c1/shift 4/subop/left %eax 8/imm8
1091       09/or %eax 3/r32/ebx
1092       66 ef/write-ax-into-port-dx
1093       49/decrement-ecx
1094       49/decrement-ecx
1095       # reset first-byte
1096       bb/copy-to-ebx 0xffff/imm32
1097       eb/jump loop/disp8
1098     }
1099     # write out final first-byte if necessary
1100 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "I" 7 0)
1101     81 7/subop/compare %ebx 0xff/imm32
1102     {
1103       7f/jump-if-> break/disp8
1104       89/<- %eax 3/r32/ebx
1105       66 ef/write-ax-into-port-dx
1106       49/decrement-ecx
1107       49/decrement-ecx
1108     }
1109     # pad zeroes
1110 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "P" 7 0)
1111     31/xor %eax 0/r32/eax
1112     {
1113       81 7/subop/compare %ecx 0/imm32
1114       74/jump-if-= break/disp8
1115       66 ef/write-ax-into-port-dx
1116       49/decrement-ecx
1117       49/decrement-ecx
1118       eb/jump loop/disp8
1119     }
1120     # next sector
1121 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "N" 7 0)
1122     ff 1/subop/decrement *(ebp+0x10)
1123     81 7/subop/compare *(ebp+0x10) 0/imm32
1124     7e/jump-if-<= break/disp8
1125 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "W" 7 0)
1126     (wait-400ns *(ebp+8))
1127 #?     (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "L" 7 0)
1128     e9/jump loop/disp32
1129   }
1130 #?   (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "F" 7 0)
1131   (flush-ata-cache *(ebp+8))
1132 #?   (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "Y" 7 0)
1133 $store-sectors:end:
1134   # . restore registers
1135   5b/pop-to-ebx
1136   5a/pop-to-edx
1137   59/pop-to-ecx
1138   58/pop-to-eax
1139   # . epilogue
1140   89/<- %esp 5/r32/ebp
1141   5d/pop-to-ebp
1142   c3/return
1143 
1144 +--289 lines: # disk helpers ------------------------------------------------------------------------------------------------------------------------------------------------------------
1433 
1434 ## Controlling a PS/2 mouse
1435 # Uses no IRQs, just polling.
1436 # Thanks Dave Long: https://github.com/jtauber/cleese/blob/master/necco/kernel/bochs/py8042.py
1437 #
1438 # Resources:
1439 #   https://wiki.osdev.org/Mouse_Input
1440 
1441 # results x/eax, y/ecx range from -256 to +255
1442 # See https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Format_of_First_3_Packet_Bytes
1443 read-mouse-event:  # -> _/eax: int, _/ecx: int
1444   # . prologue
1445   55/push-ebp
1446   89/<- %ebp 4/r32/esp
1447   # . save registers
1448   52/push-edx
1449   53/push-ebx
1450   # if no event, return 0, 0
1451   b8/copy-to-eax 0/imm32
1452   b9/copy-to-ecx 0/imm32
1453   (any-mouse-event?)  # => eax
1454   3d/compare-eax-and 0/imm32/false
1455   74/jump-if-= $read-mouse-event:end/disp8
1456   # var f1/edx: byte = inb(0x60)
1457   31/xor %eax 0/r32/eax
1458   e4/read-port-into-al 0x60/imm8
1459   89/<- %edx 0/r32/eax
1460   (wait-for-mouse-event)
1461   # var dx/ebx: byte = inb(0x60)
1462   31/xor %eax 0/r32/eax
1463   e4/read-port-into-al 0x60/imm8
1464   89/<- %ebx 0/r32/eax
1465   (wait-for-mouse-event)
1466   # var dy/ecx: byte = inb(0x60)
1467   31/xor %eax 0/r32/eax
1468   e4/read-port-into-al 0x60/imm8
1469   89/<- %ecx 0/r32/eax
1470   # eax = dx
1471   89/<- %eax 3/r32/ebx
1472   # if (f1 & 0x10) dx = -dx
1473   {
1474     f6 0/subop/test-bits %dl 0x10/imm8
1475     74/jump-if-zero break/disp8
1476     0d/or-eax-with 0xffffff00/imm32
1477   }
1478   # if (f1 & 0x20) dy = -dy
1479   {
1480     f6 0/subop/test-bits %dl 0x20/imm8
1481     74/jump-if-zero break/disp8
1482     81 1/subop/or %ecx 0xffffff00/imm32
1483   }
1484 $read-mouse-event:end:
1485   # . restore registers
1486   5b/pop-to-ebx
1487   5a/pop-to-edx
1488   # . epilogue
1489   89/<- %esp 5/r32/ebp
1490   5d/pop-to-ebp
1491   c3/return
1492 
1493 +--147 lines: # mouse helpers -----------------------------------------------------------------------------------------------------------------------------------------------------------
1640 
1641 # vim:ft=subx