This commit is contained in:
Niklas Gollenstede
2025-10-31 22:37:36 +01:00
commit 174fe17e89
197 changed files with 79558 additions and 0 deletions

189
kernel/device/graphics.cc Normal file
View File

@@ -0,0 +1,189 @@
#include "./graphics.h"
#include "../boot/multiboot/data.h"
#include "./debug/output.h"
#include "./utils/string.h"
/// Replace size with `Graphics::SCREEN_BUF_SIZE`
/// We don't do this here, as this drastically increases the size of the kernel
/// image.
char frontbuffer[1] __attribute__((aligned(8)));
char backbuffer[1] __attribute__((aligned(8)));
/*! \brief Mode Information of the *Vesa BIOS Extension*
*
* \see Vesa BIOS Extension [ModeInfoBlock struc](vbe3.pdf#page=38)
*/
struct VbeModeInfo {
enum ModeAttributes : uint16_t {
SUPPORTED = 1 << 0, ///< Mode supported by hardware configuration
TTY = 1 << 2, ///< TTY Output functions supported by BIOS
COLOR = 1 << 3, ///< Color mode (otherwise monochrome)
GRAPHICS = 1 << 4, ///< Graphic mode (otherwise text)
VGA = 1 << 5, ///< VGA compatible
VGA_PAGED =
1 << 6, ///< VGA compatible windowed memory mode is available
LFB = 1 << 7, ///< Linear frame buffer mode is available
};
uint16_t mode_attributes;
// Window functions (used by all VBE revisions, but ignored here)
struct __attribute__((packed)) {
uint8_t attrib_a;
uint8_t attrib_b;
uint16_t granularity;
uint16_t size;
uint16_t segment_a;
uint16_t segment_b;
uint32_t func_ptr;
} win;
uint16_t pitch; ///< Bytes per scan line
uint16_t
width; ///< Horizontal resolution in pixels (GRAPHICS) or characters
uint16_t
height; ///< Vertical resolution in pixels (GRAPHICS) or characters
uint8_t char_width; ///< Character cell width in pixels (deprecated)
uint8_t char_height; ///< Character cell height in pixels (deprecated)
uint8_t planes; ///< Number of memory planes
uint8_t bpp; ///< Bits per pixel
uint8_t banks; ///< Number of banks
enum MemoryModel : uint8_t {
TEXT_MODE = 0, ///< Text mode
CGA = 1, ///< CGA graphics
HERCULES = 2, ///< Hercules graphics
PLANAR = 3, ///< Planar
PACKED = 4, ///< Packed pixel
NON_CHAIN_4 = 5, ///< Non-chain 4, 256 color
DIRECT_COLOR = 6, ///< Direct Color
YUV = 7 ///< YUV
} memory_model; ///< Memory model type
uint8_t bank_size; ///< Bank size in KB
uint8_t image_pages; ///< Number of images
uint8_t reserved; ///< Reserved for page function
// Direct Color fields (required for DIRECT_COLOR and YUV memory models)
uint8_t bits_red; ///< Size of direct color red mask in bits
uint8_t offset_red; ///< Bit position of lsb of red mask
uint8_t bits_green; ///< Size of direct color green mask in bits
uint8_t offset_green; ///< Bit position of lsb of green mask
uint8_t bits_blue; ///< Size of direct color blue mask in bits
uint8_t offset_blue; ///< Bit position of lsb of blue mask
uint8_t bits_rsv; ///< Size of direct color reserved mask in bits
uint8_t offset_rsv; ///< Bit position of lsb of reserved mask
enum DirectColorAttributes : uint8_t {
DYNAMIC_COLOR_RAMP =
1 << 0, ///< Programmable (otherwise fixed) color ramp
USABLE_BITS =
1 << 1 ///< Bits in reserved mask are usable (otherwise reserved)
};
uint8_t directcolor_attributes; ///< direct color mode attributes
// Mandatory information for VBE 2.0 and above
uint32_t address; ///< physical address for flat memory frame buffer
uint32_t offscreen_memory_offset; ///< reserved
uint16_t offscreen_memory_size; ///< reserved
} __attribute__((packed));
Graphics::Graphics()
: buffer_size(SCREEN_BUF_SIZE),
buffer{frontbuffer, backbuffer},
scanout_buffer(0),
refresh(false) {}
bool Graphics::init(bool force) {
assert(sizeof(frontbuffer) >= buffer_size &&
sizeof(backbuffer) >= buffer_size &&
"Buffers are too small, adjust them!!!");
// Most boot loaders support the Vesa BIOS Extension (VBE) information quite
// well
Multiboot::VBE *vbe = Multiboot::getVesaBiosExtensionInfo();
if (vbe != nullptr) {
// However, you have to manually parse the required data out of all the
// outdated crap
struct VbeModeInfo *vbe_info = reinterpret_cast<struct VbeModeInfo *>(
static_cast<uintptr_t>(vbe->mode_info));
// Is there linear frame buffer (or just ignore the check with `force`
// due to a misleading information)?
if (force || (vbe_info->mode_attributes &
VbeModeInfo::ModeAttributes::LFB) != 0) {
// Is there a suitable precompiled graphic mode
printer = AbstractGraphicsPrinter::getMode(
vbe_info->bpp, vbe_info->offset_red, vbe_info->offset_green,
vbe_info->offset_blue, vbe_info->bits_red, vbe_info->bits_green,
vbe_info->bits_blue);
if (printer != nullptr) {
address = reinterpret_cast<void *>(
static_cast<uintptr_t>(vbe_info->address));
size = vbe_info->height * vbe_info->pitch;
if (size > buffer_size) {
DBG_VERBOSE << "The current graphic buffer (" << buffer_size
<< " bytes) is too small (at least " << size
<< " bytes required)!" << endl;
return false;
} else {
printer->init(vbe_info->width, vbe_info->height,
vbe_info->pitch);
printer->buffer(buffer[1 - scanout_buffer]);
return true;
}
}
} else {
DBG_VERBOSE << "Unsupported graphic mode" << endl;
}
}
// Actually, \ref Multiboot::Framebuffer has everything we need in a real
// smart structure, however boot loader like PXE don't support them (yet --
// quite new, added in the last Multiboot revision).
Multiboot::Framebuffer *fb = Multiboot::getFramebufferInfo();
if (fb != nullptr) {
if (force || fb->type == Multiboot::Framebuffer::RGB) {
printer = AbstractGraphicsPrinter::getMode(
fb->bpp, fb->offset_red, fb->offset_green, fb->offset_blue,
fb->bits_red, fb->bits_green, fb->bits_blue);
if (printer != nullptr) {
address = reinterpret_cast<void *>(
static_cast<uintptr_t>(fb->address));
size = fb->height * fb->pitch;
if (size > buffer_size) {
DBG_VERBOSE << "The current graphic buffer (" << buffer_size
<< " bytes) is too small (at least " << size
<< " bytes required)!" << endl;
return false;
} else {
printer->init(fb->width, fb->height, fb->pitch);
printer->buffer(buffer[1 - scanout_buffer]);
return true;
}
}
} else {
DBG_VERBOSE << "Unsupported graphic mode" << endl;
}
}
// Unable to initialize mode
return false;
}
bool Graphics::switchBuffers() {
if (!refresh) {
printer->buffer(buffer[scanout_buffer]);
scanout_buffer = 1 - scanout_buffer;
refresh = true;
return true;
} else {
return false;
}
}
void Graphics::scanoutFrontbuffer() {
if (refresh) {
memcpy(address, buffer[scanout_buffer], size);
refresh = false;
}
}

