Handout
This commit is contained in:
189
kernel/device/graphics.cc
Normal file
189
kernel/device/graphics.cc
Normal 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
284
kernel/device/graphics.h
Normal 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);
|
||||
}
|
||||
};
|
||||
131
kernel/device/graphicsstream.cc
Normal file
131
kernel/device/graphicsstream.cc
Normal 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;
|
||||
}
|
||||
}
|
||||
135
kernel/device/graphicsstream.h
Normal file
135
kernel/device/graphicsstream.h
Normal 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
131
kernel/device/keydecoder.cc
Normal 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;
|
||||
}
|
||||
40
kernel/device/keydecoder.h
Normal file
40
kernel/device/keydecoder.h
Normal 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);
|
||||
};
|
||||
169
kernel/device/ps2controller.cc
Normal file
169
kernel/device/ps2controller.cc
Normal 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
|
||||
150
kernel/device/ps2controller.h
Normal file
150
kernel/device/ps2controller.h
Normal 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
|
||||
37
kernel/device/serialstream.cc
Normal file
37
kernel/device/serialstream.cc
Normal 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]);
|
||||
}
|
||||
}
|
||||
138
kernel/device/serialstream.h
Normal file
138
kernel/device/serialstream.h
Normal 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);
|
||||
};
|
||||
10
kernel/device/textstream.cc
Normal file
10
kernel/device/textstream.cc
Normal 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;
|
||||
}
|
||||
45
kernel/device/textstream.h
Normal file
45
kernel/device/textstream.h
Normal 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;
|
||||
};
|
||||
Reference in New Issue
Block a user