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:         [0x00007c00, 0x0007de00)
  19 #   system font:  [0x00100000, 0x00f00000)
  20 #   stack:        (0x02000000, 0x01000000]
  21 #   heap:         [0x02000000, 0x80000000)
  22 #     see 120allocate.subx; Qemu initializes with 128MB RAM by default; simulating 2GB RAM is known to work
  23 # Consult https://wiki.osdev.org/Memory_Map_(x86) before modifying any of
  24 # this. And don't forget to keep *stack-debug.subx in sync.
  25 
  26 == code
  27 
  28 ## 16-bit entry point: 0x7c00
  29 
  30 # Upon reset, the IBM PC:
  31 #   - loads the first sector (512 bytes)
  32 #     from some bootable image (look for the boot-sector-marker further down this file)
  33 #     to the address range [0x7c00, 0x7e00)
  34 #   - starts executing code at address 0x7c00
  35 
  36   fa/disable-interrupts
  37 
  38   # initialize segment registers
  39   b8/copy-to-ax 0/imm16
  40   8e/->seg 3/mod/direct 0/rm32/ax 3/r32/ds
  41   8e/->seg 3/mod/direct 0/rm32/ax 0/r32/es
  42   8e/->seg 3/mod/direct 0/rm32/ax 4/r32/fs
  43   8e/->seg 3/mod/direct 0/rm32/ax 5/r32/gs
  44 
  45   # Temporarily initialize stack to 0x00070000 in real mode.
  46   # We don't read or write the stack before we get to 32-bit mode, but BIOS
  47   # calls do. We need to move the stack in case BIOS initializes it to some
  48   # low address that we want to write code into.
  49   b8/copy-to-ax 0x7000/imm16
  50   8e/->seg 3/mod/direct 0/rm32/ax 2/r32/ss
  51   bc/copy-to-esp 0/imm16
  52 
  53   # undo the A20 hack: https://en.wikipedia.org/wiki/A20_line
  54   # this is from https://github.com/mit-pdos/xv6-public/blob/master/bootasm.S
  55   {
  56     e4/read-port-into-al 0x64/imm8
  57     a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
  58     75/jump-if-!zero loop/disp8
  59     b0/copy-to-al 0xd1/imm8
  60     e6/write-al-into-port 0x64/imm8
  61   }
  62   {
  63     e4/read-port-into-al 0x64/imm8
  64     a8/test-bits-in-al 0x02/imm8  # set zf if bit 1 (second-least significant) is not set
  65     75/jump-if-!zero loop/disp8
  66     b0/copy-to-al 0xdf/imm8
  67     e6/write-al-into-port 0x64/imm8
  68   }
  69 
  70   # load remaining sectors from first two tracks of disk into addresses [0x7e00, 0x17800)
  71   b4/copy-to-ah 2/imm8/read-drive
  72   # dl comes conveniently initialized at boot time with the index of the device being booted
  73   b5/copy-to-ch 0/imm8/cylinder
  74   b6/copy-to-dh 0/imm8/head                   # <====
  75   b1/copy-to-cl 2/imm8/sector  # 1-based
  76   b0/copy-to-al 0x7d/imm8/num-sectors  # 2*63 - 1 = 125
  77   # address to write sectors to = es:bx = 0x7e00, contiguous with boot segment
  78   bb/copy-to-bx 0/imm16
  79   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
  80   bb/copy-to-bx 0x7e00/imm16                  # <====
  81   cd/syscall 0x13/imm8/bios-disk-services
  82   0f 82/jump-if-carry disk_error/disp16
  83 
  84   # load two more tracks of disk into addresses [0x17800, 0x27400)
  85   b4/copy-to-ah 2/imm8/read-drive
  86   # dl comes conveniently initialized at boot time with the index of the device being booted
  87   b5/copy-to-ch 0/imm8/cylinder
  88   b6/copy-to-dh 2/imm8/head                   # <====
  89   b1/copy-to-cl 1/imm8/sector  # 1-based
  90   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
  91   # address to write sectors to = es:bx = 0x17800, contiguous with boot segment
  92   bb/copy-to-bx 0x1780/imm16                  # <====
  93   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
  94   bb/copy-to-bx 0/imm16
  95   cd/syscall 0x13/imm8/bios-disk-services
  96   0f 82/jump-if-carry disk_error/disp16
  97 
  98   # load two more tracks of disk into addresses [0x27400, 0x37000)
  99   b4/copy-to-ah 2/imm8/read-drive
 100   # dl comes conveniently initialized at boot time with the index of the device being booted
 101   b5/copy-to-ch 0/imm8/cylinder
 102   b6/copy-to-dh 4/imm8/head                   # <====
 103   b1/copy-to-cl 1/imm8/sector  # 1-based
 104   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 105   # address to write sectors to = es:bx = 0x27400, contiguous with boot segment
 106   bb/copy-to-bx 0x2740/imm16                  # <====
 107   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 108   bb/copy-to-bx 0/imm16
 109   cd/syscall 0x13/imm8/bios-disk-services
 110   0f 82/jump-if-carry disk_error/disp16
 111 
 112   # load two more tracks of disk into addresses [0x37000, 0x46c00)
 113   b4/copy-to-ah 2/imm8/read-drive
 114   # dl comes conveniently initialized at boot time with the index of the device being booted
 115   b5/copy-to-ch 0/imm8/cylinder
 116   b6/copy-to-dh 6/imm8/head                   # <====
 117   b1/copy-to-cl 1/imm8/sector  # 1-based
 118   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 119   # address to write sectors to = es:bx = 0x37000, contiguous with boot segment
 120   bb/copy-to-bx 0x3700/imm16                  # <====
 121   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 122   bb/copy-to-bx 0/imm16
 123   cd/syscall 0x13/imm8/bios-disk-services
 124   0f 82/jump-if-carry disk_error/disp16
 125 
 126   # load two more tracks of disk into addresses [0x46c00, 0x56800)
 127   b4/copy-to-ah 2/imm8/read-drive
 128   # dl comes conveniently initialized at boot time with the index of the device being booted
 129   b5/copy-to-ch 0/imm8/cylinder
 130   b6/copy-to-dh 8/imm8/head                   # <====
 131   b1/copy-to-cl 1/imm8/sector  # 1-based
 132   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 133   # address to write sectors to = es:bx = 0x46c00, contiguous with boot segment
 134   bb/copy-to-bx 0x46c0/imm16                  # <====
 135   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 136   bb/copy-to-bx 0/imm16
 137   cd/syscall 0x13/imm8/bios-disk-services
 138   0f 82/jump-if-carry disk_error/disp16
 139 
 140   # load two more tracks of disk into addresses [0x56800, 0x66400)
 141   b4/copy-to-ah 2/imm8/read-drive
 142   # dl comes conveniently initialized at boot time with the index of the device being booted
 143   b5/copy-to-ch 0/imm8/cylinder
 144   b6/copy-to-dh 0xa/imm8/head                 # <====
 145   b1/copy-to-cl 1/imm8/sector  # 1-based
 146   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 147   # address to write sectors to = es:bx = 0x56800, contiguous with boot segment
 148   bb/copy-to-bx 0x5680/imm16                  # <====
 149   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 150   bb/copy-to-bx 0/imm16
 151   cd/syscall 0x13/imm8/bios-disk-services
 152   0f 82/jump-if-carry disk_error/disp16
 153 
 154   # load two more tracks of disk into addresses [0x66400, 0x76000)
 155   b4/copy-to-ah 2/imm8/read-drive
 156   # dl comes conveniently initialized at boot time with the index of the device being booted
 157   b5/copy-to-ch 0/imm8/cylinder
 158   b6/copy-to-dh 0xc/imm8/head                 # <====
 159   b1/copy-to-cl 1/imm8/sector  # 1-based
 160   b0/copy-to-al 0x7e/imm8/num-sectors  # 2*63 = 126
 161   # address to write sectors to = es:bx = 0x56800, contiguous with boot segment
 162   bb/copy-to-bx 0x6640/imm16                  # <====
 163   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 164   bb/copy-to-bx 0/imm16
 165   cd/syscall 0x13/imm8/bios-disk-services
 166   0f 82/jump-if-carry disk_error/disp16
 167 
 168   # load one final track of disk into addresses [0x76000, 0x7de00)
 169   b4/copy-to-ah 2/imm8/read-drive
 170   # dl comes conveniently initialized at boot time with the index of the device being booted
 171   b5/copy-to-ch 0/imm8/cylinder
 172   b6/copy-to-dh 0xe/imm8/head                 # <====
 173   b1/copy-to-cl 1/imm8/sector  # 1-based
 174   b0/copy-to-al 0x3f/imm8/num-sectors=63
 175   # address to write sectors to = es:bx = 0x56800, contiguous with boot segment
 176   bb/copy-to-bx 0x7600/imm16                  # <====
 177   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 178   bb/copy-to-bx 0/imm16
 179   cd/syscall 0x13/imm8/bios-disk-services
 180   0f 82/jump-if-carry disk_error/disp16
 181 
 182   ### Loading more code tracks would clobber BIOS; we need a new compilation strategy.
 183 
 184   # reset es
 185   bb/copy-to-bx 0/imm16
 186   8e/->seg 3/mod/direct 3/rm32/bx 0/r32/es
 187 
 188   # adjust video mode
 189   b4/copy-to-ah 0x4f/imm8  # VBE commands
 190   b0/copy-to-al 2/imm8  # set video mode
 191   bb/copy-to-bx 0x4105/imm16  # 0x0105 | 0x4000
 192                               # 0x0105 = graphics mode 1024x768x256
 193                               #  (alternative candidate: 0x0101 for 640x480x256)
 194                               # 0x4000 bit = configure linear frame buffer in Bochs emulator; hopefully this doesn't hurt anything when running natively
 195   cd/syscall 0x10/imm8/bios-video-services
 196 
 197   # load information for the (hopefully) current video mode
 198   # mostly just for the address to the linear frame buffer
 199   b4/copy-to-ah 0x4f/imm8  # VBE commands
 200   b0/copy-to-al 1/imm8  # get video mode info
 201   b9/copy-to-cx 0x0105/imm16  # mode we requested
 202   bf/copy-to-di Video-mode-info/imm16
 203   cd/syscall 0x10/imm8/bios-video-services
 204 
 205   ## switch to 32-bit mode
 206   # load global descriptor table
 207   # We can't refer to the label directly because SubX doesn't do the right
 208   # thing for lgdt, so rather than make errors worse in most places we instead
 209   # pin gdt_descriptor below.
 210   0f 01 2/subop/lgdt 0/mod/indirect 6/rm32/use-disp16 0x7de0/disp16/gdt_descriptor
 211   # enable paging
 212   0f 20/<-cr 3/mod/direct 0/rm32/eax 0/r32/cr0
 213   66 83 1/subop/or 3/mod/direct 0/rm32/eax 1/imm8  # eax <- or 0x1
 214   0f 22/->cr 3/mod/direct 0/rm32/eax 0/r32/cr0
 215   # far jump to initialize_32bit_mode that sets cs to offset 8 in the gdt in the process
 216   # We can't refer to the label directly because SubX doesn't have syntax for
 217   # segment selectors. So we instead pin initialize_32bit_mode below.
 218   ea/jump-far-absolute 0x00087e00/disp32  # address 0x7e00 in offset 8 of the gdt
 219 
 220 disk_error:
 221   # print 'D' to top-left of screen to indicate disk error
 222   # *0xb8000 <- 0x0f44
 223   bb/copy-to-bx 0xb800/imm16
 224   8e/->seg 3/mod/direct 3/rm32/bx 3/r32/ds
 225   b0/copy-to-al 0x44/imm8/D
 226   b4/copy-to-ah 0x0f/imm8/white-on-black
 227   bb/copy-to-bx 0/imm16
 228   89/<- 0/mod/indirect 7/rm32/bx 0/r32/ax  # *ds:bx <- ax
 229   # loop forever
 230   {
 231     eb/jump loop/disp8
 232   }
 233 
 234 ## GDT: 3 records of 8 bytes each
 235 == data 0x7de0
 236 gdt_descriptor:
 237   0x17/imm16  # final index of gdt = size of gdt - 1
 238   gdt_start/imm32/start
 239 
 240 gdt_start:
 241 # offset 0: gdt_null:  mandatory null descriptor
 242   00 00 00 00 00 00 00 00
 243 # offset 8: gdt_code
 244   ff ff  # limit[0:16]
 245   00 00 00  # base[0:24]
 246   9a  # 1/present 00/privilege 1/descriptor type = 1001b
 247       # 1/code 0/conforming 1/readable 0/accessed =draw-code-point`
