Skip to content

Commit

Permalink
Add a UART keyboard and mouse driver (#74)
Browse files Browse the repository at this point in the history
  • Loading branch information
hgruniaux authored Nov 26, 2024
1 parent 784b8aa commit 87bf021
Show file tree
Hide file tree
Showing 22 changed files with 819 additions and 176 deletions.
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

0 comments on commit 87bf021

Please sign in to comment.