284
kernel/device/graphics.h Normal file
View File

@@ -0,0 +1,284 @@
/*! \file
* \brief The \ref Graphics device is an interface for the \ref Framebuffer
*/
#pragma once
/*! \defgroup gfx Graphics
*
* Graphical VESA video modes.
*/
#include "../graphics/primitives.h"
#include "../graphics/printer.h"
/*! \brief Driver managing the video mode and synchronizing its buffer with the
* \ref AbstractGraphicsPrinter "graphics printer"
* \ingroup gfx
*
* This device detects the current video mode set by the \ref Multiboot
* compliant boot loader and initializes a suitable \ref GraphicsPrinter.
*
* With the methods \ref Graphics::switchBuffers() (to exchange front- and
* backbuffer) and \ref Graphics::scanoutFrontbuffer() (copying the contents of
* the frontbuffer into the video memory) it provides some kind of manually
* [triple
* buffering](https://en.wikipedia.org/wiki/Multiple_buffering#Triple_buffering).
*
* A typical usage is to fully prepare the back buffer before switching it with
* the front buffer
* \code{.cpp}
* graphics.init();
* while(true) {
* // Draw on back buffer
* // using the primitives provided by the driver
*
* graphics.switchBuffers();
* }
* \endcode
*
* The method \ref Graphics::scanoutFrontbuffer() has to be executed either
* inside the loop (right after \ref Graphics::switchBuffers() in the example
* above) or at a predefined interval by employing the \ref LAPIC::Timer.
*
* \note The driver requires \ref Multiboot to initialize a video mode, which
* can be configured using the flags in `boot/multiboot/config.inc`.
*/
class Graphics {
/*! \brief Pointer to a \ref GraphicsPrinter supporting the current video
* mode
*/
AbstractGraphicsPrinter* printer;
/*! \brief Pointer to the physical address of the video memory (linear frame
* buffer)
*/
void* address;
/*! \brief Video memory size required for a full screen picture
*/
unsigned size;
/*! \brief Size of the front (or back) buffer (which has to be at least \ref
* size)
*/
unsigned buffer_size;
/*! \brief Pointer to the two buffers
* (used alternately as front and back buffers)
*/
void* const buffer[2];
/*! \brief Index of the current front buffer
*/
int scanout_buffer;
/*! \brief Has the current front buffer already been drawn?
*/
bool refresh;
public:
/// MULTIBOOT_VIDEO_WIDTH * MULTIBOOT_VIDEO_HEIGHT *
/// MULTIBOOT_VIDEO_BITDEPTH (in bits)
static constexpr size_t SCREEN_BUF_SIZE =
1920UL * 1080UL * 32UL / sizeof(char);
/*! \brief Constructor
*/
Graphics();
/*! \brief Initialize \ref GraphicsPrinter according to the current video
* mode
*
* \param force Do not check video attributes for the linear frame buffer
* (required on our test systems due to some strange VBE
* behaviour)
* \return 'true' if a suitable \ref GraphicsPrinter was found for the video
* mode
*/
bool init(bool force = false);
/*! \brief Switch front and back buffer
* (only if front buffer was already copied to video memory)
*
* \return `true` if buffers have been switched, `false` if previous front
* buffer wasn't yet fully copied to video memory.
*/
bool switchBuffers();
/*! \brief Copy current front buffer to the video memory
*/
void scanoutFrontbuffer();
/*! \brief Clear all pixel of the current back buffer
* (set full screen to black)
*/
void clear() { printer->clear(); }
/*! \brief Check if a \ref Point can be displayed at the current resolution
*
* \param p Coordinates to check
* \return 'true' if can be displayed
*/
bool valid(const Point& p) { return printer->valid(p); }
/*! \brief Number of vertical pixels in current resolution
*
* \return Height of the screen in current video mode
*/
unsigned height() { return printer->height(); }
/*! \brief Number of horizontal pixels in current resolution
*
* \return Width of the screen in current video mode
*/
unsigned width() { return printer->width(); }
/*! \brief Draw a pixel on the current back buffer
*
* \param p Coordinates of the pixel
* \param color Color of the pixel
*/
void pixel(const Point& p, const Color& color) { printer->pixel(p, color); }
/// \copydoc pixel
void pixel(const Point& p, const ColorAlpha& color) {
printer->pixel(p, color);
}
/*! \brief Draw a line on the current back buffer
*
* \param start Coordinates of the begin of the line
* \param end Coordinates of the end of the line
* \param color Color of the line
*/
void line(const Point& start, const Point& end, const Color& color) {
printer->line(start, end, color);
}
/// \copydoc line
void line(const Point& start, const Point& end, const ColorAlpha& color) {
printer->line(start, end, color);
}
/*! \brief Draw a rectangle on the current back buffer
*
* \param start Coordinate of the rectangles upper left corner
* \param end Coordinate of the rectangles lower right corner
* \param color Color of the rectangle
* \param filled If set, the rectangle will be filled with the same color.
* (otherwise only borders will be drawn)
*/
void rectangle(const Point& start, const Point& end, const Color& color,
bool filled = true) {
printer->rectangle(start, end, color, filled);
}
/// \copydoc rectangle
void rectangle(const Point& start, const Point& end,
const ColorAlpha& color, bool filled = true) {
printer->rectangle(start, end, color, filled);
}
/*! \brief Change the current font for text output in video mode
*
* \param new_font Font to be used on subsequent calls to \ref text (without
* explicit font parameter)
*/
void font(const Font& new_font) { printer->font(new_font); }
/*! \brief Print text (without automatic word wrap) on the current back
* buffer
*
* \param p Upper left start position of the text
* \param string Pointer to char array containing the text to be displayed
* \param len Number of characters to be displayed
* \param color Color for the text characters
* \param font Explicit font -- or `nullptr` to use default font (set by
* \ref font method)
*/
void text(const Point& p, const char* string, unsigned len,
const Color& color, const Font* font = nullptr) {
printer->text(p, string, len, color, font);
}
/// \copydoc text
void text(const Point& p, const char* string, unsigned len,
const ColorAlpha& color, const Font* font = nullptr) {
printer->text(p, string, len, color, font);
}
/*! \brief Draw a \ref PNG image [detail] on the current back buffer.
*
* The image can has to be in a supported \ref PNG format.
* Alpha blending (transparency) is supported.
*
* \param p Coordinate of the images upper left corner
* \param image Source image to display
* \param width Width of the image detail (full image width of the source
* image if zero/default value)
* \param height Height of the image detail (full image height of the source
* if zero/default value)
* \param offset_x Right offset of the source image
* \param offset_y Top offset of the source image
*/
void image(const Point& p, PNG& image, unsigned width = 0,
unsigned height = 0, unsigned offset_x = 0,
unsigned offset_y = 0) {
printer->image(p, image, width, height, offset_x, offset_y);
}
/*! \brief Draw a GIMP image [detail] on the current back buffer.
*
* The image has to be exported as C-source (without `Glib` types!) in
* [GIMP](https://www.gimp.org/), alpha blending (transparency) is
* supported.
*
* \param p Coordinate of the images upper left corner
* \param image Source image to display
* \param width Width of the image detail (full image width of the source
* image if zero/default value)
* \param height Height of the image detail (full image height of the source
* if zero/default value)
* \param offset_x Right offset of the source image
* \param offset_y Top offset of the source image
*/
void image(const Point& p, const GIMP& image, unsigned width = 0,
unsigned height = 0, unsigned offset_x = 0,
unsigned offset_y = 0) {
printer->image(p, image, width, height, offset_x, offset_y);
}
/*! \brief Draw a sprite on the current back buffer.
*
* Each element in the source array will be displayed as a single pixel.
*
* \param p Coordinate of the sprites upper left corner
* \param image Source sprite to display
* \param width Width of the sprite detail
* \param height Height of the sprite detail
* \param offset_x Right offset of the source sprite
* \param offset_y Top offset of the source sprite
*/
void image(const Point& p, const Color* image, unsigned width,
unsigned height, unsigned offset_x = 0, unsigned offset_y = 0) {
printer->image(p, image, width, height, offset_x, offset_y);
}
/*! \brief Draw a sprite with alpha blending (transparency) on the current
* back buffer.
*
* Each element in the source array will be displayed as a single pixel.
*
* \param p Coordinate of the sprites upper left corner
* \param image Source sprite to display
* \param width Width of the sprite detail
* \param height Height of the sprite detail
* \param offset_x Right offset of the source sprite
* \param offset_y Top offset of the source sprite
*/
void image(const Point& p, const ColorAlpha* image, unsigned width,
unsigned height, unsigned offset_x = 0, unsigned offset_y = 0) {
printer->image(p, image, width, height, offset_x, offset_y);
}
};

