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 # Memory map of a Mu computer:
  15 #   code: 4 tracks of disk to [0x00007c00, 0x00027400)
  16 #   stack grows down from 0x00070000
  17 #     see below
  18 #   heap: [0x01000000, 0x02000000)
  19 #     see 120allocate.subx
  20 # Consult https://wiki.osdev.org/Memory_Map_(x86) before modifying any of this.
  21 
  22 == code
  23 
  24 ## 16-bit entry point: 0x7c00
  25 
  26 # Upon reset, the IBM PC:
  27 #   - loads the first sector (512 bytes)
  28 #     from some bootable image (look for the boot-sector-marker further down this file)
  29 #     to the address range [0x7c00, 0x7e00)
  30 #   - starts executing code at address 0x7c00
  31 
  32   fa/disable-interrupts
  33 
  34   # initialize segment registers
  35   b8/copy-to-ax 0/imm16
  36   8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
  37   8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
  38   8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
  39   8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
  40 
  41   # initialize stack to 0x00070000
  42   # We don't read or write the stack before we get to 32-bit mode, but BIOS
  43   # calls do. We need to move the stack in case BIOS initializes it to some
  44   # low address that we want to write code into.
  45   b8/copy-to-ax 0x7000/imm16
  46   8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
  47   bc/copy-to-esp 0/imm16
  48 
  49   # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
  50   # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
  51   {
  52     e4/read-port-into-al 0x64/imm8
  53     a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
  54     75/jump-if-!zero loop/imm8
  55     b0/copy-to-al 0xd1/imm8
  56     e6/write-al-into-port 0x64/imm8
  57   }
  58   {
  59     e4/read-port-into-al 0x64/imm8
  60     a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
  61     75/jump-if-!zero loop/imm8
  62     b0/copy-to-al 0xdf/imm8
  63     e6/write-al-into-port 0x64/imm8
  64   }
  65 
  66   # load remaining sectors from first two tracks of disk into addresses [0x7e00, 0x17800)
  67   b4/copy-to-ah 2/imm8/read-drive
  68   # dl comes conveniently initialized at boot time with the index of the device being booted
  69   b5/copy-to-ch 0/imm8/cylinder
  70   b6/copy-to-dh 0/imm8/head
  71   b1/copy-to-cl 2/imm8/sector  # 1-based
  72   b0/copy-to-al 0x7d/imm8/num-sectors  # 2*63 - 1 = 125
  73   # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
  74   bb/copy-to-bx 0/imm16
  75   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
  76   bb/copy-to-bx 0x7e00/imm16
  77   cd/syscall 0x13/imm8/bios-disk-services
  78   0f 82/jump-if-carry disk_error/disp16
  79 
  80   # load two more tracks of disk into addresses [0x17800, 0x27400)
  81   b4/copy-to-ah 2/imm8/read-drive
  82   # dl comes conveniently initialized at boot time with the index of the device being booted
  83   b5/copy-to-ch 0/imm8/cylinder
  84   b6/copy-to-dh 2/imm8/head
  85   b1/copy-to-cl 1/imm8/sector  # 1-based
  86   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
  87   # address to write sectors to = es:bx = 0x17800, contiguous with boot segment
  88   bb/copy-to-bx 0x1780/imm16
  89   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
  90   bb/copy-to-bx 0/imm16
  91   cd/syscall 0x13/imm8/bios-disk-services
  92   0f 82/jump-if-carry disk_error/disp16
  93 
  94   # load two more tracks of disk into addresses [0x27400, 0x37000)
  95   b4/copy-to-ah 2/imm8/read-drive
  96   # dl comes conveniently initialized at boot time with the index of the device being booted
  97   b5/copy-to-ch 0/imm8/cylinder
  98   b6/copy-to-dh 4/imm8/head
  99   b1/copy-to-cl 1/imm8/sector  # 1-based
 100   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 101   # address to write sectors to = es:bx = 0x27400, contiguous with boot segment
 102   bb/copy-to-bx 0x2740/imm16
 103   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 104   bb/copy-to-bx 0/imm16
 105   cd/syscall 0x13/imm8/bios-disk-services
 106   0f 82/jump-if-carry disk_error/disp16
 107 
 108   # reset es
 109   bb/copy-to-bx 0/imm16
 110   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 111 
 112   # adjust video mode
 113   b4/copy-to-ah 0x4f/imm8  # VBE commands
 114   b0/copy-to-al 2/imm8  # set video mode
 115   bb/copy-to-bx 0x4105/imm16  # 0x0105 | 0x4000
 116                               # 0x0105 = graphics mode 1024x768x256
 117                               #  (alternative candidate: 0x0101 for 640x480x256)
 118                               # 0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively
 119   cd/syscall 0x10/imm8/bios-video-services
 120 
 121   # load information for the (hopefully) current video mode
 122   # mostly just for the address to the linear frame buffer
 123   b4/copy-to-ah 0x4f/imm8  # VBE commands
 124   b0/copy-to-al 1/imm8  # get video mode info
 125   b9/copy-to-cx 0x0105/imm16  # mode we requested
 126   bf/copy-to-di Video-mode-info/imm16
 127   cd/syscall 0x10/imm8/bios-video-services
 128 
 129   ## switch to 32-bit mode
 130   # load global descriptor table
 131   # We can't refer to the label directly because SubX doesn't do the right
 132   # thing for lgdt, so rather than make errors worse in most places we instead
 133   # pin gdt_descriptor below.
 134   0f 01 2/subop/lgdt 0/mod/indirect 6/rm32/use-disp16 0x7cf8/disp16/gdt_descriptor
 135   # enable paging
 136   0f 20/<-cr 3/mod/direct 0/rm32/eax 0/r32/cr0
 137   66 83 1/subop/or 3/mod/direct 0/rm32/eax 1/imm8  # eax <- or 0x1
 138   0f 22/->cr 3/mod/direct 0/rm32/eax 0/r32/cr0
 139   # far jump to initialize_32bit_mode that sets cs to offset 8 in the gdt in the process
 140   # We can't refer to the label directly because SubX doesn't have syntax for
 141   # segment selectors. So we instead pin initialize_32bit_mode below.
 142   ea/jump-far-absolute 0x00087d00/disp32  # address 0x7d00 in offset 8 of the gdt
 143 
 144 disk_error:
 145   # print 'D' to top-left of screen to indicate disk error
 146   # *0xb8000 <- 0x0f44
 147   bb/copy-to-bx 0xb800/imm16
 148   8e/->seg 3/mod/direct 3/rm32/bx 3/r32/ds
 149   b0/copy-to-al 0x44/imm8/D
 150   b4/copy-to-ah 0x0f/imm8/white-on-black
 151   bb/copy-to-bx 0/imm16
 152   89/<- 0/mod/indirect 7/rm32/bx 0/r32/ax  # *ds:bx <- ax
 153   # loop forever
 154   {
 155     eb/jump loop/disp8
 156   }
 157 
 158 ## GDT: 3 records of 8 bytes each
 159 == data
 160 
 161 gdt_start:
 162 # offset 0: gdt_null:  mandatory null descriptor
 163   00 00 00 00 00 00 00 00
 164 # offset 8: gdt_code
 165   ff ff  # limit[0:16]
 166   00 00 00  # base[0:24]
 167   9a  # 1/present 00/privilege 1/descriptor type = 1001b
 168       # 1/code 0/conforming 1/readable 0/accessed = 1010b
 169   cf  # 1/granularity 1/32-bit 0/64-bit-segment 0/AVL = 1100b
 170       # limit[16:20] = 1111b
 171   00  # base[24:32]
 172 # offset 16: gdt_data
 173   ff ff  # limit[0:16]
 174   00 00 00  # base[0:24]
 175   92  # 1/present 00/privilege 1/descriptor type = 1001b
 176       # 0/data 0/conforming 1/readable 0/accessed = 0010b
 177   cf  # same as gdt_code
 178   00  # base[24:32]
 179 # gdt_end:
 180 
 181 == data 0x7cf8
 182 gdt_descriptor:
 183   0x17/imm16  # final index of gdt = size of gdt - 1
 184   gdt_start/imm32/start
 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 0x8000/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   ## enable floating point
 221   db/floating-point-coprocessor e3/initialize
 222   # eax <- cr4
 223   0f 20/<-cr 3/mod/direct 0/rm32/eax 4/r32/cr4
 224   # eax <- or bit 9
 225   0f ba/bit-test 5/subop/bit-test-and-set 3/mod/direct 0/rm32/eax 9/imm8
 226   # cr4 <- eax
 227   0f 22/->cr 3/mod/direct 0/rm32/eax 4/r32/cr4
 228 
 229   e9/jump Entry/disp32
 230 
 231 == boot-sector-marker 0x7dfe
 232 # final 2 bytes of boot sector
 233 55 aa
 234 
 235 ## sector 2 onwards loaded by load_disk, not automatically on boot
 236 == code
 237 
 238 null-interrupt-handler:
 239   cf/return-from-interrupt
 240 
 241 keyboard-interrupt-handler:
 242   # prologue
 243   fa/disable-interrupts
 244   60/push-all-registers
 245   # acknowledge interrupt
 246   b0/copy-to-al 0x20/imm8
 247   e6/write-al-into-port 0x20/imm8
 248   31/xor %eax 0/r32/eax
 249   # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html)
 250   e4/read-port-into-al 0x64/imm8
 251   a8/test-bits-in-al 0x01/imm8  # set zf if bit 0 (least significant) is not set
 252   74/jump-if-not-set $keyboard-interrupt-handler:epilogue/disp8
 253   # - if keyboard buffer is full, return
 254   # var dest-addr/ecx: (addr byte) = (keyboard-buffer + *keyboard-buffer:write)
 255   31/xor %ecx 1/r32/ecx
 256   8a/byte-> *Keyboard-buffer:write 1/r32/cl
 257   81 0/subop/add %ecx Keyboard-buffer:data/imm32
 258   # al = *dest-addr
 259   8a/byte-> *ecx 0/r32/al
 260   # if (al != 0) return
 261   3c/compare-al-and 0/imm8
 262   75/jump-if-!= $keyboard-interrupt-handler:epilogue/disp8
 263   # - read keycode
 264   e4/read-port-into-al 0x60/imm8
 265   # - key released
 266   # if (al == 0xaa) shift = false  # left shift is being lifted
 267   {
 268     3c/compare-al-and 0xaa/imm8
 269     75/jump-if-!= break/disp8
 270     # *shift = 0
 271     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
 272   }
 273   # if (al == 0xb6) shift = false  # right shift is being lifted
 274   {
 275     3c/compare-al-and 0xb6/imm8
 276     75/jump-if-!= break/disp8
 277     # *shift = 0
 278     c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32
 279   }
 280   # if (al == 0x9d) ctrl = false  # ctrl is being lifted
 281   {
 282     3c/compare-al-and 0x9d/imm8
 283     75/jump-if-!= break/disp8
 284     # *ctrl = 0
 285     c7 0/subop/copy *Keyboard-ctrl-pressed? 0/imm32
 286   }
 287   # if (al & 0x80) a key is being lifted; return
 288   50/push-eax
 289   24/and-al-with 0x80/imm8
 290   3c/compare-al-and 0/imm8
 291   58/pop-to-eax
 292   75/jump-if-!= $keyboard-interrupt-handler:epilogue/disp8
 293   # - key pressed
 294   # if (al == 0x2a) shift = true, return  # left shift pressed
 295   {
 296     3c/compare-al-and 0x2a/imm8
 297     75/jump-if-!= break/disp8
 298     # *shift = 1
 299     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
 300     # return
 301     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 302   }
 303   # if (al == 0x36) shift = true, return  # right shift pressed
 304   {
 305     3c/compare-al-and 0x36/imm8
 306     75/jump-if-!= break/disp8
 307     # *shift = 1
 308     c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32
 309     # return
 310     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 311   }
 312   # if (al == 0x1d) ctrl = true, return
 313   {
 314     3c/compare-al-and 0x1d/imm8
 315     75/jump-if-!= break/disp8
 316     # *ctrl = 1
 317     c7 0/subop/copy *Keyboard-ctrl-pressed? 1/imm32
 318     # return
 319     eb/jump $keyboard-interrupt-handler:epilogue/disp8
 320   }
 321   # - convert key to character
 322   # if (shift) use keyboard shift map
 323   {
 324     81 7/subop/compare *Keyboard-shift-pressed? 0/imm32
 325     74/jump-if-= break/disp8
 326     # sigils don't currently support labels inside *(eax+label)
 327     05/add-to-eax Keyboard-shift-map/imm32
 328     8a/byte-> *eax 0/r32/al
 329     eb/jump $keyboard-interrupt-handler:select-map-done/disp8
 330   }
 331   # if (ctrl) al = *(ctrl map + al)
 332   {
 333     81 7/subop/compare *Keyboard-ctrl-pressed? 0/imm32
 334     74/jump-if-= break/disp8
 335     05/add-to-eax Keyboard-ctrl-map/imm32
 336     8a/byte-> *eax 0/r32/al
 337     eb/jump $keyboard-interrupt-handler:select-map-done/disp8
 338   }
 339   # otherwise al = *(normal map + al)
 340   05/add-to-eax Keyboard-normal-map/imm32
 341   8a/byte-> *eax 0/r32/al
 342 $keyboard-interrupt-handler:select-map-done:
 343   # - if there's no character mapping, return
 344   {
 345     3c/compare-al-and 0/imm8
 346     74/jump-if-= break/disp8
 347     # - store al in keyboard buffer
 348     88/<- *ecx 0/r32/al
 349     # increment index
 350     fe/increment-byte *Keyboard-buffer:write
 351     # clear top nibble of index (keyboard buffer is circular)
 352     80 4/subop/and-byte *Keyboard-buffer:write 0x0f/imm8
 353   }
 354 $keyboard-interrupt-handler:epilogue:
 355   # epilogue
 356   61/pop-all-registers
 357   fb/enable-interrupts
 358   cf/return-from-interrupt
 359 
 360 ## the rest of this file is all data
 361 
 362 == data 0x8000
 363 idt_descriptor:
 364   ff 03  # final index of idt = size of idt - 1
 365   idt_start/imm32/start
 366 
 367 Keyboard-shift-pressed?:  # boolean
 368   0/imm32
 369 
 370 Keyboard-ctrl-pressed?:  # boolean
 371   0/imm32
 372 
 373 # var keyboard circular buffer
 374 Keyboard-buffer:write:  # nibble
 375   0/imm32
 376 Keyboard-buffer:read:  # nibble
 377   0/imm32
 378 Keyboard-buffer:data:  # byte[16]
 379   00 00 00 00
 380   00 00 00 00
 381   00 00 00 00
 382   00 00 00 00
 383 
 384 == data 0x8100
 385 Video-mode-info:
 386 +-- 53 lines: # video mode info ---------------------------------------------------------------------------------------------------------------------------------------------------------
 439 
 440 == data 0x8200
 441 +--161 lines: # interrupt descriptor table ----------------------------------------------------------------------------------------------------------------------------------------------
 602 
 603 == data 0x8600
 604 +-- 72 lines: # translating keys to ASCII -----------------------------------------------------------------------------------------------------------------------------------------------
 676 
 677 == data 0x8c00
 678 Font:
 679 +--236 lines: # Bitmaps for some ASCII characters (soon Unicode) ------------------------------------------------------------------------------------------------------------------------
 915 
 916 # offset 1800 (address 0x9400)
 917 == code 0x9400
 918 
 919 # Use 28-bit PIO mode to read the first sector (512 bytes) from an IDE (ATA)
 920 # disk drive.
 921 # Inspired by https://colorforth.github.io/ide.html
 922 #
 923 # Resources:
 924 #   https://wiki.osdev.org/ATA_PIO_Mode
 925 #   https://forum.osdev.org/viewtopic.php?f=1&p=167798
 926 #   read-sector, according to https://www.scs.stanford.edu/11wi-cs140/pintos/specs/ata-3-std.pdf
 927 load-first-sector-from-primary-bus-secondary-drive:  # out: (addr stream byte)
 928   # . prologue
 929   55/push-ebp
 930   89/<- %ebp 4/r32/esp
 931   # . save registers
 932   50/push-eax
 933   51/push-ecx
 934   52/push-edx
 935   # check for drive
 936   (secondary-drive-exists?)  # => eax
 937   3d/compare-eax-and 0/imm32/false
 938   0f 84/jump-if-= $load-first-sector-from-primary-bus-secondary-drive:end/disp32
 939   # kick off read
 940   (ata-drive-select 0xf0)  # primary bus, secondary drive; 4 LSBs contain 4 upper bits of LBA (here 0)
 941   (clear-ata-error)
 942   (ata-sector-count 1)
 943   (ata-lba 0 0 0)  # lower 24 bits of LBA, all 0
 944   (ata-command 0x20)  # read sectors with retries
 945   # poll for results
 946   (while-ata-busy)
 947   (until-ata-data-available)
 948   # emit results
 949   31/xor %eax 0/r32/eax
 950   ba/copy-to-edx 0x1f0/imm32
 951   b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
 952   {
 953     81 7/subop/compare %ecx 0/imm32
 954     74/jump-if-= break/disp8
 955     66 ed/read-port-dx-into-ax
 956     # write 2 bytes to stream one at a time
 957     (append-byte *(ebp+8) %eax)
 958     49/decrement-ecx
 959     c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
 960     (append-byte *(ebp+8) %eax)
 961     49/decrement-ecx
 962     eb/jump loop/disp8
 963   }
 964 $load-first-sector-from-primary-bus-secondary-drive:end:
 965   # . restore registers
 966   5a/pop-to-edx
 967   59/pop-to-ecx
 968   58/pop-to-eax
 969   # . epilogue
 970   89/<- %esp 5/r32/ebp
 971   5d/pop-to-ebp
 972   c3/return
 973 
 974 store-first-sector-to-primary-bus-secondary-drive:  # in: (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   53/push-ebx
 983   # check for drive
 984   (secondary-drive-exists?)  # => eax
 985   3d/compare-eax-and 0/imm32/false
 986   0f 84/jump-if-= $store-first-sector-to-primary-bus-secondary-drive:end/disp32
 987   # kick off write
 988   (ata-drive-select 0xf0)  # primary bus, secondary drive; 4 LSBs contain 4 upper bits of LBA (here 0)
 989   (clear-ata-error)
 990   (ata-sector-count 1)
 991   (ata-lba 0 0 0)  # lower 24 bits of LBA, all 0
 992   (ata-command 0x30)  # write sectors with retries
 993   # wait
 994   (while-ata-busy)
 995   (until-ata-ready-for-data)
 996   # send data
 997   ba/copy-to-edx 0x1f0/imm32
 998   b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
 999   # var first-byte/ebx: byte