- `clear-screen`

- `draw-text-rightward`: draws a single line of text, stopping when it reaches
  either the provided bound or the right screen margin.
- `draw-stream-rightward`
- `draw-text-rightward-over-full-screen`: does not provide a bound.
- `draw-text-wrapping-right-then-down`: draws multiple lines of text on screen
  with simplistic word-wrap (no hyphenation) within (x, y) bounds.
- `draw-stream-wrapping-right-then-down`
- `draw-text-wrapping-right-then-down-over-full-screen`
- `draw-int32-hex-wrapping-right-then-down`
- `draw-int32-hex-wrapping-right-then-down-over-full-screen`
- `draw-int32-decimal-wrapping-right-then-down`
- `draw-int32-decimal-wrapping-right-then-down-over-full-screen`

Similar primitives for writing text top-to-bottom, left-to-right.

- `draw-text-downward`
- `draw-stream-downward`
- `draw-text-wrapping-down-then-right`
- `draw-stream-wrapping-down-then-right`
- `draw-text-wrapping-down-then-right-over-full-screen`
- `draw-int32-hex-wrapping-down-then-right`
- `draw-int32-hex-wrapping-down-then-right-over-full-screen`
- `draw-int32-decimal-wrapping-down-then-right`
- `draw-int32-decimal-wrapping-down-then-right-over-full-screen`