View File

@@ -0,0 +1,131 @@
#include "./graphicsstream.h"
#include "../debug/assert.h"
#include "../graphics/fonts/font.h"
#include "../interrupt/guard.h"
#include "../utils/alloc.h"
#include "../utils/math.h"
const Color GraphicsStream::BLACK(0x00, 0x00, 0x00);
const Color GraphicsStream::BLUE(0x00, 0x00, 0xAA);
const Color GraphicsStream::GREEN(0x00, 0xAA, 0x00);
const Color GraphicsStream::CYAN(0x00, 0xAA, 0xAA);
const Color GraphicsStream::RED(0xAA, 0x00, 0x00);
const Color GraphicsStream::MAGENTA(0xAA, 0x00, 0xAA);
const Color GraphicsStream::BROWN(0xAA, 0x55, 0x00);
const Color GraphicsStream::LIGHT_GREY(0xAA, 0xAA, 0xAA);
const Color GraphicsStream::DARK_GREY(0x55, 0x55, 0x55);
const Color GraphicsStream::LIGHT_BLUE(0x55, 0x55, 0xFF);
const Color GraphicsStream::LIGHT_GREEN(0x55, 0xFF, 0x55);
const Color GraphicsStream::LIGHT_CYAN(0x55, 0xFF, 0xFF);
const Color GraphicsStream::LIGHT_RED(0xFF, 0x55, 0x55);
const Color GraphicsStream::LIGHT_MAGENTA(0xFF, 0x55, 0xFF);
const Color GraphicsStream::YELLOW(0xFF, 0xFF, 0x55);
const Color GraphicsStream::WHITE(0xFF, 0xFF, 0xFF);
GraphicsStream::GraphicsStream(const Point &start, unsigned width,
unsigned height, Font *const font)
: offset(0),
x(0),
y(0),
FONT(font == nullptr ? Font::get() : font),
START(start),
ROWS(height / this->FONT->height),
COLUMNS(width / this->FONT->width) {
cell = reinterpret_cast<Cell *>(calloc(ROWS * COLUMNS, sizeof(Cell)));
}
void GraphicsStream::setPos(int x, int y) {
if (x < 0) {
x += COLUMNS;
}
if (y < 0) {
y += ROWS;
}
if (x >= 0 && static_cast<unsigned>(x) < COLUMNS && y >= 0 &&
static_cast<unsigned>(y) <= ROWS) {
this->x = x;
this->y = y;
}
}
void GraphicsStream::getPos(int &x, int &y) const {
x = this->x;
y = this->y;
}
void GraphicsStream::show(int x, int y, char character, const Color &color) {
if (x < 0) {
x += COLUMNS;
}
if (y < 0) {
y += ROWS;
}
// only print if position within the screen range
if (x >= 0 && static_cast<unsigned>(x) < COLUMNS && y >= 0 &&
static_cast<unsigned>(y) < ROWS) {
cell[((offset + y) % ROWS) * COLUMNS + x] = (Cell){character, color};
}
}
void GraphicsStream::print(char *str, int length, const Color &color) {
while (length > 0) {
switch (*str) {
case '\n':
for (unsigned i = x; i < COLUMNS; ++i) {
show(i, y, ' ', color);
}
x = 0;
y++;
break;
default:
show(x, y, *str, color);
if (++x >= COLUMNS) {
x = 0;
y++;
}
break;
}
str++;
if (y >= ROWS) {
offset = (offset + 1) % ROWS;
y--;
for (unsigned i = 0; i < COLUMNS; ++i) {
show(i, y, ' ', color);
}
}
length--;
}
}
void GraphicsStream::reset(char character, const Color &color) {
for (unsigned y = 0; y < ROWS; ++y) {
for (unsigned x = 0; x < COLUMNS; ++x) {
show(x, y, character, color);
}
}
setPos(0, 0);
}
void GraphicsStream::flush() {
print(buffer, pos);
pos = 0;
}
void GraphicsStream::draw() {
flush();
Point pos = START;
for (unsigned y = 0; y < ROWS; y++) {
for (unsigned x = 0; x < COLUMNS; x++) {
Cell &c = cell[((offset + y) % ROWS) * COLUMNS + x];
Guard::enter().vault().graphics.text(pos, &c.character, 1, c.color,
FONT);
pos.x += FONT->width;
}
pos.x = START.x;
pos.y += FONT->height;
}
}

