Files
banan-os/kernel/kernel/Terminal/Serial.cpp
Bananymous e8491b34b8 Kernel/LibC: Rework TIOC{G,S}WINSZ more linux like
Userspace can freely set terminal size, kernel just updates it when for
example new font is loaded. Also SIGWINCH is now sent by kernel instead
of userspace.
2025-06-28 19:40:54 +03:00

266 lines
5.7 KiB
C++

#include <BAN/Array.h>
#include <kernel/FS/DevFS/FileSystem.h>
#include <kernel/IDT.h>
#include <kernel/InterruptController.h>
#include <kernel/IO.h>
#include <kernel/Terminal/Serial.h>
#include <ctype.h>
#define MAX_BAUD 115200
#define DATA_BITS_5 (0b00 << 0)
#define DATA_BITS_6 (0b01 << 0)
#define DATA_BITS_7 (0b10 << 0)
#define DATA_BITS_8 (0b11 << 0)
#define STOP_BITS_1 (0b0 << 2)
#define STOP_BITS_2 (0b1 << 2)
#define PARITY_NONE (0b000 << 3)
#define PARITY_ODD (0b001 << 3)
#define PARITY_EVEN (0b011 << 3)
#define PARITY_MARK (0b101 << 3)
#define PARITY_SPACE (0b111 << 3)
#define COM1_PORT 0x3F8
#define COM2_PORT 0x2F8
#define COM1_IRQ 4
#define COM2_IRQ 3
namespace Kernel
{
static BAN::Atomic<uint32_t> s_next_tty_number = 0;
static constexpr uint16_t s_serial_ports[] = { 0x3F8, 0x2F8, 0x3E8, 0x2E8, 0x5F8, 0x4F8, 0x5E8, 0x4E8 };
static BAN::Array<Serial, sizeof(s_serial_ports) / sizeof(*s_serial_ports)> s_serial_drivers;
static bool s_has_devices { false };
static BAN::RefPtr<SerialTTY> s_com1;
static BAN::RefPtr<SerialTTY> s_com2;
void Serial::initialize()
{
int count = 0;
for (size_t i = 0; i < s_serial_drivers.size(); i++)
{
if (initialize_port(s_serial_ports[i], 115200))
{
auto& driver = s_serial_drivers[i];
driver.m_port = s_serial_ports[i];
if (!driver.initialize_size())
{
// if size detection fails, just use some random size
driver.m_width = 999;
driver.m_height = 999;
}
count++;
}
}
s_has_devices = !!count;
for (auto& driver : s_serial_drivers)
if (driver.is_valid())
dprintln("{}x{} serial device at 0x{H}", driver.width(), driver.height(), driver.port());
}
void Serial::initialize_devices()
{
for (auto& serial : s_serial_drivers)
if (serial.is_valid())
MUST(SerialTTY::create(serial));
}
bool Serial::initialize_port(uint16_t port, uint32_t baud)
{
// Disable interrupts
IO::outb(port + 1, 0x00);
// configure port
uint16_t divisor = MAX_BAUD / baud;
IO::outb(port + 3, 0x80);
IO::outb(port + 0, divisor & 0xFF);
IO::outb(port + 1, divisor >> 8);
IO::outb(port + 3, DATA_BITS_8 | STOP_BITS_1 | PARITY_NONE);
IO::outb(port + 2, 0xC7);
IO::outb(port + 4, 0x0B);
// Test loopback
IO::outb(port + 4, 0x1E);
IO::outb(port + 0, 0xAE);
if(IO::inb(port + 0) != 0xAE)
return false;
// Set to normal mode
IO::outb(port + 4, 0x0F);
return true;
}
bool Serial::initialize_size()
{
const char* query = "\e[999;999H\e[6n\e[H\e[J";
const char* ptr = query;
while (*ptr)
putchar(*ptr++);
if (auto ch = getchar(); !ch.has_value() || ch.value() != '\033')
return false;
if (auto ch = getchar(); !ch.has_value() || ch.value() != '[')
return false;
auto read_number =
[&](uint8_t end) -> BAN::Optional<uint32_t>
{
uint32_t number = 0;
while (true)
{
const auto ch = getchar();
if (!ch.has_value())
return {};
if (ch.value() == end)
return number;
if (!isdigit(ch.value()))
return {};
number = (number * 10) + (ch.value() - '0');
}
};
const auto height = read_number(';');
if (!height.has_value())
return false;
const auto width = read_number('R');
if (!width.has_value())
return false;
m_height = height.value();
m_width = width.value();
return true;
}
bool Serial::has_devices()
{
return s_has_devices;
}
void Serial::putchar(uint8_t ch)
{
while (!(IO::inb(m_port + 5) & 0x20))
continue;
IO::outb(m_port, ch);
}
BAN::Optional<uint8_t> Serial::getchar()
{
for (size_t i = 0; i < 10'000'000; i++)
if (IO::inb(m_port + 5) & 0x01)
return IO::inb(m_port);
return {};
}
void Serial::putchar_any(uint8_t ch)
{
for (auto& device : s_serial_drivers)
if (device.is_valid())
return device.putchar(ch);
}
SerialTTY::SerialTTY(Serial serial)
: TTY({
.c_iflag = ICRNL,
.c_oflag = OPOST | ONLCR,
.c_cflag = CS8,
.c_lflag = ECHO | ECHOE | ECHOK | ICANON | ISIG,
.c_cc = TTY_DEFAULT_TERMIOS_CC,
.c_ospeed = B38400,
.c_ispeed = B38400,
}, 0600, 0, 0)
, m_name(MUST(BAN::String::formatted("ttyS{}", s_next_tty_number++)))
, m_serial(serial)
{
update_winsize(m_serial.width(), m_serial.height());
}
BAN::ErrorOr<BAN::RefPtr<SerialTTY>> SerialTTY::create(Serial serial)
{
auto* tty = new SerialTTY(serial);
ASSERT(tty);
// Enable interrupts for COM1 and COM2
if (serial.port() == COM1_PORT)
{
IO::outb(COM1_PORT + 1, 1);
TRY(InterruptController::get().reserve_irq(COM1_IRQ));
tty->set_irq(COM1_IRQ);
InterruptController::get().enable_irq(COM1_IRQ);
}
else if (serial.port() == COM2_PORT)
{
IO::outb(COM2_PORT + 1, 1);
TRY(InterruptController::get().reserve_irq(COM2_IRQ));
tty->set_irq(COM2_IRQ);
InterruptController::get().enable_irq(COM2_IRQ);
}
auto ref_ptr = BAN::RefPtr<SerialTTY>::adopt(tty);
DevFileSystem::get().add_device(ref_ptr);
if (serial.port() == COM1_PORT)
s_com1 = ref_ptr;
if (serial.port() == COM2_PORT)
s_com2 = ref_ptr;
return ref_ptr;
}
void SerialTTY::handle_irq()
{
uint8_t ch = IO::inb(m_serial.port());
if (ch == 0x7F)
ch = '\b';
SpinLockGuard _(m_input_lock);
if (m_input.full())
{
dwarnln("Serial buffer full");
m_input.pop();
}
m_input.push(ch);
}
void SerialTTY::update()
{
if (m_serial.port() != COM1_PORT && m_serial.port() != COM2_PORT)
return;
uint8_t buffer[128];
{
SpinLockGuard _(m_input_lock);
if (m_input.empty())
return;
uint8_t* ptr = buffer;
while (!m_input.empty())
{
*ptr = m_input.front();
m_input.pop();
ptr++;
}
*ptr = '\0';
}
const uint8_t* ptr = buffer;
while (*ptr)
handle_input_byte(*ptr++);
}
bool SerialTTY::putchar_impl(uint8_t ch)
{
m_serial.putchar(ch);
return true;
}
}