Screens remember the current cursor position.

- `cursor-position`
- `set-cursor-position`
- `draw-grapheme-at-cursor`
- `draw-code-point-at-cursor`
- `draw-cursor`: highlights the current position of the cursor. Programs must
  pass in the grapheme to draw at the cursor position, and are responsible for
  clearing the highlight when the cursor moves.
- `move-cursor-left`, `move-cursor-right`, `move-cursor-up`, `move-cursor-down`.
  These primitives always silently fail if the desired movement would go out
  of screen bounds.
- `move-cursor-to-left-margin-of-next-line`
- `move-cursor-rightward-and-downward`: move cursor one grapheme to the right

- `draw-text-rightward-from-cursor`
- `draw-text-wrapping-right-then-down-from-cursor`
- `draw-text-wrapping-right-then-down-from-cursor-over-full-screen`
- `draw-int32-hex-wrapping-right-then-down-from-cursor`
- `draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen`
- `draw-int32-decimal-wrapping-right-then-down-from-cursor`
- `draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen`

- `draw-text-wrapping-down-then-right-from-cursor`
- `draw-text-wrapping-down-then-right-from-cursor-over-full-screen`

Assertions for tests:

- `check-screen-row`: compare a screen from the left margin of a given row
  index with a string. The row index counts downward from 0 at the top of the
  screen. String can be smaller or larger than a single row, and defines the
  region of interest. Strings longer than a row wrap around to the left margin
  of the next screen row. Currently assumes text is printed left-to-right on
  the screen.