View File

@@ -0,0 +1,135 @@
/*! \file
* \brief \ref GraphicsStream, a \ref Graphics "graphical" \ref OutputStream
* "output stream" inspired by (and compatible to) \ref TextStream
*/
#pragma once
#include "../graphics/primitives.h"
#include "../object/outputstream.h"
#include "./graphics.h"
/*! \brief Output text (form different data type sources) on screen in \ref
* Graphics "graphic mode" (similar to \ref TextStream)
* \ingroup gfx
* \ingroup io
*
* Enables output of different data types using a monospaced font on a
* predefined area of the screen with activated graphics mode.
*/
class GraphicsStream : public OutputStream {
// Prevent copies and assignments
GraphicsStream(const GraphicsStream&) = delete;
GraphicsStream& operator=(const GraphicsStream&) = delete;
struct Cell {
char character;
Color color;
};
Cell* cell;
unsigned offset;
unsigned x, y; ///< Cursor position
protected:
/*! \brief Output the buffer contents of the base class \ref Stringbuffer
*
* The method is automatically called when the buffer is full,
* but can also be called explicitly to force output of the current buffer.
*/
void flush();
public:
Font* const FONT; ///< Default font
const Point START; ///< Upper left corner of the window
const unsigned ROWS; ///< Number of rows in the window
const unsigned COLUMNS; ///< Number of columns in the window
/*! \brief CGA color palette
*/
static const Color BLACK, BLUE, GREEN, CYAN, RED, MAGENTA, BROWN,
LIGHT_GREY, DARK_GREY, LIGHT_BLUE, LIGHT_GREEN, LIGHT_CYAN, LIGHT_RED,
LIGHT_MAGENTA, YELLOW, WHITE;
/*! \brief Constructor
*
* Creates a window (= area on the screen) for text output.
* Within the window text uses a virtual (invisible) cursor to offer a
* very similar behavior to \ref TextStream -- including automatic
* scrolling and column/row based positioning.
*
* \param start Coordinate of the upper left corner for the output window
* \param width Width of the output window
* \param height Height of the output window
* \param font Font used for output text (or `nullptr` for default font)
*/
GraphicsStream(const Point& start, unsigned width, unsigned height,
Font* font = nullptr);
/*! \brief Set the cursor position
*
* \param x Column in window
* \param y Row in window
*/
void setPos(int x, int y);
/*! \brief Read the current cursor position
*
* \param x Column in window
* \param y Row in window
*/
void getPos(int& x, int& y) const;
/*! \brief Display multiple characters in the window starting at the current
* cursor position
*
* This method can be used to output a string, starting at the current
* cursor position. Since the string does not need to contain a '\0'
* termination (as it is usually the case in C), the parameter `length` is
* required to specify the number of characters in the string. When the
* output is complete, the cursor will be positioned after the last
* character printed. The entire text uniformly has the color `color`
*
* If there is not enough space left at the end of the line, the output will
* be continued on the following line. As soon as the last window line is
* filled, the entire window area will be moved up one line.
* The first line disappears and the last line is blank, continuing output
* there.
*
* A line break will also occurs wherever the character `\\n` is inserted
* in the text to be output.
*
* \param str String to output
* \param length length of string
* \param color Foreground color of string
*/
void print(char* str, int length, const Color& color = LIGHT_GREY);
/*! \brief Clear window and reset cursor
*
* \param character Filling character
* \param color Foreground color
*/
void reset(char character = ' ', const Color& color = LIGHT_GREY);
/*! \brief Basic output of a (colored) character at a certain position on
* the screen.
*
* Outputs `character` at the absolute position (`x`, `y`) with the
* specified color: `x` specifies the column and `y` the row of the desired
* position, with 0 ≤ x < \ref COLUMNS and 0 ≤ `y` < \ref ROWS.
* The position (0,0) indicates the upper left corner of the window (at
* the coordinates \ref START).
*
* \param x Column for output of the character
* \param y Row for output of the character
* \param character Character to be output
* \param color Foreground color
*/
void show(int x, int y, char character, const Color& color = LIGHT_GREY);
/*! \brief Draw using the \ref Graphics device
*/
void draw();
};

