about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorKartik K. Agaram <vc@akkartik.com>2021-03-27 17:47:23 -0700
committerKartik K. Agaram <vc@akkartik.com>2021-03-27 17:50:44 -0700
commit7bf8adb893dcc524c7f1bc5e8f984385c9138d7d (patch)
tree7ae60840d8cb0356cee7a76cfd3c15bb5de4e58c
parent9818f1de981fe9206940cf89e9422388f8853cc2 (diff)
downloadmu-7bf8adb893dcc524c7f1bc5e8f984385c9138d7d.tar.gz
explicitly pass data disk to main
-rw-r--r--400.mu4
-rw-r--r--boot.subx395
-rw-r--r--ex10.mu2
-rw-r--r--ex2.mu2
-rw-r--r--ex3.mu2
-rw-r--r--ex4.mu2
-rw-r--r--ex5.mu2
-rw-r--r--ex6.mu2
-rw-r--r--ex7.mu2
-rw-r--r--ex8.mu2
-rw-r--r--ex9.mu6
-rw-r--r--life.mu2
-rw-r--r--mu-init.subx4
-rw-r--r--mu.md5
-rw-r--r--rpn.mu2
15 files changed, 316 insertions, 118 deletions
diff --git a/400.mu b/400.mu
index 2cf3c126..7bef992a 100644
--- a/400.mu
+++ b/400.mu
@@ -9,8 +9,8 @@ sig draw-cursor-on-real-screen g: grapheme
 sig read-key kbd: (addr keyboard) -> _/eax: byte
 
 # disk
-sig load-first-sector-from-primary-bus-secondary-drive out: (addr stream byte)
-sig store-first-sector-to-primary-bus-secondary-drive out: (addr stream byte)
+sig load-sector disk: (addr disk), lba: int, out: (addr stream byte)
+sig store-sector disk: (addr disk), lba: int, out: (addr stream byte)
 
 # mouse
 sig read-mouse-event -> _/eax: int, _/ecx: int
diff --git a/boot.subx b/boot.subx
index bc49b49d..8a4a5595 100644
--- a/boot.subx
+++ b/boot.subx
@@ -814,7 +814,42 @@ Font:
 #   https://forum.osdev.org/viewtopic.php?f=1&p=167798
 #   read-sector, according to https://www.scs.stanford.edu/11wi-cs140/pintos/specs/ata-3-std.pdf
 
-load-first-sector-from-primary-bus-secondary-drive:  # out: (addr stream byte)
+== data
+
+# We'll be gaining access just to the secondary drive on the primary bus for
+# now. It will have the designated 'data' disk so we don't mess with the code
+# disk.
+#
+# The type definition for this variable is in safe Mu (rather than unsafe
+# SubX) code.
+# All ports are 8-bit except data-port, which is 16-bit.
+Primary-bus-secondary-drive:
+  # command-port: int (write)
+  0x1f7/imm32
+  # status-port: int (read)
+  0x1f7/imm32
+  # alternative-status-port: int (read)
+  0x3f6/imm32
+  # error-port: int (read)
+  0x1f1/imm32
+  # drive-and-head-port: int
+  0x1f6/imm32
+  # sector-count-port: int
+  0x1f2/imm32
+  # lba-low-port: int
+  0x1f3/imm32
+  # lba-mid-port: int
+  0x1f4/imm32
+  # lba-high-port: int
+  0x1f5/imm32
+  # data-port: int
+  0x1f0/imm32
+  # drive-code: byte                # only drive-specific field
+  0xf0/imm32  # LBA mode also enabled
+
+== code
+
+load-sector:  # disk: (addr disk), lba: int, out: (addr stream byte)
   # . prologue
   55/push-ebp
   89/<- %ebp 4/r32/esp
@@ -823,35 +858,46 @@ load-first-sector-from-primary-bus-secondary-drive:  # out: (addr stream byte)
   51/push-ecx
   52/push-edx
   # check for drive
-  (secondary-drive-exists?)  # => eax
+  (drive-exists? *(ebp+8))  # => eax
   3d/compare-eax-and 0/imm32/false
