Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add a UART keyboard and mouse driver #74

Merged
merged 4 commits into from
Nov 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ root = true

[*]
charset = utf-8
indent_size = 4
indent_size = 2
indent_style = space
trim_trailing_whitespace = true
insert_final_newline = true
Empty file added .gitmodules
Empty file.
28 changes: 27 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ cmake -S . -Bbuild -DGCC_PREFIX=aarch64-linux-gnu- --toolchain=cmake/GCCToolchai
make -j -C build kernel-img
```

Or (for Clang):
```shell
# Change -17 by whatever version of Clang is installed on your system (or use nothing to call `clang` as is).
cmake -S . -Bbuild -DCLANG_SUFFIX=-17 --toolchain=cmake/ClangToolchain.cmake
make -j -C build kernel-img
```

You should specify `-DTARGET_QEMU=ON` when configuring CMake if you intend to build an image for QEMU.

## Testing the kernel

You can either run the kernel on real Raspberry PI (at least version 3 required) hardware.
Expand All @@ -43,7 +52,24 @@ Then to run the kernel, just type:

```shell
# You may need to change qemu-system-aarch64 by whatever is QEMU for aarch64 is named on your computer.
qemu-system-aarch64 -M raspi3b -serial stdio -kernel build/kernel/kernel8.img -dtb doc/DeviceTree/pi3.dtb -device loader,file=build/binfs/fs.img,addr=0x18000000,force-raw=on
qemu-system-aarch64 \
-M raspi3b \
-serial pipe:/tmp/uart-input \
-serial stdio \
-kernel build/kernel/kernel8.img \
-dtb doc/DeviceTree/pi3.dtb \
-device loader,file=build/binuser/fs.img,addr=0x18000000,force-raw=on
```
and launch `python tools/uart-input-server.py` (for the UART keyboard and mouse driver).

If you don't want to use the UART keyboard and mouse driver (you will probably need to modify kernel.cpp):
```shell
qemu-system-aarch64 \
-M raspi3b \
-serial stdio \
-kernel build/kernel/kernel8.img \
-dtb doc/DeviceTree/pi3.dtb \
-device loader,file=build/binuser/fs.img,addr=0x18000000,force-raw=on
```

## The userspace file structure
Expand Down
10 changes: 8 additions & 2 deletions kernel/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ add_executable(kernel
hardware/ps2_keyboard.cpp
hardware/ps2_keyboard.hpp

hardware/ps2_keyboard.cpp
hardware/ps2_keyboard.hpp
hardware/uart_keyboard.cpp
hardware/uart_keyboard.hpp

# IRQ
hardware/irq/bcm2837_irq_manager.hpp
Expand Down Expand Up @@ -160,6 +160,12 @@ add_executable(kernel
wm/data/pika_icon.hpp
wm/data/pika_icon.cpp

# Input management
input/keyboard_input.hpp
input/keyboard_input.cpp
input/mouse_input.hpp
input/mouse_input.cpp

# Filesystem
fs/filesystem.hpp
fs/filesystem.cpp
Expand Down
2 changes: 1 addition & 1 deletion kernel/boot/startup.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ extern "C" void _startup(uintptr_t dtb) {
GPIO::init();

// Try to initialize UART early as possible.
UART log(1000000); // Set to a High Baud-rate, otherwise UART is THE bottleneck :/
UART log(1000000, "uart1", /* irqs= */false); // Set to a High Baud-rate, otherwise UART is THE bottleneck :/
libk::register_logger(log);

// Set up the System Timer
Expand Down
2 changes: 1 addition & 1 deletion kernel/hardware/kernel_dt.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ uintptr_t KernelDT::convert_soc_address(uintptr_t soc_address) {
uintptr_t KernelDT::force_get_device_address(libk::StringView device) {
uintptr_t device_address;
if (!KernelDT::get_device_address(device, &device_address)) {
LOG_ERROR("Unable to retrieved {} address.", device);
LOG_ERROR("Unable to retrieve {} address.", device);
libk::panic("Kernel Device Tree Fatal Error.");
}

Expand Down
2 changes: 2 additions & 0 deletions kernel/hardware/ps2_keyboard.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,8 @@ void init() {
GPIO::set_event_callback(CLOCK_PIN, &gpio_clock_handler);

GPIO::set_falling_edge_async_detect(CLOCK_PIN, true);

LOG_INFO("PS/2 keyboard driver initialized");
}

void set_on_event(Event ev) {
Expand Down
57 changes: 37 additions & 20 deletions kernel/hardware/uart.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,59 @@
#include "hardware/mailbox.hpp"
#include "kernel_dt.hpp"

/** UART0 Data Register (size: 32) */
/** UART Data Register (size: 32) */
static inline constexpr int UART_DR = 0x0;

/** UART0 Flag Register (size: 32) */
/** UART Flag Register (size: 32) */
static inline constexpr int UART_FR = 0x18;

/** UART0 Integer Baud rate divisor (size: 32) */
/** UART Integer Baud rate divisor (size: 32) */
static inline constexpr int UART_IBRD = 0x24;

/** UART0 Fractional Baud rate divisor (size: 32) */
/** UART Fractional Baud rate divisor (size: 32) */
static inline constexpr int UART_FBRD = 0x28;

/** UART0 Line Control Register (size: 32) */
/** UART Line Control Register (size: 32) */
static inline constexpr int UART_LCRH = 0x2c;

/** UART0 Control Register (size: 32) */
/** UART Control Register (size: 32) */
static inline constexpr int UART_CR = 0x30;

/** UART0 Interrupt FIFO Level Select Register (size: 32) */
/** UART Interrupt FIFO Level Select Register (size: 32) */
// static inline constexpr int UART_IFLS = 0x34;

/** UART0 Interrupt Mask Set Clear Register (size: 32) */
// static inline constexpr int UART_IMSC = 0x38;
/** UART Interrupt Mask Set Clear Register (size: 32) */
static inline constexpr int UART_IMSC = 0x38;

/** UART0 Raw Interrupt Status Register (size: 32) */
/** UART Raw Interrupt Status Register (size: 32) */
// static inline constexpr int UART_RIS = 0x3c;

/** UART0 Masked Interrupt Status Register (size: 32) */
/** UART Masked Interrupt Status Register (size: 32) */
// static inline constexpr int UART_MIS = 0x40;

/** UART0 Interrupt Clear Register (size: 32) */
/** UART Interrupt Clear Register (size: 32) */
// static inline constexpr int UART_ICR = 0x44;

/** UART0 DMA Control Register (size: 32) */
/** UART DMA Control Register (size: 32) */
// static inline constexpr int UART_DMACR = 0x48;

/** UART0 Test Control Register (size: 32) */
/** UART Test Control Register (size: 32) */
// static inline constexpr int UART_ITCR = 0x80;

/** UART0 Integration Test Input Register (size: 32) */
/** UART Integration Test Input Register (size: 32) */
// static inline constexpr int UART_ITIP = 0x84;

/** UART0 Integration Test Output Register (size: 32) */
/** UART Integration Test Output Register (size: 32) */
// static inline constexpr int UART_ITOP = 0x88;

/** UART0 Test Data Register (size: 32) */
/** UART Test Data Register (size: 32) */
// static inline constexpr int UART_TDR = 0x8c;

UART::UART(uint32_t baud_rate) : _uart_base(KernelDT::force_get_device_address("uart0")) {
UART::UART(uint32_t baud_rate, libk::StringView name, bool enabling_irqs)
: _uart_base(KernelDT::force_get_device_address(name)) {
// We don't yet support IRQs for UART1 (UART1 is a bit different from the others).
KASSERT(!enabling_irqs || name != "uart1");

// Get the UART Clock
uint32_t uart_clock = Device::get_clock_rate(Device::UART);

Expand All @@ -78,11 +82,24 @@ UART::UART(uint32_t baud_rate) : _uart_base(KernelDT::force_get_device_address("

// Enable UART0, receive & transfer part of UART.
libk::write32(_uart_base + UART_CR, (1 << 0) | (1 << 8) | (1 << 9));

if (enabling_irqs) {
// Enable IRQs
libk::write32(_uart_base + UART_IMSC, (1 << 1) | (1 << 4));
}
}

bool UART::is_fifo_empty() const {
return (libk::read32(_uart_base + UART_FR) & (1 << 4)) != 0;
}

bool UART::is_fifo_full() const {
return (libk::read32(_uart_base + UART_FR) & (1 << 5)) != 0;
}

void UART::write_one(char value) const {
// Wait for UART to become ready to transmit.
while ((libk::read32(_uart_base + UART_FR) & (1 << 5)) != 0) {
while (is_fifo_full()) {
libk::yield();
}

Expand All @@ -91,7 +108,7 @@ void UART::write_one(char value) const {

char UART::read_one() const {
// Wait for UART to have received something.
while ((libk::read32(_uart_base + UART_FR) & (1 << 4)) != 0) {
while (is_fifo_empty()) {
libk::yield();
}

Expand Down
7 changes: 6 additions & 1 deletion kernel/hardware/uart.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,12 @@ class UART : public libk::Logger {
* - 1500000 [Tested]
* - 2000000 [Tested]
*/
explicit UART(uint32_t baud_rate);
explicit UART(uint32_t baud_rate, libk::StringView name = "uart0", bool enabling_irqs = false);

/** Returns true if the UART FIFO is empty (aka cannot read anymore from it). */
[[nodiscard]] bool is_fifo_empty() const;
/** Returns true if the UART FIFO is full (aka cannot write anymore to it). */
[[nodiscard]] bool is_fifo_full() const;

/** Writes the given @a value into this UART. */
void write_one(char value) const;
Expand Down
71 changes: 71 additions & 0 deletions kernel/hardware/uart_keyboard.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
#include "uart_keyboard.hpp"
#include "hardware/irq/irq_lists.hpp"
#include "hardware/irq/irq_manager.hpp"
#include "input/keyboard_input.hpp"
#include "input/mouse_input.hpp"

namespace UARTKeyboard {
UART* keyboard_uart = nullptr;

void init(UART* uart) {
KASSERT(uart != nullptr);
keyboard_uart = uart;

IRQManager::register_irq_handler(
VC_UART,
[](void*) {
if (keyboard_uart->is_fifo_empty())
return;

uint8_t header = (uint8_t)keyboard_uart->read_one();

switch (header & 0xF) {
case 0x1: // mouse move
{
uint8_t dx_mag = (uint8_t)keyboard_uart->read_one();
uint8_t dy_mag = (uint8_t)keyboard_uart->read_one();

int16_t dx = dx_mag;
int16_t dy = dy_mag;

if ((header & (1 << 4)) != 0)
dx = -dx;
if ((header & (1 << 5)) != 0)
dy = -dy;

MouseSystem::notify_hardware_move_event(dx, dy);
} break;
case 0x2: // mouse click
{
uint8_t button = (uint8_t)keyboard_uart->read_one();
} break;
case 0x3: // mouse scroll
{
uint8_t dx_mag = (uint8_t)keyboard_uart->read_one();
uint8_t dy_mag = (uint8_t)keyboard_uart->read_one();

int16_t dx = dx_mag;
int16_t dy = dy_mag;

if (header & (1 << 4) != 0)
dx = -dx;
if (header & (1 << 5) != 0)
dy = -dy;

MouseSystem::notify_hardware_scroll_event(dx, dy);
} break;
case 0x4: // key press/release
{
uint16_t key;
keyboard_uart->read((char*)&key, sizeof(key));

bool is_pressed = (header & (1 << 4)) != 0;
KeyboardSystem::notify_hardware_event((sys_key_code_t)key, is_pressed);
} break;
}
},
nullptr);

LOG_INFO("UART keyboard & mouse driver initialized");
}
} // namespace UARTKeyboard
8 changes: 8 additions & 0 deletions kernel/hardware/uart_keyboard.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#pragma once

#include <cstdint>
#include "hardware/uart.hpp"

namespace UARTKeyboard {
void init(UART* uart);
} // namespace UARTKeyboard
Loading
Loading