131
kernel/device/keydecoder.cc Normal file
View File

@@ -0,0 +1,131 @@
#include "keydecoder.h"
#include "ps2controller.h"
// Constants used for key decoding
constexpr unsigned char BREAK_BIT = 0x80;
constexpr unsigned char PREFIX_1 = 0xe0;
constexpr unsigned char PREFIX_2 = 0xe1;
Key KeyDecoder::decode(unsigned char code) {
Key key = modifier;
// All keys that are introduced by the MF II keyboard (compared to the older
// AT keyboard) always send a prefix value as first byte.
if (code == PREFIX_1 || code == PREFIX_2) {
prefix = code;
} else {
// Releasing a key is, for us, only important for the modifier keys such
// as SHIFT, CTRL and ALT, For other, non-modifier keys, we ignore the
// break code.
bool pressed = (code & BREAK_BIT) == 0;
// A key's break code is identical to its make code with an additionally
// set BREAK_BIT
Key::Scancode scancode =
static_cast<Key::Scancode>(code & (~BREAK_BIT));
// We ignore "new" special keys, such as the Windows key
if (scancode < Key::Scancode::KEYS) {
// save state
status[static_cast<size_t>(scancode)] = pressed;
// Take a closer look at modifier make and break events
bool isModifier = true;
switch (scancode) {
// both shifts are handled equally
case Key::Scancode::KEY_LEFT_SHIFT:
case Key::Scancode::KEY_RIGHT_SHIFT:
modifier.shift = pressed;
break;
case Key::Scancode::KEY_LEFT_ALT:
if (prefix == PREFIX_1) {
modifier.alt_right = pressed;
} else {
modifier.alt_left = pressed;
}
break;
case Key::Scancode::KEY_LEFT_CTRL:
if (prefix == PREFIX_1) {
modifier.ctrl_right = pressed;
} else {
modifier.ctrl_left = pressed;
}
break;
default:
isModifier = false;
}
// For keys other than modifiers, we only care about the make code
if (pressed && !isModifier) {
switch (scancode) {
case Key::Scancode::KEY_CAPS_LOCK:
modifier.caps_lock ^= 1;
setLed(PS2Controller::LED_CAPS_LOCK,
modifier.caps_lock);
break;
case Key::Scancode::KEY_SCROLL_LOCK:
modifier.scroll_lock ^= 1;
setLed(PS2Controller::LED_SCROLL_LOCK,
modifier.scroll_lock);
break;
case Key::Scancode::KEY_NUM_LOCK: // Can be both NumLock
// and pause
// On old keyboards, the pause functionality was only
// accessible by pressing Ctrl+NumLock. Modern MF-II
// keyboards therefore send exactly this code
// combination when the pause key was pressed. Normally,
// the pause key does not provide an ASCII code, but we
// check that anyway. In either case, we're now done
// decoding.
if (modifier.ctrl_left) { // pause key
key.scancode = scancode;
} else { // NumLock
modifier.num_lock ^= 1;
setLed(PS2Controller::LED_NUM_LOCK,
modifier.num_lock);
}
break;
// Special case scan code 53: This code is used by both the
// minus key on the main keyboard and the division key on
// the number block. When the division key was pressed, we
// adjust the scancode accordingly.
case Key::Scancode::KEY_SLASH:
if (prefix == PREFIX_1) {
key.scancode = Key::Scancode::KEY_DIV;
key.shift = true;
} else {
key.scancode = scancode;
}
break;
default:
key.scancode = scancode;
// When NumLock is enabled and a key on the keypad was
// pressed, we want return the ASCII and scan codes of
// the corresponding numerical key instead of the arrow
// keys. The keys on the cursor block (prefix ==
// PREFIX_1), however, should remain usable. Therefore,
// as a little hack, we deactivate the NumLock for these
// keys.
if (modifier.num_lock && prefix == PREFIX_1) {
key.num_lock = false;
}
}
}
}
// The prefix is only valid for the immediately following code, which
// was just handled.
prefix = 0;
}
return key;
}

View File

@@ -0,0 +1,40 @@
/*! \file
* \brief \ref KeyDecoder decodes a keystroke to the corresponding \ref Key
* object
*/
#pragma once
#include "../object/key.h"
#include "../types.h"
/*! \brief Decoder for \ref ps2keyboardset1 "keyboard codes" received from the
* \ref PS2Controller
* \ingroup io
*
* Extracts the make and break codes, modifier and scan codes from the pressed
* key.
*/
class KeyDecoder {
unsigned char prefix; ///< Prefix byte for keys
Key modifier; ///< activated modifier keys (e.g., caps lock)
public:
/*! \brief Current state (pressed or released) of all keys.
*/
bool status[static_cast<size_t>(Key::Scancode::KEYS)];
/*! \brief Default constructor
*/
KeyDecoder() {}
/*! \brief Interprets the \ref ps2keyboardset1 "make and break codes"
* received from the keyboard and derives the corresponding scan code and
* further information about other pressed keys, such as \key{shift} and
* \key{ctrl}.
*
* \param code Byte from Keyboard to decode
* \return Pressed key (\ref Key::valid returns `false` if the key is not
* yet complete)
*/
Key decode(unsigned char code);
};

View File