-  0f 84/jump-if-= $load-first-sector-from-primary-bus-secondary-drive:end/disp32
+  0f 84/jump-if-= $load-sector:end/disp32
   # kick off read
-  (ata-drive-select 0xf0)  # primary bus, secondary drive; 4 LSBs contain 4 upper bits of LBA (here 0)
-  (clear-ata-error)
-  (ata-sector-count 1)
-  (ata-lba 0 0 0)  # lower 24 bits of LBA, all 0
-  (ata-command 0x20)  # read sectors with retries
+  (ata-drive-select *(ebp+8) *(ebp+0xc))
+  (clear-ata-error *(ebp+8))
+  (ata-sector-count *(ebp+8) 1)
+  (ata-lba *(ebp+8) *(ebp+0xc))
+  (ata-command *(ebp+8) 0x20)  # read sectors with retries
   # poll for results
-  (while-ata-busy)
-  (until-ata-data-available)
+  (while-ata-busy *(ebp+8))
+  (until-ata-data-available *(ebp+8))
+  # var data-port/edx = disk->data-port
+  8b/-> *(ebp+8) 0/r32/eax
+  8b/-> *(eax+0x24) 2/r32/edx
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "A: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   # emit results
   31/xor %eax 0/r32/eax
-  ba/copy-to-edx 0x1f0/imm32
   b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
   {
     81 7/subop/compare %ecx 0/imm32
     74/jump-if-= break/disp8
     66 ed/read-port-dx-into-ax
     # write 2 bytes to stream one at a time
-    (append-byte *(ebp+8) %eax)
+    (append-byte *(ebp+0x10) %eax)
     49/decrement-ecx
     c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
-    (append-byte *(ebp+8) %eax)
+    (append-byte *(ebp+0x10) %eax)
     49/decrement-ecx
     eb/jump loop/disp8
   }
-$load-first-sector-from-primary-bus-secondary-drive:end:
+$load-sector:end:
   # . restore registers
   5a/pop-to-edx
   59/pop-to-ecx
@@ -861,7 +907,7 @@ $load-first-sector-from-primary-bus-secondary-drive:end:
   5d/pop-to-ebp
   c3/return
 
-store-first-sector-to-primary-bus-secondary-drive:  # in: (addr stream byte)
+store-sector:  # disk: (addr disk), lba: int, in: (addr stream byte)
   # . prologue
   55/push-ebp
   89/<- %ebp 4/r32/esp
@@ -871,40 +917,42 @@ store-first-sector-to-primary-bus-secondary-drive:  # in: (addr stream byte)
   52/push-edx
   53/push-ebx
   # check for drive
-  (secondary-drive-exists?)  # => eax
+  (drive-exists? *(ebp+8))  # => eax
   3d/compare-eax-and 0/imm32/false
-  0f 84/jump-if-= $store-first-sector-to-primary-bus-secondary-drive:end/disp32
+  0f 84/jump-if-= $store-sector:end/disp32
   # kick off write
-  (ata-drive-select 0xf0)  # primary bus, secondary drive; 4 LSBs contain 4 upper bits of LBA (here 0)
-  (clear-ata-error)
-  (ata-sector-count 1)
-  (ata-lba 0 0 0)  # lower 24 bits of LBA, all 0
-  (ata-command 0x30)  # write sectors with retries
+  (ata-drive-select *(ebp+8) *(ebp+0xc))
+  (clear-ata-error *(ebp+8))
+  (ata-sector-count *(ebp+8) 1)
+  (ata-lba *(ebp+8) *(ebp+0xc))
+  (ata-command *(ebp+8) 0x30)  # write sectors with retries
   # wait
-  (while-ata-busy)
-  (until-ata-ready-for-data)
+  (while-ata-busy *(ebp+8))
+  (until-ata-ready-for-data *(ebp+8))
+  # var data-port/edx = disk->data-port
+  8b/-> *(ebp+8) 0/r32/eax
+  8b/-> *(eax+0x24) 2/r32/edx
   # send data
