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/imm8
      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/imm8
      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 keyboard IRQ (1)
     215   b0/copy-to-al 0xfd/imm8  # disable mask for 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   cf/return-from-interrupt
     304
     305 keyboard-interrupt-handler:
     306   # prologue
     307   fa/disable-interrupts
     308   60/push-all-registers
     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   # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html)
     314   e4/read-port-into-al 0x64/imm8
     315   a8/test-bits-in-al 0x01/imm8  # set zf if bit 0 (least significant) is not set
     316   74/jump-if-not-set $keyboard-interrupt-handler:epilogue/disp8
     317   # - if keyboard buffer is full, return
     318   # var dest-addr/ecx: (addr byte) = (keyboard-buffer + *keyboard-buffer:write)
     319   31/xor %ecx 1/r32/ecx
     320   8a/byte-> *Keyboard-buffer:write 1/r32/cl
     321   81 0/subop/add %ecx Keyboard-buffer:data/imm32
     322   # al = *dest-addr
     323   8a/byte-> *ecx 0/r32/al
     324   # if (al != 0) return
     325   3c/compare-al-and 0/imm8
     326   75/jump-if-!= $keyboard-interrupt-handler:epilogue/disp8
     327   # - read keycode
     328   e4/read-port-into-al 0x60/imm8
     329   # - key released
     330   # if (al == 0xaa) shift = false  # left shift is being lifted
     331   {
     332     3c/compare-al-and 0xaa/imm8
     333     75/jump-if-!= break/disp8
     334     # *shift = 0
     335     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
     336   }
     337   # if (al == 0xb6) shift = false  # right shift is being lifted
     338   {
     339     3c/compare-al-and 0xb6/imm8
     340     75/jump-if-!= break/disp8
     341     # *shift = 0
     342     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
     343   }
     344   # if (al == 0x9d) ctrl = false  # ctrl is being lifted
     345   {
     346     3c/compare-al-and 0x9d/imm8
     347     75/jump-if-!= break/disp8
     348     # *ctrl = 0
     349     c7 0/subop/copy *Keyboard-ctrl-pressed? 0/imm32
     350   }
     351   # if (al & 0x80) a key is being lifted; return
     352   50/push-eax
     353   24/and-al-with 0x80/imm8
     354   3c/compare-al-and 0/imm8
     355   58/pop-to-eax
     356   75/jump-if-!= $keyboard-interrupt-handler:epilogue/disp8
     357   # - key pressed
     358   # if (al == 0x2a) shift = true, return  # left shift pressed
     359   {
     360     3c/compare-al-and 0x2a/imm8
     361     75/jump-if-!= break/disp8
     362     # *shift = 1
     363     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
     364     # return
     365     eb/jump $keyboard-interrupt-handler:epilogue/disp8
     366   }
     367   # if (al == 0x36) shift = true, return  # right shift pressed
     368   {
     369     3c/compare-al-and 0x36/imm8
     370     75/jump-if-!= break/disp8
     371     # *shift = 1
     372     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
     373     # return
     374     eb/jump $keyboard-interrupt-handler:epilogue/disp8
     375   }
     376   # if (al == 0x1d) ctrl = true, return
     377   {
     378     3c/compare-al-and 0x1d/imm8
     379     75/jump-if-!= break/disp8
     380     # *ctrl = 1
     381     c7 0/subop/copy *Keyboard-ctrl-pressed? 1/imm32
     382     # return
     383     eb/jump $keyboard-interrupt-handler:epilogue/disp8
     384   }
     385   # - convert key to character
     386   # if (shift) use keyboard shift map
     387   {
     388     81 7/subop/compare *Keyboard-shift-pressed? 0/imm32
     389     74/jump-if-= break/disp8
     390     # sigils don't currently support labels inside *(eax+label)
     391     05/add-to-eax Keyboard-shift-map/imm32
     392     8a/byte-> *eax 0/r32/al
     393     eb/jump $keyboard-interrupt-handler:select-map-done/disp8
     394   }
     395   # if (ctrl) al = *(ctrl map + al)
     396   {
     397     81 7/subop/compare *Keyboard-ctrl-pressed? 0/imm32
     398     74/jump-if-= break/disp8
     399     05/add-to-eax Keyboard-ctrl-map/imm32
     400     8a/byte-> *eax 0/r32/al
     401     eb/jump $keyboard-interrupt-handler:select-map-done/disp8
     402   }
     403   # otherwise al = *(normal map + al)
     404   05/add-to-eax Keyboard-normal-map/imm32
     405   8a/byte-> *eax 0/r32/al
     406 $keyboard-interrupt-handler:select-map-done:
     407   # - if there's no character mapping, return
     408   {
     409     3c/compare-al-and 0/imm8
     410     74/jump-if-= break/disp8
     411     # - store al in keyboard buffer
     412     88/<- *ecx 0/r32/al
     413     # increment index
     414     fe/increment-byte *Keyboard-buffer:write
     415     # clear top nibble of index (keyboard buffer is circular)
     416     80 4/subop/and-byte *Keyboard-buffer:write 0x0f/imm8
     417   }
     418 $keyboard-interrupt-handler:epilogue:
     419   # epilogue
     420   61/pop-all-registers
     421   fb/enable-interrupts
     422   cf/return-from-interrupt
     423
     424 == data
     425 Keyboard-shift-pressed?:  # boolean
     426   0/imm32
     427
     428 Keyboard-ctrl-pressed?:  # boolean
     429   0/imm32
     430
     431 # var keyboard circular buffer
     432 Keyboard-buffer:write:  # nibble
     433   0/imm32
     434 Keyboard-buffer:read:  # nibble
     435   0/imm32
     436 Keyboard-buffer:data:  # byte[16]
     437   00 00 00 00
     438   00 00 00 00
     439   00 00 00 00
     440   00 00 00 00
     441
     442 +-- 70 lines: # Keyboard maps for translating keys to ASCII -----------------------------------------------------------------------------------------------------------------------------
     512
     513 Video-mode-info:
     514 +-- 53 lines: # video mode info ---------------------------------------------------------------------------------------------------------------------------------------------------------
     567
     568 Font:
     569 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------
     805
     806 == code
     807
     808 ## Controlling IDE (ATA) hard disks
     809 # Uses 28-bit PIO mode.
     810 # Inspired by https://colorforth.github.io/ide.html
     811 #
     812 # Resources:
     813 #   https://wiki.osdev.org/ATA_PIO_Mode
     814 #   https://forum.osdev.org/viewtopic.php?f=1&p=167798
     815 #   read-sector, according to https://www.scs.stanford.edu/11wi-cs140/pintos/specs/ata-3-std.pdf
     816
     817 load-first-sector-from-primary-bus-secondary-drive:  # out: (addr stream byte)
     818   # . prologue
     819   55/push-ebp
     820   89/<- %ebp 4/r32/esp
     821   # . save registers
     822   50/push-eax
     823   51/push-ecx
     824   52/push-edx
     825   # check for drive
     826   (secondary-drive-exists?)  # => eax
     827   3d/compare-eax-and 0/imm32/false
     828   0f 84/jump-if-= $load-first-sector-from-primary-bus-secondary-drive:end/disp32
     829   # kick off read
     830   (ata-drive-select 0xf0)  # primary bus, secondary drive; 4 LSBs contain 4 upper bits of LBA (here 0)
     831   (clear-ata-error)
     832   (ata-sector-count 1)
     833   (ata-lba 0 0 0)  # lower 24 bits of LBA, all 0
     834   (ata-command 0x20)  # read sectors with retries
     835   # poll for results
     836   (while-ata-busy)
     837   (until-ata-data-available)
     838   # emit results
     839   31/xor %eax 0/r32/eax
     840   ba/copy-to-edx 0x1f0/imm32
     841   b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
     842   {
     843     81 7/subop/compare %ecx 0/imm32
     844     74/jump-if-= break/disp8
     845     66 ed/read-port-dx-into-ax
     846     # write 2 bytes to stream one at a time
     847     (append-byte *(ebp+8) %eax)
     848     49/decrement-ecx
     849     c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
     850     (append-byte *(ebp+8) %eax)
     851     49/decrement-ecx
     852     eb/jump loop/disp8
     853   }
     854 $load-first-sector-from-primary-bus-secondary-drive:end:
     855   # . restore registers
     856   5a/pop-to-edx
     857   59/pop-to-ecx
     858   58/pop-to-eax
     859   # . epilogue
     860   89/<- %esp 5/r32/ebp
     861   5d/pop-to-ebp
     862   c3/return
     863
     864 store-first-sector-to-primary-bus-secondary-drive:  # in: (addr stream byte)
     865   # . prologue
     866   55/push-ebp
     867   89/<- %ebp 4/r32/esp
     868   # . save registers
     869   50/push-eax
     870   51/push-ecx
     871   52/push-edx
     872   53/push-ebx
     873   # check for drive
     874   (secondary-drive-exists?)  # => eax
     875   3d/compare-eax-and 0/imm32/false
     876   0f 84/jump-if-= $store-first-sector-to-primary-bus-secondary-drive:end/disp32
     877   # kick off write
     878   (ata-drive-select 0xf0)  # primary bus, secondary drive; 4 LSBs contain 4 upper bits of LBA (here 0)
     879   (clear-ata-error)
     880   (ata-sector-count 1)
     881   (ata-lba 0 0 0)  # lower 24 bits of LBA, all 0
     882   (ata-command 0x30)  # write sectors with retries
     883   # wait
     884   (while-ata-busy)
     885   (until-ata-ready-for-data)
     886   # send data
     887   ba/copy-to-edx 0x1f0/imm32
     888   b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
     889   # var first-byte/ebx: byte
     890   # when it's more than 0xff, we're at an even-numbered byte
     891   bb/copy-to-ebx 0xffff/imm32
     892 $store-first-sector-to-primary-bus-secondary-drive:loop:
     893   {
     894     81 7/subop/compare %ecx 0/imm32
     895     74/jump-if-= break/disp8
     896     # this loop is slow, but the ATA spec also requires a small delay
     897     (stream-empty? *(ebp+8))  # => eax
     898     3d/compare-eax-and 0/imm32/false
     899     75/jump-if-!= break/disp8
     900     # read byte from stream
     901     (read-byte *(ebp+8))  # => eax
     902     # if we're at an odd-numbered byte, save it to first-byte
     903     81 7/subop/compare %ebx 0xff/imm32
     904     {
     905       7e/jump-if-<= break/disp8
     906       89/<- %ebx 0/r32/eax
     907       eb/jump $store-first-sector-to-primary-bus-secondary-drive:loop/disp8
     908     }
     909     # otherwise OR it with first-byte and write it out
     910     c1/shift 4/subop/left %eax 8/imm8
     911     09/or %eax 3/r32/ebx
     912     66 ef/write-ax-into-port-dx
     913     49/decrement-ecx
     914     49/decrement-ecx
     915     # reset first-byte
     916     bb/copy-to-ebx 0xffff/imm32
     917     eb/jump loop/disp8
     918   }
     919   # write out first-byte if necessary
     920   81 7/subop/compare %ebx 0xff/imm32
     921   {
     922     7f/jump-if-> break/disp8
     923     89/<- %eax 3/r32/ebx
     924     66 ef/write-ax-into-port-dx
     925     49/decrement-ecx
     926     49/decrement-ecx
     927   }
     928   # pad zeroes
     929   31/xor %eax 0/r32/eax
     930   {
     931     81 7/subop/compare %ecx 0/imm32
     932     74/jump-if-= break/disp8
     933     66 ef/write-ax-into-port-dx
     934     49/decrement-ecx
     935     49/decrement-ecx
     936     eb/jump loop/disp8
     937   }
     938 $store-first-sector-to-primary-bus-secondary-drive:end:
     939   # . restore registers
     940   5b/pop-to-ebx
     941   5a/pop-to-edx
     942   59/pop-to-ecx
     943   58/pop-to-eax
     944   # . epilogue
     945   89/<- %esp 5/r32/ebp
     946   5d/pop-to-ebp
     947   c3/return
     948
     949 +--241 lines: # disk helpers ------------------------------------------------------------------------------------------------------------------------------------------------------------
    1190
    1191 ## Controlling a PS/2 mouse
    1192 # Uses no IRQs, just polling.
    1193 # Thanks Dave Long: https://github.com/jtauber/cleese/blob/master/necco/kernel/bochs/py8042.py
    1194 #
    1195 # Resources:
    1196 #   https://wiki.osdev.org/Mouse_Input
    1197
    1198 # results x/eax, y/ecx range from -256 to +255
    1199 # See https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Format_of_First_3_Packet_Bytes
    1200 read-mouse-event:  # -> _/eax: int, _/ecx: int
    1201   # . prologue
    1202   55/push-ebp
    1203   89/<- %ebp 4/r32/esp
    1204   # . save registers
    1205   52/push-edx
    1206   53/push-ebx
    1207   # if no event, return 0, 0
    1208   b8/copy-to-eax 0/imm32
    1209   b9/copy-to-ecx 0/imm32
    1210   (any-mouse-event?)  # => eax
    1211   3d/compare-eax-and 0/imm32/false
    1212   74/jump-if-= $read-mouse-event:end/disp8
    1213   # var f1/edx: byte = inb(0x60)
    1214   31/xor %eax 0/r32/eax
    1215   e4/read-port-into-al 0x60/imm8
    1216   89/<- %edx 0/r32/eax
    1217   (wait-for-mouse-event)
    1218   # var dx/ebx: byte = inb(0x60)
    1219   31/xor %eax 0/r32/eax
    1220   e4/read-port-into-al 0x60/imm8
    1221   89/<- %ebx 0/r32/eax
    1222   (wait-for-mouse-event)
    1223   # var dy/ecx: byte = inb(0x60)
    1224   31/xor %eax 0/r32/eax
    1225   e4/read-port-into-al 0x60/imm8
    1226   89/<- %ecx 0/r32/eax
    1227   # eax = dx
    1228   89/<- %eax 3/r32/ebx
    1229   # if (f1 & 0x10) dx = -dx
    1230   {
    1231     f6 0/subop/test-bits %dl 0x10/imm8
    1232     74/jump-if-zero break/disp8
    1233     0d/or-eax-with 0xffffff00/imm32
    1234   }
    1235   # if (f1 & 0x20) dy = -dy
    1236   {
    1237     f6 0/subop/test-bits %dl 0x20/imm8
    1238     74/jump-if-zero break/disp8
    1239     81 1/subop/or %ecx 0xffffff00/imm32
    1240   }
    1241 $read-mouse-event:end:
    1242   # . restore registers
    1243   5b/pop-to-ebx
    1244   5a/pop-to-edx
    1245   # . epilogue
    1246   89/<- %esp 5/r32/ebp
    1247   5d/pop-to-ebp
    1248   c3/return
    1249
    1250 +--152 lines: # mouse helpers -----------------------------------------------------------------------------------------------------------------------------------------------------------
    1402
    1403 # vim:ft=subx