@@ -0,0 +1,169 @@
#include "ps2controller.h"
#include "../arch/apic.h"
#include "../arch/core_interrupt.h"
#include "../arch/ioapic.h"
#include "../arch/ioport.h"
#include "keydecoder.h"
namespace PS2Controller {
// I/O Ports of the PS2 Controller
/// Access status- (read) and command (write) register
static const IOPort ctrl_port(0x64);
/// Access PS/2 device [keyboard] output- (read) and input (write) buffer
static const IOPort data_port(0x60);
/* The buffers are used to communicate with the controller or the connected
* PS/2 devices alike:
* - For the output buffer, the controller decides to which PS/2 device the
* data gets forwarded to -- by default it is the primary PS/2 device
* (keyboard).
* - The source device from which the data was gathered can be determined using
* the status flag (\ref IS_MOUSE).
*
* Please also note, that the naming of the buffer may be a bit contra-intuitive
* since it is the perspective of the PS/2 controller due to historical reasons.
*/
// Key decoder (stores the state of the modifier keys)
static KeyDecoder key_decoder;
// To store the current state of the Keyboard LEDs
static uint8_t leds = 0;
/*! \brief Flags in the PS/2 controller status register
*/
enum Status {
HAS_OUTPUT = 1 << 0, ///< Output buffer non-empty?
INPUT_PENDING = 1 << 1, ///< Is input buffer full?
SYSTEM_FLAG = 1 << 2, ///< set on soft reset, cleared on power up
IS_COMMAND = 1 << 3, ///< Is command Byte? (otherwise data)
IS_MOUSE = 1 << 5, ///< Mouse output has data
TIMEOUT_ERROR = 1 << 6, ///< Timeout error
PARITY_ERROR = 1 << 7 ///< Parity error
};
/*! \brief Commands to be send to the Keyboard
*/
enum KeyboardCommand : uint8_t {
KEYBOARD_SET_LED =
0xed, ///< Set the LED (according to the following parameter byte)
KEYBOARD_SEND_ECHO = 0xee, ///< Send an echo packet
KEYBOARD_SET_SPEED = 0xf3, ///< Set the repeat rate (according to the
///< following parameter byte)
KEYBOARD_ENABLE = 0xf4, ///< Enable Keyboard
KEYBOARD_DISABLE = 0xf5, ///< Disable Keyboard
KEYBOARD_SET_DEFAULT = 0xf6, ///< Load defaults
};
/*! \brief Replies
*/
enum Reply {
ACK = 0xfa, ///< Acknowledgement
RESEND = 0xfe, ///< Request to resend (not required to implement)
ECHO = 0xee ///< Echo answer
};
/*! \brief Commands for the PS/2 Controller
*
* These commands are processed by the controller and *not* send to
* keyboard/mouse. They have to be written into the command register.
*/
enum ControllerCommand {
CONTROLLER_GET_COMMAND_BYTE =
0x20, ///< Read Command Byte of PS/2 Controller
CONTROLLER_SET_COMMAND_BYTE =
0x60, ///< Write Command Byte of PS/2 Controller
CONTROLLER_MOUSE_DISABLE = 0xa7, ///< Disable mouse interface
CONTROLLER_MOUSE_ENABLE = 0xa8, ///< Enable mouse interface
CONTROLLER_KEYBOARD_DISABLE = 0xad, ///< Disable keyboard interface
CONTROLLER_KEYBOARD_ENABLE = 0xae, ///< Enable keyboard interface
CONTROLLER_SEND_TO_MOUSE = 0xd4, ///< Send parameter to mouse device
};
/*! \brief Send a command or data to a connected PS/2 device
*
* The value must only be written into the input buffer after the previously
* written values have been fetched (\ref INPUT_PENDING in the status register).
*
*
* \param value data to be sent
*/
[[maybe_unused]] static void sendData(uint8_t value) {
// First of all, we have to wait for the controller to fetch all the
// characters we have written so far.
while ((ctrl_port.inb() & INPUT_PENDING) != 0) {
} // wait for keyboard inbuffer
// Send the data byte, which gets acked (each) by the keyboard
data_port.outb(value);
}
void init() {
// Switch all LEDs off (on many PCs NumLock is turned on after power up)
setLed(LED_CAPS_LOCK, false);
setLed(LED_SCROLL_LOCK, false);
setLed(LED_NUM_LOCK, false);
// Set to maximum speed & minimum delay
setRepeatRate(SPEED_30_0CPS, DELAY_250MS);
IOAPIC::config(APIC::getIOAPICSlot(APIC::Device::KEYBOARD),
Core::Interrupt::Vector::KEYBOARD,
IOAPIC::TriggerMode::LEVEL);
IOAPIC::allow(APIC::getIOAPICSlot(APIC::Device::KEYBOARD));
drainBuffer();
}
bool fetch(Key& pressed) {
int control = ctrl_port.inb();
if ((control & HAS_OUTPUT) != 0) {
// Get data
uint8_t code = data_port.inb();
// Ignore mouse
if ((control & IS_MOUSE) == 0) {
// Decode key
pressed = key_decoder.decode(code);
// Return if valid
if (pressed.valid()) {
return true;
}
}
}
return false;
}
void setRepeatRate(Speed speed, Delay delay) {
// Implementation was hidden behind the `keyboard_out` tag
union {
struct {
Speed speed : 5;
Delay delay : 2;
uint8_t : 1;
} __attribute__((packed));
uint8_t value;
} rate;
rate.speed = speed;
rate.delay = delay;
sendData(KEYBOARD_SET_SPEED); // Command for the Keyboard
sendData(rate.value); // Parameter
}
void setLed(enum LED led, bool on) {
if (on) {
leds |= led;
} else {
leds &= ~led;
}
sendData(KEYBOARD_SET_LED); // Command for the Keyboard
sendData(leds); // Parameter
}
void drainBuffer() {
while ((ctrl_port.inb() & HAS_OUTPUT) != 0) {
data_port.inb();
}
}
} // namespace PS2Controller

View File