-  ba/copy-to-edx 0x1f0/imm32
   b9/copy-to-ecx 0x200/imm32  # 512 bytes per sector
-  # var first-byte/ebx: byte
-  # when it's more than 0xff, we're at an even-numbered byte
+  # . var first-byte/ebx: byte
+  # . when it's more than 0xff, we're at an even-numbered byte
   bb/copy-to-ebx 0xffff/imm32
-$store-first-sector-to-primary-bus-secondary-drive:loop:
+$store-sector:loop:
   {
     81 7/subop/compare %ecx 0/imm32
     74/jump-if-= break/disp8
     # this loop is slow, but the ATA spec also requires a small delay
-    (stream-empty? *(ebp+8))  # => eax
+    (stream-empty? *(ebp+0x10))  # => eax
     3d/compare-eax-and 0/imm32/false
     75/jump-if-!= break/disp8
     # read byte from stream
-    (read-byte *(ebp+8))  # => eax
+    (read-byte *(ebp+0x10))  # => eax
     # if we're at an odd-numbered byte, save it to first-byte
     81 7/subop/compare %ebx 0xff/imm32
     {
       7e/jump-if-<= break/disp8
       89/<- %ebx 0/r32/eax
-      eb/jump $store-first-sector-to-primary-bus-secondary-drive:loop/disp8
+      eb/jump $store-sector:loop/disp8
     }
     # otherwise OR it with first-byte and write it out
     c1/shift 4/subop/left %eax 8/imm8
@@ -935,7 +983,7 @@ $store-first-sector-to-primary-bus-secondary-drive:loop:
     49/decrement-ecx
     eb/jump loop/disp8
   }
-$store-first-sector-to-primary-bus-secondary-drive:end:
+$store-sector:end:
   # . restore registers
   5b/pop-to-ebx
   5a/pop-to-edx
@@ -948,7 +996,7 @@ $store-first-sector-to-primary-bus-secondary-drive:end:
 
 # disk helpers {{{
 
-secondary-drive-exists?:  # -> _/eax: boolean
+drive-exists?:  # disk: (addr disk) -> _/eax: boolean
   # . prologue
   55/push-ebp
   89/<- %ebp 4/r32/esp