- `check-screen-row-from`: compare a fragment of a screen (left to write, top
  to bottom) starting from a given (x, y) coordinate with an expected string.
  Currently assumes text is printed left-to-right and top-to-bottom on the
  screen.
- `check-screen-row-in-color`: like `check-screen-row` but:
  - also compares foreground color
  - ignores screen locations where the expected string contains spaces
- `check-screen-row-in-color-from`
- `check-screen-row-in-background-color`
- `check-screen-row-in-background-color-from`
- `check-background-color-in-screen-row`: unlike previous functions, this
  doesn't check screen contents, only background color. Ignores background
  color where expected string contains spaces, and compares background color
  where expected string does not contain spaces. Never compares the character
  at any screen location.
- `check-background-color-in-screen-row-from`

#### events

`read-key` reads a single key from the keyboard and returns it if it exists.
Returns 0 if no key has been pressed.

`read-mouse-event` returns a recent change in x and y coordinate.

Mu doesn't currently support interrupt-based mouse events.

#### persistent storage

`load-sector` synchronously reads a single _sector_ from a _disk_ of persistent
storage. The disk must follow the ATA specification with a 28-bit sector
address. Each sector is 512 bytes. Therefore, Mu currently supports ATA hard
disks of up to 128GB capacity.

Similarly, `store-sector` synchronously writes a single sector to disk.