@@ -0,0 +1,150 @@
/*! \file
* \brief \ref PS2Controller "PS/2 Controller" (Intel 8042, also known as
* Keyboard Controller)
*/
#pragma once
#include "../object/key.h"
#include "../types.h"
#include "./textstream.h"
/*! \brief PS/2 Controller
* \ingroup io
*
* Initializes the PS/2 devices (Keyboard and optional Mouse), and
* determines both the scan code and ASCII character of a pressed key from the
* transmitted make and break codes using the \ref KeyDecoder.
*
* \note This controller is also known as Intel 8042 (nowadays integrated in
* the mainboard) or *Keyboard Controller*.
* But to avoid confusion with the actual Keyboard and since we use the
* PS/2-compatible mode to support the Mouse as well, the name
* PS/2 Controller was chosen for the sake of simplicity.
*
* \note Since modern PCs sometimes don't have an PS/2 connector, USB keyboards
* and mice are emulated as PS/2 device with USB Legacy Support.
*/
namespace PS2Controller {
/*! \brief Initialization of connected devices
*
* All status LEDs of the keyboard are switched off and the repetition rate is
* set to maximum speed.
*
* Later the \ref IOAPIC is configured to receive corresponding interrupts.
*
* \note The keyboard interrupts should be configured as \ref IOAPIC::LEVEL
* "level triggered". According to the standard we would have to check the
* corresponding entry in
* \ref ACPI::MADS::Interrupt_Source_Override and use these values. Most
* likely this would suggest an \ref IOAPIC::EDGE "edge-triggered mode" -- which
* would work as well. However, using a \ref IOAPIC::LEVEL "level-triggered
* mode" is more forgiving because it resends the interrupt request even if an
* interrupt was lost (e.g. the required handling, retrieving the buffer entry,
* was not performed).
*
*/
void init();
/*! \brief Retrieve the keyboard event
*
* Retrieves make and brake events from the keyboard.
* If a valid (non special) key was pressed, the scan code is determined
* using \ref KeyDecoder::decode into a \ref Key object.
* Events on special keys like \key{Shift}, \key{Alt}, \key{CapsLock} etc. are
* stored (in \ref KeyDecoder) and applied on subsequent keystrokes, while no
* valid key is retrieved.
*
* Mouse events are ignored.
*
*
*
* \param pressed Reference to an object which will contain the pressed \ref Key
* on success
* \return `true` if a valid key was decoded
*/
bool fetch(Key& pressed);
/*! \brief Delay before the keyboard starts repeating sending a pressed key
*/
enum Delay {
DELAY_250MS = 0, ///< Delay of 0.25s
DELAY_500MS = 1, ///< Delay of 0.5s
DELAY_750MS = 2, ///< Delay of 0.75s
DELAY_1000MS = 3 ///< Delay of 1s
};
/*! \brief Repeat Rate of Characters
*
* \see \ref ps2keyboard
*/
enum Speed {
SPEED_30_0CPS = 0x00, ///< 30 characters per second
SPEED_26_7CPS = 0x01, ///< 26.7 characters per second
SPEED_24_0CPS = 0x02, ///< 24 characters per second
SPEED_21_8CPS = 0x03, ///< 12.8 characters per second
SPEED_20_7CPS = 0x04, ///< 20.7 characters per second
SPEED_18_5CPS = 0x05, ///< 18.5 characters per second
SPEED_17_1CPS = 0x06, ///< 17.1 characters per second
SPEED_16_0CPS = 0x07, ///< 16 characters per second
SPEED_15_0CPS = 0x08, ///< 15 characters per second
SPEED_13_3CPS = 0x09, ///< 13.3 characters per second
SPEED_12_0CPS = 0x0a, ///< 12 characters per second
SPEED_10_9CPS = 0x0b, ///< 10.9 characters per second
SPEED_10_0CPS = 0x0c, ///< 10 characters per second
SPEED_09_2CPS = 0x0d, ///< 9.2 characters per second
SPEED_08_6CPS = 0x0e, ///< 8.6 characters per second
SPEED_08_0CPS = 0x0f, ///< 8 characters per second
SPEED_07_5CPS = 0x10, ///< 7.5 characters per second
SPEED_06_7CPS = 0x11, ///< 6.7 characters per second
SPEED_06_0CPS = 0x12, ///< 6 characters per second
SPEED_05_5CPS = 0x13, ///< 5.5 characters per second
SPEED_05_0CPS = 0x14, ///< 5 characters per second
SPEED_04_6CPS = 0x15, ///< 4.6 characters per second
SPEED_04_3CPS = 0x16, ///< 4.3 characters per second
SPEED_04_0CPS = 0x17, ///< 4 characters per second
SPEED_03_7CPS = 0x18, ///< 3.7 characters per second
SPEED_03_3CPS = 0x19, ///< 3.3 characters per second
SPEED_03_0CPS = 0x1a, ///< 3 characters per second
SPEED_02_7CPS = 0x1b, ///< 2.7 characters per second
SPEED_02_5CPS = 0x1c, ///< 2.5 characters per second
SPEED_02_3CPS = 0x1d, ///< 2.3 characters per second
SPEED_02_1CPS = 0x1e, ///< 2.1 characters per second
SPEED_02_0CPS = 0x1f, ///< 2 characters per second
};
/*! \brief Configure the repeat rate of the keyboard
*
* \param delay configures how long a key must be pressed before the repetition
* begins.
* \param speed determines how fast the key codes should follow each other.
* Valid values are between `0` (30 characters per second) and
* `31` (2 characters per second).
*/
void setRepeatRate(Speed speed, Delay delay);
/*! \brief Keyboard LEDs
*/
enum LED {
LED_SCROLL_LOCK = 1 << 0, ///< Scroll Lock
LED_NUM_LOCK = 1 << 1, ///< Num Lock
LED_CAPS_LOCK = 1 << 2, ///< Caps Lock
};
/*! \brief Enable or disable a keyboard LED
*
* \param led LED to enable or disable
* \param on `true` will enable the specified LED, `false` disable
*/
void setLed(enum LED led, bool on);
/*! \brief Empties the keyboard buffer.
*
* The keyboard may not send any interrupts if the buffer is not empty.
* To prevent unhandled keystrokes (for example during boot) the buffer
* should be emptied once right before allowing keyboard interrupts
* (even if keystrokes might be lost).
*
*/
void drainBuffer();
} // namespace PS2Controller

View File

