From 49a99383330f1d0d5dfb2887ad7e9fd782680c3d Mon Sep 17 00:00:00 2001 From: "Kartik K. Agaram" Date: Tue, 23 Mar 2021 21:14:12 -0700 Subject: [PATCH] mouse support that requires polling --- 400.mu | 3 + README.md | 24 +++--- bochsrc | 2 +- boot.subx | 215 +++++++++++++++++++++++++++++++++++++++++++++++++++++- ex10.mu | 42 +++++++++++ 5 files changed, 271 insertions(+), 15 deletions(-) create mode 100644 ex10.mu 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 + } +}