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