banan-os/kernel/kernel/Terminal/Serial.cpp

269 lines
5.3 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 (getchar() != '\033')
return false;
if (getchar() != '[')
return false;
auto read_number =
[&](char end)
{
uint32_t number = 0;
while (true)
{
char c = getchar();
if (c == end)
break;
if (!isdigit(c))
return UINT32_MAX;
number = (number * 10) + (c - '0');
}
return number;
};
m_height = read_number(';');
if (m_height == UINT32_MAX)
{
m_port = 0;
return false;
}
m_width = read_number('R');
if (m_width == UINT32_MAX)
{
m_port = 0;
return false;
}
return true;
}
bool Serial::has_devices()
{
return s_has_devices;
}
void Serial::putchar(char c)
{
while (!(IO::inb(m_port + 5) & 0x20))
continue;
IO::outb(m_port, c);
}
char Serial::getchar()
{
while (!(IO::inb(m_port + 5) & 0x01))
continue;
return IO::inb(m_port);
}
void Serial::putchar_any(char c)
{
for (auto& device : s_serial_drivers)
if (device.is_valid())
device.putchar(c);
}
SerialTTY::SerialTTY(Serial serial)
: TTY(0600, 0, 0)
, m_name(MUST(BAN::String::formatted("ttyS{}", s_next_tty_number++)))
, m_serial(serial)
{}
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);
tty->enable_interrupt();
}
else if (serial.port() == COM2_PORT)
{
IO::outb(COM2_PORT + 1, 1);
TRY(InterruptController::get().reserve_irq(COM2_IRQ));
tty->set_irq(COM2_IRQ);
tty->enable_interrupt();
}
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());
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();
if (*ptr == '\r')
*ptr = '\n';
if (*ptr == 127)
*ptr++ = '\b', *ptr++ = ' ', *ptr = '\b';
m_input.pop();
ptr++;
}
*ptr = '\0';
}
const uint8_t* ptr = buffer;
while (*ptr)
handle_input_byte(*ptr++);
}
uint32_t SerialTTY::width() const
{
return m_serial.width();
}
uint32_t SerialTTY::height() const
{
return m_serial.height();
}
void SerialTTY::putchar_impl(uint8_t ch)
{
m_serial.putchar(ch);
}
}