Mu doesn't currently support asynchronous transfers to or from a disk.
acknowledge interrupt 389 b0/copy-to-al 0x20/imm8 390 e6/write-al-into-port 0x20/imm8 391 31/xor %eax 0/r32/eax 392 # update *Timer-current-color 393 ff 0/subop/increment *Timer-counter 394 $timer-interrupt-handler:epilogue: 395 # epilogue 396 9d/pop-flags 397 61/pop-all-registers 398 fb/enable-interrupts 399 cf/return-from-interrupt 400 401 == data 402 Timer-counter: 403 0/imm32 404 405 == code 406 keyboard-interrupt-handler: 407 # prologue 408 fa/disable-interrupts 409 60/push-all-registers 410 9c/push-flags 411 # acknowledge interrupt 412 b0/copy-to-al 0x20/imm8 413 e6/write-al-into-port 0x20/imm8 414 31/xor %eax 0/r32/eax 415 # check output buffer of 8042 keyboard controller (https://web.archive.org/web/20040604041507/http://panda.cs.ndsu.nodak.edu/~achapwes/PICmicro/keyboard/atkeyboard.html) 416 e4/read-port-into-al 0x64/imm8 417 a8/test-bits-in-al 0x01/imm8 # set zf if bit 0 (least significant) is not set 418 0f 84/jump-if-not-set $keyboard-interrupt-handler:end/disp32 419 # - if keyboard buffer is full, return 420 # var dest-addr/ecx: (addr byte) = (keyboard-buffer + *keyboard-buffer:write) 421 31/xor %ecx 1/r32/ecx 422 8a/byte-> *Keyboard-buffer:write 1/r32/cl 423 81 0/subop/add %ecx Keyboard-buffer:data/imm32 424 # al = *dest-addr 425 8a/byte-> *ecx 0/r32/al 426 # if (al != 0) return 427 3c/compare-al-and 0/imm8 428 0f 85/jump-if-!= $keyboard-interrupt-handler:end/disp32 429 # - read keycode 430 e4/read-port-into-al 0x60/imm8 431 # - key released 432 # if (al == 0xaa) shift = false # left shift is being lifted 433 { 434 3c/compare-al-and 0xaa/imm8 435 75/jump-if-!= break/disp8 436 # *shift = 0 437 c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32 438 } 439 # if (al == 0xb6) shift = false # right shift is being lifted 440 { 441 3c/compare-al-and 0xb6/imm8 442 75/jump-if-!= break/disp8 443 # *shift = 0 444 c7 0/subop/copy *Keyboard-shift-pressed? 0/imm32 445 } 446 # if (al == 0x9d) ctrl = false # ctrl is being lifted 447 { 448 3c/compare-al-and 0x9d/imm8 449 75/jump-if-!= break/disp8 450 # *ctrl = 0 451 c7 0/subop/copy *Keyboard-ctrl-pressed? 0/imm32 452 } 453 # if (al & 0x80) a key is being lifted; return 454 50/push-eax 455 24/and-al-with 0x80/imm8 456 3c/compare-al-and 0/imm8 457 58/pop-to-eax 458 75/jump-if-!= $keyboard-interrupt-handler:end/disp8 459 # - key pressed 460 # if (al == 0x2a) shift = true, return # left shift pressed 461 { 462 3c/compare-al-and 0x2a/imm8 463 75/jump-if-!= break/disp8 464 # *shift = 1 465 c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32 466 # return 467 eb/jump $keyboard-interrupt-handler:end/disp8 468 } 469 # if (al == 0x36) shift = true, return # right shift pressed 470 { 471 3c/compare-al-and 0x36/imm8 472 75/jump-if-!= break/disp8 473 # *shift = 1 474 c7 0/subop/copy *Keyboard-shift-pressed? 1/imm32 475 # return 476 eb/jump $keyboard-interrupt-handler:end/disp8 477 } 478 # if (al == 0x1d) ctrl = true, return 479 { 480 3c/compare-al-and 0x1d/imm8 481 75/jump-if-!= break/disp8 482 # *ctrl = 1 483 c7 0/subop/copy *Keyboard-ctrl-pressed? 1/imm32 484 # return 485 eb/jump $keyboard-interrupt-handler:end/disp8 486 } 487 # - convert key to character 488 # if (shift) use keyboard shift map 489 { 490 81 7/subop/compare *Keyboard-shift-pressed? 0/imm32 491 74/jump-if-= break/disp8 492 # sigils don't currently support labels inside *(eax+label) 493 05/add-to-eax Keyboard-shift-map/imm32 494 8a/byte-> *eax 0/r32/al 495 eb/jump $keyboard-interrupt-handler:select-map-done/disp8 496 } 497 # if (ctrl) al = *(ctrl map + al) 498 { 499 81 7/subop/compare *Keyboard-ctrl-pressed? 0/imm32 500 74/jump-if-= break/disp8 501 05/add-to-eax Keyboard-ctrl-map/imm32 502 8a/byte-> *eax 0/r32/al 503 eb/jump $keyboard-interrupt-handler:select-map-done/disp8 504 } 505 # otherwise al = *(normal map + al) 506 05/add-to-eax Keyboard-normal-map/imm32 507 8a/byte-> *eax 0/r32/al 508 $keyboard-interrupt-handler:select-map-done: 509 # - if there's no character mapping, return 510 { 511 3c/compare-al-and 0/imm8 512 74/jump-if-= break/disp8 513 # - store al in keyboard buffer 514 88/<- *ecx 0/r32/al 515 # increment index 516 fe/increment-byte *Keyboard-buffer:write 517 # clear top nibble of index (keyboard buffer is circular) 518 80 4/subop/and-byte *Keyboard-buffer:write 0x0f/imm8 519 } 520 $keyboard-interrupt-handler:end: 521 # epilogue 522 9d/pop-flags 523 61/pop-all-registers 524 fb/enable-interrupts 525 cf/return-from-interrupt 526 527 == data 528 Keyboard-shift-pressed?: # boolean 529 0/imm32 530 531 Keyboard-ctrl-pressed?: # boolean 532 0/imm32 533 534 # var keyboard circular buffer 535 Keyboard-buffer:write: # nibble 536 0/imm32 537 Keyboard-buffer:read: # nibble 538 0/imm32 539 Keyboard-buffer:data: # byte[16] 540 00 00 00 00 541 00 00 00 00 542 00 00 00 00 543 00 00 00 00 544 545 +-- 95 lines: # Keyboard maps for translating keys to ASCII --------------------------------------------------------------------------------------------------------------------------- 640 641 Video-mode-info: 642 +-- 53 lines: # video mode info ------------------------------------------------------------------------------------------------------------------------------------------------------- 695 696 ## Controlling IDE (ATA) hard disks 697 # Uses 28-bit PIO mode. 698 # Inspired by https://colorforth.github.io/ide.html 699 # 700 # Resources: 701 # https://wiki.osdev.org/ATA_PIO_Mode 702 # https://forum.osdev.org/viewtopic.php?f=1&p=167798 703 # read-sector, according to https://www.scs.stanford.edu/11wi-cs140/pintos/specs/ata-3-std.pdf 704 705 == data 706 707 # code disk 708 # All ports are 8-bit except data-port, which is 16-bit. 709 Primary-bus-primary-drive: 710 # command-port: int (write) 711 0x1f7/imm32 712 # status-port: int (read) 713 0x1f7/imm32 714 # alternative-status-port: int (read) 715 0x3f6/imm32 716 # error-port: int (read) 717 0x1f1/imm32 718 # drive-and-head-port: int 719 0x1f6/imm32 720 # sector-count-port: int 721 0x1f2/imm32 722 # lba-low-port: int 723 0x1f3/imm32 724 # lba-mid-port: int 725 0x1f4/imm32 726 # lba-high-port: int 727 0x1f5/imm32 728 # data-port: int 729 0x1f0/imm32 730 # drive-code: byte # only drive-specific field 731 0xe0/imm32 # LBA mode also enabled 732 733 # data disk 734 # All ports are 8-bit except data-port, which is 16-bit. 735 Primary-bus-secondary-drive: 736 # command-port: int (write) 737 0x1f7/imm32 738 # status-port: int (read) 739 0x1f7/imm32 740 # alternative-status-port: int (read) 741 0x3f6/imm32 742 # error-port: int (read) 743 0x1f1/imm32 744 # drive-and-head-port: int 745 0x1f6/imm32 746 # sector-count-port: int 747 0x1f2/imm32 748 # lba-low-port: int 749 0x1f3/imm32 750 # lba-mid-port: int 751 0x1f4/imm32 752 # lba-high-port: int 753 0x1f5/imm32 754 # data-port: int 755 0x1f0/imm32 756 # drive-code: byte # only drive-specific field 757 0xf0/imm32 # LBA mode also enabled 758 759 == code 760 761 # No more than 0x100 sectors 762 read-ata-disk: # disk: (addr disk), lba: int, n: int, out: (addr stream byte) 763 # . prologue 764 55/push-ebp 765 89/<- %ebp 4/r32/esp 766 # . save registers 767 50/push-eax 768 51/push-ecx 769 52/push-edx 770 # check precondition 771 81 7/subop/compare *(ebp+0x10) 0x100/imm32 772 { 773 7e/jump-if-<= break/disp8 774 (abort "read-ata-disk: no more than 0x100 sectors") 775 } 776 # check for drive 777 (drive-exists? *(ebp+8)) # => eax 778 3d/compare-eax-and 0/imm32/false 779 0f 84/jump-if-= $read-ata-disk:end/disp32 780 # kick off read 781 (ata-drive-select *(ebp+8) *(ebp+0xc)) 782 (clear-ata-error *(ebp+8)) 783 (ata-sector-count *(ebp+8) *(ebp+0x10)) 784 (ata-lba *(ebp+8) *(ebp+0xc)) 785 (ata-command *(ebp+8) 0x20) # read sectors with retries 786 # for each sector 787 { 788 # poll for results 789 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "waiting for sector.." 7 0) 790 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "." 7 0) 791 (while-ata-busy *(ebp+8)) 792 (until-ata-data-available *(ebp+8)) 793 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "reading\n" 7 0) 794 # var data-port/edx = disk->data-port 795 8b/-> *(ebp+8) 0/r32/eax 796 8b/-> *(eax+0x24) 2/r32/edx 797 # emit results 798 31/xor %eax 0/r32/eax 799 b9/copy-to-ecx 0x200/imm32 # 512 bytes per sector 800 { 801 81 7/subop/compare %ecx 0/imm32 802 74/jump-if-= break/disp8 803 66 ed/read-port-dx-into-ax 804 # write 2 bytes to stream one at a time 805 (append-byte *(ebp+0x14) %eax) 806 49/decrement-ecx 807 c1/shift 5/subop/right-padding-zeroes %eax 8/imm8 808 (append-byte *(ebp+0x14) %eax) 809 49/decrement-ecx 810 eb/jump loop/disp8 811 } 812 # next sector 813 ff 1/subop/decrement *(ebp+0x10) 814 #? (draw-int32-decimal-wrapping-right-then-down-from-cursor-over-full-screen 0 *(ebp+0x10) 0xc 0) 815 81 7/subop/compare *(ebp+0x10) 0/imm32 816 7e/jump-if-<= break/disp8 817 (wait-400ns *(ebp+8)) 818 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "next sector\n" 7 0) 819 e9/jump loop/disp32 820 } 821 $read-ata-disk:end: 822 # . restore registers 823 5a/pop-to-edx 824 59/pop-to-ecx 825 58/pop-to-eax 826 # . epilogue 827 89/<- %esp 5/r32/ebp 828 5d/pop-to-ebp 829 c3/return 830 831 write-ata-disk: # disk: (addr disk), lba: int, n: int, in: (addr stream byte) 832 # . prologue 833 55/push-ebp 834 89/<- %ebp 4/r32/esp 835 # . save registers 836 50/push-eax 837 51/push-ecx 838 52/push-edx 839 53/push-ebx 840 # check for drive 841 (drive-exists? *(ebp+8)) # => eax 842 3d/compare-eax-and 0/imm32/false 843 0f 84/jump-if-= $write-ata-disk:end/disp32 844 # kick off write 845 (ata-drive-select *(ebp+8) *(ebp+0xc)) 846 (clear-ata-error *(ebp+8)) 847 (ata-sector-count *(ebp+8) *(ebp+0x10)) 848 (ata-lba *(ebp+8) *(ebp+0xc)) 849 (ata-command *(ebp+8) 0x30) # write sectors with retries 850 # for each sector 851 #? (set-cursor-position 0 0 0) 852 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "0" 7 0) 853 { 854 # wait 855 (while-ata-busy *(ebp+8)) 856 (until-ata-ready-for-data *(ebp+8)) 857 # var data-port/edx = disk->data-port 858 8b/-> *(ebp+8) 0/r32/eax 859 8b/-> *(eax+0x24) 2/r32/edx 860 # send data 861 b9/copy-to-ecx 0x200/imm32 # 512 bytes per sector 862 # . var first-byte/ebx: byte 863 # . when it's more than 0xff, we're at an even-numbered byte 864 bb/copy-to-ebx 0xffff/imm32 865 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "D" 7 0) 866 $write-ata-disk:store-sector: 867 { 868 81 7/subop/compare %ecx 0/imm32 869 74/jump-if-= break/disp8 870 # this loop is slow, but the ATA spec also requires a small delay 871 (stream-empty? *(ebp+0x14)) # => eax 872 3d/compare-eax-and 0/imm32/false 873 75/jump-if-!= break/disp8 874 # read byte from stream 875 (read-byte *(ebp+0x14)) # => eax 876 # if we're at an odd-numbered byte, save it to first-byte 877 81 7/subop/compare %ebx 0xff/imm32 878 { 879 7e/jump-if-<= break/disp8 880 89/<- %ebx 0/r32/eax 881 eb/jump $write-ata-disk:store-sector/disp8 882 } 883 # otherwise OR it with first-byte and write it out 884 c1/shift 4/subop/left %eax 8/imm8 885 09/or %eax 3/r32/ebx 886 66 ef/write-ax-into-port-dx 887 49/decrement-ecx 888 49/decrement-ecx 889 # reset first-byte 890 bb/copy-to-ebx 0xffff/imm32 891 eb/jump loop/disp8 892 } 893 # write out final first-byte if necessary 894 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "I" 7 0) 895 81 7/subop/compare %ebx 0xff/imm32 896 { 897 7f/jump-if-> break/disp8 898 89/<- %eax 3/r32/ebx 899 66 ef/write-ax-into-port-dx 900 49/decrement-ecx 901 49/decrement-ecx 902 } 903 # pad zeroes 904 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "P" 7 0) 905 31/xor %eax 0/r32/eax 906 { 907 81 7/subop/compare %ecx 0/imm32 908 74/jump-if-= break/disp8 909 66 ef/write-ax-into-port-dx 910 49/decrement-ecx 911 49/decrement-ecx 912 eb/jump loop/disp8 913 } 914 # next sector 915 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "N" 7 0) 916 ff 1/subop/decrement *(ebp+0x10) 917 81 7/subop/compare *(ebp+0x10) 0/imm32 918 7e/jump-if-<= break/disp8 919 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "W" 7 0) 920 (wait-400ns *(ebp+8)) 921 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "L" 7 0) 922 e9/jump loop/disp32 923 } 924 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "F" 7 0) 925 (flush-ata-cache *(ebp+8)) 926 #? (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "Y" 7 0) 927 $write-ata-disk:end: 928 # . restore registers 929 5b/pop-to-ebx 930 5a/pop-to-edx 931 59/pop-to-ecx 932 58/pop-to-eax 933 # . epilogue 934 89/<- %esp 5/r32/ebp 935 5d/pop-to-ebp 936 c3/return 937 938 +--289 lines: # disk helpers ---------------------------------------------------------------------------------------------------------------------------------------------------------- 1227 1228 ## Controlling a PS/2 mouse 1229 # Uses no IRQs, just polling. 1230 # Thanks Dave Long: https://github.com/jtauber/cleese/blob/master/necco/kernel/bochs/py8042.py 1231 # 1232 # Resources: 1233 # https://wiki.osdev.org/Mouse_Input 1234 1235 # results x/eax, y/ecx range from -256 to +255 1236 # See https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Format_of_First_3_Packet_Bytes 1237 read-mouse-event: # -> _/eax: int, _/ecx: int 1238 # . prologue 1239 55/push-ebp 1240 89/<- %ebp 4/r32/esp 1241 # . save registers 1242 52/push-edx 1243 53/push-ebx 1244 # if no event, return 0, 0 1245 b8/copy-to-eax 0/imm32 1246 b9/copy-to-ecx 0/imm32 1247 (any-mouse-event?) # => eax 1248 3d/compare-eax-and 0/imm32/false 1249 74/jump-if-= $read-mouse-event:end/disp8 1250 # var f1/edx: byte = inb(0x60) 1251 31/xor %eax 0/r32/eax 1252 e4/read-port-into-al 0x60/imm8 1253 89/<- %edx 0/r32/eax 1254 (wait-for-mouse-event) 1255 # var dx/ebx: byte = inb(0x60) 1256 31/xor %eax 0/r32/eax 1257 e4/read-port-into-al 0x60/imm8 1258 89/<- %ebx 0/r32/eax 1259 (wait-for-mouse-event) 1260 # var dy/ecx: byte = inb(0x60) 1261 31/xor %eax 0/r32/eax 1262 e4/read-port-into-al 0x60/imm8 1263 89/<- %ecx 0/r32/eax 1264 # eax = dx 1265 89/<- %eax 3/r32/ebx 1266 # if (f1 & 0x10) dx = -dx 1267 { 1268 f6 0/subop/test-bits %dl 0x10/imm8 1269 74/jump-if-zero break/disp8 1270 0d/or-eax-with 0xffffff00/imm32 1271 } 1272 # if (f1 & 0x20) dy = -dy 1273 { 1274 f6 0/subop/test-bits %dl 0x20/imm8 1275 74/jump-if-zero break/disp8 1276 81 1/subop/or %ecx 0xffffff00/imm32 1277 } 1278 $read-mouse-event:end: 1279 # . restore registers 1280 5b/pop-to-ebx 1281 5a/pop-to-edx 1282 # . epilogue 1283 89/<- %esp 5/r32/ebp 1284 5d/pop-to-ebp 1285 c3/return 1286 1287 +--147 lines: # mouse helpers --------------------------------------------------------------------------------------------------------------------------------------------------------- 1434 1435 # vim:ft=subx