This commit is contained in:
Niklas Gollenstede
2025-04-14 11:20:52 +02:00
commit 5a2e32aaeb
126 changed files with 16742 additions and 0 deletions

122
device/keydecoder.cc Normal file
View File

@@ -0,0 +1,122 @@
#include "keydecoder.h"
#include "ps2controller.h"
// Constants used for key decoding
const unsigned char BREAK_BIT = 0x80;
const unsigned char PREFIX_1 = 0xe0;
const 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[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
device/keydecoder.h Normal file
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[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);
};

130
device/ps2controller.cc Normal file
View File

@@ -0,0 +1,130 @@
#include "ps2controller.h"
#include "../arch/core_interrupt.h"
#include "../arch/ioport.h"
#include "../compiler/fix.h"
#include "../debug/output.h"
#include "keydecoder.h"
namespace PS2Controller {
// I/O Ports of the PS2 Controller
static const IOPort ctrl_port(
0x64); ///< Access status- (read) and command (write) register
static const IOPort data_port(0x60); ///< Access PS/2 device [keyboard] output-
///< (read) and input (write) buffer
/* 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).
*
* \todo(11) Implement method
*
* \param value data to be sent
*/
[[maybe_unused]] static void sendData(uint8_t value) {
// TODO: You have to implement this method
(void)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);
}
bool fetch(Key &pressed) {
// TODO: You have to implement this method
(void)pressed;
return false;
}
void setRepeatRate(Speed speed, Delay delay) {
(void)speed;
(void)delay;
}
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() {}
} // namespace PS2Controller

153
device/ps2controller.h Normal file
View File

@@ -0,0 +1,153 @@
/*! \file
* \brief \ref PS2Controller "PS/2 Controller" (Intel 8042, also known as
* Keyboard Controller)
*/
#pragma once
#include "../object/key.h"
#include "../types.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).
*
* \todo(12) Register with \ref IOAPIC
*/
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.
*
* \todo(11) Implement Method
*
* \todo(12) Adjust method (unless it is already non-blocking)
*
* \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).
*
* \todo(12) Implement method
*/
void drainBuffer();
} // namespace PS2Controller

30
device/serialstream.cc Normal file
View File

@@ -0,0 +1,30 @@
#include "serialstream.h"
SerialStream::SerialStream(ComPort port, BaudRate baud_rate, DataBits data_bits,
StopBits stop_bits, Parity parity) {
(void)port;
(void)baud_rate;
(void)data_bits;
(void)stop_bits;
(void)parity;
}
void SerialStream::flush() {}
void SerialStream::setForeground(Color c) { (void)c; }
void SerialStream::setBackground(Color c) { (void)c; }
void SerialStream::setAttribute(Attrib a) { (void)a; }
void SerialStream::reset() {}
void SerialStream::setPos(int x, int y) {
(void)x;
(void)y;
}
void SerialStream::print(char* str, int length) {
(void)str;
(void)length;
}

143
device/serialstream.h Normal file
View File

@@ -0,0 +1,143 @@
/*! \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 Attrib {
RESET = 0, ///< Turn off character attributes
BRIGHT = 1, ///< Bold
DIM = 2, ///< Low intensity (dimmed)
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 Color {
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
*
* \todo(11) Implement Method
*/
explicit SerialStream(ComPort port = COM1, BaudRate baud_rate = BAUD_115200,
DataBits data_bits = DATA_8BIT,
StopBits stop_bits = STOP_1BIT,
Parity parity = 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.
*
* \todo(11) Implement Method
*/
void flush() override;
/*! \brief Change foreground color (for subsequent output)
*
* \todo(11) Implement Method
*
* \param c Color
*/
void setForeground(Color c);
/*! \brief Change background color (for subsequent output)
*
* \todo(11) Implement Method
*
* \param c Color
*/
void setBackground(Color c);
/*! \brief Change text attribute (for subsequent output)
*
* \todo(11) Implement Method
*
* \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.
*
* \todo(11) Implement Method
*/
void reset();
/*! \brief Set the cursor position
*
* \param x Column in window
* \param y Row in window
*
* \todo(11) Implement Method
*/
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);
};

12
device/textstream.cc Normal file
View File

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

41
device/textstream.h Normal file
View File

@@ -0,0 +1,41 @@
/*! \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 "../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 {
// 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.
*
*
* \todo(11) Implement method
*/
void flush();
};