1000   # when it's more than 0xff, we're at an even-numbered byte
1001   bb/copy-to-ebx 0xffff/imm32
1002 $store-first-sector-to-primary-bus-secondary-drive:loop:
1003   {
1004     81 7/subop/compare %ecx 0/imm32
1005     74/jump-if-= break/disp8
1006     # this loop is slow, but the ATA spec also requires a small delay
1007     (stream-empty? *(ebp+8))  # => eax
1008     3d/compare-eax-and 0/imm32/false
1009     75/jump-if-!= break/disp8
1010     # read byte from stream
1011     (read-byte *(ebp+8))  # => eax
1012     # if we're at an odd-numbered byte, save it to first-byte
1013     81 7/subop/compare %ebx 0xff/imm32
1014     {
1015       7e/jump-if-<= break/disp8
1016       89/<- %ebx 0/r32/eax
1017       eb/jump $store-first-sector-to-primary-bus-secondary-drive:loop/disp8
1018     }
1019     # otherwise OR it with first-byte and write it out
1020     c1/shift 4/subop/left %eax 8/imm8
1021     09/or %eax 3/r32/ebx
1022     66 ef/write-ax-into-port-dx
1023     49/decrement-ecx
1024     49/decrement-ecx
1025     # reset first-byte
1026     bb/copy-to-ebx 0xffff/imm32
1027     eb/jump loop/disp8
1028   }
1029   # write out first-byte if necessary
1030   81 7/subop/compare %ebx 0xff/imm32
1031   {
1032     7f/jump-if-> break/disp8
1033     89/<- %eax 3/r32/ebx
1034     66 ef/write-ax-into-port-dx
1035     49/decrement-ecx
1036     49/decrement-ecx
1037   }
1038   # pad zeroes
1039   31/xor %eax 0/r32/eax
1040   {
1041     81 7/subop/compare %ecx 0/imm32
1042     74/jump-if-= break/disp8
1043     66 ef/write-ax-into-port-dx
1044     49/decrement-ecx
1045     49/decrement-ecx
1046     eb/jump loop/disp8
1047   }
1048 $store-first-sector-to-primary-bus-secondary-drive:end:
1049   # . restore registers
1050   5b/pop-to-ebx
1051   5a/pop-to-edx
1052   59/pop-to-ecx
1053   58/pop-to-eax
1054   # . epilogue
1055   89/<- %esp 5/r32/ebp
1056   5d/pop-to-ebp
1057   c3/return
1058 
1059 secondary-drive-exists?:  # -> _/eax: boolean
1060   # . prologue
1061   55/push-ebp
1062   89/<- %ebp 4/r32/esp
1063   # . save registers
1064   52/push-edx
1065   # check for floating bus
1066   {
1067     31/xor %eax 0/r32/eax
1068     ba/copy-to-edx 0x1f7/imm32
1069     ec/read-port-dx-into-al
1070     3d/compare-eax-and 0xff/imm32
1071     # if eax is 0xff, primary bus has no drives
1072     b8/copy-to-eax 0/imm32/false
1073     74/jump-if-= $secondary-drive-exists?:end/disp8
1074   }
1075   # identify
1076   (ata-drive-select 0xb0)  # primary bus, secondary drive
1077   (ata-sector-count 0)
1078   (ata-lba 0 0 0)
1079   (ata-command 0xec)  # identify
1080   # read status register
1081   # TODO: might need to spin here for 400ns: https://wiki.osdev.org/index.php?title=ATA_PIO_Mode&oldid=25664#400ns_delays
1082   31/xor %eax 0/r32/eax
1083   ba/copy-to-edx 0x1f7/imm32
1084   ec/read-port-dx-into-al
1085   # if eax is 0, secondary drive does not exist
1086   3d/compare-eax-and 0/imm32
1087   {
1088     74/jump-if-= break/disp8
1089     b8/copy-to-eax 1/imm32/true
1090     eb/jump $secondary-drive-exists?:complete-identify/disp8
1091   }
1092   # TODO: might need to perform remaining steps at https://wiki.osdev.org/index.php?title=ATA_PIO_Mode&oldid=25664#IDENTIFY_command
1093   b8/copy-to-eax 0/imm32/false
1094 $secondary-drive-exists?:complete-identify:
1095   50/push-eax
1096   # clear FIFO from the drive
1097   ba/copy-to-edx 0x1f0/imm32
1098   b9/copy-to-ecx 0x200/imm32
1099   {
1100     81 7/subop/compare %ecx 0/imm32
1101     74/jump-if-= break/disp8
1102     # read 4 bytes
1103     ed/read-port-dx-into-eax
1104     49/decrement-ecx
1105     49/decrement-ecx
1106     49/decrement-ecx
1107     49/decrement-ecx
1108     eb/jump loop/disp8
1109   }
1110   58/pop-to-eax
1111 $secondary-drive-exists?:end:
1112   # . restore registers
1113   5a/pop-to-edx
1114   # . epilogue
1115   89/<- %esp 5/r32/ebp
1116   5d/pop-to-ebp
1117   c3/return
1118 
1119 ata-drive-select:  # n: byte
1120   # . prologue
1121   55/push-ebp
1122   89/<- %ebp 4/r32/esp
1123   # . save registers
1124   50/push-eax
1125   52/push-edx
1126   #
1127   8b/-> *(ebp+8) 0/r32/eax
1128   ba/copy-to-edx 0x1f6/imm32
1129   ee/write-al-into-port-dx
1130 $ata-drive-select:end:
1131   # . restore registers
1132   5a/pop-to-edx
1133   58/pop-to-eax
1134   # . epilogue
1135   89/<- %esp 5/r32/ebp
1136   5d/pop-to-ebp
1137   c3/return
1138 
1139 clear-ata-error:
1140   # . prologue
1141   55/push-ebp
1142   89/<- %ebp 4/r32/esp
1143   # . save registers
1144   50/push-eax
1145   52/push-edx
1146   #
1147   b8/copy-to-eax 0/imm32
1148   ba/copy-to-edx 0x1f1/imm32
1149   ee/write-al-into-port-dx
1150 $ata-error:end:
1151   # . restore registers
1152   5a/pop-to-edx
1153   58/pop-to-eax
1154   # . epilogue
1155   89/<- %esp 5/r32/ebp
1156   5d/pop-to-ebp
1157   c3/return
1158 
1159 ata-sector-count:  # n: byte
1160   # . prologue
1161   55/push-ebp
1162   89/<- %ebp 4/r32/esp
1163   # . save registers
1164   50/push-eax
1165   52/push-edx
1166   #
1167   8b/-> *(ebp+8) 0/r32/eax
1168   ba/copy-to-edx 0x1f2/imm32
1169   ee/write-al-into-port-dx
1170 $ata-sector-count:end:
1171   # . restore registers
1172   5a/pop-to-edx
1173   58/pop-to-eax
1174   # . epilogue
1175   89/<- %esp 5/r32/ebp
1176   5d/pop-to-ebp
1177   c3/return
1178 
1179 ata-lba:  # lo: byte, mid: byte, hi: byte
1180   # . prologue
1181   55/push-ebp
1182   89/<- %ebp 4/r32/esp
1183   # . save registers
1184   50/push-eax
1185   52/push-edx
1186   # lo
1187   8b/-> *(ebp+8) 0/r32/eax
1188   ba/copy-to-edx 0x1f3/imm32
1189   ee/write-al-into-port-dx
1190   # mid
1191   8b/-> *(ebp+0xc) 0/r32/eax
1192   ba/copy-to-edx 0x1f4/imm32
1193   ee/write-al-into-port-dx
1194   # hi
1195   8b/-> *(ebp+0x10) 0/r32/eax
1196   ba/copy-to-edx 0x1f5/imm32
1197   ee/write-al-into-port-dx
1198 $ata-lba:end:
1199   # . restore registers
1200   5a/pop-to-edx
1201   58/pop-to-eax
1202   # . epilogue
1203   89/<- %esp 5/r32/ebp
1204   5d/pop-to-ebp
1205   c3/return
1206 
1207 # sector: [1, 63]
1208 # cylinder: [0, 1023]
1209 ata-cyl-sector:  # sector: byte, cyl-lo: byte, cyl-hi: byte
1210   # . prologue
1211   55/push-ebp
1212   89/<- %ebp 4/r32/esp
1213   # . save registers
1214   50/push-eax
1215   52/push-edx
1216   # sector
1217   8b/-> *(ebp+8) 0/r32/eax
1218   ba/copy-to-edx 0x1f3/imm32
1219   ee/write-al-into-port-dx
1220   # cyl-lo
1221   8b/-> *(ebp+0xc) 0/r32/eax
1222   ba/copy-to-edx 0x1f4/imm32
1223   ee/write-al-into-port-dx
1224   # cyl-hi
1225   8b/-> *(ebp+0x10) 0/r32/eax
1226   ba/copy-to-edx 0x1f5/imm32
1227   ee/write-al-into-port-dx
1228 $ata-lba:end:
1229   # . restore registers
1230   5a/pop-to-edx
1231   58/pop-to-eax
1232   # . epilogue
1233   89/<- %esp 5/r32/ebp
1234   5d/pop-to-ebp
1235   c3/return
1236 
1237 ata-command:  # cmd: byte
1238   # . prologue
1239   55/push-ebp
1240   89/<- %ebp 4/r32/esp
1241   # . save registers
1242   50/push-eax
1243   52/push-edx
1244   #
1245   8b/-> *(ebp+8) 0/r32/eax
1246   ba/copy-to-edx 0x1f7/imm32
1247   ee/write-al-into-port-dx
1248 $ata-command:end:
1249   # . restore registers
1250   5a/pop-to-edx
1251   58/pop-to-eax
1252   # . epilogue
1253   89/<- %esp 5/r32/ebp
1254   5d/pop-to-ebp
1255   c3/return
1256 
1257 while-ata-busy:
1258   # . save registers
1259   50/push-eax
1260   52/push-edx
1261   #
1262   ba/copy-to-edx 0x1f7/imm32
1263   {
1264     ec/read-port-dx-into-al
1265     a8/test-bits-in-al 0x80/imm8/bsy  # set zf if bit 7 (most significant) is not set
1266     75/jump-if-zf-not-set-and-bit-7-set loop/disp8
1267   }
1268 $while-ata-busy:end:
1269   # . restore registers
1270   5a/pop-to-edx
1271   58/pop-to-eax
1272   # . epilogue
1273   c3/return
1274 
1275 until-ata-data-available:
1276   # . save registers
1277   50/push-eax
1278   52/push-edx
1279   #
1280   ba/copy-to-edx 0x1f7/imm32
1281   {
1282     ec/read-port-dx-into-al
1283     a8/test-bits-in-al 8/imm8/drq  # set zf if bit 3 is not set
1284     74/jump-if-zf-set-and-bit-3-not-set loop/disp8
1285   }
1286 $while-ata-busy:end:
1287   # . restore registers
1288   5a/pop-to-edx
1289   58/pop-to-eax
1290   # . epilogue
1291   c3/return
1292 
1293 until-ata-ready-for-data:
1294   (until-ata-data-available)
1295   c3/return
1296 
1297 # vim:ft=subx