@@ -0,0 +1,37 @@
#include "serialstream.h"
SerialStream::SerialStream(ComPort port, BaudRate baud_rate, DataBits data_bits,
StopBits stop_bits, Parity parity)
: Serial(port, baud_rate, data_bits, stop_bits, parity) {}
void SerialStream::flush() {
print(buffer, pos);
pos = 0;
}
void SerialStream::setForeground(Color c) {
*this << "\e[" << (30 + static_cast<uint32_t>(c)) << 'm';
}
void SerialStream::setBackground(Color c) {
*this << "\e[" << (40 + static_cast<uint32_t>(c)) << 'm';
}
void SerialStream::setAttribute(Attrib a) {
*this << "\e[" << static_cast<uint32_t>(a) << 'm';
}
void SerialStream::reset() { *this << "\ec" << ::flush; }
void SerialStream::setPos(int x, int y) {
*this << "\e[" << (y + 1) << ';' << (x + 1) << 'H' << ::flush;
}
void SerialStream::print(char* str, int length) {
for (int p = 0; p < length; p++) {
if (str[p] == '\n' && (p == 0 || str[p - 1] != '\r')) {
write('\r');
}
write(str[p]);
}
}

View File

@@ -0,0 +1,138 @@
/*! \file
* \brief \ref Serial \ref SerialStream "output stream"
*/
#pragma once
#include "../arch/serial.h"
#include "../object/outputstream.h"
#include "../types.h"
/*! \brief Console (VT100 compatible) via \ref Serial interface.
* \ingroup io
*
* This class allows to connect a VT100-compatible display terminal via
* the serial interface.
*
* The utility 'screen' can be used to attach a terminal to an interface
* at a specified connection speed: `screen /dev/ttyS0 115200`
*
* Color and position can be adjusted with the help of
* [escape
* codes](http://web.archive.org/web/20181008150037/http://www.termsys.demon.co.uk/vtansi.htm).
*/
class SerialStream : public OutputStream, public Serial {
public:
/*! \brief Attributes
* can be used to influence the display of the output.
*
* \note The attributes might not be supported or have a different effect
* depending on the terminal emulator!
*/
enum class Attrib : uint32_t {
RESET = 0, ///< Turn off character attributes
BRIGHT = 1, ///< Bold
DIM = 2, ///< Low intensity (dimmed)
ITALIC = 3, ///< Italic
UNDERSCORE = 4, ///< Underline
BLINK = 5, ///< Blink (slow)
REVERSE = 7, ///< Swap fore & background
HIDDEN = 8, ///< Concealed
};
/*! \brief Color codes
*
* Default VT100 supports eight colors for both foreground and background
* (later versions 256 [8 bit] and even true color [32 bit]).
* The actual color is affected by the attributes and can look significantly
* different depending on the terminal emulator.
*/
enum class Color : uint32_t {
BLACK = 0,
RED = 1,
GREEN = 2,
YELLOW = 3,
BLUE = 4,
MAGENTA = 5,
CYAN = 6,
WHITE = 7
};
/*! \brief Constructor for the VT100-compatible console
*
* Sets up the serial connection as well
*
*/
explicit SerialStream(ComPort port = Serial::ComPort::COM1,
BaudRate baud_rate = Serial::BaudRate::B115200,
DataBits data_bits = Serial::DataBits::D8,
StopBits stop_bits = Serial::StopBits::S1,
Parity parity = Serial::Parity::NONE);
/*! \brief Method to output the buffer contents of the base class \ref
* Stringbuffer
*
* The method is automatically called when the buffer is full,
* but can also be called explicitly to force output of the current buffer.
*
*/
void flush() override;
/*! \brief Change foreground color (for subsequent output)
*
*
* \param c Color
*/
void setForeground(Color c);
/*! \brief Change background color (for subsequent output)
*
*
* \param c Color
*/
void setBackground(Color c);
/*! \brief Change text attribute (for subsequent output)
*
*
* \param a Attribute
*/
void setAttribute(Attrib a);
/*! \brief Reset terminal
*
* Clear screen, place cursor at the beginning and reset colors
* and attributes to the default value.
*
*/
void reset();
/*! \brief Set the cursor position
*
* \param x Column in window
* \param y Row in window
*
*/
void setPos(int x, int y);
/*! \brief Display multiple characters in the window starting at the current
* cursor position
*
* This method can be used to output a string, starting at the current
* cursor position. Since the string does not need to contain a '\0'
* termination (as it is usually the case in C), the parameter `length` is
* required to specify the number of characters in the string.
*
* The text is displayed using the previously configured
* \ref setAttribute() "attributes", \ref setForeground() "fore-"
* and \ref setBackground "background" color.
*
* A line break will occur wherever the character `\n` is inserted
* in the text to be output (for compatibility reasons a `\r` is
* automatically appended).
*
* \param str String to output
* \param length length of string
*/
void print(char* str, int length);
};

View File

@@ -0,0 +1,10 @@
#include "textstream.h"
TextStream::TextStream(unsigned from_col, unsigned to_col, unsigned from_row,
unsigned to_row, bool use_cursor)
: TextWindow(from_col, to_col, from_row, to_row, use_cursor) {}
void TextStream::flush() {
print(buffer, pos);
pos = 0;
}

View File

@@ -0,0 +1,45 @@
/*! \file
* \brief \ref TextStream outputs text onto the screen in \ref CGA
*/
/*! \defgroup io I/O subsystem
* \brief The input/output subsystem
*/
#pragma once
#include "../arch/textwindow.h"
#include "../object/outputstream.h"
#include "../types.h"
/*! \brief Output text (form different data type sources) on screen in text
* mode
* \ingroup io
*
* Allows the output of different data types as strings on the \ref CGA
* screen of a PC.
* To achieve this, \ref TextStream is derived from both \ref OutputStream and
* \ref TextWindow and only implements the method \ref TextStream::flush().
* Further formatting or special effects are implemented in \ref TextWindow.
*/
class TextStream : public OutputStream, public TextWindow {
// Prevent copies and assignments
TextStream(const TextStream&) = delete;
TextStream& operator=(const TextStream&) = delete;
public:
/// \copydoc
/// TextWindow::TextWindow(unsigned,unsigned,unsigned,unsigned,bool)
TextStream(unsigned from_col, unsigned to_col, unsigned from_row,
unsigned to_row, bool use_cursor = false);
/*! \brief Output the buffer contents of the base class \ref Stringbuffer
*
* The method is automatically called when the buffer is full,
* but can also be called explicitly to force output of the current buffer.
*
*
*/
// NOTE: We can only add the `override` once we inherit from `TextWindow`
// which is part of the solution.
void flush() override;
};