about summary refs log tree commit diff stats
diff options
context:
space:
mode:
-rw-r--r--400.mu3
-rw-r--r--README.md24
-rw-r--r--bochsrc2
-rw-r--r--boot.subx215
-rw-r--r--ex10.mu42
5 files changed, 271 insertions, 15 deletions
diff --git a/400.mu b/400.mu
index 2a0be7cf..2cf3c126 100644
--- a/400.mu
+++ b/400.mu
@@ -12,6 +12,9 @@ sig read-key kbd: (addr keyboard) -> _/eax: byte
 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)
 
+# mouse
+sig read-mouse-event -> _/eax: int, _/ecx: int
+
 # tests
 sig count-test-failure
 sig num-test-failures -> _/eax: int
diff --git a/README.md b/README.md
index 491e4028..3de83450 100644
--- a/README.md
+++ b/README.md
@@ -100,24 +100,24 @@ $ ./translate_emulated ex2.mu  # ~2 mins to emit disk.img
 Mu programs can be written for two very different environments:
 
 * At the top-level, Mu programs emit a bootable image that runs without an OS
-  (under emulation; I haven't tested on native hardware yet). There's just a
-  screen and a keyboard, and that's it. No mouse, no hardware acceleration, no
-  virtual memory, no process separation, no multi-tasking, no persistent
-  storage, no network. All tests run on boot. `main` only runs if all tests
-  pass.
+  (under emulation; I haven't tested on native hardware yet). There's rudimentary
+  support for some core peripherals: a 1024x768 screen, a keyboard with some
+  key-combinations, a PS/2 mouse that must be polled, a slow ATA disk drive.
+  No hardware acceleration, no virtual memory, no process separation, no
+  multi-tasking, no network. Boot always runs all tests, and only gets to
+  `main` if all tests pass.
 
 * The top-level is built using tools created under the `linux/` sub-directory.
-  This sub-directory contains an entirely separate set of standard libraries
-  intended for building programs that run with just a Linux kernel, reading
-  from stdin and writing to stdout. The Mu compiler is such a program, at
-  `linux/mu.subx`. Individual programs typically run tests if given some
-  commandline argument like `test`.
+  This sub-directory contains an entirely separate set of libraries intended
+  for building programs that run with just a Linux kernel, reading from stdin
+  and writing to stdout. The Mu compiler is such a program, at `linux/mu.subx`.
+  Individual programs typically run tests if given some commandline argument
+  like `test`.
 
 While I currently focus on programs without an OS, the `linux/` sub-directory
 is fairly ergonomic. There's a couple of dozen example programs to try out
 there. It is likely to be the option for a network stack in the foreseeable
-future; I have no idea how to write to disk or interact on the network without
-Linux.
+future; I have no idea how to interact on the network without Linux.
 
 ## Syntax
 
diff --git a/bochsrc b/bochsrc
index a1c8a4bc..cd5f046d 100644
--- a/bochsrc
+++ b/bochsrc
@@ -11,5 +11,5 @@ display_library: sdl2
 
 ata0-master: type=disk, path="disk.img", mode=flat, cylinders=20, heads=16, spt=63  # 10MB, 512 bytes per sector
 boot: disk
-# PS/2 mouse requires black magic that I don't know how to explain.
+mouse: enabled=1, toggle=ctrl+f10
 log: -
diff --git a/boot.subx b/boot.subx
index bdea95ad..59569889 100644
--- a/boot.subx
+++ b/boot.subx
@@ -219,6 +219,8 @@ initialize_32bit_mode:
 
   fb/enable-interrupts
 
+  (initialize-mouse)
+
   ## enable floating point
   db/floating-point-coprocessor e3/initialize
   # eax <- cr4
@@ -805,14 +807,15 @@ Font:
 
 == code
 
-# Use 28-bit PIO mode to read the first sector (512 bytes) from an IDE (ATA)
-# disk drive.
+## Controlling IDE (ATA) hard disks
+# Uses 28-bit PIO mode.
 # Inspired by https://colorforth.github.io/ide.html
 #
 # Resources:
 #   https://wiki.osdev.org/ATA_PIO_Mode
 #   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)
   # . prologue
   55/push-ebp
@@ -1183,4 +1186,212 @@ until-ata-ready-for-data:
   (until-ata-data-available)
   c3/return
 
+## Controlling a PS/2 mouse
+# Uses no IRQs, just polling.
+# Thanks Dave Long: https://github.com/jtauber/cleese/blob/master/necco/kernel/bochs/py8042.py
+#
+# Resources:
+#   https://wiki.osdev.org/Mouse_Input
+
+# results x/eax, y/ecx range from -256 to +255
+# See https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Format_of_First_3_Packet_Bytes
+read-mouse-event:  # -> _/eax: int, _/ecx: int
+  # . prologue
+  55/push-ebp
+  89/<- %ebp 4/r32/esp
+  # . save registers
+  52/push-edx
+  53/push-ebx
+  # if no event, return 0, 0
+  b8/copy-to-eax 0/imm32
+  b9/copy-to-ecx 0/imm32
+  (any-mouse-event?)  # => eax
+  3d/compare-eax-and 0/imm32/false
+  74/jump-if-= $read-mouse-event:end/disp8
+  # var f1/edx: byte = inb(0x60)
+  31/xor %eax 0/r32/eax
+  e4/read-port-into-al 0x60/imm8
+  89/<- %edx 0/r32/eax
+  (wait-for-mouse-event)
+  # var dx/ebx: byte = inb(0x60)
+  31/xor %eax 0/r32/eax
+  e4/read-port-into-al 0x60/imm8
+  89/<- %ebx 0/r32/eax
+  (wait-for-mouse-event)
+  # var dy/ecx: byte = inb(0x60)
+  31/xor %eax 0/r32/eax
+  e4/read-port-into-al 0x60/imm8
+  89/<- %ecx 0/r32/eax
+  # eax = dx
+  89/<- %eax 3/r32/ebx
+  # if (f1 & 0x10) dx = -dx
+  {
+    f6 0/subop/test-bits %dl 0x10/imm8
+    74/jump-if-zero break/disp8
+    0d/or-eax-with 0xffffff00/imm32
+  }
+  # if (f1 & 0x20) dy = -dy
+  {
+    f6 0/subop/test-bits %dl 0x20/imm8
+    74/jump-if-zero break/disp8
+    81 1/subop/or %ecx 0xffffff00/imm32
+  }
+$read-mouse-event:end:
+  # . restore registers
+  5b/pop-to-ebx
+  5a/pop-to-edx
+  # . epilogue
+  89/<- %esp 5/r32/ebp
+  5d/pop-to-ebp
+  c3/return
+
+wait-for-mouse-event:
+  # . save registers
+  50/push-eax
+  #
+  {
+    (any-mouse-event?)  # => eax
+    3d/compare-eax-and 0/imm32/false
+    74/jump-if-= loop/disp8
+  }
+$wait-for-mouse-event:end:
+  # . restore registers
+  58/pop-to-eax
+  # .
+  c3/return
+
+any-mouse-event?:  # -> _/eax: boolean
+  31/xor %eax 0/r32/eax
+  # 0x1 bit: there's data from the keyboard controller
+  # 0x20 bit: it's data from the aux port (the mouse)
+  e4/read-port-into-al 0x60/imm8
+  24/and-al-with 0x21/imm8
+  3c/compare-al-with 0x21/imm8
+  0f 94/set-byte-if-= %al
+  c3/return
+
+initialize-mouse:
+  (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "A" 7 0)
+  (enable-keyboard-controller-aux-device)
+  (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "B" 7 0)
+  # tell mouse to use default settings
+  (send-mouse-command 0xf6)
+  (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "P" 7 0)
+  # enable mouse
+  (send-mouse-command 0xf4)
+  (draw-text-wrapping-right-then-down-from-cursor-over-full-screen 0 "Z" 7 0)
+  c3/return
+
+enable-keyboard-controller-aux-device:
+  (command-keyboard-controller 0xa8)
+  c3/return
+
+send-mouse-command:  # command: byte
+  # . prologue
+  55/push-ebp
+  89/<- %ebp 4/r32/esp
+  #
+  (command-keyboard-controller 0xd4)
+  (send-keyboard-controller-data *(ebp+8))
+  (wait-for-ack-from-mouse)
+$send-mouse-command:end:
+  # . epilogue
+  89/<- %esp 5/r32/ebp
+  5d/pop-to-ebp
+  c3/return
+
+wait-for-ack-from-mouse:
+  # . save registers
+  50/push-eax
+  {
+    (read-keyboard-controller-data)  # => eax
+    (draw-int32-hex-wrapping-right-then-down-from-cursor-over-full-screen 0 %eax 7 0)  # screen n fg bg
+    81 7/subop/compare %eax 0xfa/imm32
+    75/jump-if-!= loop/disp8
+  }
+$wait-for-ack-from-mouse:end:
+  # . restore registers
+  58/pop-eax
+  c3/return
+
+command-keyboard-controller:  # command: byte
+  # . prologue
+  55/push-ebp
+  89/<- %ebp 4/r32/esp
+  # . save registers
+  50/push-eax
+  #
+  (poll-keyboard-controller-to-write)
+  8b/-> *(ebp+8) 0/r32/eax
+  e6/write-al-into-port 0x64/imm8
+$command-keyboard-controller:end:
+  # . restore registers
+  58/pop-to-eax
+  # . epilogue
+  89/<- %esp 5/r32/ebp
+  5d/pop-to-ebp
+  c3/return
+
+send-keyboard-controller-data:  # data: byte
+  # . prologue
+  55/push-ebp
+  89/<- %ebp 4/r32/esp
+  # . save registers
+  50/push-eax
+  #
+  (poll-keyboard-controller-to-write)
+  8b/-> *(ebp+8) 0/r32/eax
+  e6/write-al-into-port 0x60/imm8
+$send-keyboard-controller-data:end:
+  # . restore registers
+  58/pop-to-eax
+  # . epilogue
+  89/<- %esp 5/r32/ebp
+  5d/pop-to-ebp
+  c3/return
+
+read-keyboard-controller-data:  # -> _/eax: byte
+  (poll-keyboard-controller-to-read-data-port)
+  31/xor %eax 0/r32/eax
+  e4/read-port-into-al 0x60/imm8
+  c3/return
+
+poll-keyboard-controller-to-write:
+  # . save registers
+  50/push-eax
+  # "All output to port 0x60 or 0x64 must be preceded by waiting for bit 1
+  # (value=2) of port 0x64 to become clear."
+  #   https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Waiting_to_Send_Bytes_to_Port_0x60_and_0x64
+  {
+    e4/read-port-into-al 0x64/imm8
+    a8/test-bits-in-al 2/imm8  # set zf if bit 1 (second-least significant) is not set
+    75/jump-if-zf-not-set-and-bit-1-set loop/disp8
+  }
+$poll-keyboard-controller-to-write:end:
+  # . restore registers
+  58/pop-to-eax
+  # . epilogue
+  c3/return
+
+poll-keyboard-controller-to-read-data-port:
+  # . prologue
+  55/push-ebp
+  89/<- %ebp 4/r32/esp
+  # . save registers
+  50/push-eax
+  # "Bytes cannot be read from port 0x60 until bit 0 (value=1) of port 0x64 is set."
+  #   https://wiki.osdev.org/index.php?title=Mouse_Input&oldid=25663#Waiting_to_Send_Bytes_to_Port_0x60_and_0x64
+  {
+    e4/read-port-into-al 0x64/imm8
+    a8/test-bits-in-al 1/imm8  # set zf if bit 0 (least significant) is not set
+    74/jump-if-zf-set-and-bit-0-not-set loop/disp8
+  }
+$poll-keyboard-controller-to-read-data-port:end:
+  # . restore registers
+  58/pop-to-eax
+  # . epilogue
+  89/<- %esp 5/r32/ebp
+  5d/pop-to-ebp
+  c3/return
+
 # vim:ft=subx
diff --git a/ex10.mu b/ex10.mu
new file mode 100644
index 00000000..adc58d87
--- /dev/null
+++ b/ex10.mu
@@ -0,0 +1,42 @@
+# Demo of mouse, showing deltas in x and y position for every event.
+#
+# To build a disk image:
+#   ./translate ex10.mu            # emits disk.img
+# To run:
+#   qemu-system-i386 disk.img
+# Or:
+#   bochs -f bochsrc               # bochsrc loads disk.img
+#
+# Expected output:
+#   Values between -256 and +255 as you move the mouse over the window.
+#   You might need to click on the window once.
+
+fn main {
+  # repeatedly print out mouse driver results if non-zero
+  $main:event-loop: {
+    var dx/eax: int <- copy 0
+    var dy/ecx: int <- copy 0
+    dx, dy <- read-mouse-event
+    {
+      compare dx, 0
+      break-if-!=
+      compare dy, 0
+      break-if-!=
+      loop $main:event-loop
+    }
+    {
+      var dummy1/eax: int <- copy 0
+      var dummy2/ecx: int <- copy 0
+      dummy1, dummy2 <- draw-text-wrapping-right-then-down-over-full-screen 0/screen, "         ", 0/x, 0x10/y, 0x31/fg, 0/bg
+    }
+    {
+      var dummy/ecx: int <- copy 0
+      dx, dummy <- draw-int32-decimal-wrapping-right-then-down-over-full-screen 0/screen, dx, 0/x, 0x10/y, 0x31/fg, 0/bg
+    }
+    {
+      var dummy/eax: int <- copy 0
+      dummy, dy <- draw-int32-decimal-wrapping-right-then-down-over-full-screen 0/screen, dy, 5/x, 0x10/y, 0x31/fg, 0/bg
+    }
+    loop
+  }
+}