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, 0x00027400)
  19 #   stack: grows down from 0x00070000
  20 #   heap: [0x01000000, 0x02000000)
  21 #     see 120allocate.subx
  22 # Consult https://wiki.osdev.org/Memory_Map_(x86) before modifying any of this.
  23 
  24 == code
  25 
  26 ## 16-bit entry point: 0x7c00
  27 
  28 # Upon reset, the IBM PC:
  29 #   - loads the first sector (512 bytes)
  30 #     from some bootable image (look for the boot-sector-marker further down this file)
  31 #     to the address range [0x7c00, 0x7e00)
  32 #   - starts executing code at address 0x7c00
  33 
  34   fa/disable-interrupts
  35 
  36   # initialize segment registers
  37   b8/copy-to-ax 0/imm16
  38   8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
  39   8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
  40   8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
  41   8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
  42 
  43   # initialize stack to 0x00070000
  44   # We don't read or write the stack before we get to 32-bit mode, but BIOS
  45   # calls do. We need to move the stack in case BIOS initializes it to some
  46   # low address that we want to write code into.
  47   b8/copy-to-ax 0x7000/imm16
  48   8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
  49   bc/copy-to-esp 0/imm16
  50 
  51   # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
  52   # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
  53   {
  54     e4/read-port-into-al 0x64/imm8
  55     a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
  56     75/jump-if-!zero loop/disp8
  57     b0/copy-to-al 0xd1/imm8
  58     e6/write-al-into-port 0x64/imm8
  59   }
  60   {
  61     e4/read-port-into-al 0x64/imm8
  62     a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
  63     75/jump-if-!zero loop/disp8
  64     b0/copy-to-al 0xdf/imm8
  65     e6/write-al-into-port 0x64/imm8
  66   }
  67 
  68   # load remaining sectors from first two tracks of disk into addresses [0x7e00, 0x17800)
  69   b4/copy-to-ah 2/imm8/read-drive
  70   # dl comes conveniently initialized at boot time with the index of the device being booted
  71   b5/copy-to-ch 0/imm8/cylinder
  72   b6/copy-to-dh 0/imm8/head
  73   b1/copy-to-cl 2/imm8/sector  # 1-based
  74   b0/copy-to-al 0x7d/imm8/num-sectors  # 2*63 - 1 = 125
  75   # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
  76   bb/copy-to-bx 0/imm16
  77   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
  78   bb/copy-to-bx 0x7e00/imm16
  79   cd/syscall 0x13/imm8/bios-disk-services
  80   0f 82/jump-if-carry disk_error/disp16
  81 
  82   # load two more tracks of disk into addresses [0x17800, 0x27400)
  83   b4/copy-to-ah 2/imm8/read-drive
  84   # dl comes conveniently initialized at boot time with the index of the device being booted
  85   b5/copy-to-ch 0/imm8/cylinder
  86   b6/copy-to-dh 2/imm8/head
  87   b1/copy-to-cl 1/imm8/sector  # 1-based
  88   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
  89   # address to write sectors to = es:bx = 0x17800, contiguous with boot segment
  90   bb/copy-to-bx 0x1780/imm16
  91   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
  92   bb/copy-to-bx 0/imm16
  93   cd/syscall 0x13/imm8/bios-disk-services
  94   0f 82/jump-if-carry disk_error/disp16
  95 
  96   # load two more tracks of disk into addresses [0x27400, 0x37000)
  97   b4/copy-to-ah 2/imm8/read-drive
  98   # dl comes conveniently initialized at boot time with the index of the device being booted
  99   b5/copy-to-ch 0/imm8/cylinder
 100   b6/copy-to-dh 4/imm8/head
 101   b1/copy-to-cl 1/imm8/sector  # 1-based
 102   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 103   # address to write sectors to = es:bx = 0x27400, contiguous with boot segment
 104   bb/copy-to-bx 0x2740/imm16
 105   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 106   bb/copy-to-bx 0/imm16
 107   cd/syscall 0x13/imm8/bios-disk-services
 108   0f 82/jump-if-carry disk_error/disp16
 109 
 110   # reset es
 111   bb/copy-to-bx 0/imm16
 112   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 113 
 114   # adjust video mode
 115   b4/copy-to-ah 0x4f/imm8  # VBE commands
 116   b0/copy-to-al 2/imm8  # set video mode
 117   bb/copy-to-bx 0x4105/imm16  # 0x0105 | 0x4000
 118                               # 0x0105 = graphics mode 1024x768x256
 119                               #  (alternative candidate: 0x0101 for 640x480x256)
 120                               # 0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively
 121   cd/syscall 0x10/imm8/bios-video-services
 122 
 123   # load information for the (hopefully) current video mode
 124   # mostly just for the address to the linear frame buffer
 125   b4/copy-to-ah 0x4f/imm8  # VBE commands
 126   b0/copy-to-al 1/imm8  # get video mode info
 127   b9/copy-to-cx 0x0105/imm16  # mode we requested
 128   bf/copy-to-di Video-mode-info/imm16
 129   cd/syscall 0x10/imm8/bios-video-services
 130 
 131   ## switch to 32-bit mode
 132   # load global descriptor table
 133   # We can't refer to the label directly because SubX doesn't do the right
 134   # thing for lgdt, so rather than make errors worse in most places we instead
 135   # pin gdt_descriptor below.
 136   0f 01 2/subop/lgdt 0/mod/indirect 6/rm32/use-disp16 0x7ce0/disp16/gdt_descriptor
 137   # enable paging
 138   0f 20/<-cr 3/mod/direct 0/rm32/eax 0/r32/cr0
 139   66 83 1/subop/or 3/mod/direct 0/rm32/eax 1/imm8  # eax <- or 0x1
 140   0f 22/->cr 3/mod/direct 0/rm32/eax 0/r32/cr0
 141   # far jump to initialize_32bit_mode that sets cs to offset 8 in the gdt in the process
 142   # We can't refer to the label directly because SubX doesn't have syntax for
 143   # segment selectors. So we instead pin initialize_32bit_mode below.
 144   ea/jump-far-absolute 0x00087d00/disp32  # address 0x7d00 in offset 8 of the gdt
 145 
 146 disk_error:
 147   # print 'D' to top-left of screen to indicate disk error
 148   # *0xb8000 <- 0x0f44
 149   bb/copy-to-bx 0xb800/imm16
 150   8e/->seg 3/mod/direct 3/rm32/bx 3/r32/ds
 151   b0/copy-to-al 0x44/imm8/D
 152   b4/copy-to-ah 0x0f/imm8/white-on-black
 153   bb/copy-to-bx 0/imm16
 154   89/<- 0/mod/indirect 7/rm32/bx 0/r32/ax  # *ds:bx <- ax
 155   # loop forever
 156   {
 157     eb/jump loop/disp8
 158   }
 159 
 160 ## GDT: 3 records of 8 bytes each
 161 == data 0x7ce0
 162 gdt_descriptor:
 163   0x17/imm16  # final index of gdt = size of gdt - 1
 164   gdt_start/imm32/start
 165 
 166 gdt_start:
 167 # offset 0: gdt_null:  mandatory null descriptor
 168   00 00 00 00 00 00 00 00
 169 # offset 8: gdt_code
 170   ff ff  # limit[0:16]
 171   00 00 00  # base[0:24]
 172   9a  # 1/present 00/privilege 1/descriptor type = 1001b
 173       # 1/code 0/conforming 1/readable 0/accessed = 1010b
 174   cf  # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b
 175       # limit[16:20] = 1111b
 176   00  # base[24:32]
 177 # offset 16: gdt_data
 178   ff ff  # limit[0:16]
 179   00 00 00  # base[0:24]
 180   92  # 1/present 00/privilege 1/descriptor type = 1001b
 181       # 0/data 0/conforming 1/readable 0/accessed = 0010b
 182   cf  # same as gdt_code
 183   00  # base[24:32]
 184 # gdt_end:
 185 
 186 ## 32-bit code from this point
 187 
 188 == code 0x7d00
 189 initialize_32bit_mode:
 190   66 b8/copy-to-ax 0x10/imm16  # offset 16 from gdt_start
 191   8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
 192   8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
 193   8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
 194   8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
 195   8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
 196 
 197   bc/copy-to-esp 0x00070000/imm32
 198 
 199   ## load interrupt handlers
 200   # We can't refer to the label directly because SubX doesn't do the right
 201   # thing for lidt, so rather than make errors worse in most places we instead
 202   # pin idt_descriptor below.
 203   0f 01 3/subop/lidt 0/mod/indirect 5/rm32/use-disp32 0x7e00/disp32/idt_descriptor
 204 
 205   # For now, not bothering reprogramming the IRQ to not conflict with software
 206   # exceptions.
 207   #   https://wiki.osdev.org/index.php?title=8259_PIC&oldid=24650#Protected_Mode
 208   #
 209   # Interrupt 1 (keyboard) conflicts with debugger faults. We don't use a
 210   # debugger.
 211   # Reference:
 212   #   https://wiki.osdev.org/Exceptions
 213 
 214   # enable timer IRQ0 and keyboard IRQ1
 215   b0/copy-to-al 0xfc/imm8  # disable mask for IRQ0 and IRQ1
 216   e6/write-al-into-port 0x21/imm8
 217 
 218   fb/enable-interrupts
 219 
 220   (initialize-mouse)
 221 
 222   ## enable floating point
 223   db/floating-point-coprocessor e3/initialize
 224   # eax <- cr4
 225   0f 20/<-cr 3/mod/direct 0/rm32/eax 4/r32/cr4
 226   # eax <- or bit 9
 227   0f ba/bit-test 5/subop/bit-test-and-set 3/mod/direct 0/rm32/eax 9/imm8
 228   # cr4 <- eax
 229   0f 22/->cr 3/mod/direct 0/rm32/eax 4/r32/cr4
 230 
 231   e9/jump Entry/disp32
 232 
 233 == boot-sector-marker 0x7dfe
 234 # final 2 bytes of boot sector
 235 55 aa
 236 
 237 ## sector 2 onwards loaded by load_disk, not automatically on boot
 238 
 239 == data 0x7e00
 240 idt_descriptor:
 241   ff 03  # final index of idt = size of idt - 1
 242   idt_start/imm32/start
 243 
 244 +-- 55 lines: # interrupt descriptor table ----------------------------------------------------------------------------------------------------------------------------------------------
 299 
 300 == code
 301 
 302 null-interrupt-handler:
 303   # prologue
 304   # Don't disable interrupts; the timer has the highest priority anyway,
 305   # and this interrupt triggers extremely frequently.
 306   fa/disable-interrupts
 307   60/push-all-registers
 308   9c/push-flags
 309   # acknowledge interrupt
 310   b0/copy-to-al 0x20/imm8
 311   e6/write-al-into-port 0x20/imm8
 312   31/xor %eax 0/r32/eax
 313 $null-interrupt-handler:epilogue:
 314   # epilogue
 315   9d/pop-flags
 316   61/pop-all-registers
 317   fb/enable-interrupts
 318   cf/return-from-interrupt
 319 
 320 timer-interrupt-handler:
 321   # prologue
 322   # Don't disable interrupts; the timer has the highest priority anyway,
 323   # and this interrupt triggers extremely frequently.
 324   fa/disable-interrupts
 325   60/push-all-registers
 326   9c/push-flags
 327   # acknowledge interrupt
 328   b0/copy-to-al 0x20/imm8
 329   e6/write-al-into-port 0x20/imm8
 330   31/xor %eax 0/r32/eax
 331 $timer-interrupt-handler:epilogue:
 332   # epilogue
 333   9d/pop-flags
 334   61/pop-all-registers
 335   fb/enable-interrupts
 336   cf/return-from-interrupt
 337 
 338 keyboard-interrupt-handler:
 339   # prologue
 340   fa/disable-interrupts
 341   60/push-all-registers
 342   9c/push-flags
 343   # acknowledge interrupt
 344   b0/copy-to-al 0x20/imm8
 345   e6/write-al-into-port 0x20/imm8
 346   31/xor %eax 0/r32/eax
 347   # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html)
 348   e4/read-port-into-al 0x64/imm8
 349   a8/test-bits-in-al 0x01/imm8  # set zf if bit 0 (least significant) is not set
 350   74/jump-if-not-set $keyboard-interrupt-handler:epilogue/disp8
 351   # - if keyboard buffer is full, return
 352   # var dest-addr/ecx: (addr byte) = (keyboard-buffer + *keyboard-buffer:write)
 353   31/xor %ecx 1/r32/ecx
 354   8a/byte-> *Keyboard-buffer:write 1/r32/cl
 355   81 0/subop/add %ecx Keyboard-buffer:data/imm32
 356   # al = *dest-addr
 357   8a/byte-> *ecx 0/r32/al
 358   # if (al != 0) return
 359   3c/compare-al-and 0/imm8
 360   75/jump-if-!= $keyboard-interrupt-handler:epilogue/disp8
 361   # - read keycode
 362   e4/read-port-into-al 0x60/imm8
 363   # - key released
 364   # if (al == 0xaa) shift = false  # left shift is being lifted
 365   {
 366     3c/compare-al-and 0xaa/imm8
 367     75/jump-if-!= break/disp8
 368     # *shift = 0
 369     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
 370   }
 371   # if (al == 0xb6) shift = false  # right shift is being lifted
 372   {
 373     3c/compare-al-and 0xb6/imm8
 374     75/jump-if-!= break/disp8
 375     # *shift = 0
 376     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
 377   }
 378   # if (al == 0x9d) ctrl = false  # ctrl is being lifted
 379   {
 380     3c/compare-al-and 0x9d/imm8
 381     75/jump-if-!= break/disp8
 382     # *ctrl = 0
 383     c7 0/subop/copy *Keyboard-ctrl-pressed? 0/imm32
 384   }
 385   # if (al & 0x80) a key is being lifted; return
 386   50/push-eax
 387   24/and-al-with 0x80/imm8
 388   3c/compare-al-and 0/imm8
 389   58/pop-to-eax
 390   75/jump-if-!= $keyboard-interrupt-handler:epilogue/disp8
 391   # - key pressed
 392   # if (al == 0x2a) shift = true, return  # left shift pressed
 393   {
 394     3c/compare-al-and 0x2a/imm8
 395     75/jump-if-!= break/disp8
 396     # *shift = 1
 397     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
 398     # return
 399     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 400   }
 401   # if (al == 0x36) shift = true, return  # right shift pressed
 402   {
 403     3c/compare-al-and 0x36/imm8
 404     75/jump-if-!= break/disp8
 405     # *shift = 1
 406     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
 407     # return
 408     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 409   }
 410   # if (al == 0x1d) ctrl = true, return
 411   {
 412     3c/compare-al-and 0x1d/imm8
 413     75/jump-if-!= break/disp8
 414     # *ctrl = 1
 415     c7 0/subop/copy *Keyboard-ctrl-pressed? 1/imm32
 416     # return
 417     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 418   }
 419   # - convert key to character
 420   # if (shift) use keyboard shift map
 421   {
 422     81 7/subop/compare *Keyboard-shift-pressed? 0/imm32
 423     74/jump-if-= break/disp8
 424     # sigils don't currently support labels inside *(eax+label)
 425     05/add-to-eax Keyboard-shift-map/imm32
 426     8a/byte-> *eax 0/r32/al
 427     eb/jump $keyboard-interrupt-handler:select-map-done/disp8
 428   }
 429   # if (ctrl) al = *(ctrl map + al)
 430   {
 431     81 7/subop/compare *Keyboard-ctrl-pressed? 0/imm32
 432     74/jump-if-= break/disp8
 433     05/add-to-eax Keyboard-ctrl-map/imm32
 434     8a/byte-> *eax 0/r32/al
 435     eb/jump $keyboard-interrupt-handler:select-map-done/disp8
 436   }
 437   # otherwise al = *(normal map + al)
 438   05/add-to-eax Keyboard-normal-map/imm32
 439   8a/byte-> *eax 0/r32/al
 440 $keyboard-interrupt-handler:select-map-done:
 441   # - if there's no character mapping, return
 442   {
 443     3c/compare-al-and 0/imm8
 444     74/jump-if-= break/disp8
 445     # - store al in keyboard buffer
 446     88/<- *ecx 0/r32/al
 447     # increment index
 448     fe/increment-byte *Keyboard-buffer:write
 449     # clear top nibble of index (keyboard buffer is circular)
 450     80 4/subop/and-byte *Keyboard-buffer:write 0x0f/imm8
 451   }
 452 $keyboard-interrupt-handler:epilogue:
 453   # epilogue
 454   9d/pop-flags
 455   61/pop-all-registers
 456   fb/enable-interrupts
 457   cf/return-from-interrupt
 458 
 459 == data
 460 Keyboard-shift-pressed?:  # boolean
 461   0/imm32
 462 
 463 Keyboard-ctrl-pressed?:  # boolean
 464   0/imm32
 465 
 466 # var keyboard circular buffer
 467 Keyboard-buffer:write:  # nibble
 468   0/imm32
 469 Keyboard-buffer:read:  # nibble
 470   0/imm32
 471 Keyboard-buffer:data:  # byte[16]
 472   00 00 00 00
 473   00 00 00 00
 474   00 00 00 00
 475   00 00 00 00
 476 
 477 +-- 95 lines: # Keyboard maps for translating keys to ASCII -----------------------------------------------------------------------------------------------------------------------------
 572 
 573 Video-mode-info:
 574 +-- 53 lines: # video mode info ---------------------------------------------------------------------------------------------------------------------------------------------------------
 627 
 628 Font:
 629 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------
 865 
 866 ## Controlling IDE (ATA) hard disks
 867 # Uses 28-bit PIO mode.
 868 # Inspired by https://colorforth.github.io/ide.html
 869 #
 870 # Resources:
 871 #   https://wiki.osdev.org/ATA_PIO_Mode
 872 #   https://forum.osdev.org/viewtopic.php?f=1&p=167798
 873 #   read-sector, according to https://www.scs.stanford.edu/11wi-cs140/pintos/specs/ata-3-std.pdf
 874 
 875 == data
 876 
 877 # We'll be gaining access just to the secondary drive on the primary bus for
 878 # now. It will have the designated 'data' disk so we don't mess with the code
 879 # disk.
 880 #
 881 # The type definition for this variable is in safe Mu (rather than unsafe
 882 # SubX) code.
 883 # All ports are 8-bit except data-port, which is 16-bit.
 884 Primary-bus-secondary-drive:
 885   # command-port: int (write)
 886   0x1f7/imm32
 887   # status-port: int (read)
 888   0x1f7/imm32
 889   # alternative-status-port: int (read)
 890   0x3f6/imm32
 891   # error-port: int (read)
 892   0x1f1/imm32
 893   # drive-and-head-port: int
 894   0x1f6/imm32
 895   # sector-count-port: int
 896   0x1f2/imm32
 897   # lba-low-port: int
 898   0x1f3/imm32
 899   # lba-mid-port: int
 900   0x1f4/imm32
 901   # lba-high-port: int
 902   0x1f5/imm32
 903   # data-port: int
 904   0x1f0/imm32
 905   # drive-code: byte                # only drive-specific field
 906   0xf0/imm32  # LBA mode also enabled
 907 
 908 == code
 909 
 910 load-sector:  # disk: (addr disk), lba: int, out: (addr stream byte)
 911   # . prologue
 912   55/push-ebp
 913   89/<- %ebp 4/r32/esp
 914   # . save registers
 915   50/push-eax
 916   51/push-ecx
 917   52/push-edx
 918   # check for drive
 919   (drive-exists? *(ebp+8))  # => eax
 920   3d/compare-eax-and 0/imm32/false
 921   0f 84/jump-if-= $load-sector:end/disp32
 922   # kick off read
 923   (ata-drive-select *(ebp+8) *(ebp+0xc))
 924   (clear-ata-error *(ebp+8))
 925   (ata-sector-count *(ebp+8) 1)
 926   (ata-lba *(ebp+8) *(ebp+0xc))
 927   (ata-command *(ebp+8) 0x20)  # read sectors with retries
 928   # poll for results
 929   (while-ata-busy *(ebp+8))
 930   (until-ata-data-available *(ebp+8))
 931   # var data-port/edx = disk->data-port
 932   8b/-> *(ebp+8) 0/r32/eax
 933   8b/-> *(eax+0x24) 2/r32/edx
 934   # emit results
 935   31/xor %eax 0/r32/eax
 936   b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
 937   {
 938     81 7/subop/compare %ecx 0/imm32
 939     74/jump-if-= break/disp8
 940     66 ed/read-port-dx-into-ax
 941     # write 2 bytes to stream one at a time
 942     (append-byte *(ebp+0x10) %eax)
 943     49/decrement-ecx
 944     c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
 945     (append-byte *(ebp+0x10) %eax)
 946     49/decrement-ecx
 947     eb/jump loop/disp8
 948   }
 949 $load-sector:end:
 950   # . restore registers
 951   5a/pop-to-edx
 952   59/pop-to-ecx
 953   58/pop-to-eax
 954   # . epilogue
 955   89/<- %esp 5/r32/ebp
 956   5d/pop-to-ebp
 957   c3/return
 958 
 959 store-sector:  # disk: (addr disk), lba: int, in: (addr stream byte)
 960   # . prologue
 961   55/push-ebp
 962   89/<- %ebp 4/r32/esp
 963   # . save registers
 964   50/push-eax
 965   51/push-ecx
 966   52/push-edx
 967   53/push-ebx
 968   # check for drive
 969   (drive-exists? *(ebp+8))  # => eax
 970   3d/compare-eax-and 0/imm32/false
 971   0f 84/jump-if-= $store-sector:end/disp32
 972   # kick off write
 973   (ata-drive-select *(ebp+8) *(ebp+0xc))
 974   (clear-ata-error *(ebp+8))
 975   (ata-sector-count *(ebp+8) 1)
 976   (ata-lba *(ebp+8) *(ebp+0xc))
 977   (ata-command *(ebp+8) 0x30)  # write sectors with retries
 978   # wait
 979   (while-ata-busy *(ebp+8))
 980   (until-ata-ready-for-data *(ebp+8))
 981   # var data-port/edx = disk->data-port
 982   8b/-> *(ebp+8) 0/r32/eax
 983   8b/-> *(eax+0x24) 2/r32/edx
 984   # send data
 985   b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
 986   # . var first-byte/ebx: byte
 987   # . when it's more than 0xff, we're at an even-numbered byte
 988   bb/copy-to-ebx 0xffff/imm32
 989 $store-sector:loop:
 990   {
 991     81 7/subop/compare %ecx 0/imm32
 992     74/jump-if-= break/disp8
 993     # this loop is slow, but the ATA spec also requires a small delay
 994     (stream-empty? *(ebp+0x10))  # => eax
 995     3d/compare-eax-and 0/imm32/false
 996     75/jump-if-!= break/disp8
 997     # read byte from stream
 998     (read-byte *(ebp+0x10))  # => eax
 999     # if we're at an odd-numbered byte, save it to first-byte
1000     81 7/subop/compare %ebx 0xff/imm32
1001     {
1002       7e/jump-if-<= break/disp8
1003       89/<- %ebx 0/r32/eax
1004       eb/jump $store-sector:loop/disp8
1005     }
1006     # otherwise OR it with first-byte and write it out
1007     c1/shift 4/subop/left %eax 8/imm8
1008     09/or %eax 3/r32/ebx
1009     66 ef/write-ax-into-port-dx
1010     49/decrement-ecx
1011     49/decrement-ecx
1012     # reset first-byte
1013     bb/copy-to-ebx 0xffff/imm32
1014     eb/jump loop/disp8
1015   }
1016   # write out first-byte if necessary
1017   81 7/subop/compare %ebx 0xff/imm32
1018   {
1019     7f/jump-if-> break/disp8
1020     89/<- %eax 3/r32/ebx
1021     66 ef/write-ax-into-port-dx
1022     49/decrement-ecx
1023     49/decrement-ecx
1024   }
1025   # pad zeroes
1026   31/xor %eax 0/r32/eax
1027   {
1028     81 7/subop/compare %ecx 0/imm32
1029     74/jump-if-= break/disp8
1030     66 ef/write-ax-into-port-dx
1031     49/decrement-ecx
1032     49/decrement-ecx
1033     eb/jump loop/disp8
1034   }
1035 $store-sector:end:
1036   # . restore registers
1037   5b/pop-to-ebx
1038   5a/pop-to-edx
1039   59/pop-to-ecx
1040   58/pop-to-eax
1041   # . epilogue
1042   89/<- %esp 5/r32/ebp
1043   5d/pop-to-ebp
1044   c3/return
1045 
1046 +--235 lines: # disk helpers ------------------------------------------------------------------------------------------------------------------------------------------------------------
1281 
1282 ## Controlling a PS/2 mouse
1283 # Uses no IRQs, just polling.
1284 # Thanks Dave Long: https://github.com/jtauber/cleese/blob/master/necco/kernel/bochs/py8042.py
1285 #
1286 # Resources:
1287 #   https://wiki.osdev.org/Mouse_Input
1288 
1289 # results x/eax, y/ecx range from -256 to +255
1290 # See https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Format_of_First_3_Packet_Bytes
1291 read-mouse-event:  # -> _/eax: int, _/ecx: int
1292   # . prologue
1293   55/push-ebp
1294   89/<- %ebp 4/r32/esp
1295   # . save registers
1296   52/push-edx
1297   53/push-ebx
1298   # if no event, return 0, 0
1299   b8/copy-to-eax 0/imm32
1300   b9/copy-to-ecx 0/imm32
1301   (any-mouse-event?)  # => eax
1302   3d/compare-eax-and 0/imm32/false
1303   74/jump-if-= $read-mouse-event:end/disp8
1304   # var f1/edx: byte = inb(0x60)
1305   31/xor %eax 0/r32/eax
1306   e4/read-port-into-al 0x60/imm8
1307   89/<- %edx 0/r32/eax
1308   (wait-for-mouse-event)
1309   # var dx/ebx: byte = inb(0x60)
1310   31/xor %eax 0/r32/eax
1311   e4/read-port-into-al 0x60/imm8
1312   89/<- %ebx 0/r32/eax
1313   (wait-for-mouse-event)
1314   # var dy/ecx: byte = inb(0x60)
1315   31/xor %eax 0/r32/eax
1316   e4/read-port-into-al 0x60/imm8
1317   89/<- %ecx 0/r32/eax
1318   # eax = dx
1319   89/<- %eax 3/r32/ebx
1320   # if (f1 & 0x10) dx = -dx
1321   {
1322     f6 0/subop/test-bits %dl 0x10/imm8
1323     74/jump-if-zero break/disp8
1324     0d/or-eax-with 0xffffff00/imm32
1325   }
1326   # if (f1 & 0x20) dy = -dy
1327   {
1328     f6 0/subop/test-bits %dl 0x20/imm8
1329     74/jump-if-zero break/disp8
1330     81 1/subop/or %ecx 0xffffff00/imm32
1331   }
1332 $read-mouse-event:end:
1333   # . restore registers
1334   5b/pop-to-ebx
1335   5a/pop-to-edx
1336   # . epilogue
1337   89/<- %esp 5/r32/ebp
1338   5d/pop-to-ebp
1339   c3/return
1340 
1341 +--147 lines: # mouse helpers -----------------------------------------------------------------------------------------------------------------------------------------------------------
1488 
1489 # vim:ft=subx