@@ -958,37 +1006,69 @@ secondary-drive-exists?:  # -> _/eax: boolean
   {
     31/xor %eax 0/r32/eax
     ba/copy-to-edx 0x1f7/imm32
+    {
+      50/push-eax
+      51/push-ecx
+      (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "B: " 7 0)
+      (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+      (move-cursor-to-left-margin-of-next-line 0)
+      59/pop-to-ecx
+      58/pop-to-eax
+    }
     ec/read-port-dx-into-al
     3d/compare-eax-and 0xff/imm32
     # if eax is 0xff, primary bus has no drives
     b8/copy-to-eax 0/imm32/false
-    74/jump-if-= $secondary-drive-exists?:end/disp8
+    74/jump-if-= $drive-exists?:end/disp8
   }
   # identify
-  (ata-drive-select 0xf0)  # primary bus, secondary drive
-  (ata-sector-count 0)
-  (ata-lba 0 0 0)
-  (ata-command 0xec)  # identify
-  # read status register
+  (ata-drive-select *(ebp+8) 0)
+  (ata-sector-count *(ebp+8) 0)
+  (ata-lba *(ebp+8) 0)
+  (ata-command *(ebp+8) 0xec)  # identify
+  # var status-port/edx = disk->status-port
+  8b/-> *(ebp+8) 0/r32/eax
+  8b/-> *(eax+4) 2/r32/edx  # 4 = status-port offset
+  # read status port
   # TODO: might need to spin here for 400ns: https://wiki.osdev.org/index.php?title=ATA_PIO_Mode&oldid=25664#400ns_delays
   31/xor %eax 0/r32/eax
-  ba/copy-to-edx 0x1f7/imm32
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "C: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   ec/read-port-dx-into-al
-  # if eax is 0, secondary drive does not exist
+  # if eax is 0, drive does not exist
   3d/compare-eax-and 0/imm32
   {
     74/jump-if-= break/disp8
     b8/copy-to-eax 1/imm32/true
-    eb/jump $secondary-drive-exists?:complete-identify/disp8
+    eb/jump $drive-exists?:complete-identify/disp8
   }
   # TODO: might need to perform remaining steps at https://wiki.osdev.org/index.php?title=ATA_PIO_Mode&oldid=25664#IDENTIFY_command
   b8/copy-to-eax 0/imm32/false
-$secondary-drive-exists?:complete-identify:
+$drive-exists?:complete-identify:
   50/push-eax
+  # var data-port/edx = disk->data-port
+  8b/-> *(ebp+8) 0/r32/eax
+  8b/-> *(eax+0x24) 2/r32/edx  # 0x24 = data-port offset
   # clear FIFO from the drive
-  ba/copy-to-edx 0x1f0/imm32
   b9/copy-to-ecx 0x200/imm32
   {
+      50/push-eax
+      51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "D: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+      59/pop-to-ecx
+      58/pop-to-eax
+  }
+  {
     81 7/subop/compare %ecx 0/imm32
     74/jump-if-= break/disp8
     # read 4 bytes
@@ -1000,7 +1080,7 @@ $secondary-drive-exists?:complete-identify:
     eb/jump loop/disp8
   }
   58/pop-to-eax
-$secondary-drive-exists?:end:
+$drive-exists?:end:
   # . restore registers
   5a/pop-to-edx
   # . epilogue
@@ -1008,19 +1088,45 @@ $secondary-drive-exists?:end:
   5d/pop-to-ebp
   c3/return
 
-ata-drive-select:  # n: byte
+ata-drive-select:  # disk: (addr disk), lba: int
   # . prologue
   55/push-ebp
   89/<- %ebp 4/r32/esp
   # . save registers
   50/push-eax
   52/push-edx
-  #
-  8b/-> *(ebp+8) 0/r32/eax
-  ba/copy-to-edx 0x1f6/imm32
+  56/push-esi
+  # esi = disk
+  8b/-> *(ebp+8) 6/r32/esi
+  # var drive-head/edx: byte = lba >> 24
+  8b/-> *(ebp+0xc) 2/r32/edx
+  c1/shift 5/subop/right-padding-zeroes %edx 0x18/imm8
+  # var drive-code/eax: byte = disk->drive-code | drive-head
+  8b/-> *(esi+0x28) 0/r32/eax  # 0x28 = drive-code offset
+  09/or= %eax 2/r32/edx
+  # var drive-and-head-port/edx: int
+  8b/-> *(esi+0x10) 2/r32/edx  # 0x10 = drive-and-head-port offset
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "E: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 7 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   ee/write-al-into-port-dx
 $ata-drive-select:end:
   # . restore registers
+  5e/pop-to-esi
   5a/pop-to-edx
   58/pop-to-eax
   # . epilogue
@@ -1028,16 +1134,35 @@ $ata-drive-select:end:
   5d/pop-to-ebp
   c3/return
 
-clear-ata-error:
+clear-ata-error:  # disk: (addr disk)
   # . prologue
   55/push-ebp
   89/<- %ebp 4/r32/esp
   # . save registers
   50/push-eax
   52/push-edx
+  # var error-port/edx = disk->error-port
+  8b/-> *(ebp+8) 0/r32/eax
+  8b/-> *(eax+0xc) 2/r32/edx  # 0xc = error-port offset
   #
   b8/copy-to-eax 0/imm32
-  ba/copy-to-edx 0x1f1/imm32
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "F: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 7 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   ee/write-al-into-port-dx
 $ata-error:end:
   # . restore registers
@@ -1048,16 +1173,35 @@ $ata-error:end:
   5d/pop-to-ebp
   c3/return
 
-ata-sector-count:  # n: byte
+ata-sector-count:  # disk: (addr disk), n: byte
   # . prologue
   55/push-ebp
   89/<- %ebp 4/r32/esp
   # . save registers
   50/push-eax
   52/push-edx
-  #
+  # var sector-count-port/edx = disk->sector-count-port
   8b/-> *(ebp+8) 0/r32/eax
-  ba/copy-to-edx 0x1f2/imm32
+  8b/-> *(eax+0x14) 2/r32/edx  # 0x14 = sector-count-port offset
+  #
+  8b/-> *(ebp+0xc) 0/r32/eax
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "G: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 7 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   ee/write-al-into-port-dx
 $ata-sector-count:end:
   # . restore registers
@@ -1068,24 +1212,68 @@ $ata-sector-count:end:
   5d/pop-to-ebp
   c3/return
 
-ata-lba:  # lo: byte, mid: byte, hi: byte
+ata-lba:  # disk: (addr disk), lba: int
   # . prologue
   55/push-ebp
   89/<- %ebp 4/r32/esp
   # . save registers
   50/push-eax
   52/push-edx
-  # lo
+  # var port/edx = disk->port
   8b/-> *(ebp+8) 0/r32/eax
-  ba/copy-to-edx 0x1f3/imm32
+  8b/-> *(eax+0x18) 2/r32/edx  # 0x18 = lba-low-port offset
+  # eax = lba
+  8b/-> *(ebp+0xc) 0/r32/eax
+  # lo
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "H: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 7 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   ee/write-al-into-port-dx
   # mid
-  8b/-> *(ebp+0xc) 0/r32/eax
-  ba/copy-to-edx 0x1f4/imm32
+  42/increment-dx  # lba-mid-port
+  c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
+  {
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "I: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+  }
   ee/write-al-into-port-dx
   # hi
-  8b/-> *(ebp+0x10) 0/r32/eax
-  ba/copy-to-edx 0x1f5/imm32
+  42/increment-dx  # lba-high-port
+  c1/shift 5/subop/right-padding-zeroes %eax 8/imm8
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "J: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 7 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   ee/write-al-into-port-dx
 $ata-lba:end:
   # . restore registers
@@ -1096,46 +1284,35 @@ $ata-lba:end:
   5d/pop-to-ebp
   c3/return
 
-# sector: [1, 63]
-# cylinder: [0, 1023]
-ata-cyl-sector:  # sector: byte, cyl-lo: byte, cyl-hi: byte
+ata-command:  # disk: (addr disk), cmd: byte
   # . prologue
   55/push-ebp
   89/<- %ebp 4/r32/esp
   # . save registers
   50/push-eax
   52/push-edx
-  # sector
+  # var command-port/edx = disk->command-port
   8b/-> *(ebp+8) 0/r32/eax
-  ba/copy-to-edx 0x1f3/imm32
-  ee/write-al-into-port-dx
-  # cyl-lo
-  8b/-> *(ebp+0xc) 0/r32/eax
-  ba/copy-to-edx 0x1f4/imm32
-  ee/write-al-into-port-dx
-  # cyl-hi
-  8b/-> *(ebp+0x10) 0/r32/eax
-  ba/copy-to-edx 0x1f5/imm32
-  ee/write-al-into-port-dx
-$ata-lba:end:
-  # . restore registers
-  5a/pop-to-edx
-  58/pop-to-eax
-  # . epilogue
-  89/<- %esp 5/r32/ebp
-  5d/pop-to-ebp
-  c3/return
-
-ata-command:  # cmd: byte
-  # . prologue
-  55/push-ebp
-  89/<- %ebp 4/r32/esp
-  # . save registers
-  50/push-eax
-  52/push-edx
+  8b/-> *(eax+0) 2/r32/edx  # 0 = command-port offset
   #
-  8b/-> *(ebp+8) 0/r32/eax
-  ba/copy-to-edx 0x1f7/imm32
+  8b/-> *(ebp+0xc) 0/r32/eax
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "K: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 " " 7 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   ee/write-al-into-port-dx
 $ata-command:end:
   # . restore registers
@@ -1146,12 +1323,22 @@ $ata-command:end:
   5d/pop-to-ebp
   c3/return
 
-while-ata-busy:
+while-ata-busy:  # disk: (addr disk)
   # . save registers
   50/push-eax
   52/push-edx
-  #
-  ba/copy-to-edx 0x1f7/imm32
+  # var status-port/edx = disk->status-port
+  8b/-> *(ebp+8) 0/r32/eax
+  8b/-> *(eax+4) 2/r32/edx  # 4 = status-port offset
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "L: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   {
     ec/read-port-dx-into-al
     a8/test-bits-in-al 0x80/imm8/bsy  # set zf if bit 7 (most significant) is not set
@@ -1164,12 +1351,22 @@ $while-ata-busy:end:
   # . epilogue
   c3/return
 
-until-ata-data-available:
+until-ata-data-available:  # disk: (addr disk)
   # . save registers
   50/push-eax
   52/push-edx
-  #
-  ba/copy-to-edx 0x1f7/imm32
+  # var status-port/edx = disk->status-port
+  8b/-> *(ebp+8) 0/r32/eax
+  8b/-> *(eax+4) 2/r32/edx  # 4 = status-port offset
+  {
+    50/push-eax
+    51/push-ecx
+    (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "M: " 7 0)
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %edx 7 0)
+    (move-cursor-to-left-margin-of-next-line 0)
+    59/pop-to-ecx
+    58/pop-to-eax
+  }
   {
     ec/read-port-dx-into-al
     a8/test-bits-in-al 8/imm8/drq  # set zf if bit 3 is not set
diff --git a/ex10.mu b/ex10.mu
index 4d30ac70..608fdcec 100644
--- a/ex10.mu
+++ b/ex10.mu
@@ -11,7 +11,7 @@
 #   Values between -256 and +255 as you move the mouse over the window.
 #   You might need to click on the window once.
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   # repeatedly print out mouse driver results if non-zero
   $main:event-loop: {
     var dx/eax: int <- copy 0
diff --git a/ex2.mu b/ex2.mu
index e47b3add..f595510c 100644
--- a/ex2.mu
+++ b/ex2.mu
@@ -7,7 +7,7 @@
 # Or:
 #   bochs -f bochsrc               # bochsrc loads disk.img
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   var y/eax: int <- copy 0
   {
     compare y, 0x300/screen-height=768
diff --git a/ex3.mu b/ex3.mu
index b4014dbd..bf4af1d9 100644
--- a/ex3.mu
+++ b/ex3.mu
@@ -11,7 +11,7 @@
 # Expected output: a new green pixel starting from the top left corner of the
 # screen every time you press a key (letter or digit)
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   var x/ecx: int <- copy 0
   var y/edx: int <- copy 0
   {
diff --git a/ex4.mu b/ex4.mu
index 77a89f81..caed4e5c 100644
--- a/ex4.mu
+++ b/ex4.mu
@@ -9,6 +9,6 @@
 #
 # Expected output: letter 'A' in green near the top-left corner of screen
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   draw-code-point screen, 0x41/A, 2/row, 1/col, 0xa/fg, 0/bg
 }
diff --git a/ex5.mu b/ex5.mu
index 90bcac2f..6f776d16 100644
--- a/ex5.mu
+++ b/ex5.mu
@@ -10,7 +10,7 @@
 #
 # Expected output: text in green near the top-left corner of screen
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   var dummy/eax: int <- draw-text-rightward screen, "hello from baremetal Mu!", 0x10/x, 0x400/xmax, 0x10/y, 0xa/fg, 0/bg
   dummy <- draw-text-rightward screen, "you shouldn't see this", 0x10/x, 0xa0/xmax, 0x30/y, 3/fg, 0/bg  # xmax is too narrow
 }
diff --git a/ex6.mu b/ex6.mu
index 64607678..16aa34eb 100644
--- a/ex6.mu
+++ b/ex6.mu
@@ -9,7 +9,7 @@
 #
 # Expected output: a box and text that doesn't overflow it
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   # drawing text within a bounding box
   draw-box-on-real-screen 0xf, 0x1f, 0x79, 0x51, 0x4
   var x/eax: int <- copy 0x20
diff --git a/ex7.mu b/ex7.mu
index 0b84d584..ea4bf6aa 100644
--- a/ex7.mu
+++ b/ex7.mu
@@ -10,7 +10,7 @@
 # Expected output: an interactive game a bit like "snakes". Try pressing h, j,
 # k, l.
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   var space/eax: grapheme <- copy 0x20
   set-cursor-position screen, 0, 0
   {
diff --git a/ex8.mu b/ex8.mu
index 1be5dfc9..4d7e2836 100644
--- a/ex8.mu
+++ b/ex8.mu
@@ -6,7 +6,7 @@
 #   bochs -f bochsrc               # bochsrc loads disk.img
 # Set a breakpoint at 0x7c00 and start stepping.
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   var n/eax: int <- copy 0
   var result/xmm0: float <- convert n
 }
diff --git a/ex9.mu b/ex9.mu
index 003eeb4c..dc5dffd0 100644
--- a/ex9.mu
+++ b/ex9.mu
@@ -15,17 +15,17 @@
 #   6. Notice that the data disk now contains the word count of the original text.
 #       xxd data.img |head
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   var text-storage: (stream byte 0x200)
   var text/esi: (addr stream byte) <- address text-storage
-  load-first-sector-from-primary-bus-secondary-drive text
+  load-sector data-disk, 0/lba, text
 
   var word-count/eax: int <- word-count text
 
   var result-storage: (stream byte 0x10)
   var result/edi: (addr stream byte) <- address result-storage
   write-int32-decimal result, word-count
-  store-first-sector-to-primary-bus-secondary-drive result
+  store-sector data-disk, 0/lba, result
 }
 
 fn word-count in: (addr stream byte) -> _/eax: int {
diff --git a/life.mu b/life.mu
index af69c589..829443b7 100644
--- a/life.mu
+++ b/life.mu
@@ -211,7 +211,7 @@ fn render grid: (addr array boolean) {
   }
 }
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
 #?   # allocate on the stack
 #?   var grid1-storage: (array boolean 0xc000)  # width * height
 #?   var grid1/esi: (addr array boolean) <- address grid1-storage
diff --git a/mu-init.subx b/mu-init.subx
index 399a9a3f..08a8856e 100644
--- a/mu-init.subx
+++ b/mu-init.subx
@@ -3,7 +3,7 @@
 # See translate for how this file is used.
 #
 # Mu programs start at a function called 'main' with this signature:
-#   fn main screen: (addr screen), keyboard: (addr keyboard)
+#   fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk)
 #
 # All tests must pass first (the "power-on unit test").
 
@@ -22,7 +22,7 @@ Entry:
     (clear-real-screen)
     c7 0/subop/copy *Real-screen-cursor-x 0/imm32
     c7 0/subop/copy *Real-screen-cursor-y 0/imm32
-    (main 0 0)
+    (main 0 0 Primary-bus-secondary-drive)
   }
 
   # hang indefinitely
diff --git a/mu.md b/mu.md
index 09856068..dc6d9ff0 100644
--- a/mu.md
+++ b/mu.md
@@ -117,15 +117,16 @@ It takes an array of strings and returns a status code to Linux in register
 Without an OS, the signature looks like this:
 
 ```
-fn main screen: (addr screen), keyboard: (addr keyboard)
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk)
 ```
 
 A screen and keyboard are explicitly passed in. The goal is for all hardware
 dependencies to always be explicit. However there are currently gaps:
   * The mouse is accessed implicitly
-  * The disk is accessed implicitly
   * The screen argument only supports text-mode graphics. Pixel graphics rely
     on implicit access to the screen.
+  * The Mu computer has two disks, and the disk containing Mu code is not
+    accessible.
 
 ## Blocks
 
diff --git a/rpn.mu b/rpn.mu
index 719fd03d..8214e420 100644
--- a/rpn.mu
+++ b/rpn.mu
@@ -15,7 +15,7 @@
 #
 # Error handling is non-existent. This is just a prototype.
 
-fn main screen: (addr screen), keyboard: (addr keyboard) {
+fn main screen: (addr screen), keyboard: (addr keyboard), data-disk: (addr disk) {
   var in-storage: (stream byte 0x80)
   var in/esi: (addr stream byte) <- address in-storage
   var y/ecx: int <- copy 0