Userspace programs can call tty_ctrl() to disable/enable tty from handling input and displaying output. This API is probably going to change in the future to ioctl calls but I'm not sure how ioctl is used and what functionality should it have. I decided to create whole new function and syscall for now. Next I will expose framebuffer in /dev/fb0 and then I can start work on graphical environment! :D
294 lines
6.0 KiB
C++
294 lines
6.0 KiB
C++
#include <BAN/Array.h>
|
|
#include <BAN/CircularQueue.h>
|
|
#include <kernel/CriticalScope.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>
|
|
#include <sys/sysmacros.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 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::CircularQueue<uint8_t, 128> s_com1_input;
|
|
static BAN::CircularQueue<uint8_t, 128> s_com2_input;
|
|
static BAN::RefPtr<SerialTTY> s_com1;
|
|
static BAN::RefPtr<SerialTTY> s_com2;
|
|
|
|
static void irq3_handler()
|
|
{
|
|
if (!s_serial_drivers[1].is_valid())
|
|
return;
|
|
uint8_t ch = IO::inb(COM2_PORT);
|
|
if (s_com2_input.full())
|
|
{
|
|
dwarnln("COM2 buffer full");
|
|
s_com2_input.pop();
|
|
}
|
|
s_com2_input.push(ch);
|
|
}
|
|
|
|
static void irq4_handler()
|
|
{
|
|
if (!s_serial_drivers[0].is_valid())
|
|
return;
|
|
uint8_t ch = IO::inb(COM1_PORT);
|
|
if (s_com1_input.full())
|
|
{
|
|
dwarnln("COM1 buffer full");
|
|
s_com1_input.pop();
|
|
}
|
|
s_com1_input.push(ch);
|
|
}
|
|
|
|
static dev_t next_rdev()
|
|
{
|
|
static dev_t major = DevFileSystem::get().get_next_dev();
|
|
static dev_t minor = 0;
|
|
return makedev(major, minor++);
|
|
}
|
|
|
|
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())
|
|
continue;
|
|
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(0660, 0, 0)
|
|
, m_serial(serial)
|
|
, m_rdev(next_rdev())
|
|
{
|
|
m_name = BAN::String::formatted("ttyS{}", minor(rdev()));
|
|
}
|
|
|
|
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);
|
|
InterruptController::get().enable_irq(COM1_IRQ);
|
|
IDT::register_irq_handler(COM1_IRQ, irq4_handler);
|
|
}
|
|
else if (serial.port() == COM2_PORT)
|
|
{
|
|
IO::outb(COM2_PORT + 1, 1);
|
|
InterruptController::get().enable_irq(COM2_IRQ);
|
|
IDT::register_irq_handler(COM2_IRQ, irq3_handler);
|
|
}
|
|
|
|
auto ref_ptr = BAN::RefPtr<SerialTTY>::adopt(tty);
|
|
DevFileSystem::get().add_device(ref_ptr->name(), 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::update()
|
|
{
|
|
if (m_serial.port() != COM1_PORT && m_serial.port() != COM2_PORT)
|
|
return;
|
|
|
|
static uint8_t buffer[128];
|
|
|
|
auto update_com =
|
|
[&](auto& device, auto& input_queue)
|
|
{
|
|
if (input_queue.empty())
|
|
return;
|
|
uint8_t* ptr = buffer;
|
|
while (!input_queue.empty())
|
|
{
|
|
*ptr = input_queue.front();
|
|
if (*ptr == '\r')
|
|
*ptr = '\n';
|
|
if (*ptr == 127)
|
|
*ptr++ = '\b', *ptr++ = ' ', *ptr = '\b';
|
|
input_queue.pop();
|
|
ptr++;
|
|
}
|
|
*ptr = '\0';
|
|
|
|
ptr = buffer;
|
|
while (*ptr)
|
|
device->handle_input_byte(*ptr++);
|
|
};
|
|
|
|
CriticalScope _;
|
|
if (m_serial.port() == COM1_PORT)
|
|
update_com(s_com1, s_com1_input);
|
|
if (m_serial.port() == COM2_PORT)
|
|
update_com(s_com2, s_com2_input);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
} |